2023-07-03 19:06:18 +02:00
import { saveSettings , callPopup , substituteParams , getTokenCount , getRequestHeaders , chat _metadata , this _chid , characters , saveCharacterDebounced , menu _type , eventSource , event _types } from "../script.js" ;
2023-08-21 20:10:11 +02:00
import { download , debounce , initScrollHeight , resetScrollHeight , parseJsonFile , extractDataFromPng , getFileBuffer , getCharaFilename , deepClone , getSortableDelay , escapeRegex , PAGINATION _TEMPLATE } from "./utils.js" ;
2023-07-20 19:32:15 +02:00
import { getContext } from "./extensions.js" ;
import { NOTE _MODULE _NAME , metadata _keys , shouldWIAddPrompt } from "./authors-note.js" ;
import { registerSlashCommand } from "./slash-commands.js" ;
import { deviceInfo } from "./RossAscends-mods.js" ;
2023-08-22 00:51:31 +02:00
import { FILTER _TYPES , FilterHelper } from "./filters.js" ;
2023-07-20 19:32:15 +02:00
export {
world _info ,
world _info _budget ,
world _info _depth ,
world _info _recursive ,
2023-07-30 04:15:54 +02:00
world _info _overflow _alert ,
2023-07-20 19:32:15 +02:00
world _info _case _sensitive ,
world _info _match _whole _words ,
world _info _character _strategy ,
2023-08-10 19:45:57 +02:00
world _info _budget _cap ,
2023-07-20 19:32:15 +02:00
world _names ,
checkWorldInfo ,
deleteWorldInfo ,
setWorldInfoSettings ,
getWorldInfoPrompt ,
}
const world _info _insertion _strategy = {
evenly : 0 ,
character _first : 1 ,
global _first : 2 ,
} ;
let world _info = { } ;
let selected _world _info = [ ] ;
let world _names ;
let world _info _depth = 2 ;
let world _info _budget = 25 ;
let world _info _recursive = false ;
2023-07-30 04:15:54 +02:00
let world _info _overflow _alert = false ;
2023-07-20 19:32:15 +02:00
let world _info _case _sensitive = false ;
let world _info _match _whole _words = false ;
let world _info _character _strategy = world _info _insertion _strategy . character _first ;
2023-08-10 19:45:57 +02:00
let world _info _budget _cap = 0 ;
2023-07-20 19:32:15 +02:00
const saveWorldDebounced = debounce ( async ( name , data ) => await _save ( name , data ) , 1000 ) ;
const saveSettingsDebounced = debounce ( ( ) => {
Object . assign ( world _info , { globalSelect : selected _world _info } )
saveSettings ( )
} , 1000 ) ;
const sortFn = ( a , b ) => b . order - a . order ;
2023-08-21 20:10:11 +02:00
const navigation _option = { none : 0 , previous : 1 , last : 2 , } ;
let updateEditor = ( navigation ) => { navigation ; } ;
2023-08-21 17:16:40 +02:00
2023-08-22 00:51:31 +02:00
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
const worldInfoFilter = new FilterHelper ( ( ) => updateEditor ( ) ) ;
2023-08-10 19:45:57 +02:00
export function getWorldInfoSettings ( ) {
return {
world _info ,
world _info _depth ,
world _info _budget ,
world _info _recursive ,
world _info _overflow _alert ,
world _info _case _sensitive ,
world _info _match _whole _words ,
world _info _character _strategy ,
world _info _budget _cap ,
}
}
2023-07-20 19:32:15 +02:00
const world _info _position = {
before : 0 ,
after : 1 ,
ANTop : 2 ,
ANBottom : 3 ,
} ;
const worldInfoCache = { } ;
async function getWorldInfoPrompt ( chat2 , maxContext ) {
let worldInfoString = "" , worldInfoBefore = "" , worldInfoAfter = "" ;
const activatedWorldInfo = await checkWorldInfo ( chat2 , maxContext ) ;
worldInfoBefore = activatedWorldInfo . worldInfoBefore ;
worldInfoAfter = activatedWorldInfo . worldInfoAfter ;
worldInfoString = worldInfoBefore + worldInfoAfter ;
return { worldInfoString , worldInfoBefore , worldInfoAfter } ;
}
function setWorldInfoSettings ( settings , data ) {
if ( settings . world _info _depth !== undefined )
world _info _depth = Number ( settings . world _info _depth ) ;
if ( settings . world _info _budget !== undefined )
world _info _budget = Number ( settings . world _info _budget ) ;
if ( settings . world _info _recursive !== undefined )
world _info _recursive = Boolean ( settings . world _info _recursive ) ;
2023-07-30 04:15:54 +02:00
if ( settings . world _info _overflow _alert !== undefined )
world _info _overflow _alert = Boolean ( settings . world _info _overflow _alert ) ;
2023-07-20 19:32:15 +02:00
if ( settings . world _info _case _sensitive !== undefined )
world _info _case _sensitive = Boolean ( settings . world _info _case _sensitive ) ;
if ( settings . world _info _match _whole _words !== undefined )
world _info _match _whole _words = Boolean ( settings . world _info _match _whole _words ) ;
if ( settings . world _info _character _strategy !== undefined )
world _info _character _strategy = Number ( settings . world _info _character _strategy ) ;
2023-08-10 19:45:57 +02:00
if ( settings . world _info _budget _cap !== undefined )
world _info _budget _cap = Number ( settings . world _info _budget _cap ) ;
2023-07-20 19:32:15 +02:00
// Migrate old settings
if ( world _info _budget > 100 ) {
world _info _budget = 25 ;
}
// Reset selected world from old string and delete old keys
// TODO: Remove next release
const existingWorldInfo = settings . world _info ;
if ( typeof existingWorldInfo === "string" ) {
delete settings . world _info ;
selected _world _info = [ existingWorldInfo ] ;
} else if ( Array . isArray ( existingWorldInfo ) ) {
delete settings . world _info ;
selected _world _info = existingWorldInfo ;
}
world _info = settings . world _info ? ? { }
$ ( "#world_info_depth_counter" ) . text ( world _info _depth ) ;
$ ( "#world_info_depth" ) . val ( world _info _depth ) ;
$ ( "#world_info_budget_counter" ) . text ( world _info _budget ) ;
$ ( "#world_info_budget" ) . val ( world _info _budget ) ;
$ ( "#world_info_recursive" ) . prop ( 'checked' , world _info _recursive ) ;
2023-07-30 04:15:54 +02:00
$ ( "#world_info_overflow_alert" ) . prop ( 'checked' , world _info _overflow _alert ) ;
2023-07-20 19:32:15 +02:00
$ ( "#world_info_case_sensitive" ) . prop ( 'checked' , world _info _case _sensitive ) ;
$ ( "#world_info_match_whole_words" ) . prop ( 'checked' , world _info _match _whole _words ) ;
$ ( ` #world_info_character_strategy option[value=' ${ world _info _character _strategy } '] ` ) . prop ( 'selected' , true ) ;
$ ( "#world_info_character_strategy" ) . val ( world _info _character _strategy ) ;
2023-08-10 19:45:57 +02:00
$ ( "#world_info_budget_cap" ) . val ( world _info _budget _cap ) ;
$ ( "#world_info_budget_cap_counter" ) . text ( world _info _budget _cap ) ;
2023-07-20 19:32:15 +02:00
world _names = data . world _names ? . length ? data . world _names : [ ] ;
// Add to existing selected WI if it exists
selected _world _info = selected _world _info . concat ( settings . world _info ? . globalSelect ? . filter ( ( e ) => world _names . includes ( e ) ) ? ? [ ] ) ;
if ( world _names . length > 0 ) {
$ ( "#world_info" ) . empty ( ) ;
}
world _names . forEach ( ( item , i ) => {
$ ( "#world_info" ) . append ( ` <option value=' ${ i } ' ${ selected _world _info . includes ( item ) ? ' selected' : '' } > ${ item } </option> ` ) ;
$ ( "#world_editor_select" ) . append ( ` <option value=' ${ i } '> ${ item } </option> ` ) ;
} ) ;
$ ( "#world_editor_select" ) . trigger ( "change" ) ;
}
// World Info Editor
async function showWorldEditor ( name ) {
if ( ! name ) {
hideWorldEditor ( ) ;
return ;
}
const wiData = await loadWorldInfoData ( name ) ;
displayWorldEntries ( name , wiData ) ;
}
async function loadWorldInfoData ( name ) {
if ( ! name ) {
return ;
}
if ( worldInfoCache [ name ] ) {
return worldInfoCache [ name ] ;
}
const response = await fetch ( "/getworldinfo" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { name : name } ) ,
cache : 'no-cache' ,
} ) ;
if ( response . ok ) {
const data = await response . json ( ) ;
worldInfoCache [ name ] = data ;
return data ;
}
return null ;
}
async function updateWorldInfoList ( ) {
var result = await fetch ( "/getsettings" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { } ) ,
} ) ;
if ( result . ok ) {
var data = await result . json ( ) ;
world _names = data . world _names ? . length ? data . world _names : [ ] ;
$ ( "#world_info" ) . find ( 'option[value!=""]' ) . remove ( ) ;
$ ( "#world_editor_select" ) . find ( 'option[value!=""]' ) . remove ( ) ;
world _names . forEach ( ( item , i ) => {
$ ( "#world_info" ) . append ( ` <option value=' ${ i } ' ${ selected _world _info . includes ( item ) ? ' selected' : '' } > ${ item } </option> ` ) ;
$ ( "#world_editor_select" ) . append ( ` <option value=' ${ i } '> ${ item } </option> ` ) ;
} ) ;
}
}
function hideWorldEditor ( ) {
displayWorldEntries ( null , null ) ;
}
function getWIElement ( name ) {
const wiElement = $ ( "#world_info" ) . children ( ) . filter ( function ( ) {
return $ ( this ) . text ( ) . toLowerCase ( ) === name . toLowerCase ( )
} ) ;
return wiElement ;
}
function nullWorldInfo ( ) {
toastr . info ( "Create or import a new World Info file first." , "World Info is not set" , { timeOut : 10000 , preventDuplicates : true } ) ;
}
2023-08-21 20:10:11 +02:00
function displayWorldEntries ( name , data , navigation = navigation _option . none ) {
updateEditor = ( navigation ) => displayWorldEntries ( name , data , navigation ) ;
2023-07-20 19:32:15 +02:00
$ ( "#world_popup_entries_list" ) . empty ( ) . show ( ) ;
if ( ! data || ! ( "entries" in data ) ) {
$ ( "#world_popup_new" ) . off ( 'click' ) . on ( 'click' , nullWorldInfo ) ;
$ ( "#world_popup_name_button" ) . off ( 'click' ) . on ( 'click' , nullWorldInfo ) ;
$ ( "#world_popup_export" ) . off ( 'click' ) . on ( 'click' , nullWorldInfo ) ;
$ ( "#world_popup_delete" ) . off ( 'click' ) . on ( 'click' , nullWorldInfo ) ;
$ ( "#world_popup_entries_list" ) . hide ( ) ;
2023-08-22 00:51:31 +02:00
$ ( '#world_info_pagination' ) . html ( '' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
2023-08-21 20:10:11 +02:00
function getDataArray ( callback ) {
// Convert the data.entries object into an array
2023-08-22 00:51:31 +02:00
let entriesArray = Object . keys ( data . entries ) . map ( uid => {
2023-08-21 20:10:11 +02:00
const entry = data . entries [ uid ] ;
entry . displayIndex = entry . displayIndex ? ? entry . uid ;
return entry ;
} ) ;
// Sort the entries array by displayIndex and uid
entriesArray . sort ( ( a , b ) => a . displayIndex - b . displayIndex || a . uid - b . uid ) ;
2023-08-22 00:51:31 +02:00
entriesArray = worldInfoFilter . applyFilters ( entriesArray ) ;
2023-08-21 20:10:11 +02:00
callback ( entriesArray ) ;
2023-08-22 00:51:31 +02:00
return entriesArray ;
2023-08-21 20:10:11 +02:00
}
let startPage = 1 ;
if ( navigation === navigation _option . previous ) {
startPage = $ ( "#world_info_pagination" ) . pagination ( 'getCurrentPageNum' ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-21 20:10:11 +02:00
const storageKey = 'WI_PerPage' ;
$ ( "#world_info_pagination" ) . pagination ( {
dataSource : getDataArray ,
2023-08-22 00:51:31 +02:00
pageSize : 25 ,
//pageSize: Number(localStorage.getItem(storageKey)) || 25,
//sizeChangerOptions: [10, 25, 50, 100],
//showSizeChanger: true,
2023-08-21 20:10:11 +02:00
pageRange : 1 ,
pageNumber : startPage ,
position : 'top' ,
showPageNumbers : false ,
prevText : '<' ,
nextText : '>' ,
formatNavigator : PAGINATION _TEMPLATE ,
showNavigator : true ,
callback : function ( page ) {
$ ( "#world_popup_entries_list" ) . empty ( ) ;
const blocks = page . map ( entry => getWorldEntry ( name , data , entry ) ) ;
$ ( "#world_popup_entries_list" ) . append ( blocks ) ;
} ,
afterSizeSelectorChange : function ( e ) {
localStorage . setItem ( storageKey , e . target . value ) ;
}
} ) ;
2023-07-20 19:32:15 +02:00
2023-08-21 20:10:11 +02:00
if ( navigation === navigation _option . last ) {
$ ( "#world_info_pagination" ) . pagination ( 'go' , $ ( "#world_info_pagination" ) . pagination ( 'getTotalPage' ) ) ;
2023-07-20 19:32:15 +02:00
}
$ ( "#world_popup_new" ) . off ( 'click' ) . on ( 'click' , ( ) => {
createWorldInfoEntry ( name , data ) ;
} ) ;
$ ( "#world_popup_name_button" ) . off ( 'click' ) . on ( 'click' , async ( ) => {
await renameWorldInfo ( name , data ) ;
} ) ;
$ ( "#world_popup_export" ) . off ( 'click' ) . on ( 'click' , ( ) => {
if ( name && data ) {
const jsonValue = JSON . stringify ( data ) ;
const fileName = ` ${ name } .json ` ;
download ( jsonValue , fileName , "application/json" ) ;
}
} ) ;
$ ( "#world_popup_delete" ) . off ( 'click' ) . on ( 'click' , async ( ) => {
const confirmation = await callPopup ( ` <h3>Delete the World/Lorebook: " ${ name } "?</h3>This action is irreversible! ` , "confirm" ) ;
if ( ! confirmation ) {
return ;
}
if ( world _info . charLore ) {
world _info . charLore . forEach ( ( charLore , index ) => {
if ( charLore . extraBooks ? . includes ( name ) ) {
const tempCharLore = charLore . extraBooks . filter ( ( e ) => e !== name ) ;
if ( tempCharLore . length === 0 ) {
world _info . charLore . splice ( index , 1 ) ;
} else {
charLore . extraBooks = tempCharLore ;
}
}
} ) ;
saveSettingsDebounced ( ) ;
}
// Selected world_info automatically refreshes
await deleteWorldInfo ( name ) ;
} ) ;
// Check if a sortable instance exists
if ( $ ( '#world_popup_entries_list' ) . sortable ( 'instance' ) !== undefined ) {
// Destroy the instance
$ ( '#world_popup_entries_list' ) . sortable ( 'destroy' ) ;
}
$ ( "#world_popup_entries_list" ) . sortable ( {
2023-08-18 12:41:46 +02:00
delay : getSortableDelay ( ) ,
2023-07-20 19:32:15 +02:00
handle : ".drag-handle" ,
stop : async function ( event , ui ) {
$ ( '#world_popup_entries_list .world_entry' ) . each ( function ( index ) {
const uid = $ ( this ) . data ( 'uid' ) ;
// Update the display index in the data array
const item = data . entries [ uid ] ;
if ( ! item ) {
console . debug ( ` Could not find entry with uid ${ uid } ` ) ;
return ;
}
item . displayIndex = index ;
setOriginalDataValue ( data , uid , 'extensions.display_index' , index ) ;
} ) ;
console . table ( Object . keys ( data . entries ) . map ( uid => data . entries [ uid ] ) . map ( x => ( { uid : x . uid , key : x . key . join ( ',' ) , displayIndex : x . displayIndex } ) ) ) ;
await saveWorldInfo ( name , data , true ) ;
}
} ) ;
//$("#world_popup_entries_list").disableSelection();
}
function setOriginalDataValue ( data , uid , key , value ) {
if ( data . originalData && Array . isArray ( data . originalData . entries ) ) {
let originalEntry = data . originalData . entries . find ( x => x . uid === uid ) ;
if ( ! originalEntry ) {
return ;
}
const keyParts = key . split ( '.' ) ;
let currentObject = originalEntry ;
for ( let i = 0 ; i < keyParts . length - 1 ; i ++ ) {
const part = keyParts [ i ] ;
if ( ! currentObject . hasOwnProperty ( part ) ) {
currentObject [ part ] = { } ;
}
currentObject = currentObject [ part ] ;
}
currentObject [ keyParts [ keyParts . length - 1 ] ] = value ;
}
}
function deleteOriginalDataValue ( data , uid ) {
if ( data . originalData && Array . isArray ( data . originalData . entries ) ) {
const originalIndex = data . originalData . entries . findIndex ( x => x . uid === uid ) ;
if ( originalIndex >= 0 ) {
data . originalData . entries . splice ( originalIndex , 1 ) ;
}
}
}
2023-08-21 20:10:11 +02:00
function getWorldEntry ( name , data , entry ) {
2023-07-20 19:32:15 +02:00
const template = $ ( "#entry_edit_template .world_entry" ) . clone ( ) ;
template . data ( "uid" , entry . uid ) ;
// key
const keyInput = template . find ( 'textarea[name="key"]' ) ;
keyInput . data ( "uid" , entry . uid ) ;
keyInput . on ( "click" , function ( event ) {
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
keyInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . val ( ) ;
resetScrollHeight ( this ) ;
data . entries [ uid ] . key = value
. split ( "," )
. map ( ( x ) => x . trim ( ) )
. filter ( ( x ) => x ) ;
setOriginalDataValue ( data , uid , "keys" , data . entries [ uid ] . key ) ;
saveWorldInfo ( name , data ) ;
} ) ;
keyInput . val ( entry . key . join ( "," ) ) . trigger ( "input" ) ;
initScrollHeight ( keyInput ) ;
// logic AND/NOT
const selectiveLogicDropdown = template . find ( 'select[name="entryLogicType"]' ) ;
selectiveLogicDropdown . data ( "uid" , entry . uid ) ;
selectiveLogicDropdown . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = Number ( $ ( this ) . val ( ) ) ;
console . debug ( ` logic for ${ entry . uid } set to ${ value } ` )
data . entries [ uid ] . selectiveLogic = ! isNaN ( value ) ? value : 0 ;
setOriginalDataValue ( data , uid , "selectiveLogic" , data . entries [ uid ] . selectiveLogic ) ;
saveWorldInfo ( name , data ) ;
} ) ;
template
. find ( ` select[name="entryLogicType"] option[value= ${ entry . selectiveLogic } ] ` )
. prop ( "selected" , true )
. trigger ( "input" ) ;
// keysecondary
const keySecondaryInput = template . find ( 'textarea[name="keysecondary"]' ) ;
keySecondaryInput . data ( "uid" , entry . uid ) ;
keySecondaryInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . val ( ) ;
resetScrollHeight ( this ) ;
data . entries [ uid ] . keysecondary = value
. split ( "," )
. map ( ( x ) => x . trim ( ) )
. filter ( ( x ) => x ) ;
setOriginalDataValue ( data , uid , "secondary_keys" , data . entries [ uid ] . keysecondary ) ;
saveWorldInfo ( name , data ) ;
} ) ;
keySecondaryInput . val ( entry . keysecondary . join ( "," ) ) . trigger ( "input" ) ;
initScrollHeight ( keySecondaryInput ) ;
// comment
const commentInput = template . find ( 'textarea[name="comment"]' ) ;
const commentToggle = template . find ( 'input[name="addMemo"]' ) ;
commentInput . data ( "uid" , entry . uid ) ;
commentInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . val ( ) ;
data . entries [ uid ] . comment = value ;
setOriginalDataValue ( data , uid , "comment" , data . entries [ uid ] . comment ) ;
saveWorldInfo ( name , data ) ;
} ) ;
commentToggle . data ( "uid" , entry . uid ) ;
commentToggle . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
//console.log(value)
const commentContainer = $ ( this )
. closest ( ".world_entry" )
. find ( ".commentContainer" ) ;
data . entries [ uid ] . addMemo = value ;
saveWorldInfo ( name , data ) ;
value ? commentContainer . show ( ) : commentContainer . hide ( ) ;
} ) ;
commentInput . val ( entry . comment ) . trigger ( "input" ) ;
commentToggle . prop ( "checked" , true /* entry.addMemo */ ) . trigger ( "input" ) ;
commentToggle . parent ( ) . hide ( )
// content
2023-08-21 17:16:40 +02:00
const counter = template . find ( ".world_entry_form_token_counter" ) ;
2023-08-21 20:10:11 +02:00
const countTokensDebounced = debounce ( function ( counter , value ) {
const numberOfTokens = getTokenCount ( value ) ;
$ ( counter ) . text ( numberOfTokens ) ;
} , 1000 ) ;
2023-07-20 19:32:15 +02:00
const contentInput = template . find ( 'textarea[name="content"]' ) ;
contentInput . data ( "uid" , entry . uid ) ;
2023-08-20 17:32:02 +02:00
contentInput . on ( "input" , function ( _ , { skipCount } = { } ) {
2023-07-20 19:32:15 +02:00
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . val ( ) ;
data . entries [ uid ] . content = value ;
setOriginalDataValue ( data , uid , "content" , data . entries [ uid ] . content ) ;
saveWorldInfo ( name , data ) ;
2023-08-20 12:15:02 +02:00
if ( skipCount ) {
return ;
}
2023-07-20 19:32:15 +02:00
// count tokens
2023-08-21 17:16:40 +02:00
countTokensDebounced ( counter , value ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-08-20 12:15:02 +02:00
contentInput . val ( entry . content ) . trigger ( "input" , { skipCount : true } ) ;
2023-07-20 19:32:15 +02:00
//initScrollHeight(contentInput);
2023-08-20 12:15:02 +02:00
template . find ( '.inline-drawer-toggle' ) . on ( 'click' , function ( ) {
if ( counter . data ( 'first-run' ) ) {
counter . data ( 'first-run' , false ) ;
2023-08-21 17:16:40 +02:00
countTokensDebounced ( counter , contentInput . val ( ) ) ;
2023-08-20 12:15:02 +02:00
}
} ) ;
2023-07-20 19:32:15 +02:00
// selective
const selectiveInput = template . find ( 'input[name="selective"]' ) ;
selectiveInput . data ( "uid" , entry . uid ) ;
selectiveInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
data . entries [ uid ] . selective = value ;
setOriginalDataValue ( data , uid , "selective" , data . entries [ uid ] . selective ) ;
saveWorldInfo ( name , data ) ;
const keysecondary = $ ( this )
. closest ( ".world_entry" )
. find ( ".keysecondary" ) ;
const keysecondarytextpole = $ ( this )
. closest ( ".world_entry" )
. find ( ".keysecondarytextpole" ) ;
const keyprimarytextpole = $ ( this )
. closest ( ".world_entry" )
. find ( ".keyprimarytextpole" ) ;
const keyprimaryHeight = keyprimarytextpole . outerHeight ( ) ;
keysecondarytextpole . css ( 'height' , keyprimaryHeight + 'px' ) ;
value ? keysecondary . show ( ) : keysecondary . hide ( ) ;
} ) ;
//forced on, ignored if empty
selectiveInput . prop ( "checked" , true /* entry.selective */ ) . trigger ( "input" ) ;
selectiveInput . parent ( ) . hide ( ) ;
// constant
const constantInput = template . find ( 'input[name="constant"]' ) ;
constantInput . data ( "uid" , entry . uid ) ;
constantInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
data . entries [ uid ] . constant = value ;
setOriginalDataValue ( data , uid , "constant" , data . entries [ uid ] . constant ) ;
saveWorldInfo ( name , data ) ;
} ) ;
constantInput . prop ( "checked" , entry . constant ) . trigger ( "input" ) ;
// order
const orderInput = template . find ( 'input[name="order"]' ) ;
orderInput . data ( "uid" , entry . uid ) ;
orderInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . order = ! isNaN ( value ) ? value : 0 ;
setOriginalDataValue ( data , uid , "insertion_order" , data . entries [ uid ] . order ) ;
saveWorldInfo ( name , data ) ;
} ) ;
orderInput . val ( entry . order ) . trigger ( "input" ) ;
// probability
if ( entry . probability === undefined ) {
entry . probability = null ;
}
const probabilityInput = template . find ( 'input[name="probability"]' ) ;
probabilityInput . data ( "uid" , entry . uid ) ;
probabilityInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = parseInt ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . probability = ! isNaN ( value ) ? value : null ;
// Clamp probability to 0-100
if ( data . entries [ uid ] . probability !== null ) {
data . entries [ uid ] . probability = Math . min ( 100 , Math . max ( 0 , data . entries [ uid ] . probability ) ) ;
if ( data . entries [ uid ] . probability !== value ) {
$ ( this ) . val ( data . entries [ uid ] . probability ) ;
}
}
setOriginalDataValue ( data , uid , "extensions.probability" , data . entries [ uid ] . probability ) ;
saveWorldInfo ( name , data ) ;
} ) ;
probabilityInput . val ( entry . probability ) . trigger ( "input" ) ;
// probability toggle
if ( entry . useProbability === undefined ) {
entry . useProbability = false ;
}
const probabilityToggle = template . find ( 'input[name="useProbability"]' ) ;
probabilityToggle . data ( "uid" , entry . uid ) ;
probabilityToggle . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
data . entries [ uid ] . useProbability = value ;
const probabilityContainer = $ ( this )
. closest ( ".world_entry" )
. find ( ".probabilityContainer" ) ;
saveWorldInfo ( name , data ) ;
value ? probabilityContainer . show ( ) : probabilityContainer . hide ( ) ;
if ( value && data . entries [ uid ] . probability === null ) {
data . entries [ uid ] . probability = 100 ;
}
if ( ! value ) {
data . entries [ uid ] . probability = null ;
}
probabilityInput . val ( data . entries [ uid ] . probability ) . trigger ( "input" ) ;
} ) ;
//forced on, 100% by default
probabilityToggle . prop ( "checked" , true /* entry.useProbability */ ) . trigger ( "input" ) ;
probabilityToggle . parent ( ) . hide ( ) ;
// position
if ( entry . position === undefined ) {
entry . position = 0 ;
}
const positionInput = template . find ( 'select[name="position"]' ) ;
positionInput . data ( "uid" , entry . uid ) ;
positionInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . position = ! isNaN ( value ) ? value : 0 ;
// Spec v2 only supports before_char and after_char
setOriginalDataValue ( data , uid , "position" , data . entries [ uid ] . position == 0 ? 'before_char' : 'after_char' ) ;
// Write the original value as extensions field
setOriginalDataValue ( data , uid , "extensions.position" , data . entries [ uid ] . position ) ;
saveWorldInfo ( name , data ) ;
} ) ;
template
. find ( ` select[name="position"] option[value= ${ entry . position } ] ` )
. prop ( "selected" , true )
. trigger ( "input" ) ;
// display uid
template . find ( ".world_entry_form_uid_value" ) . text ( entry . uid ) ;
// disable
const disableInput = template . find ( 'input[name="disable"]' ) ;
disableInput . data ( "uid" , entry . uid ) ;
disableInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
data . entries [ uid ] . disable = value ;
setOriginalDataValue ( data , uid , "enabled" , ! data . entries [ uid ] . disable ) ;
saveWorldInfo ( name , data ) ;
} ) ;
disableInput . prop ( "checked" , entry . disable ) . trigger ( "input" ) ;
const excludeRecursionInput = template . find ( 'input[name="exclude_recursion"]' ) ;
excludeRecursionInput . data ( "uid" , entry . uid ) ;
excludeRecursionInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
data . entries [ uid ] . excludeRecursion = value ;
setOriginalDataValue ( data , uid , "extensions.exclude_recursion" , data . entries [ uid ] . excludeRecursion ) ;
saveWorldInfo ( name , data ) ;
} ) ;
excludeRecursionInput . prop ( "checked" , entry . excludeRecursion ) . trigger ( "input" ) ;
// delete button
const deleteButton = template . find ( "input.delete_entry_button" ) ;
deleteButton . data ( "uid" , entry . uid ) ;
deleteButton . on ( "click" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
deleteWorldInfoEntry ( data , uid ) ;
deleteOriginalDataValue ( data , uid ) ;
saveWorldInfo ( name , data ) ;
2023-08-21 20:10:11 +02:00
updateEditor ( navigation _option . previous ) ;
2023-07-20 19:32:15 +02:00
} ) ;
template . find ( '.inline-drawer-content' ) . css ( 'display' , 'none' ) ; //entries start collapsed
return template ;
}
async function deleteWorldInfoEntry ( data , uid ) {
if ( ! data || ! ( "entries" in data ) ) {
return ;
}
delete data . entries [ uid ] ;
}
function createWorldInfoEntry ( name , data ) {
const newEntryTemplate = {
key : [ ] ,
keysecondary : [ ] ,
comment : "" ,
content : "" ,
constant : false ,
selective : true ,
selectiveLogic : 0 ,
addMemo : false ,
order : 100 ,
position : 0 ,
disable : false ,
excludeRecursion : false ,
probability : 100 ,
useProbability : true ,
} ;
const newUid = getFreeWorldEntryUid ( data ) ;
if ( ! Number . isInteger ( newUid ) ) {
console . error ( "Couldn't assign UID to a new entry" ) ;
return ;
}
const newEntry = { uid : newUid , ... newEntryTemplate } ;
data . entries [ newUid ] = newEntry ;
2023-08-21 20:10:11 +02:00
updateEditor ( navigation _option . last ) ;
2023-07-20 19:32:15 +02:00
}
async function _save ( name , data ) {
const response = await fetch ( "/editworldinfo" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { name : name , data : data } ) ,
} ) ;
}
async function saveWorldInfo ( name , data , immediately ) {
if ( ! name || ! data ) {
return ;
}
delete worldInfoCache [ name ] ;
if ( immediately ) {
return await _save ( name , data ) ;
}
saveWorldDebounced ( name , data ) ;
}
async function renameWorldInfo ( name , data ) {
const oldName = name ;
const newName = await callPopup ( "<h3>Rename World Info</h3>Enter a new name:" , 'input' , oldName ) ;
if ( oldName === newName || ! newName ) {
console . debug ( "World info rename cancelled" ) ;
return ;
}
const entryPreviouslySelected = selected _world _info . findIndex ( ( e ) => e === oldName ) ;
await saveWorldInfo ( newName , data , true ) ;
await deleteWorldInfo ( oldName ) ;
const existingCharLores = world _info . charLore ? . filter ( ( e ) => e . extraBooks . includes ( oldName ) ) ;
if ( existingCharLores && existingCharLores . length > 0 ) {
existingCharLores . forEach ( ( charLore ) => {
const tempCharLore = charLore . extraBooks . filter ( ( e ) => e !== oldName ) ;
tempCharLore . push ( newName ) ;
charLore . extraBooks = tempCharLore ;
} ) ;
saveSettingsDebounced ( ) ;
}
if ( entryPreviouslySelected !== - 1 ) {
const wiElement = getWIElement ( newName ) ;
wiElement . prop ( "selected" , true ) ;
$ ( "#world_info" ) . trigger ( 'change' ) ;
}
const selectedIndex = world _names . indexOf ( newName ) ;
if ( selectedIndex !== - 1 ) {
$ ( '#world_editor_select' ) . val ( selectedIndex ) . trigger ( 'change' ) ;
}
}
async function deleteWorldInfo ( worldInfoName ) {
if ( ! world _names . includes ( worldInfoName ) ) {
return ;
}
const response = await fetch ( "/deleteworldinfo" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { name : worldInfoName } ) ,
} ) ;
if ( response . ok ) {
const existingWorldIndex = selected _world _info . findIndex ( ( e ) => e === worldInfoName ) ;
if ( existingWorldIndex !== - 1 ) {
selected _world _info . splice ( existingWorldIndex , 1 ) ;
saveSettingsDebounced ( ) ;
}
await updateWorldInfoList ( ) ;
$ ( '#world_editor_select' ) . trigger ( 'change' ) ;
if ( $ ( '#character_world' ) . val ( ) === worldInfoName ) {
$ ( '#character_world' ) . val ( '' ) . trigger ( 'change' ) ;
setWorldInfoButtonClass ( undefined , false ) ;
if ( menu _type != 'create' ) {
saveCharacterDebounced ( ) ;
}
}
}
}
function getFreeWorldEntryUid ( data ) {
if ( ! data || ! ( "entries" in data ) ) {
return null ;
}
const MAX _UID = 1_000_000 ; // <- should be safe enough :)
for ( let uid = 0 ; uid < MAX _UID ; uid ++ ) {
if ( uid in data . entries ) {
continue ;
}
return uid ;
}
return null ;
}
function getFreeWorldName ( ) {
const MAX _FREE _NAME = 100_000 ;
for ( let index = 1 ; index < MAX _FREE _NAME ; index ++ ) {
const newName = ` New World ( ${ index } ) ` ;
if ( world _names . includes ( newName ) ) {
continue ;
}
return newName ;
}
return undefined ;
}
async function createNewWorldInfo ( worldInfoName ) {
const worldInfoTemplate = { entries : { } } ;
if ( ! worldInfoName ) {
return ;
}
await saveWorldInfo ( worldInfoName , worldInfoTemplate , true ) ;
await updateWorldInfoList ( ) ;
const selectedIndex = world _names . indexOf ( worldInfoName ) ;
if ( selectedIndex !== - 1 ) {
$ ( '#world_editor_select' ) . val ( selectedIndex ) . trigger ( 'change' ) ;
} else {
hideWorldEditor ( ) ;
}
}
// Gets a string that respects the case sensitivity setting
function transformString ( str ) {
return world _info _case _sensitive ? str : str . toLowerCase ( ) ;
}
async function getCharacterLore ( ) {
const character = characters [ this _chid ] ;
const name = character ? . name ;
let worldsToSearch = new Set ( ) ;
const baseWorldName = character ? . data ? . extensions ? . world ;
if ( baseWorldName ) {
worldsToSearch . add ( baseWorldName ) ;
} else {
console . debug ( ` Character ${ name } 's base world could not be found or is empty! Skipping... ` )
return [ ] ;
}
// TODO: Maybe make the utility function not use the window context?
const fileName = getCharaFilename ( this _chid ) ;
const extraCharLore = world _info . charLore ? . find ( ( e ) => e . name === fileName ) ;
if ( extraCharLore ) {
worldsToSearch = new Set ( [ ... worldsToSearch , ... extraCharLore . extraBooks ] ) ;
}
let entries = [ ] ;
for ( const worldName of worldsToSearch ) {
if ( selected _world _info . includes ( worldName ) ) {
console . debug ( ` Character ${ name } 's world ${ worldName } is already activated in global world info! Skipping... ` ) ;
continue ;
}
const data = await loadWorldInfoData ( worldName ) ;
const newEntries = data ? Object . keys ( data . entries ) . map ( ( x ) => data . entries [ x ] ) : [ ] ;
entries = entries . concat ( newEntries ) ;
}
console . debug ( ` Character ${ characters [ this _chid ] ? . name } lore ( ${ baseWorldName } ) has ${ entries . length } world info entries ` ) ;
return entries ;
}
async function getGlobalLore ( ) {
if ( ! selected _world _info ) {
return [ ] ;
}
let entries = [ ] ;
for ( const worldName of selected _world _info ) {
const data = await loadWorldInfoData ( worldName ) ;
const newEntries = data ? Object . keys ( data . entries ) . map ( ( x ) => data . entries [ x ] ) : [ ] ;
entries = entries . concat ( newEntries ) ;
}
console . debug ( ` Global world info has ${ entries . length } entries ` ) ;
return entries ;
}
async function getSortedEntries ( ) {
try {
const globalLore = await getGlobalLore ( ) ;
const characterLore = await getCharacterLore ( ) ;
let entries ;
switch ( Number ( world _info _character _strategy ) ) {
case world _info _insertion _strategy . evenly :
console . debug ( 'WI using evenly' )
entries = [ ... globalLore , ... characterLore ] . sort ( sortFn ) ;
break ;
case world _info _insertion _strategy . character _first :
console . debug ( 'WI using char first' )
entries = [ ... characterLore . sort ( sortFn ) , ... globalLore . sort ( sortFn ) ] ;
break ;
case world _info _insertion _strategy . global _first :
console . debug ( 'WI using global first' )
entries = [ ... globalLore . sort ( sortFn ) , ... characterLore . sort ( sortFn ) ] ;
break ;
default :
console . error ( "Unknown WI insertion strategy: " , world _info _character _strategy , "defaulting to evenly" ) ;
entries = [ ... globalLore , ... characterLore ] . sort ( sortFn ) ;
break ;
}
console . debug ( ` Sorted ${ entries . length } world lore entries using strategy ${ world _info _character _strategy } ` ) ;
// Need to deep clone the entries to avoid modifying the cached data
return deepClone ( entries ) ;
}
catch ( e ) {
console . error ( e ) ;
return [ ] ;
}
}
async function checkWorldInfo ( chat , maxContext ) {
const context = getContext ( ) ;
const messagesToLookBack = world _info _depth * 2 || 1 ;
let textToScan = transformString ( chat . slice ( 0 , messagesToLookBack ) . join ( "" ) ) ;
let needsToScan = true ;
let count = 0 ;
let allActivatedEntries = new Set ( ) ;
let failedProbabilityChecks = new Set ( ) ;
let allActivatedText = '' ;
2023-08-10 19:45:57 +02:00
let budget = Math . round ( world _info _budget * maxContext / 100 ) || 1 ;
if ( world _info _budget _cap > 0 && budget > world _info _budget _cap ) {
console . debug ( ` Budget ${ budget } exceeds cap ${ world _info _budget _cap } , using cap ` ) ;
budget = world _info _budget _cap ;
}
console . debug ( ` Context size: ${ maxContext } ; WI budget: ${ budget } (max% = ${ world _info _budget } %, cap = ${ world _info _budget _cap } ) ` ) ;
2023-07-20 19:32:15 +02:00
const sortedEntries = await getSortedEntries ( ) ;
if ( sortedEntries . length === 0 ) {
return { worldInfoBefore : '' , worldInfoAfter : '' } ;
}
while ( needsToScan ) {
// Track how many times the loop has run
count ++ ;
let activatedNow = new Set ( ) ;
for ( let entry of sortedEntries ) {
if ( failedProbabilityChecks . has ( entry ) ) {
continue ;
}
if ( allActivatedEntries . has ( entry ) || entry . disable == true || ( count > 1 && world _info _recursive && entry . excludeRecursion ) ) {
continue ;
}
if ( entry . constant ) {
entry . content = substituteParams ( entry . content )
activatedNow . add ( entry ) ;
continue ;
}
if ( Array . isArray ( entry . key ) && entry . key . length ) { //check for keywords existing
primary : for ( let key of entry . key ) {
const substituted = substituteParams ( key ) ;
console . debug ( ` ${ entry . uid } : ${ substituted } ` )
if ( substituted && matchKeys ( textToScan , substituted . trim ( ) ) ) {
console . debug ( ` ${ entry . uid } : got primary match ` )
//selective logic begins
if (
entry . selective && //all entries are selective now
Array . isArray ( entry . keysecondary ) && //always true
entry . keysecondary . length //ignore empties
) {
console . debug ( ` uid: ${ entry . uid } : checking logic: ${ entry . selectiveLogic } ` )
secondary : for ( let keysecondary of entry . keysecondary ) {
const secondarySubstituted = substituteParams ( keysecondary ) ;
console . debug ( ` uid: ${ entry . uid } : filtering ${ secondarySubstituted } ` ) ;
// If selectiveLogic isn't found, assume it's AND
const selectiveLogic = entry . selectiveLogic ? ? 0 ;
//AND operator
if ( selectiveLogic === 0 ) {
console . debug ( 'saw AND logic, checking..' )
if ( secondarySubstituted && matchKeys ( textToScan , secondarySubstituted . trim ( ) ) ) {
2023-07-28 20:58:18 +02:00
console . debug ( ` activating entry ${ entry . uid } with AND found ` )
2023-07-20 19:32:15 +02:00
activatedNow . add ( entry ) ;
break secondary ;
}
}
//NOT operator
if ( selectiveLogic === 1 ) {
console . debug ( ` uid ${ entry . uid } : checking NOT logic for ${ secondarySubstituted } ` )
if ( secondarySubstituted && matchKeys ( textToScan , secondarySubstituted . trim ( ) ) ) {
console . debug ( ` uid ${ entry . uid } : canceled; filtered out by ${ secondarySubstituted } ` )
break primary ;
} else {
console . debug ( ` ${ entry . uid } : activated; passed NOT filter ` )
activatedNow . add ( entry ) ;
break secondary ;
}
}
}
//handle cases where secondary is empty
} else {
console . debug ( ` uid ${ entry . uid } : activated without filter logic ` )
activatedNow . add ( entry ) ;
break primary ;
}
} else { console . debug ( 'no active entries for logic checks yet' ) }
}
}
}
needsToScan = world _info _recursive && activatedNow . size > 0 ;
const newEntries = [ ... activatedNow ]
. sort ( ( a , b ) => sortedEntries . indexOf ( a ) - sortedEntries . indexOf ( b ) ) ;
let newContent = "" ;
const textToScanTokens = getTokenCount ( allActivatedText ) ;
const probabilityChecksBefore = failedProbabilityChecks . size ;
console . debug ( ` -- PROBABILITY CHECKS BEGIN -- ` )
for ( const entry of newEntries ) {
const rollValue = Math . random ( ) * 100 ;
if ( entry . useProbability && rollValue > entry . probability ) {
console . debug ( ` WI entry ${ entry . uid } ${ entry . key } failed probability check, skipping ` ) ;
failedProbabilityChecks . add ( entry ) ;
continue ;
} else { console . debug ( ` uid: ${ entry . uid } passed probability check, inserting to prompt ` ) }
newContent += ` ${ substituteParams ( entry . content ) } \n ` ;
if ( textToScanTokens + getTokenCount ( newContent ) >= budget ) {
console . debug ( ` WI budget reached, stopping ` ) ;
2023-07-30 04:15:54 +02:00
if ( world _info _overflow _alert ) {
console . log ( "Alerting" ) ;
toastr . warning ( ` World info budget reached after ${ count } entries. ` , 'World Info' ) ;
}
2023-07-20 19:32:15 +02:00
needsToScan = false ;
break ;
}
allActivatedEntries . add ( entry ) ;
console . debug ( 'WI entry activated:' , entry ) ;
}
const probabilityChecksAfter = failedProbabilityChecks . size ;
if ( ( probabilityChecksAfter - probabilityChecksBefore ) === activatedNow . size ) {
console . debug ( ` WI probability checks failed for all activated entries, stopping ` ) ;
needsToScan = false ;
}
if ( needsToScan ) {
const text = newEntries
. filter ( x => ! failedProbabilityChecks . has ( x ) )
. map ( x => x . content ) . join ( '\n' ) ;
const currentlyActivatedText = transformString ( text ) ;
textToScan = ( currentlyActivatedText + '\n' + textToScan ) ;
allActivatedText = ( currentlyActivatedText + '\n' + allActivatedText ) ;
}
}
// Forward-sorted list of entries for joining
const WIBeforeEntries = [ ] ;
const WIAfterEntries = [ ] ;
const ANTopEntries = [ ] ;
const ANBottomEntries = [ ] ;
// Appends from insertion order 999 to 1. Use unshift for this purpose
[ ... allActivatedEntries ] . sort ( sortFn ) . forEach ( ( entry ) => {
switch ( entry . position ) {
case world _info _position . before :
WIBeforeEntries . unshift ( substituteParams ( entry . content ) ) ;
break ;
case world _info _position . after :
WIAfterEntries . unshift ( substituteParams ( entry . content ) ) ;
break ;
case world _info _position . ANTop :
ANTopEntries . unshift ( entry . content ) ;
break ;
case world _info _position . ANBottom :
ANBottomEntries . unshift ( entry . content ) ;
break ;
default :
break ;
}
} ) ;
const worldInfoBefore = WIBeforeEntries . length ? ` ${ WIBeforeEntries . join ( "\n" ) } \n ` : '' ;
const worldInfoAfter = WIAfterEntries . length ? ` ${ WIAfterEntries . join ( "\n" ) } \n ` : '' ;
if ( shouldWIAddPrompt ) {
const originalAN = context . extensionPrompts [ NOTE _MODULE _NAME ] . value ;
const ANWithWI = ` ${ ANTopEntries . join ( "\n" ) } \n ${ originalAN } \n ${ ANBottomEntries . join ( "\n" ) } `
context . setExtensionPrompt ( NOTE _MODULE _NAME , ANWithWI , chat _metadata [ metadata _keys . position ] , chat _metadata [ metadata _keys . depth ] ) ;
}
return { worldInfoBefore , worldInfoAfter } ;
}
function matchKeys ( haystack , needle ) {
const transformedString = transformString ( needle ) ;
if ( world _info _match _whole _words ) {
const keyWords = transformedString . split ( /\s+/ ) ;
if ( keyWords . length > 1 ) {
return haystack . includes ( transformedString ) ;
}
else {
2023-08-18 13:54:38 +02:00
const regex = new RegExp ( ` \\ b ${ escapeRegex ( transformedString ) } \\ b ` ) ;
2023-07-20 19:32:15 +02:00
if ( regex . test ( haystack ) ) {
return true ;
}
}
} else {
return haystack . includes ( transformedString ) ;
}
return false ;
}
function convertAgnaiMemoryBook ( inputObj ) {
const outputObj = { entries : { } } ;
inputObj . entries . forEach ( ( entry , index ) => {
outputObj . entries [ index ] = {
uid : index ,
key : entry . keywords ,
keysecondary : [ ] ,
comment : entry . name ,
content : entry . entry ,
constant : false ,
selective : false ,
order : entry . weight ,
position : 0 ,
disable : ! entry . enabled ,
addMemo : ! ! entry . name ,
excludeRecursion : false ,
displayIndex : index ,
probability : null ,
useProbability : false ,
} ;
} ) ;
return outputObj ;
}
function convertRisuLorebook ( inputObj ) {
const outputObj = { entries : { } } ;
inputObj . data . forEach ( ( entry , index ) => {
outputObj . entries [ index ] = {
uid : index ,
key : entry . key . split ( ',' ) . map ( x => x . trim ( ) ) ,
keysecondary : entry . secondkey ? entry . secondkey . split ( ',' ) . map ( x => x . trim ( ) ) : [ ] ,
comment : entry . comment ,
content : entry . content ,
constant : entry . alwaysActive ,
selective : entry . selective ,
order : entry . insertorder ,
position : world _info _position . before ,
disable : false ,
addMemo : true ,
excludeRecursion : false ,
displayIndex : index ,
probability : entry . activationPercent ? ? null ,
useProbability : entry . activationPercent ? ? false ,
} ;
} ) ;
return outputObj ;
}
function convertNovelLorebook ( inputObj ) {
const outputObj = {
entries : { }
} ;
inputObj . entries . forEach ( ( entry , index ) => {
const displayName = entry . displayName ;
const addMemo = displayName !== undefined && displayName . trim ( ) !== '' ;
outputObj . entries [ index ] = {
uid : index ,
key : entry . keys ,
keysecondary : [ ] ,
comment : displayName || '' ,
content : entry . text ,
constant : false ,
selective : false ,
order : entry . contextConfig ? . budgetPriority ? ? 0 ,
position : 0 ,
disable : ! entry . enabled ,
addMemo : addMemo ,
excludeRecursion : false ,
displayIndex : index ,
probability : null ,
useProbability : false ,
} ;
} ) ;
return outputObj ;
}
function convertCharacterBook ( characterBook ) {
const result = { entries : { } , originalData : characterBook } ;
characterBook . entries . forEach ( ( entry , index ) => {
// Not in the spec, but this is needed to find the entry in the original data
if ( entry . id === undefined ) {
entry . id = index ;
}
result . entries [ entry . id ] = {
uid : entry . id ,
key : entry . keys ,
keysecondary : entry . secondary _keys || [ ] ,
comment : entry . comment || "" ,
content : entry . content ,
constant : entry . constant || false ,
selective : entry . selective || false ,
order : entry . insertion _order ,
position : entry . extensions ? . position ? ? ( entry . position === "before_char" ? world _info _position . before : world _info _position . after ) ,
excludeRecursion : entry . extensions ? . exclude _recursion ? ? false ,
disable : ! entry . enabled ,
addMemo : entry . comment ? true : false ,
displayIndex : entry . extensions ? . display _index ? ? index ,
probability : entry . extensions ? . probability ? ? null ,
useProbability : entry . extensions ? . useProbability ? ? false ,
} ;
} ) ;
return result ;
}
export function setWorldInfoButtonClass ( chid , forceValue = undefined ) {
if ( forceValue !== undefined ) {
$ ( '#set_character_world, #world_button' ) . toggleClass ( 'world_set' , forceValue ) ;
return ;
}
if ( ! chid ) {
return ;
}
const world = characters [ chid ] ? . data ? . extensions ? . world ;
const worldSet = Boolean ( world && world _names . includes ( world ) ) ;
$ ( '#set_character_world, #world_button' ) . toggleClass ( 'world_set' , worldSet ) ;
}
export function checkEmbeddedWorld ( chid ) {
$ ( '#import_character_info' ) . hide ( ) ;
if ( chid === undefined ) {
return false ;
}
if ( characters [ chid ] ? . data ? . character _book ) {
$ ( '#import_character_info' ) . data ( 'chid' , chid ) . show ( ) ;
// Only show the alert once per character
const checkKey = ` AlertWI_ ${ characters [ chid ] . avatar } ` ;
const worldName = characters [ chid ] ? . data ? . extensions ? . world ;
if ( ! localStorage . getItem ( checkKey ) && ( ! worldName || ! world _names . includes ( worldName ) ) ) {
toastr . info (
2023-07-25 00:13:32 +02:00
'To import and use it, select "Import Card Lore" in the "More..." dropdown menu on the character panel.' ,
2023-07-20 19:32:15 +02:00
` ${ characters [ chid ] . name } has an embedded World/Lorebook ` ,
{ timeOut : 10000 , extendedTimeOut : 20000 , positionClass : 'toast-top-center' } ,
) ;
localStorage . setItem ( checkKey , 1 ) ;
}
return true ;
}
return false ;
}
export async function importEmbeddedWorldInfo ( ) {
const chid = $ ( '#import_character_info' ) . data ( 'chid' ) ;
if ( chid === undefined ) {
return ;
}
const bookName = characters [ chid ] ? . data ? . character _book ? . name || ` ${ characters [ chid ] ? . name } 's Lorebook ` ;
const confirmationText = ( ` <h3>Are you sure you want to import " ${ bookName } "?</h3> ` ) + ( world _names . includes ( bookName ) ? 'It will overwrite the World/Lorebook with the same name.' : '' ) ;
const confirmation = await callPopup ( confirmationText , 'confirm' ) ;
if ( ! confirmation ) {
return ;
}
const convertedBook = convertCharacterBook ( characters [ chid ] . data . character _book ) ;
await saveWorldInfo ( bookName , convertedBook , true ) ;
await updateWorldInfoList ( ) ;
$ ( '#character_world' ) . val ( bookName ) . trigger ( 'change' ) ;
toastr . success ( ` The world " ${ bookName } " has been imported and linked to the character successfully. ` , 'World/Lorebook imported' ) ;
const newIndex = world _names . indexOf ( bookName ) ;
if ( newIndex >= 0 ) {
$ ( "#world_editor_select" ) . val ( newIndex ) . trigger ( 'change' ) ;
}
setWorldInfoButtonClass ( chid , true ) ;
}
function onWorldInfoChange ( _ , text ) {
if ( _ !== '__notSlashCommand__' ) { // if it's a slash command
if ( text !== undefined ) { // and args are provided
const slashInputSplitText = text . trim ( ) . toLowerCase ( ) . split ( "," ) ;
slashInputSplitText . forEach ( ( worldName ) => {
const wiElement = getWIElement ( worldName ) ;
if ( wiElement . length > 0 ) {
2023-08-13 18:20:00 +02:00
selected _world _info . push ( wiElement . text ( ) ) ;
2023-07-20 19:32:15 +02:00
wiElement . prop ( "selected" , true ) ;
toastr . success ( ` Activated world: ${ wiElement . text ( ) } ` ) ;
} else {
toastr . error ( ` No world found named: ${ worldName } ` ) ;
}
2023-08-13 18:20:00 +02:00
} ) ;
$ ( "#world_info" ) . trigger ( "change" ) ;
2023-07-20 19:32:15 +02:00
} else { // if no args, unset all worlds
toastr . success ( 'Deactivated all worlds' ) ;
selected _world _info = [ ] ;
$ ( "#world_info" ) . val ( "" ) ;
}
} else { //if it's a pointer selection
let tempWorldInfo = [ ] ;
let selectedWorlds = $ ( "#world_info" ) . val ( ) . map ( ( e ) => Number ( e ) ) . filter ( ( e ) => ! isNaN ( e ) ) ;
if ( selectedWorlds . length > 0 ) {
selectedWorlds . forEach ( ( worldIndex ) => {
const existingWorldName = world _names [ worldIndex ] ;
if ( existingWorldName ) {
tempWorldInfo . push ( existingWorldName ) ;
} else {
const wiElement = getWIElement ( existingWorldName ) ;
wiElement . prop ( "selected" , false ) ;
toastr . error ( ` The world with ${ existingWorldName } is invalid or corrupted. ` ) ;
}
} ) ;
}
selected _world _info = tempWorldInfo ;
}
saveSettingsDebounced ( ) ;
2023-07-03 19:06:18 +02:00
eventSource . emit ( event _types . WORLDINFO _SETTINGS _UPDATED ) ;
2023-07-20 19:32:15 +02:00
}
export async function importWorldInfo ( file ) {
if ( ! file ) {
return ;
}
const formData = new FormData ( ) ;
formData . append ( 'avatar' , file ) ;
try {
let jsonData ;
if ( file . name . endsWith ( '.png' ) ) {
const buffer = new Uint8Array ( await getFileBuffer ( file ) ) ;
jsonData = extractDataFromPng ( buffer , 'naidata' ) ;
} else {
// File should be a JSON file
jsonData = await parseJsonFile ( file ) ;
}
if ( jsonData === undefined || jsonData === null ) {
toastr . error ( ` File is not valid: ${ file . name } ` ) ;
return ;
}
// Convert Novel Lorebook
if ( jsonData . lorebookVersion !== undefined ) {
console . log ( 'Converting Novel Lorebook' ) ;
formData . append ( 'convertedData' , JSON . stringify ( convertNovelLorebook ( jsonData ) ) ) ;
}
// Convert Agnai Memory Book
if ( jsonData . kind === 'memory' ) {
console . log ( 'Converting Agnai Memory Book' ) ;
formData . append ( 'convertedData' , JSON . stringify ( convertAgnaiMemoryBook ( jsonData ) ) ) ;
}
// Convert Risu Lorebook
if ( jsonData . type === 'risu' ) {
console . log ( 'Converting Risu Lorebook' ) ;
formData . append ( 'convertedData' , JSON . stringify ( convertRisuLorebook ( jsonData ) ) ) ;
}
} catch ( error ) {
toastr . error ( ` Error parsing file: ${ error } ` ) ;
return ;
}
jQuery . ajax ( {
type : "POST" ,
url : "/importworldinfo" ,
data : formData ,
beforeSend : ( ) => { } ,
cache : false ,
contentType : false ,
processData : false ,
success : async function ( data ) {
if ( data . name ) {
await updateWorldInfoList ( ) ;
const newIndex = world _names . indexOf ( data . name ) ;
if ( newIndex >= 0 ) {
$ ( "#world_editor_select" ) . val ( newIndex ) . trigger ( 'change' ) ;
}
toastr . info ( ` World Info " ${ data . name } " imported successfully! ` ) ;
}
} ,
error : ( jqXHR , exception ) => { } ,
} ) ;
}
jQuery ( ( ) => {
$ ( document ) . ready ( function ( ) {
registerSlashCommand ( 'world' , onWorldInfoChange , [ ] , "– sets active World, or unsets if no args provided" , true , true ) ;
} )
$ ( "#world_info" ) . on ( 'mousedown change' , async function ( e ) {
// If there's no world names, don't do anything
if ( world _names . length === 0 ) {
e . preventDefault ( ) ;
return ;
}
/ *
if ( deviceInfo . device . type === 'desktop' ) {
let selectScrollTop = null ;
e . preventDefault ( ) ;
const option = $ ( e . target ) ;
const selectElement = $ ( this ) [ 0 ] ;
selectScrollTop = selectElement . scrollTop ;
option . prop ( 'selected' , ! option . prop ( 'selected' ) ) ;
await delay ( 1 ) ;
selectElement . scrollTop = selectScrollTop ;
}
* /
onWorldInfoChange ( '__notSlashCommand__' ) ;
} ) ;
//**************************WORLD INFO IMPORT EXPORT*************************//
$ ( "#world_import_button" ) . on ( 'click' , function ( ) {
$ ( "#world_import_file" ) . trigger ( 'click' ) ;
} ) ;
$ ( "#world_import_file" ) . on ( "change" , async function ( e ) {
const file = e . target . files [ 0 ] ;
await importWorldInfo ( file ) ;
// Will allow to select the same file twice in a row
2023-08-22 00:51:31 +02:00
e . target . value = '' ;
2023-07-20 19:32:15 +02:00
} ) ;
$ ( "#world_create_button" ) . on ( 'click' , async ( ) => {
const tempName = getFreeWorldName ( ) ;
const finalName = await callPopup ( "<h3>Create a new World Info?</h3>Enter a name for the new file:" , "input" , tempName ) ;
if ( finalName ) {
await createNewWorldInfo ( finalName ) ;
}
} ) ;
$ ( "#world_editor_select" ) . on ( 'change' , async ( ) => {
2023-08-22 00:51:31 +02:00
$ ( "#world_info_search" ) . val ( '' ) ;
worldInfoFilter . setFilterData ( FILTER _TYPES . WORLD _INFO _SEARCH , '' , true ) ;
2023-07-20 19:32:15 +02:00
const selectedIndex = $ ( "#world_editor_select" ) . find ( ":selected" ) . val ( ) ;
if ( selectedIndex === "" ) {
hideWorldEditor ( ) ;
} else {
const worldName = world _names [ selectedIndex ] ;
showWorldEditor ( worldName ) ;
}
} ) ;
2023-07-03 19:06:18 +02:00
const saveSettings = ( ) => {
saveSettingsDebounced ( )
eventSource . emit ( event _types . WORLDINFO _SETTINGS _UPDATED ) ;
}
2023-07-20 19:32:15 +02:00
$ ( document ) . on ( "input" , "#world_info_depth" , function ( ) {
world _info _depth = Number ( $ ( this ) . val ( ) ) ;
$ ( "#world_info_depth_counter" ) . text ( $ ( this ) . val ( ) ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
$ ( document ) . on ( "input" , "#world_info_budget" , function ( ) {
world _info _budget = Number ( $ ( this ) . val ( ) ) ;
$ ( "#world_info_budget_counter" ) . text ( $ ( this ) . val ( ) ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
$ ( document ) . on ( "input" , "#world_info_recursive" , function ( ) {
world _info _recursive = ! ! $ ( this ) . prop ( 'checked' ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} )
$ ( '#world_info_case_sensitive' ) . on ( 'input' , function ( ) {
world _info _case _sensitive = ! ! $ ( this ) . prop ( 'checked' ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} )
$ ( '#world_info_match_whole_words' ) . on ( 'input' , function ( ) {
world _info _match _whole _words = ! ! $ ( this ) . prop ( 'checked' ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
$ ( '#world_info_character_strategy' ) . on ( 'change' , function ( ) {
world _info _character _strategy = $ ( this ) . val ( ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-07-30 04:15:54 +02:00
$ ( '#world_info_overflow_alert' ) . on ( 'change' , function ( ) {
2023-08-07 21:12:50 +02:00
world _info _overflow _alert = ! ! $ ( this ) . prop ( 'checked' ) ;
2023-07-30 04:15:54 +02:00
saveSettingsDebounced ( ) ;
} ) ;
2023-08-10 19:45:57 +02:00
$ ( '#world_info_budget_cap' ) . on ( 'input' , function ( ) {
world _info _budget _cap = Number ( $ ( this ) . val ( ) ) ;
$ ( "#world_info_budget_cap_counter" ) . text ( world _info _budget _cap ) ;
2023-08-13 18:50:12 +02:00
saveSettings ( ) ;
2023-08-10 19:45:57 +02:00
} ) ;
2023-07-20 19:32:15 +02:00
$ ( '#world_button' ) . on ( 'click' , async function ( ) {
const chid = $ ( '#set_character_world' ) . data ( 'chid' ) ;
if ( chid ) {
const worldName = characters [ chid ] ? . data ? . extensions ? . world ;
const hasEmbed = checkEmbeddedWorld ( chid ) ;
if ( worldName && world _names . includes ( worldName ) ) {
if ( ! $ ( '#WorldInfo' ) . is ( ':visible' ) ) {
$ ( '#WIDrawerIcon' ) . trigger ( 'click' ) ;
}
const index = world _names . indexOf ( worldName ) ;
$ ( "#world_editor_select" ) . val ( index ) . trigger ( 'change' ) ;
} else if ( hasEmbed ) {
await importEmbeddedWorldInfo ( ) ;
saveCharacterDebounced ( ) ;
}
else {
$ ( '#char-management-dropdown' ) . val ( $ ( '#set_character_world' ) . val ( ) ) . trigger ( 'change' ) ;
}
}
} ) ;
2023-08-22 00:51:31 +02:00
$ ( '#world_info_search' ) . on ( 'input' , function ( ) {
const term = $ ( this ) . val ( ) ;
worldInfoFilter . setFilterData ( FILTER _TYPES . WORLD _INFO _SEARCH , term ) ;
2023-07-20 19:32:15 +02:00
} ) ;
// Not needed on mobile
if ( deviceInfo . device . type === 'desktop' ) {
$ ( '#world_info' ) . select2 ( {
width : '100%' ,
placeholder : 'No Worlds active. Click here to select.' ,
allowClear : true ,
closeOnSelect : false ,
} ) ;
}
} )