2023-10-16 22:03:42 +02:00
import { saveSettings , callPopup , substituteParams , getRequestHeaders , chat _metadata , this _chid , characters , saveCharacterDebounced , menu _type , eventSource , event _types , getExtensionPrompt , MAX _INJECTION _DEPTH , extension _prompt _types , getExtensionPromptByName , saveMetadata , getCurrentChatId } from "../script.js" ;
2023-10-05 22:30:18 +02:00
import { download , debounce , initScrollHeight , resetScrollHeight , parseJsonFile , extractDataFromPng , getFileBuffer , getCharaFilename , getSortableDelay , escapeRegex , PAGINATION _TEMPLATE , navigation _option , waitUntilCondition } from "./utils.js" ;
2023-10-12 05:44:52 +02:00
import { extension _settings , getContext } from "./extensions.js" ;
2023-07-20 19:32:15 +02:00
import { NOTE _MODULE _NAME , metadata _keys , shouldWIAddPrompt } from "./authors-note.js" ;
import { registerSlashCommand } from "./slash-commands.js" ;
2023-08-24 22:52:03 +02:00
import { getDeviceInfo } from "./RossAscends-mods.js" ;
2023-08-22 00:51:31 +02:00
import { FILTER _TYPES , FilterHelper } from "./filters.js" ;
2023-08-23 01:38:43 +02:00
import { getTokenCount } from "./tokenizers.js" ;
2023-10-10 17:08:08 +02:00
import { power _user } from "./power-user.js" ;
2023-07-20 19:32:15 +02:00
export {
world _info ,
world _info _budget ,
world _info _depth ,
2023-11-01 18:02:38 +01:00
world _info _min _activations ,
world _info _min _activations _depth _max ,
2023-07-20 19:32:15 +02:00
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 ;
2023-10-30 22:55:32 +01:00
let world _info _min _activations = 0 ; // if > 0, will continue seeking chat until minimum world infos are activated
2023-11-01 18:36:20 +01:00
let world _info _min _activations _depth _max = 0 ; // used when (world_info_min_activations > 0)
2023-10-30 22:55:32 +01:00
2023-07-20 19:32:15 +02:00
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
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-10-05 22:30:18 +02:00
const SORT _ORDER _KEY = 'world_info_sort_order' ;
2023-10-16 22:03:42 +02:00
const METADATA _KEY = 'world_info' ;
2023-08-22 00:51:31 +02:00
2023-09-24 13:45:04 +02:00
const DEFAULT _DEPTH = 4 ;
2023-08-10 19:45:57 +02:00
export function getWorldInfoSettings ( ) {
return {
world _info ,
world _info _depth ,
2023-11-01 18:02:38 +01:00
world _info _min _activations ,
world _info _min _activations _depth _max ,
2023-08-10 19:45:57 +02:00
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 ,
2023-09-21 09:04:34 +02:00
atDepth : 4 ,
2023-07-20 19:32:15 +02:00
} ;
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 ;
2023-09-24 13:41:56 +02:00
return {
worldInfoString ,
worldInfoBefore ,
worldInfoAfter ,
worldInfoDepth : activatedWorldInfo . WIDepthEntries
} ;
2023-07-20 19:32:15 +02:00
}
function setWorldInfoSettings ( settings , data ) {
if ( settings . world _info _depth !== undefined )
world _info _depth = Number ( settings . world _info _depth ) ;
2023-11-01 18:02:38 +01:00
if ( settings . world _info _min _activations !== undefined )
world _info _min _activations = Number ( settings . world _info _min _activations ) ;
if ( settings . world _info _min _activations _depth _max !== undefined )
world _info _min _activations _depth _max = Number ( settings . world _info _min _activations _depth _max ) ;
2023-07-20 19:32:15 +02:00
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 ? ? { }
2023-10-26 20:01:25 +02:00
$ ( "#world_info_depth_counter" ) . val ( world _info _depth ) ;
2023-07-20 19:32:15 +02:00
$ ( "#world_info_depth" ) . val ( world _info _depth ) ;
2023-11-01 18:02:38 +01:00
$ ( "#world_info_min_activations_counter" ) . val ( world _info _min _activations ) ;
$ ( "#world_info_min_activations" ) . val ( world _info _min _activations ) ;
$ ( "#world_info_min_activations_depth_max_counter" ) . val ( world _info _min _activations _depth _max ) ;
$ ( "#world_info_min_activations_depth_max" ) . val ( world _info _min _activations _depth _max ) ;
2023-10-26 20:01:25 +02:00
$ ( "#world_info_budget_counter" ) . val ( world _info _budget ) ;
2023-07-20 19:32:15 +02:00
$ ( "#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 ) ;
2023-10-26 20:01:25 +02:00
$ ( "#world_info_budget_cap_counter" ) . val ( world _info _budget _cap ) ;
2023-08-10 19:45:57 +02:00
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> ` ) ;
} ) ;
2023-10-05 22:30:18 +02:00
$ ( '#world_info_sort_order' ) . val ( localStorage . getItem ( SORT _ORDER _KEY ) || '0' ) ;
2023-07-20 19:32:15 +02:00
$ ( "#world_editor_select" ) . trigger ( "change" ) ;
2023-10-16 22:03:42 +02:00
eventSource . on ( event _types . CHAT _CHANGED , ( ) => {
const hasWorldInfo = ! ! chat _metadata [ METADATA _KEY ] && world _names . includes ( chat _metadata [ METADATA _KEY ] ) ;
$ ( '.chat_lorebook_button' ) . toggleClass ( 'world_set' , hasWorldInfo ) ;
} ) ;
2023-07-20 19:32:15 +02:00
}
// 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 ( ) {
2023-11-12 21:35:17 +01:00
const result = await fetch ( "/getsettings" , {
2023-07-20 19:32:15 +02:00
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 ;
}
2023-10-05 22:30:18 +02:00
/ * *
* @ param { any [ ] } data WI entries
* @ returns { any [ ] } Sorted data
* /
function sortEntries ( data ) {
const option = $ ( '#world_info_sort_order' ) . find ( ":selected" ) ;
const sortField = option . data ( 'field' ) ;
const sortOrder = option . data ( 'order' ) ;
const sortRule = option . data ( 'rule' ) ;
const orderSign = sortOrder === 'asc' ? 1 : - 1 ;
2023-11-11 19:16:57 +01:00
if ( sortRule === 'custom' ) {
// First by display index, then by order, then by uid
data . sort ( ( a , b ) => {
const aValue = a . displayIndex ;
const bValue = b . displayIndex ;
return ( aValue - bValue || b . order - a . order || a . uid - b . uid ) ;
} ) ;
} else if ( sortRule === 'priority' ) {
2023-10-05 22:30:18 +02:00
// First constant, then normal, then disabled. Then sort by order
data . sort ( ( a , b ) => {
const aValue = a . constant ? 0 : a . disable ? 2 : 1 ;
const bValue = b . constant ? 0 : b . disable ? 2 : 1 ;
return ( aValue - bValue || b . order - a . order ) ;
} ) ;
} else {
2023-10-06 00:18:50 +02:00
const primarySort = ( a , b ) => {
2023-10-05 22:30:18 +02:00
const aValue = a [ sortField ] ;
const bValue = b [ sortField ] ;
// Sort strings
if ( typeof aValue === 'string' && typeof bValue === 'string' ) {
if ( sortRule === 'length' ) {
// Sort by string length
return orderSign * ( aValue . length - bValue . length ) ;
} else {
// Sort by A-Z ordinal
return orderSign * aValue . localeCompare ( bValue ) ;
}
}
// Sort numbers
return orderSign * ( Number ( aValue ) - Number ( bValue ) ) ;
2023-10-06 00:18:50 +02:00
} ;
2023-10-08 13:12:14 +02:00
const secondarySort = ( a , b ) => a . order - b . order ;
2023-10-06 00:18:50 +02:00
const tertiarySort = ( a , b ) => a . uid - b . uid ;
data . sort ( ( a , b ) => {
const primary = primarySort ( a , b ) ;
if ( primary !== 0 ) {
return primary ;
}
const secondary = secondarySort ( a , b ) ;
if ( secondary !== 0 ) {
return secondary ;
}
return tertiarySort ( a , b ) ;
2023-10-05 22:30:18 +02:00
} ) ;
}
return data ;
}
2023-07-20 19:32:15 +02:00
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-10-05 22:30:18 +02:00
entriesArray = sortEntries ( entriesArray ) ;
2023-08-22 00:51:31 +02:00
entriesArray = worldInfoFilter . applyFilters ( entriesArray ) ;
2023-10-05 22:30:18 +02:00
typeof callback === 'function' && 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' ;
2023-10-05 22:30:18 +02:00
const perPageDefault = 25 ;
2023-08-21 20:10:11 +02:00
$ ( "#world_info_pagination" ) . pagination ( {
dataSource : getDataArray ,
2023-10-05 22:30:18 +02:00
pageSize : Number ( localStorage . getItem ( storageKey ) ) || perPageDefault ,
2023-09-18 00:52:41 +02:00
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 ,
2023-11-11 19:16:57 +01:00
callback : function ( /** @type {object[]} */ page ) {
2023-08-21 20:10:11 +02:00
$ ( "#world_popup_entries_list" ) . empty ( ) ;
2023-09-25 11:03:10 +02:00
const keywordHeaders = `
2023-10-05 22:55:39 +02:00
< div id = "WIEntryHeaderTitlesPC" class = "flex-container wide100p spaceBetween justifyCenter textAlignCenter" style = "padding:0 2.5em;" >
2023-10-05 20:40:39 +02:00
< small class = "flex1" >
Title / Memo
< / s m a l l >
2023-11-04 16:44:43 +01:00
< small style = "width: calc(3.5em + 5px)" >
2023-10-05 20:02:43 +02:00
Status
< / s m a l l >
2023-11-04 16:44:43 +01:00
< small style = "width: calc(3.5em + 20px)" >
2023-10-05 16:40:48 +02:00
Position
< / s m a l l >
2023-11-04 16:44:43 +01:00
< small style = "width: calc(3.5em + 15px)" >
2023-10-05 16:40:48 +02:00
Depth
< / s m a l l >
2023-11-04 16:44:43 +01:00
< small style = "width: calc(3.5em + 15px)" >
2023-10-05 16:40:48 +02:00
Order
< / s m a l l >
2023-11-04 16:44:43 +01:00
< small style = "width: calc(3.5em + 15px)" >
2023-10-05 20:02:43 +02:00
Trigger %
2023-10-05 16:40:48 +02:00
< / s m a l l >
2023-09-25 11:03:10 +02:00
< / d i v > `
2023-11-04 19:02:38 +01:00
const blocks = page . map ( entry => getWorldEntry ( name , data , entry ) ) . filter ( x => x ) ;
2023-11-11 19:16:57 +01:00
const isCustomOrder = $ ( '#world_info_sort_order' ) . find ( ':selected' ) . data ( 'rule' ) === 'custom' ;
if ( ! isCustomOrder ) {
blocks . forEach ( block => {
block . find ( '.drag-handle' ) . remove ( ) ;
} ) ;
}
2023-09-25 11:03:10 +02:00
$ ( "#world_popup_entries_list" ) . append ( keywordHeaders ) ;
2023-08-21 20:10:11 +02:00
$ ( "#world_popup_entries_list" ) . append ( blocks ) ;
} ,
afterSizeSelectorChange : function ( e ) {
localStorage . setItem ( storageKey , e . target . value ) ;
}
} ) ;
2023-07-20 19:32:15 +02:00
2023-10-05 22:30:18 +02:00
if ( typeof navigation === 'number' && Number ( navigation ) >= 0 ) {
const selector = ` #world_popup_entries_list [uid=" ${ navigation } "] ` ;
const data = getDataArray ( ) ;
const uidIndex = data . findIndex ( x => x . uid === navigation ) ;
const perPage = Number ( localStorage . getItem ( storageKey ) ) || perPageDefault ;
const page = Math . floor ( uidIndex / perPage ) + 1 ;
$ ( "#world_info_pagination" ) . pagination ( 'go' , page ) ;
waitUntilCondition ( ( ) => document . querySelector ( selector ) !== null ) . finally ( ( ) => {
const element = $ ( selector ) ;
if ( element . length === 0 ) {
console . log ( ` Could not find element for uid ${ navigation } ` ) ;
return ;
}
const elementOffset = element . offset ( ) ;
const parentOffset = element . parent ( ) . offset ( ) ;
const scrollOffset = elementOffset . top - parentOffset . top ;
$ ( '#WorldInfo' ) . scrollTop ( scrollOffset ) ;
element . addClass ( 'flash animated' ) ;
setTimeout ( ( ) => element . removeClass ( 'flash animated' ) , 2000 ) ;
} ) ;
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 ) ;
} ) ;
2023-10-05 22:56:31 +02:00
$ ( "#world_backfill_memos" ) . off ( 'click' ) . on ( 'click' , async ( ) => {
let counter = 0 ;
for ( const entry of Object . values ( data . entries ) ) {
if ( ! entry . comment && Array . isArray ( entry . key ) && entry . key . length > 0 ) {
entry . comment = entry . key [ 0 ] ;
setOriginalDataValue ( data , entry . uid , "comment" , entry . comment ) ;
counter ++ ;
}
}
if ( counter > 0 ) {
toastr . info ( ` Backfilled ${ counter } titles ` ) ;
await saveWorldInfo ( name , data , true ) ;
updateEditor ( navigation _option . previous ) ;
}
} ) ;
2023-07-20 19:32:15 +02:00
$ ( "#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 ) {
2023-11-11 19:16:57 +01:00
const firstEntryUid = $ ( '#world_popup_entries_list .world_entry' ) . first ( ) . data ( 'uid' ) ;
const minDisplayIndex = data ? . entries [ firstEntryUid ] ? . displayIndex ? ? 0 ;
2023-07-20 19:32:15 +02:00
$ ( '#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 ;
}
2023-11-11 19:16:57 +01:00
item . displayIndex = minDisplayIndex + index ;
setOriginalDataValue ( data , uid , 'extensions.display_index' , item . displayIndex ) ;
2023-07-20 19:32:15 +02:00
} ) ;
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-11-04 19:02:38 +01:00
if ( ! data . entries [ entry . uid ] ) {
return ;
}
2023-07-20 19:32:15 +02:00
const template = $ ( "#entry_edit_template .world_entry" ) . clone ( ) ;
template . data ( "uid" , entry . uid ) ;
2023-10-05 22:30:18 +02:00
template . attr ( "uid" , entry . uid ) ;
2023-07-20 19:32:15 +02:00
// 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" ) ;
2023-08-22 12:07:24 +02:00
const value = String ( $ ( this ) . val ( ) ) ;
2023-07-20 19:32:15 +02:00
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 ) ;
} ) ;
2023-11-11 20:53:11 +01:00
keyInput . val ( entry . key . join ( ", " ) ) . trigger ( "input" ) ;
2023-10-05 20:02:43 +02:00
//initScrollHeight(keyInput);
2023-07-20 19:32:15 +02:00
// logic AND/NOT
const selectiveLogicDropdown = template . find ( 'select[name="entryLogicType"]' ) ;
selectiveLogicDropdown . data ( "uid" , entry . uid ) ;
2023-10-01 03:21:19 +02:00
selectiveLogicDropdown . on ( "click" , function ( event ) {
event . stopPropagation ( ) ;
} )
2023-07-20 19:32:15 +02:00
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 ) ;
} ) ;
2023-10-12 05:44:52 +02:00
2023-07-20 19:32:15 +02:00
template
. find ( ` select[name="entryLogicType"] option[value= ${ entry . selectiveLogic } ] ` )
. prop ( "selected" , true )
. trigger ( "input" ) ;
2023-09-23 21:35:51 +02:00
// Character filter
const characterFilterLabel = template . find ( ` label[for="characterFilter"] > small ` ) ;
characterFilterLabel . text ( ! ! ( entry . characterFilter ? . isExclude ) ? "Exclude Character(s)" : "Filter to Character(s)" ) ;
// exclude characters checkbox
const characterExclusionInput = template . find ( ` input[name="character_exclusion"] ` ) ;
characterExclusionInput . data ( "uid" , entry . uid ) ;
characterExclusionInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . prop ( "checked" ) ;
characterFilterLabel . text ( value ? "Exclude Character(s)" : "Filter to Character(s)" ) ;
if ( data . entries [ uid ] . characterFilter ) {
if ( ! value && data . entries [ uid ] . characterFilter . names . length === 0 ) {
delete data . entries [ uid ] . characterFilter ;
} else {
data . entries [ uid ] . characterFilter . isExclude = value
}
} else if ( value ) {
Object . assign (
data . entries [ uid ] ,
2023-09-24 13:45:04 +02:00
{
2023-09-23 21:35:51 +02:00
characterFilter : {
isExclude : true ,
names : [ ]
}
}
) ;
}
setOriginalDataValue ( data , uid , "character_filter" , data . entries [ uid ] . characterFilter ) ;
saveWorldInfo ( name , data ) ;
} ) ;
characterExclusionInput . prop ( "checked" , entry . characterFilter ? . isExclude ? ? false ) . trigger ( "input" ) ;
const characterFilter = template . find ( ` select[name="characterFilter"] ` ) ;
characterFilter . data ( "uid" , entry . uid )
const deviceInfo = getDeviceInfo ( ) ;
if ( deviceInfo && deviceInfo . device . type === 'desktop' ) {
$ ( characterFilter ) . select2 ( {
width : '100%' ,
placeholder : 'All characters will pull from this entry.' ,
allowClear : true ,
closeOnSelect : false ,
} ) ;
}
const characters = getContext ( ) . characters ;
characters . forEach ( ( character ) => {
const option = document . createElement ( 'option' ) ;
const name = character . avatar . replace ( /\.[^/.]+$/ , "" ) ? ? character . name
option . innerText = name
option . selected = entry . characterFilter ? . names . includes ( name )
characterFilter . append ( option )
} ) ;
characterFilter . on ( 'mousedown change' , async function ( e ) {
// If there's no world names, don't do anything
if ( world _names . length === 0 ) {
e . preventDefault ( ) ;
return ;
}
const uid = $ ( this ) . data ( "uid" ) ;
const value = $ ( this ) . val ( ) ;
if ( ( ! value || value ? . length === 0 ) && ! data . entries [ uid ] . characterFilter ? . isExclude ) {
delete data . entries [ uid ] . characterFilter ;
} else {
Object . assign (
data . entries [ uid ] ,
2023-09-24 13:45:04 +02:00
{
2023-09-23 21:35:51 +02:00
characterFilter : {
isExclude : data . entries [ uid ] . characterFilter ? . isExclude ? ? false ,
names : value
}
}
) ;
}
setOriginalDataValue ( data , uid , "character_filter" , data . entries [ uid ] . characterFilter ) ;
saveWorldInfo ( name , data ) ;
} ) ;
2023-07-20 19:32:15 +02:00
// keysecondary
const keySecondaryInput = template . find ( 'textarea[name="keysecondary"]' ) ;
keySecondaryInput . data ( "uid" , entry . uid ) ;
keySecondaryInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
2023-08-22 12:07:24 +02:00
const value = String ( $ ( this ) . val ( ) ) ;
2023-07-20 19:32:15 +02:00
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 ) ;
} ) ;
2023-11-11 20:53:11 +01:00
keySecondaryInput . val ( entry . keysecondary . join ( ", " ) ) . trigger ( "input" ) ;
2023-07-20 19:32:15 +02:00
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 ( ) ;
2023-10-05 20:02:43 +02:00
resetScrollHeight ( this ) ;
2023-07-20 19:32:15 +02:00
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" ) ;
2023-10-05 20:02:43 +02:00
initScrollHeight ( commentInput ) ;
2023-07-20 19:32:15 +02:00
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
2023-10-01 03:21:19 +02:00
/ *
2023-07-20 19:32:15 +02:00
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" ) ;
2023-10-01 03:21:19 +02:00
* /
2023-07-20 19:32:15 +02:00
// 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 ;
2023-09-28 17:27:12 +02:00
updatePosOrdDisplay ( uid )
2023-07-20 19:32:15 +02:00
setOriginalDataValue ( data , uid , "insertion_order" , data . entries [ uid ] . order ) ;
saveWorldInfo ( name , data ) ;
} ) ;
orderInput . val ( entry . order ) . trigger ( "input" ) ;
2023-11-04 16:44:43 +01:00
orderInput . css ( 'width' , 'calc(3em + 15px)' ) ;
2023-07-20 19:32:15 +02:00
// probability
if ( entry . probability === undefined ) {
entry . probability = null ;
}
2023-09-24 13:41:56 +02:00
// depth
const depthInput = template . find ( 'input[name="depth"]' ) ;
depthInput . data ( "uid" , entry . uid ) ;
2023-10-05 20:02:43 +02:00
2023-09-24 13:45:04 +02:00
depthInput . on ( "input" , function ( ) {
2023-09-24 13:41:56 +02:00
const uid = $ ( this ) . data ( "uid" ) ;
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . depth = ! isNaN ( value ) ? value : 0 ;
2023-09-28 17:27:12 +02:00
updatePosOrdDisplay ( uid )
2023-09-25 19:11:16 +02:00
setOriginalDataValue ( data , uid , "extensions.depth" , data . entries [ uid ] . depth ) ;
2023-09-24 13:41:56 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-09-24 13:45:04 +02:00
depthInput . val ( entry . depth ? ? DEFAULT _DEPTH ) . trigger ( "input" ) ;
2023-11-04 16:44:43 +01:00
depthInput . css ( 'width' , 'calc(3em + 15px)' ) ;
2023-09-24 13:41:56 +02:00
// Hide by default unless depth is specified
2023-09-24 13:45:04 +02:00
if ( entry . position === world _info _position . atDepth ) {
2023-10-05 16:40:48 +02:00
//depthInput.parent().hide();
2023-09-24 13:41:56 +02:00
}
2023-07-20 19:32:15 +02:00
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" ) ;
2023-11-04 16:44:43 +01:00
probabilityInput . css ( 'width' , 'calc(3em + 15px)' ) ;
2023-07-20 19:32:15 +02:00
// 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"]' ) ;
2023-10-05 16:40:48 +02:00
initScrollHeight ( positionInput ) ;
2023-07-20 19:32:15 +02:00
positionInput . data ( "uid" , entry . uid ) ;
2023-10-05 20:02:43 +02:00
positionInput . on ( "click" , function ( event ) {
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
2023-07-20 19:32:15 +02:00
positionInput . on ( "input" , function ( ) {
const uid = $ ( this ) . data ( "uid" ) ;
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . position = ! isNaN ( value ) ? value : 0 ;
2023-09-24 13:45:04 +02:00
if ( value === world _info _position . atDepth ) {
2023-10-05 16:40:48 +02:00
depthInput . prop ( 'disabled' , false ) ;
2023-10-05 20:02:43 +02:00
depthInput . css ( 'visibility' , 'visible' )
2023-10-05 16:40:48 +02:00
//depthInput.parent().show();
2023-09-24 13:41:56 +02:00
} else {
2023-10-05 16:40:48 +02:00
depthInput . prop ( 'disabled' , true ) ;
2023-10-05 20:02:43 +02:00
depthInput . css ( 'visibility' , 'hidden' )
2023-10-05 16:40:48 +02:00
//depthInput.parent().hide();
2023-09-24 13:41:56 +02:00
}
2023-09-28 17:27:12 +02:00
updatePosOrdDisplay ( uid )
2023-07-20 19:32:15 +02:00
// 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" ) ;
2023-09-25 11:03:10 +02:00
//add UID above content box (less important doesn't need to be always visible)
template . find ( ".world_entry_form_uid_value" ) . text ( ` (UID: ${ entry . uid } ) ` ) ;
2023-07-20 19:32:15 +02:00
// disable
2023-10-04 21:41:10 +02:00
/ *
2023-07-20 19:32:15 +02:00
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" ) ;
2023-10-01 03:21:19 +02:00
* /
//new tri-state selector for constant/normal/disabled
const entryStateSelector = template . find ( 'select[name="entryStateSelector"]' ) ;
entryStateSelector . data ( "uid" , entry . uid ) ;
console . log ( entry . uid )
entryStateSelector . on ( "click" , function ( event ) {
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
entryStateSelector . on ( "input" , function ( ) {
const uid = entry . uid ;
const value = $ ( this ) . val ( ) ;
switch ( value ) {
case "constant" :
data . entries [ uid ] . constant = true ;
data . entries [ uid ] . disable = false ;
setOriginalDataValue ( data , uid , "enabled" , true ) ;
setOriginalDataValue ( data , uid , "constant" , true ) ;
template . removeClass ( 'disabledWIEntry' ) ;
console . debug ( "set to constant" )
break
case "normal" :
data . entries [ uid ] . constant = false ;
data . entries [ uid ] . disable = false ;
setOriginalDataValue ( data , uid , "enabled" , true ) ;
setOriginalDataValue ( data , uid , "constant" , false ) ;
template . removeClass ( 'disabledWIEntry' ) ;
console . debug ( "set to normal" )
break
case "disabled" :
data . entries [ uid ] . constant = false ;
data . entries [ uid ] . disable = true ;
setOriginalDataValue ( data , uid , "enabled" , false ) ;
setOriginalDataValue ( data , uid , "constant" , false ) ;
template . addClass ( 'disabledWIEntry' ) ;
console . debug ( "set to disabled" )
break
}
saveWorldInfo ( name , data ) ;
} )
const entryState = function ( ) {
console . log ( ` constant: ${ entry . constant } , disabled: ${ entry . disable } ` )
if ( entry . constant === true ) {
console . debug ( 'found constant' )
return "constant"
} else if ( entry . disable === true ) {
console . debug ( 'found disabled' )
return "disabled"
} else {
console . debug ( 'found normal' )
return "normal"
}
}
template
. find ( ` select[name="entryStateSelector"] option[value= ${ entryState ( ) } ] ` )
. prop ( "selected" , true )
. trigger ( "input" ) ;
saveWorldInfo ( name , data ) ;
2023-07-20 19:32:15 +02:00
2023-09-23 21:35:51 +02:00
// exclude recursion
2023-07-20 19:32:15 +02:00
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
2023-09-25 11:03:10 +02:00
const deleteButton = template . find ( ".delete_entry_button" ) ;
2023-07-20 19:32:15 +02:00
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
2023-09-28 17:27:12 +02:00
function updatePosOrdDisplay ( uid ) {
// display position/order info left of keyword box
let entry = data . entries [ uid ]
let posText = entry . position
switch ( entry . position ) {
case 0 :
posText = '↑CD' ;
break
case 1 :
posText = 'CD↓' ;
break
case 2 :
posText = '↑AN' ;
break
case 3 :
posText = 'AN↓' ;
break
case 4 :
posText = ` @D ${ entry . depth } ` ;
break
}
template . find ( ".world_entry_form_position_value" ) . text ( ` ( ${ posText } ${ entry . order } ) ` ) ;
}
2023-07-20 19:32:15 +02:00
return template ;
}
async function deleteWorldInfoEntry ( data , uid ) {
if ( ! data || ! ( "entries" in data ) ) {
return ;
}
2023-09-05 11:05:20 +02:00
if ( ! confirm ( ` Delete the entry with UID: ${ uid } ? This action is irreversible! ` ) ) {
throw new Error ( "User cancelled deletion" ) ;
}
2023-07-20 19:32:15 +02:00
delete data . entries [ uid ] ;
}
function createWorldInfoEntry ( name , data ) {
const newEntryTemplate = {
key : [ ] ,
keysecondary : [ ] ,
comment : "" ,
content : "" ,
constant : false ,
selective : true ,
selectiveLogic : 0 ,
addMemo : false ,
order : 100 ,
2023-10-17 02:15:18 +02:00
position : 0 ,
2023-07-20 19:32:15 +02:00
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-10-05 22:30:18 +02:00
updateEditor ( newUid ) ;
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 ;
}
2023-10-16 22:13:32 +02:00
if ( chat _metadata [ METADATA _KEY ] === worldName ) {
console . debug ( ` Character ${ name } 's world ${ worldName } is already activated in chat lore! Skipping... ` ) ;
continue ;
}
2023-07-20 19:32:15 +02:00
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 ;
}
2023-10-16 22:03:42 +02:00
async function getChatLore ( ) {
const chatWorld = chat _metadata [ METADATA _KEY ] ;
if ( ! chatWorld ) {
return [ ] ;
}
2023-10-16 22:13:32 +02:00
if ( selected _world _info . includes ( chatWorld ) ) {
console . debug ( ` Chat world ${ chatWorld } is already activated in global world info! Skipping... ` ) ;
return [ ] ;
}
2023-10-16 22:03:42 +02:00
const data = await loadWorldInfoData ( chatWorld ) ;
const entries = data ? Object . keys ( data . entries ) . map ( ( x ) => data . entries [ x ] ) : [ ] ;
console . debug ( ` Chat lore has ${ entries . length } entries ` ) ;
return entries ;
}
2023-07-20 19:32:15 +02:00
async function getSortedEntries ( ) {
try {
const globalLore = await getGlobalLore ( ) ;
const characterLore = await getCharacterLore ( ) ;
2023-10-16 22:03:42 +02:00
const chatLore = await getChatLore ( ) ;
2023-07-20 19:32:15 +02:00
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 ;
}
2023-10-16 22:03:42 +02:00
// Chat lore always goes first
entries = [ ... chatLore . sort ( sortFn ) , ... entries ] ;
2023-07-20 19:32:15 +02:00
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
2023-09-26 08:53:04 +02:00
return structuredClone ( entries ) ;
2023-07-20 19:32:15 +02:00
}
catch ( e ) {
console . error ( e ) ;
return [ ] ;
}
}
async function checkWorldInfo ( chat , maxContext ) {
const context = getContext ( ) ;
const messagesToLookBack = world _info _depth * 2 || 1 ;
2023-10-12 05:44:52 +02:00
// Combine the chat
let textToScan = chat . slice ( 0 , messagesToLookBack ) . join ( "" ) ;
2023-10-30 22:55:32 +01:00
let minActivationMsgIndex = messagesToLookBack ; // tracks chat index to satisfy `world_info_min_activations`
2023-10-12 05:44:52 +02:00
// Add the depth or AN if enabled
// Put this code here since otherwise, the chat reference is modified
if ( extension _settings . note . allowWIScan ) {
2023-10-25 23:39:11 +02:00
for ( const key of Object . keys ( context . extensionPrompts ) ) {
if ( key . startsWith ( 'DEPTH_PROMPT' ) ) {
const depthPrompt = getExtensionPromptByName ( key )
if ( depthPrompt ) {
textToScan = ` ${ depthPrompt } \n ${ textToScan } `
}
}
2023-10-12 05:44:52 +02:00
}
2023-10-25 23:39:11 +02:00
const anPrompt = getExtensionPromptByName ( NOTE _MODULE _NAME ) ;
2023-10-12 05:44:52 +02:00
if ( anPrompt ) {
textToScan = ` ${ anPrompt } \n ${ textToScan } `
}
}
// Transform the resulting string
textToScan = transformString ( textToScan ) ;
2023-07-20 19:32:15 +02:00
let needsToScan = true ;
2023-11-01 18:02:38 +01:00
let token _budget _overflowed = false ;
2023-07-20 19:32:15 +02:00
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 ) {
2023-09-23 21:35:51 +02:00
// Check if this entry applies to the character or if it's excluded
if ( entry . characterFilter && entry . characterFilter ? . names . length > 0 ) {
const nameIncluded = entry . characterFilter . names . includes ( getCharaFilename ( ) ) ;
const filtered = entry . characterFilter . isExclude ? nameIncluded : ! nameIncluded
if ( filtered ) {
continue ;
}
}
2023-07-20 19:32:15 +02:00
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" ) ;
2023-09-01 22:14:01 +02:00
toastr . warning ( ` World info budget reached after ${ allActivatedEntries . size } entries. ` , 'World Info' ) ;
2023-07-30 04:15:54 +02:00
}
2023-07-20 19:32:15 +02:00
needsToScan = false ;
2023-11-01 18:02:38 +01:00
token _budget _overflowed = true ;
2023-07-20 19:32:15 +02:00
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 ) ;
}
2023-10-30 22:55:32 +01:00
// world_info_min_activations
2023-11-01 18:02:38 +01:00
if ( ! needsToScan && ! token _budget _overflowed ) {
2023-10-30 22:55:32 +01:00
if ( world _info _min _activations > 0 && ( allActivatedEntries . size < world _info _min _activations ) ) {
2023-11-01 18:02:38 +01:00
let over _max = false
over _max = (
world _info _min _activations _depth _max > 0 &&
minActivationMsgIndex > world _info _min _activations _depth _max
2023-11-11 19:16:57 +01:00
) || ( minActivationMsgIndex >= chat . length )
2023-11-01 18:02:38 +01:00
if ( ! over _max ) {
2023-10-30 22:55:32 +01:00
needsToScan = true
textToScan = transformString ( chat . slice ( minActivationMsgIndex , minActivationMsgIndex + 1 ) . join ( "" ) ) ;
minActivationMsgIndex += 1
}
}
}
2023-07-20 19:32:15 +02:00
}
// Forward-sorted list of entries for joining
const WIBeforeEntries = [ ] ;
const WIAfterEntries = [ ] ;
const ANTopEntries = [ ] ;
const ANBottomEntries = [ ] ;
2023-09-24 13:41:56 +02:00
const WIDepthEntries = [ ] ;
2023-07-20 19:32:15 +02:00
// 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 ;
2023-09-21 09:04:34 +02:00
case world _info _position . atDepth :
2023-09-24 13:45:04 +02:00
const existingDepthIndex = WIDepthEntries . findIndex ( ( e ) => e . depth === entry . depth ? ? DEFAULT _DEPTH ) ;
2023-09-24 13:41:56 +02:00
if ( existingDepthIndex !== - 1 ) {
WIDepthEntries [ existingDepthIndex ] . entries . unshift ( entry . content ) ;
} else {
WIDepthEntries . push ( {
depth : entry . depth ,
entries : [ entry . content ]
} ) ;
}
2023-07-20 19:32:15 +02:00
default :
break ;
}
} ) ;
2023-08-23 23:26:47 +02:00
const worldInfoBefore = WIBeforeEntries . length ? WIBeforeEntries . join ( "\n" ) : '' ;
const worldInfoAfter = WIAfterEntries . length ? WIAfterEntries . join ( "\n" ) : '' ;
2023-07-20 19:32:15 +02:00
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 ] ) ;
}
2023-09-24 13:41:56 +02:00
return { worldInfoBefore , worldInfoAfter , WIDepthEntries } ;
2023-07-20 19:32:15 +02:00
}
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 ,
2023-09-25 19:11:16 +02:00
depth : entry . extensions ? . depth ? ? DEFAULT _DEPTH ,
2023-10-28 11:28:03 +02:00
selectiveLogic : entry . extensions ? . selectiveLogic ? ? 0 ,
2023-07-20 19:32:15 +02:00
} ;
} ) ;
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 ) ) ) {
localStorage . setItem ( checkKey , 1 ) ;
2023-10-04 21:41:10 +02:00
2023-10-10 19:47:54 +02:00
if ( power _user . world _import _dialog ) {
2023-10-10 17:08:08 +02:00
callPopup ( ` <h3>This character has an embedded World/Lorebook.</h3>
< h3 > Would you like to import it now ? < / h 3 >
< div class = "m-b-1" > If you want to import it later , select "Import Card Lore" in the "More..." dropdown menu on the character panel . < / d i v > ` ,
'confirm' ,
'' ,
{ okButton : 'Yes' , } )
. then ( ( result ) => {
if ( result ) {
importEmbeddedWorldInfo ( true ) ;
}
} ) ;
}
else {
toastr . info (
'To import and use it, select "Import Card Lore" in the "More..." dropdown menu on the character panel.' ,
` ${ characters [ chid ] . name } has an embedded World/Lorebook ` ,
{ timeOut : 5000 , extendedTimeOut : 10000 , positionClass : 'toast-top-center' } ,
) ;
}
2023-07-20 19:32:15 +02:00
}
return true ;
}
return false ;
}
2023-10-04 21:41:10 +02:00
export async function importEmbeddedWorldInfo ( skipPopup = false ) {
2023-07-20 19:32:15 +02:00
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.' : '' ) ;
2023-10-04 21:41:10 +02:00
if ( ! skipPopup ) {
const confirmation = await callPopup ( confirmationText , 'confirm' ) ;
2023-07-20 19:32:15 +02:00
2023-10-04 21:41:10 +02:00
if ( ! confirmation ) {
return ;
}
2023-07-20 19:32:15 +02:00
}
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 ) {
2023-10-08 16:04:53 +02:00
//show&draw the WI panel before..
$ ( "#WIDrawerIcon" ) . trigger ( 'click' ) ;
//..auto-opening the new imported WI
2023-07-20 19:32:15 +02:00
$ ( "#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 ) => { } ,
} ) ;
}
2023-10-16 22:03:42 +02:00
function assignLorebookToChat ( ) {
const selectedName = chat _metadata [ METADATA _KEY ] ;
const template = $ ( '#chat_world_template .chat_world' ) . clone ( ) ;
const worldSelect = template . find ( 'select' ) ;
const chatName = template . find ( '.chat_name' ) ;
chatName . text ( getCurrentChatId ( ) ) ;
for ( const worldName of world _names ) {
const option = document . createElement ( 'option' ) ;
option . value = worldName ;
option . innerText = worldName ;
option . selected = selectedName === worldName ;
worldSelect . append ( option ) ;
}
worldSelect . on ( 'change' , function ( ) {
const worldName = $ ( this ) . val ( ) ;
if ( worldName ) {
chat _metadata [ METADATA _KEY ] = worldName ;
$ ( '.chat_lorebook_button' ) . addClass ( 'world_set' ) ;
} else {
delete chat _metadata [ METADATA _KEY ] ;
$ ( '.chat_lorebook_button' ) . removeClass ( 'world_set' ) ;
}
saveMetadata ( ) ;
} ) ;
callPopup ( template , 'text' ) ;
}
2023-07-20 19:32:15 +02:00
jQuery ( ( ) => {
$ ( document ) . ready ( function ( ) {
2023-10-07 18:25:36 +02:00
registerSlashCommand ( 'world' , onWorldInfoChange , [ ] , '<span class="monospace">(optional name)</span> – sets active World, or unsets if no args provided' , true , true ) ;
2023-07-20 19:32:15 +02:00
} )
$ ( "#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 ;
}
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-11-04 16:44:43 +01:00
const selectedIndex = String ( $ ( "#world_editor_select" ) . find ( ":selected" ) . val ( ) ) ;
2023-07-20 19:32:15 +02:00
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-11-04 16:44:43 +01:00
$ ( "#world_info_depth" ) . on ( 'input' , function ( ) {
2023-07-20 19:32:15 +02:00
world _info _depth = Number ( $ ( this ) . val ( ) ) ;
2023-10-26 20:01:25 +02:00
$ ( "#world_info_depth_counter" ) . val ( $ ( this ) . val ( ) ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-11-04 16:44:43 +01:00
$ ( "#world_info_min_activations" ) . on ( 'input' , function ( ) {
2023-11-01 18:02:38 +01:00
world _info _min _activations = Number ( $ ( this ) . val ( ) ) ;
$ ( "#world_info_min_activations_counter" ) . val ( $ ( this ) . val ( ) ) ;
saveSettings ( ) ;
} ) ;
2023-11-04 16:44:43 +01:00
$ ( "#world_info_min_activations_depth_max" ) . on ( 'input' , function ( ) {
2023-11-01 18:02:38 +01:00
world _info _min _activations _depth _max = Number ( $ ( this ) . val ( ) ) ;
$ ( "#world_info_min_activations_depth_max_counter" ) . val ( $ ( this ) . val ( ) ) ;
saveSettings ( ) ;
} ) ;
2023-11-04 16:44:43 +01:00
$ ( "#world_info_budget" ) . on ( 'input' , function ( ) {
2023-07-20 19:32:15 +02:00
world _info _budget = Number ( $ ( this ) . val ( ) ) ;
2023-10-26 20:01:25 +02:00
$ ( "#world_info_budget_counter" ) . val ( $ ( this ) . val ( ) ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-11-04 16:44:43 +01:00
$ ( "#world_info_recursive" ) . on ( 'input' , function ( ) {
2023-07-20 19:32:15 +02:00
world _info _recursive = ! ! $ ( this ) . prop ( 'checked' ) ;
2023-07-03 19:06:18 +02:00
saveSettings ( ) ;
2023-11-04 16:44:43 +01:00
} ) ;
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-11-04 16:44:43 +01:00
} ) ;
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 ( ) {
2023-10-17 12:55:02 +02:00
world _info _character _strategy = Number ( $ ( 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 ( ) ) ;
2023-10-26 20:01:25 +02:00
$ ( "#world_info_budget_cap_counter" ) . val ( 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
2023-10-17 12:55:02 +02:00
$ ( '#world_button' ) . on ( 'click' , async function ( event ) {
2023-07-20 19:32:15 +02:00
const chid = $ ( '#set_character_world' ) . data ( 'chid' ) ;
if ( chid ) {
const worldName = characters [ chid ] ? . data ? . extensions ? . world ;
const hasEmbed = checkEmbeddedWorld ( chid ) ;
2023-10-17 02:10:57 +02:00
if ( worldName && world _names . includes ( worldName ) && ! event . shiftKey ) {
2023-07-20 19:32:15 +02:00
if ( ! $ ( '#WorldInfo' ) . is ( ':visible' ) ) {
$ ( '#WIDrawerIcon' ) . trigger ( 'click' ) ;
}
const index = world _names . indexOf ( worldName ) ;
$ ( "#world_editor_select" ) . val ( index ) . trigger ( 'change' ) ;
2023-10-17 02:10:57 +02:00
} else if ( hasEmbed && ! event . shiftKey ) {
2023-07-20 19:32:15 +02:00
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
} ) ;
2023-10-08 11:30:12 +02:00
$ ( '#world_refresh' ) . on ( 'click' , ( ) => {
updateEditor ( navigation _option . previous ) ;
} ) ;
2023-11-11 19:16:57 +01:00
$ ( '#world_info_sort_order' ) . on ( 'change' , function ( ) {
const value = String ( $ ( this ) . find ( ":selected" ) . val ( ) ) ;
localStorage . setItem ( SORT _ORDER _KEY , value ) ;
2023-10-05 22:30:18 +02:00
updateEditor ( navigation _option . none ) ;
} )
2023-10-16 22:03:42 +02:00
$ ( document ) . on ( 'click' , '.chat_lorebook_button' , assignLorebookToChat ) ;
2023-07-20 19:32:15 +02:00
// Not needed on mobile
2023-08-24 22:52:03 +02:00
const deviceInfo = getDeviceInfo ( ) ;
if ( deviceInfo && deviceInfo . device . type === 'desktop' ) {
2023-07-20 19:32:15 +02:00
$ ( '#world_info' ) . select2 ( {
width : '100%' ,
placeholder : 'No Worlds active. Click here to select.' ,
allowClear : true ,
closeOnSelect : false ,
} ) ;
}
} )