2023-12-02 19:04:51 +01:00
import { saveSettings , callPopup , substituteParams , getRequestHeaders , chat _metadata , this _chid , characters , saveCharacterDebounced , menu _type , eventSource , event _types , getExtensionPromptByName , saveMetadata , getCurrentChatId } from '../script.js' ;
import { download , debounce , initScrollHeight , resetScrollHeight , parseJsonFile , extractDataFromPng , getFileBuffer , getCharaFilename , getSortableDelay , escapeRegex , PAGINATION _TEMPLATE , navigation _option , waitUntilCondition , isTrueBoolean } from './utils.js' ;
import { extension _settings , getContext } from './extensions.js' ;
import { NOTE _MODULE _NAME , metadata _keys , shouldWIAddPrompt } from './authors-note.js' ;
import { registerSlashCommand } from './slash-commands.js' ;
import { getDeviceInfo } from './RossAscends-mods.js' ;
import { FILTER _TYPES , FilterHelper } from './filters.js' ;
import { getTokenCount } from './tokenizers.js' ;
import { power _user } from './power-user.js' ;
import { getTagKeyForCharacter } from './tags.js' ;
import { resolveVariable } from './variables.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 ,
2023-12-02 20:11:06 +01:00
} ;
2023-07-20 19:32:15 +02:00
const world _info _insertion _strategy = {
evenly : 0 ,
character _first : 1 ,
global _first : 2 ,
} ;
2023-12-05 09:56:52 +01:00
const world _info _logic = {
2023-12-05 11:04:27 +01:00
AND _ANY : 0 ,
2023-12-05 10:00:26 +01:00
NOT _ALL : 1 ,
2023-12-05 11:04:27 +01:00
NOT _ANY : 2 ,
2023-12-05 09:56:52 +01:00
} ;
2023-07-20 19:32:15 +02:00
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 ( ( ) => {
2023-12-02 20:11:06 +01:00
Object . assign ( world _info , { globalSelect : selected _world _info } ) ;
saveSettings ( ) ;
2023-07-20 19:32:15 +02:00
} , 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-12-02 20:11:06 +01:00
} ;
2023-08-10 19:45:57 +02:00
}
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 ) {
2023-12-02 19:04:51 +01:00
let worldInfoString = '' , worldInfoBefore = '' , worldInfoAfter = '' ;
2023-07-20 19:32:15 +02:00
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 ,
2023-12-02 21:06:57 +01:00
worldInfoDepth : activatedWorldInfo . WIDepthEntries ,
2023-09-24 13:41:56 +02:00
} ;
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 ;
2023-12-02 19:04:51 +01:00
if ( typeof existingWorldInfo === 'string' ) {
2023-07-20 19:32:15 +02:00
delete settings . world _info ;
selected _world _info = [ existingWorldInfo ] ;
} else if ( Array . isArray ( existingWorldInfo ) ) {
delete settings . world _info ;
selected _world _info = existingWorldInfo ;
}
2023-12-02 20:11:06 +01:00
world _info = settings . world _info ? ? { } ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_depth_counter' ) . val ( world _info _depth ) ;
$ ( '#world_info_depth' ) . val ( world _info _depth ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_min_activations_counter' ) . val ( world _info _min _activations ) ;
$ ( '#world_info_min_activations' ) . val ( world _info _min _activations ) ;
2023-11-01 18:02:38 +01:00
2023-12-02 19:04:51 +01:00
$ ( '#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-11-01 18:02:38 +01:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_budget_counter' ) . val ( world _info _budget ) ;
$ ( '#world_info_budget' ) . val ( world _info _budget ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_recursive' ) . prop ( 'checked' , world _info _recursive ) ;
$ ( '#world_info_overflow_alert' ) . prop ( 'checked' , world _info _overflow _alert ) ;
$ ( '#world_info_case_sensitive' ) . prop ( 'checked' , world _info _case _sensitive ) ;
$ ( '#world_info_match_whole_words' ) . prop ( 'checked' , world _info _match _whole _words ) ;
2023-07-20 19:32:15 +02:00
$ ( ` #world_info_character_strategy option[value=' ${ world _info _character _strategy } '] ` ) . prop ( 'selected' , true ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info_character_strategy' ) . val ( world _info _character _strategy ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_budget_cap' ) . val ( world _info _budget _cap ) ;
$ ( '#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 ) {
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . empty ( ) ;
2023-07-20 19:32:15 +02:00
}
world _names . forEach ( ( item , i ) => {
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . append ( ` <option value=' ${ i } ' ${ selected _world _info . includes ( item ) ? ' selected' : '' } > ${ item } </option> ` ) ;
$ ( '#world_editor_select' ) . append ( ` <option value=' ${ i } '> ${ item } </option> ` ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-10-05 22:30:18 +02:00
$ ( '#world_info_sort_order' ) . val ( localStorage . getItem ( SORT _ORDER _KEY ) || '0' ) ;
2023-12-02 19:04:51 +01: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-12-01 00:50:10 +01:00
// Add slash commands
registerWorldInfoSlashCommands ( ) ;
}
function registerWorldInfoSlashCommands ( ) {
2023-12-01 20:51:49 +01:00
function reloadEditor ( file ) {
const selectedIndex = world _names . indexOf ( file ) ;
if ( selectedIndex !== - 1 ) {
$ ( '#world_editor_select' ) . val ( selectedIndex ) . trigger ( 'change' ) ;
}
}
2023-12-01 00:50:10 +01:00
async function getEntriesFromFile ( file ) {
if ( ! file || ! world _names . includes ( file ) ) {
2023-12-01 01:25:55 +01:00
toastr . warning ( 'Valid World Info file name is required' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
const data = await loadWorldInfoData ( file ) ;
2023-12-02 19:04:51 +01:00
if ( ! data || ! ( 'entries' in data ) ) {
2023-12-01 01:25:55 +01:00
toastr . warning ( 'World Info file has an invalid format' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
const entries = Object . values ( data . entries ) ;
if ( ! entries || entries . length === 0 ) {
2023-12-01 01:25:55 +01:00
toastr . warning ( 'World Info file has no entries' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
return entries ;
}
async function getChatBookCallback ( ) {
const chatId = getCurrentChatId ( ) ;
if ( ! chatId ) {
2023-12-01 01:25:55 +01:00
toastr . warning ( 'Open a chat to get a name of the chat-bound lorebook' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
if ( chat _metadata [ METADATA _KEY ] && world _names . includes ( chat _metadata [ METADATA _KEY ] ) ) {
return chat _metadata [ METADATA _KEY ] ;
}
// Replace non-alphanumeric characters with underscores, cut to 64 characters
const name = ` Chat Book ${ getCurrentChatId ( ) } ` . replace ( /[^a-z0-9]/gi , '_' ) . replace ( /_{2,}/g , '_' ) . substring ( 0 , 64 ) ;
await createNewWorldInfo ( name ) ;
chat _metadata [ METADATA _KEY ] = name ;
await saveMetadata ( ) ;
$ ( '.chat_lorebook_button' ) . addClass ( 'world_set' ) ;
return name ;
}
async function findBookEntryCallback ( args , value ) {
const file = resolveVariable ( args . file ) ;
const field = args . field || 'key' ;
const entries = await getEntriesFromFile ( file ) ;
if ( ! entries ) {
return '' ;
}
const fuse = new Fuse ( entries , {
keys : [ { name : field , weight : 1 } ] ,
includeScore : true ,
threshold : 0.3 ,
} ) ;
const results = fuse . search ( value ) ;
if ( ! results || results . length === 0 ) {
return '' ;
}
const result = results [ 0 ] ? . item ? . uid ;
if ( result === undefined ) {
return '' ;
}
return result ;
}
async function getEntryFieldCallback ( args , uid ) {
const file = resolveVariable ( args . file ) ;
const field = args . field || 'content' ;
const entries = await getEntriesFromFile ( file ) ;
if ( ! entries ) {
return '' ;
}
2023-12-07 17:45:34 +01:00
const entry = entries . find ( x => String ( x . uid ) === String ( uid ) ) ;
2023-12-01 00:50:10 +01:00
if ( ! entry ) {
2023-12-01 20:51:49 +01:00
toastr . warning ( 'Valid UID is required' ) ;
return '' ;
}
if ( newEntryTemplate [ field ] === undefined ) {
toastr . warning ( 'Valid field name is required' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
const fieldValue = entry [ field ] ;
if ( fieldValue === undefined ) {
return '' ;
}
if ( Array . isArray ( fieldValue ) ) {
return fieldValue . map ( x => substituteParams ( x ) ) . join ( ', ' ) ;
}
2023-12-01 20:51:49 +01:00
return substituteParams ( String ( fieldValue ) ) ;
2023-12-01 00:50:10 +01:00
}
async function createEntryCallback ( args , content ) {
const file = resolveVariable ( args . file ) ;
const key = args . key ;
const data = await loadWorldInfoData ( file ) ;
2023-12-02 19:04:51 +01:00
if ( ! data || ! ( 'entries' in data ) ) {
2023-12-01 01:25:55 +01:00
toastr . warning ( 'Valid World Info file name is required' ) ;
2023-12-01 00:50:10 +01:00
return '' ;
}
const entry = createWorldInfoEntry ( file , data , true ) ;
if ( key ) {
entry . key . push ( key ) ;
entry . addMemo = true ;
entry . comment = key ;
}
if ( content ) {
entry . content = content ;
}
await saveWorldInfo ( file , data , true ) ;
2023-12-01 20:51:49 +01:00
reloadEditor ( file ) ;
2023-12-01 00:50:10 +01:00
2023-12-01 20:51:49 +01:00
return entry . uid ;
}
async function setEntryFieldCallback ( args , value ) {
const file = resolveVariable ( args . file ) ;
const uid = resolveVariable ( args . uid ) ;
const field = args . field || 'content' ;
if ( value === undefined ) {
toastr . warning ( 'Value is required' ) ;
return '' ;
2023-12-01 00:50:10 +01:00
}
2023-12-01 20:51:49 +01:00
const data = await loadWorldInfoData ( file ) ;
2023-12-02 19:04:51 +01:00
if ( ! data || ! ( 'entries' in data ) ) {
2023-12-01 20:51:49 +01:00
toastr . warning ( 'Valid World Info file name is required' ) ;
return '' ;
}
const entry = data . entries [ uid ] ;
if ( ! entry ) {
toastr . warning ( 'Valid UID is required' ) ;
return '' ;
}
if ( newEntryTemplate [ field ] === undefined ) {
toastr . warning ( 'Valid field name is required' ) ;
return '' ;
}
if ( Array . isArray ( entry [ field ] ) ) {
entry [ field ] = value . split ( ',' ) . map ( x => x . trim ( ) ) . filter ( x => x ) ;
} else if ( typeof entry [ field ] === 'boolean' ) {
entry [ field ] = isTrueBoolean ( value ) ;
} else if ( typeof entry [ field ] === 'number' ) {
entry [ field ] = Number ( value ) ;
} else {
entry [ field ] = value ;
}
if ( originalDataKeyMap [ field ] ) {
setOriginalDataValue ( data , uid , originalDataKeyMap [ field ] , entry [ field ] ) ;
}
await saveWorldInfo ( file , data , true ) ;
reloadEditor ( file ) ;
return '' ;
2023-12-01 00:50:10 +01:00
}
registerSlashCommand ( 'getchatbook' , getChatBookCallback , [ 'getchatlore' , 'getchatwi' ] , '– get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe' , true , true ) ;
2023-12-02 19:04:51 +01:00
registerSlashCommand ( 'findentry' , findBookEntryCallback , [ 'findlore' , 'findwi' ] , '<span class="monospace">(file=bookName field=field [texts])</span> – find a UID of the record from the specified book using the fuzzy match of a field value (default: key) and pass it down the pipe, e.g. <tt>/findentry file=chatLore field=key Shadowfang</tt>' , true , true ) ;
2023-12-01 00:50:10 +01:00
registerSlashCommand ( 'getentryfield' , getEntryFieldCallback , [ 'getlorefield' , 'getwifield' ] , '<span class="monospace">(file=bookName field=field [UID])</span> – get a field value (default: content) of the record with the UID from the specified book and pass it down the pipe, e.g. <tt>/getentryfield file=chatLore field=content 123</tt>' , true , true ) ;
registerSlashCommand ( 'createentry' , createEntryCallback , [ 'createlore' , 'createwi' ] , '<span class="monospace">(file=bookName key=key [content])</span> – create a new record in the specified book with the key and content (both are optional) and pass the UID down the pipe, e.g. <tt>/createentry file=chatLore key=Shadowfang The sword of the king</tt>' , true , true ) ;
2023-12-01 20:51:49 +01:00
registerSlashCommand ( 'setentryfield' , setEntryFieldCallback , [ 'setlorefield' , 'setwifield' ] , '<span class="monospace">(file=bookName uid=UID field=field [value])</span> – set a field value (default: content) of the record with the UID from the specified book. To set multiple values for key fields, use comma-delimited list as a value, e.g. <tt>/setentryfield file=chatLore uid=123 field=key Shadowfang,sword,weapon</tt>' , true , true ) ;
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 ] ;
}
2023-12-06 23:09:48 +01:00
const response = await fetch ( '/api/worldinfo/get' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-07-20 19:32:15 +02:00
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-12-02 19:04:51 +01:00
const result = await fetch ( '/getsettings' , {
method : 'POST' ,
2023-07-20 19:32:15 +02:00
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { } ) ,
} ) ;
if ( result . ok ) {
var data = await result . json ( ) ;
world _names = data . world _names ? . length ? data . world _names : [ ] ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . find ( 'option[value!=""]' ) . remove ( ) ;
$ ( '#world_editor_select' ) . find ( 'option[value!=""]' ) . remove ( ) ;
2023-07-20 19:32:15 +02:00
world _names . forEach ( ( item , i ) => {
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . append ( ` <option value=' ${ i } ' ${ selected _world _info . includes ( item ) ? ' selected' : '' } > ${ item } </option> ` ) ;
$ ( '#world_editor_select' ) . append ( ` <option value=' ${ i } '> ${ item } </option> ` ) ;
2023-07-20 19:32:15 +02:00
} ) ;
}
}
function hideWorldEditor ( ) {
displayWorldEntries ( null , null ) ;
}
function getWIElement ( name ) {
2023-12-02 19:04:51 +01:00
const wiElement = $ ( '#world_info' ) . children ( ) . filter ( function ( ) {
2023-12-02 20:11:06 +01:00
return $ ( this ) . text ( ) . toLowerCase ( ) === name . toLowerCase ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
return wiElement ;
}
2023-10-05 22:30:18 +02:00
/ * *
* @ param { any [ ] } data WI entries
* @ returns { any [ ] } Sorted data
* /
function sortEntries ( data ) {
2023-12-02 19:04:51 +01:00
const option = $ ( '#world_info_sort_order' ) . find ( ':selected' ) ;
2023-10-05 22:30:18 +02:00
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 ( ) {
2023-12-02 19:04:51 +01:00
toastr . info ( 'Create or import a new World Info file first.' , 'World Info is not set' , { timeOut : 10000 , preventDuplicates : true } ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-21 20:10:11 +02:00
function displayWorldEntries ( name , data , navigation = navigation _option . none ) {
updateEditor = ( navigation ) => displayWorldEntries ( name , data , navigation ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_entries_list' ) . empty ( ) . show ( ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
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 ) {
2023-12-02 19:04:51 +01:00
startPage = $ ( '#world_info_pagination' ) . pagination ( 'getCurrentPageNum' ) ;
2023-08-21 20:10:11 +02:00
}
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-12-02 19:04:51 +01:00
$ ( '#world_info_pagination' ) . pagination ( {
2023-08-21 20:10:11 +02:00
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-12-02 19:04:51 +01: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-12-02 20:11:06 +01: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-12-02 19:04:51 +01:00
$ ( '#world_popup_entries_list' ) . append ( keywordHeaders ) ;
$ ( '#world_popup_entries_list' ) . append ( blocks ) ;
2023-08-21 20:10:11 +02:00
} ,
afterSizeSelectorChange : function ( e ) {
localStorage . setItem ( storageKey , e . target . value ) ;
2023-12-02 21:06:57 +01:00
} ,
2023-08-21 20:10:11 +02:00
} ) ;
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 ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info_pagination' ) . pagination ( 'go' , page ) ;
2023-10-05 22:30:18 +02:00
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
}
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_new' ) . off ( 'click' ) . on ( 'click' , ( ) => {
2023-07-20 19:32:15 +02:00
createWorldInfoEntry ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_name_button' ) . off ( 'click' ) . on ( 'click' , async ( ) => {
2023-07-20 19:32:15 +02:00
await renameWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_backfill_memos' ) . off ( 'click' ) . on ( 'click' , async ( ) => {
2023-10-05 22:56:31 +02:00
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 ] ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , entry . uid , 'comment' , entry . comment ) ;
2023-10-05 22:56:31 +02:00
counter ++ ;
}
}
if ( counter > 0 ) {
toastr . info ( ` Backfilled ${ counter } titles ` ) ;
await saveWorldInfo ( name , data , true ) ;
updateEditor ( navigation _option . previous ) ;
}
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_export' ) . off ( 'click' ) . on ( 'click' , ( ) => {
2023-07-20 19:32:15 +02:00
if ( name && data ) {
const jsonValue = JSON . stringify ( data ) ;
const fileName = ` ${ name } .json ` ;
2023-12-02 19:04:51 +01:00
download ( jsonValue , fileName , 'application/json' ) ;
2023-07-20 19:32:15 +02:00
}
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_delete' ) . off ( 'click' ) . on ( 'click' , async ( ) => {
const confirmation = await callPopup ( ` <h3>Delete the World/Lorebook: " ${ name } "?</h3>This action is irreversible! ` , 'confirm' ) ;
2023-07-20 19:32:15 +02:00
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' ) ;
}
2023-12-02 19:04:51 +01:00
$ ( '#world_popup_entries_list' ) . sortable ( {
2023-08-18 12:41:46 +02:00
delay : getSortableDelay ( ) ,
2023-12-02 19:04:51 +01:00
handle : '.drag-handle' ,
2023-07-20 19:32:15 +02:00
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 ) ;
2023-12-02 21:06:57 +01:00
} ,
2023-07-20 19:32:15 +02:00
} ) ;
//$("#world_popup_entries_list").disableSelection();
}
2023-12-01 20:51:49 +01:00
const originalDataKeyMap = {
'displayIndex' : 'extensions.display_index' ,
'excludeRecursion' : 'extensions.exclude_recursion' ,
'selectiveLogic' : 'selectiveLogic' ,
'comment' : 'comment' ,
'constant' : 'constant' ,
'order' : 'insertion_order' ,
'depth' : 'extensions.depth' ,
'probability' : 'extensions.probability' ,
'position' : 'extensions.position' ,
'content' : 'content' ,
'enabled' : 'enabled' ,
'key' : 'keys' ,
'keysecondary' : 'secondary_keys' ,
'selective' : 'selective' ,
2023-12-02 20:11:06 +01:00
} ;
2023-12-01 20:51:49 +01:00
2023-07-20 19:32:15 +02:00
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 ] ;
2023-12-02 16:21:57 +01:00
if ( ! Object . hasOwn ( currentObject , part ) ) {
2023-07-20 19:32:15 +02:00
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-12-02 19:04:51 +01:00
const template = $ ( '#entry_edit_template .world_entry' ) . clone ( ) ;
template . data ( 'uid' , entry . uid ) ;
template . attr ( 'uid' , entry . uid ) ;
2023-07-20 19:32:15 +02:00
// key
const keyInput = template . find ( 'textarea[name="key"]' ) ;
2023-12-02 19:04:51 +01:00
keyInput . data ( 'uid' , entry . uid ) ;
keyInput . on ( 'click' , function ( event ) {
2023-07-20 19:32:15 +02:00
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
2023-12-02 19:04:51 +01:00
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
2023-12-02 19:04:51 +01:00
. split ( ',' )
2023-07-20 19:32:15 +02:00
. map ( ( x ) => x . trim ( ) )
. filter ( ( x ) => x ) ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'keys' , data . entries [ uid ] . key ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +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"]' ) ;
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown . data ( 'uid' , entry . uid ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown . on ( 'click' , function ( event ) {
2023-10-01 03:21:19 +02:00
event . stopPropagation ( ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-10-01 03:21:19 +02:00
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
const value = Number ( $ ( this ) . val ( ) ) ;
2023-12-02 20:11:06 +01:00
console . debug ( ` logic for ${ entry . uid } set to ${ value } ` ) ;
2023-12-05 11:04:27 +01:00
data . entries [ uid ] . selectiveLogic = ! isNaN ( value ) ? value : world _info _logic . AND _ANY ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'selectiveLogic' , data . entries [ uid ] . selectiveLogic ) ;
2023-07-20 19:32:15 +02:00
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 } ] ` )
2023-12-02 19:04:51 +01:00
. prop ( 'selected' , true )
. trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
2023-09-23 21:35:51 +02:00
// Character filter
2023-12-02 19:04:51 +01:00
const characterFilterLabel = template . find ( 'label[for="characterFilter"] > small' ) ;
characterFilterLabel . text ( entry . characterFilter ? . isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)' ) ;
2023-09-23 21:35:51 +02:00
// exclude characters checkbox
2023-12-02 19:04:51 +01:00
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)' ) ;
2023-09-23 21:35:51 +02:00
if ( data . entries [ uid ] . characterFilter ) {
2023-11-14 22:54:08 +01:00
if ( ! value && data . entries [ uid ] . characterFilter . names . length === 0 && data . entries [ uid ] . characterFilter . tags . length === 0 ) {
2023-09-23 21:35:51 +02:00
delete data . entries [ uid ] . characterFilter ;
} else {
2023-12-02 20:11:06 +01:00
data . entries [ uid ] . characterFilter . isExclude = value ;
2023-09-23 21:35:51 +02:00
}
} 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 ,
2023-11-14 22:54:08 +01:00
names : [ ] ,
tags : [ ] ,
2023-12-02 21:06:57 +01:00
} ,
} ,
2023-09-23 21:35:51 +02:00
) ;
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'character_filter' , data . entries [ uid ] . characterFilter ) ;
2023-09-23 21:35:51 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
characterExclusionInput . prop ( 'checked' , entry . characterFilter ? . isExclude ? ? false ) . trigger ( 'input' ) ;
2023-09-23 21:35:51 +02:00
2023-12-02 19:04:51 +01:00
const characterFilter = template . find ( 'select[name="characterFilter"]' ) ;
2023-12-02 20:11:06 +01:00
characterFilter . data ( 'uid' , entry . uid ) ;
2023-09-23 21:35:51 +02:00
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 ,
} ) ;
}
2023-11-14 22:54:08 +01:00
2023-09-23 21:35:51 +02:00
const characters = getContext ( ) . characters ;
characters . forEach ( ( character ) => {
const option = document . createElement ( 'option' ) ;
2023-12-02 19:04:51 +01:00
const name = character . avatar . replace ( /\.[^/.]+$/ , '' ) ? ? character . name ;
2023-11-14 22:54:08 +01:00
option . innerText = name ;
option . selected = entry . characterFilter ? . names ? . includes ( name ) ;
option . setAttribute ( 'data-type' , 'character' ) ;
characterFilter . append ( option ) ;
} ) ;
const tags = getContext ( ) . tags ;
tags . forEach ( ( tag ) => {
const option = document . createElement ( 'option' ) ;
option . innerText = ` [Tag] ${ tag . name } ` ;
option . selected = entry . characterFilter ? . tags ? . includes ( tag . id ) ;
option . value = tag . id ;
option . setAttribute ( 'data-type' , 'tag' ) ;
characterFilter . append ( option ) ;
2023-09-23 21:35:51 +02:00
} ) ;
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 ;
}
2023-12-02 19:04:51 +01:00
const uid = $ ( this ) . data ( 'uid' ) ;
2023-11-14 22:54:08 +01:00
const selected = $ ( this ) . find ( ':selected' ) ;
if ( ( ! selected || selected ? . length === 0 ) && ! data . entries [ uid ] . characterFilter ? . isExclude ) {
2023-09-23 21:35:51 +02:00
delete data . entries [ uid ] . characterFilter ;
} else {
2023-11-14 22:54:08 +01:00
const names = selected . filter ( '[data-type="character"]' ) . map ( ( _ , e ) => e instanceof HTMLOptionElement && e . innerText ) . toArray ( ) ;
const tags = selected . filter ( '[data-type="tag"]' ) . map ( ( _ , e ) => e instanceof HTMLOptionElement && e . value ) . toArray ( ) ;
2023-09-23 21:35:51 +02:00
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 ,
2023-11-14 22:54:08 +01:00
names : names ,
tags : tags ,
2023-12-02 21:06:57 +01:00
} ,
} ,
2023-09-23 21:35:51 +02:00
) ;
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'character_filter' , data . entries [ uid ] . characterFilter ) ;
2023-09-23 21:35:51 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-07-20 19:32:15 +02:00
// keysecondary
const keySecondaryInput = template . find ( 'textarea[name="keysecondary"]' ) ;
2023-12-02 19:04:51 +01:00
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
2023-12-02 19:04:51 +01:00
. split ( ',' )
2023-07-20 19:32:15 +02:00
. map ( ( x ) => x . trim ( ) )
. filter ( ( x ) => x ) ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'secondary_keys' , data . entries [ uid ] . keysecondary ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +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"]' ) ;
2023-12-02 19:04:51 +01:00
commentInput . data ( 'uid' , entry . uid ) ;
commentInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
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 ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'comment' , data . entries [ uid ] . comment ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
commentToggle . data ( 'uid' , entry . uid ) ;
commentToggle . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
const value = $ ( this ) . prop ( 'checked' ) ;
2023-07-20 19:32:15 +02:00
//console.log(value)
const commentContainer = $ ( this )
2023-12-02 19:04:51 +01:00
. closest ( '.world_entry' )
. find ( '.commentContainer' ) ;
2023-07-20 19:32:15 +02:00
data . entries [ uid ] . addMemo = value ;
saveWorldInfo ( name , data ) ;
value ? commentContainer . show ( ) : commentContainer . hide ( ) ;
} ) ;
2023-12-02 19:04:51 +01:00
commentInput . val ( entry . comment ) . trigger ( 'input' ) ;
2023-10-05 20:02:43 +02:00
initScrollHeight ( commentInput ) ;
2023-12-02 19:04:51 +01:00
commentToggle . prop ( 'checked' , true /* entry.addMemo */ ) . trigger ( 'input' ) ;
2023-12-02 20:11:06 +01:00
commentToggle . parent ( ) . hide ( ) ;
2023-07-20 19:32:15 +02:00
// content
2023-12-02 19:04:51 +01: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"]' ) ;
2023-12-02 19:04:51 +01:00
contentInput . data ( 'uid' , entry . uid ) ;
contentInput . on ( 'input' , function ( _ , { skipCount } = { } ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
const value = $ ( this ) . val ( ) ;
data . entries [ uid ] . content = value ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'content' , data . entries [ uid ] . content ) ;
2023-07-20 19:32:15 +02:00
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-12-02 19:04:51 +01: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"]' ) ;
2023-12-02 19:04:51 +01:00
selectiveInput . data ( 'uid' , entry . uid ) ;
selectiveInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
const value = $ ( this ) . prop ( 'checked' ) ;
2023-07-20 19:32:15 +02:00
data . entries [ uid ] . selective = value ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'selective' , data . entries [ uid ] . selective ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
const keysecondary = $ ( this )
2023-12-02 19:04:51 +01:00
. closest ( '.world_entry' )
. find ( '.keysecondary' ) ;
2023-07-20 19:32:15 +02:00
const keysecondarytextpole = $ ( this )
2023-12-02 19:04:51 +01:00
. closest ( '.world_entry' )
. find ( '.keysecondarytextpole' ) ;
2023-07-20 19:32:15 +02:00
const keyprimarytextpole = $ ( this )
2023-12-02 19:04:51 +01:00
. closest ( '.world_entry' )
. find ( '.keyprimarytextpole' ) ;
2023-07-20 19:32:15 +02:00
const keyprimaryHeight = keyprimarytextpole . outerHeight ( ) ;
keysecondarytextpole . css ( 'height' , keyprimaryHeight + 'px' ) ;
value ? keysecondary . show ( ) : keysecondary . hide ( ) ;
} ) ;
//forced on, ignored if empty
2023-12-02 19:04:51 +01:00
selectiveInput . prop ( 'checked' , true /* entry.selective */ ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
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"]' ) ;
2023-12-02 19:04:51 +01:00
orderInput . data ( 'uid' , entry . uid ) ;
orderInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . order = ! isNaN ( value ) ? value : 0 ;
2023-12-02 20:11:06 +01:00
updatePosOrdDisplay ( uid ) ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'insertion_order' , data . entries [ uid ] . order ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
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
2023-12-07 19:06:06 +01:00
// group
const groupInput = template . find ( 'input[name="group"]' ) ;
groupInput . data ( 'uid' , entry . uid ) ;
groupInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
const value = String ( $ ( this ) . val ( ) ) . trim ( ) ;
data . entries [ uid ] . group = value ;
setOriginalDataValue ( data , uid , 'extensions.group' , data . entries [ uid ] . group ) ;
saveWorldInfo ( name , data ) ;
} ) ;
groupInput . val ( entry . group ? ? '' ) . trigger ( 'input' ) ;
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"]' ) ;
2023-12-02 19:04:51 +01:00
depthInput . data ( 'uid' , entry . uid ) ;
2023-10-05 20:02:43 +02:00
2023-12-02 19:04:51 +01:00
depthInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-09-24 13:41:56 +02:00
const value = Number ( $ ( this ) . val ( ) ) ;
data . entries [ uid ] . depth = ! isNaN ( value ) ? value : 0 ;
2023-12-02 20:11:06 +01:00
updatePosOrdDisplay ( uid ) ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'extensions.depth' , data . entries [ uid ] . depth ) ;
2023-09-24 13:41:56 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01: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"]' ) ;
2023-12-02 19:04:51 +01:00
probabilityInput . data ( 'uid' , entry . uid ) ;
probabilityInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
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 ) ;
}
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'extensions.probability' , data . entries [ uid ] . probability ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
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"]' ) ;
2023-12-02 19:04:51 +01:00
probabilityToggle . data ( 'uid' , entry . uid ) ;
probabilityToggle . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
const value = $ ( this ) . prop ( 'checked' ) ;
2023-07-20 19:32:15 +02:00
data . entries [ uid ] . useProbability = value ;
const probabilityContainer = $ ( this )
2023-12-02 19:04:51 +01:00
. closest ( '.world_entry' )
. find ( '.probabilityContainer' ) ;
2023-07-20 19:32:15 +02:00
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 ;
}
2023-12-02 19:04:51 +01:00
probabilityInput . val ( data . entries [ uid ] . probability ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
} ) ;
//forced on, 100% by default
2023-12-02 19:04:51 +01:00
probabilityToggle . prop ( 'checked' , true /* entry.useProbability */ ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
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-12-02 19:04:51 +01:00
positionInput . data ( 'uid' , entry . uid ) ;
positionInput . on ( 'click' , function ( event ) {
2023-10-05 20:02:43 +02:00
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
2023-12-02 19:04:51 +01:00
positionInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
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-12-02 20:11:06 +01: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-12-02 20:11:06 +01: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-12-02 20:11:06 +01:00
updatePosOrdDisplay ( uid ) ;
2023-07-20 19:32:15 +02:00
// Spec v2 only supports before_char and after_char
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'position' , data . entries [ uid ] . position == 0 ? 'before_char' : 'after_char' ) ;
2023-07-20 19:32:15 +02:00
// Write the original value as extensions field
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'extensions.position' , data . entries [ uid ] . position ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
template
. find ( ` select[name="position"] option[value= ${ entry . position } ] ` )
2023-12-02 19:04:51 +01:00
. prop ( 'selected' , true )
. trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
2023-09-25 11:03:10 +02:00
//add UID above content box (less important doesn't need to be always visible)
2023-12-02 19:04:51 +01:00
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"]' ) ;
2023-12-02 19:04:51 +01:00
entryStateSelector . data ( 'uid' , entry . uid ) ;
2023-12-02 20:11:06 +01:00
console . log ( entry . uid ) ;
2023-12-02 19:04:51 +01:00
entryStateSelector . on ( 'click' , function ( event ) {
2023-10-01 03:21:19 +02:00
// Prevent closing the drawer on clicking the input
event . stopPropagation ( ) ;
} ) ;
2023-12-02 19:04:51 +01:00
entryStateSelector . on ( 'input' , function ( ) {
2023-10-01 03:21:19 +02:00
const uid = entry . uid ;
const value = $ ( this ) . val ( ) ;
switch ( value ) {
2023-12-02 19:04:51 +01:00
case 'constant' :
2023-10-01 03:21:19 +02:00
data . entries [ uid ] . constant = true ;
data . entries [ uid ] . disable = false ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'enabled' , true ) ;
setOriginalDataValue ( data , uid , 'constant' , true ) ;
2023-10-01 03:21:19 +02:00
template . removeClass ( 'disabledWIEntry' ) ;
2023-12-02 20:11:06 +01:00
console . debug ( 'set to constant' ) ;
break ;
2023-12-02 19:04:51 +01:00
case 'normal' :
2023-10-01 03:21:19 +02:00
data . entries [ uid ] . constant = false ;
data . entries [ uid ] . disable = false ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'enabled' , true ) ;
setOriginalDataValue ( data , uid , 'constant' , false ) ;
2023-10-01 03:21:19 +02:00
template . removeClass ( 'disabledWIEntry' ) ;
2023-12-02 20:11:06 +01:00
console . debug ( 'set to normal' ) ;
break ;
2023-12-02 19:04:51 +01:00
case 'disabled' :
2023-10-01 03:21:19 +02:00
data . entries [ uid ] . constant = false ;
data . entries [ uid ] . disable = true ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'enabled' , false ) ;
setOriginalDataValue ( data , uid , 'constant' , false ) ;
2023-10-01 03:21:19 +02:00
template . addClass ( 'disabledWIEntry' ) ;
2023-12-02 20:11:06 +01:00
console . debug ( 'set to disabled' ) ;
break ;
2023-10-01 03:21:19 +02:00
}
saveWorldInfo ( name , data ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-10-01 03:21:19 +02:00
const entryState = function ( ) {
2023-12-02 20:11:06 +01:00
console . log ( ` constant: ${ entry . constant } , disabled: ${ entry . disable } ` ) ;
2023-10-01 03:21:19 +02:00
if ( entry . constant === true ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'found constant' ) ;
return 'constant' ;
2023-10-01 03:21:19 +02:00
} else if ( entry . disable === true ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'found disabled' ) ;
return 'disabled' ;
2023-10-01 03:21:19 +02:00
} else {
2023-12-02 20:11:06 +01:00
console . debug ( 'found normal' ) ;
return 'normal' ;
2023-10-01 03:21:19 +02:00
}
2023-12-02 20:11:06 +01:00
} ;
2023-10-01 03:21:19 +02:00
template
. find ( ` select[name="entryStateSelector"] option[value= ${ entryState ( ) } ] ` )
2023-12-02 19:04:51 +01:00
. prop ( 'selected' , true )
. trigger ( 'input' ) ;
2023-10-01 03:21:19 +02:00
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"]' ) ;
2023-12-02 19:04:51 +01:00
excludeRecursionInput . data ( 'uid' , entry . uid ) ;
excludeRecursionInput . on ( 'input' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
const value = $ ( this ) . prop ( 'checked' ) ;
2023-07-20 19:32:15 +02:00
data . entries [ uid ] . excludeRecursion = value ;
2023-12-02 19:04:51 +01:00
setOriginalDataValue ( data , uid , 'extensions.exclude_recursion' , data . entries [ uid ] . excludeRecursion ) ;
2023-07-20 19:32:15 +02:00
saveWorldInfo ( name , data ) ;
} ) ;
2023-12-02 19:04:51 +01:00
excludeRecursionInput . prop ( 'checked' , entry . excludeRecursion ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
// delete button
2023-12-02 19:04:51 +01:00
const deleteButton = template . find ( '.delete_entry_button' ) ;
deleteButton . data ( 'uid' , entry . uid ) ;
deleteButton . on ( 'click' , function ( ) {
const uid = $ ( this ) . data ( 'uid' ) ;
2023-07-20 19:32:15 +02:00
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
2023-12-02 20:11:06 +01:00
let entry = data . entries [ uid ] ;
let posText = entry . position ;
2023-09-28 17:27:12 +02:00
switch ( entry . position ) {
case 0 :
posText = '↑CD' ;
2023-12-02 20:11:06 +01:00
break ;
2023-09-28 17:27:12 +02:00
case 1 :
posText = 'CD↓' ;
2023-12-02 20:11:06 +01:00
break ;
2023-09-28 17:27:12 +02:00
case 2 :
posText = '↑AN' ;
2023-12-02 20:11:06 +01:00
break ;
2023-09-28 17:27:12 +02:00
case 3 :
posText = 'AN↓' ;
2023-12-02 20:11:06 +01:00
break ;
2023-09-28 17:27:12 +02:00
case 4 :
posText = ` @D ${ entry . depth } ` ;
2023-12-02 20:11:06 +01:00
break ;
2023-09-28 17:27:12 +02:00
}
2023-12-02 19:04:51 +01:00
template . find ( '.world_entry_form_position_value' ) . text ( ` ( ${ posText } ${ entry . order } ) ` ) ;
2023-09-28 17:27:12 +02:00
}
2023-07-20 19:32:15 +02:00
return template ;
}
async function deleteWorldInfoEntry ( data , uid ) {
2023-12-02 19:04:51 +01:00
if ( ! data || ! ( 'entries' in data ) ) {
2023-07-20 19:32:15 +02:00
return ;
}
2023-09-05 11:05:20 +02:00
if ( ! confirm ( ` Delete the entry with UID: ${ uid } ? This action is irreversible! ` ) ) {
2023-12-02 19:04:51 +01:00
throw new Error ( 'User cancelled deletion' ) ;
2023-09-05 11:05:20 +02:00
}
2023-07-20 19:32:15 +02:00
delete data . entries [ uid ] ;
}
2023-12-01 20:51:49 +01:00
const newEntryTemplate = {
key : [ ] ,
keysecondary : [ ] ,
2023-12-02 19:04:51 +01:00
comment : '' ,
content : '' ,
2023-12-01 20:51:49 +01:00
constant : false ,
selective : true ,
2023-12-05 11:04:27 +01:00
selectiveLogic : world _info _logic . AND _ANY ,
2023-12-01 20:51:49 +01:00
addMemo : false ,
order : 100 ,
position : 0 ,
disable : false ,
excludeRecursion : false ,
probability : 100 ,
useProbability : true ,
2023-12-01 21:09:13 +01:00
depth : DEFAULT _DEPTH ,
2023-12-04 17:36:05 +01:00
group : '' ,
2023-12-01 20:51:49 +01:00
} ;
2023-12-01 00:50:10 +01:00
function createWorldInfoEntry ( name , data , fromSlashCommand = false ) {
2023-07-20 19:32:15 +02:00
const newUid = getFreeWorldEntryUid ( data ) ;
if ( ! Number . isInteger ( newUid ) ) {
2023-12-02 19:04:51 +01:00
console . error ( 'Couldn\'t assign UID to a new entry' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
2023-12-01 20:51:49 +01:00
const newEntry = { uid : newUid , ... structuredClone ( newEntryTemplate ) } ;
2023-07-20 19:32:15 +02:00
data . entries [ newUid ] = newEntry ;
2023-12-01 00:50:10 +01:00
if ( ! fromSlashCommand ) {
updateEditor ( newUid ) ;
}
return newEntry ;
2023-07-20 19:32:15 +02:00
}
async function _save ( name , data ) {
2023-12-06 23:09:48 +01:00
await fetch ( '/api/worldinfo/edit' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-07-20 19:32:15 +02:00
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 ;
2023-12-02 19:04:51 +01:00
const newName = await callPopup ( '<h3>Rename World Info</h3>Enter a new name:' , 'input' , oldName ) ;
2023-07-20 19:32:15 +02:00
if ( oldName === newName || ! newName ) {
2023-12-02 19:04:51 +01:00
console . debug ( 'World info rename cancelled' ) ;
2023-07-20 19:32:15 +02:00
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 ) ;
2023-12-02 19:04:51 +01:00
wiElement . prop ( 'selected' , true ) ;
$ ( '#world_info' ) . trigger ( 'change' ) ;
2023-07-20 19:32:15 +02:00
}
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 ;
}
2023-12-06 23:09:48 +01:00
const response = await fetch ( '/api/worldinfo/delete' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-07-20 19:32:15 +02:00
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 ) {
2023-12-02 19:04:51 +01:00
if ( ! data || ! ( 'entries' in data ) ) {
2023-07-20 19:32:15 +02:00
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 {
2023-12-02 20:11:06 +01:00
console . debug ( ` Character ${ name } 's base world could not be found or is empty! Skipping... ` ) ;
2023-07-20 19:32:15 +02:00
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 :
2023-12-02 20:11:06 +01:00
console . debug ( 'WI using evenly' ) ;
2023-07-20 19:32:15 +02:00
entries = [ ... globalLore , ... characterLore ] . sort ( sortFn ) ;
break ;
case world _info _insertion _strategy . character _first :
2023-12-02 20:11:06 +01:00
console . debug ( 'WI using char first' ) ;
2023-07-20 19:32:15 +02:00
entries = [ ... characterLore . sort ( sortFn ) , ... globalLore . sort ( sortFn ) ] ;
break ;
case world _info _insertion _strategy . global _first :
2023-12-02 20:11:06 +01:00
console . debug ( 'WI using global first' ) ;
2023-07-20 19:32:15 +02:00
entries = [ ... globalLore . sort ( sortFn ) , ... characterLore . sort ( sortFn ) ] ;
break ;
default :
2023-12-02 19:04:51 +01:00
console . error ( 'Unknown WI insertion strategy: ' , world _info _character _strategy , 'defaulting to evenly' ) ;
2023-07-20 19:32:15 +02:00
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
2023-12-02 19:04:51 +01:00
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' ) ) {
2023-12-02 20:11:06 +01:00
const depthPrompt = getExtensionPromptByName ( key ) ;
2023-10-25 23:39:11 +02:00
if ( depthPrompt ) {
2023-12-02 20:11:06 +01:00
textToScan = ` ${ depthPrompt } \n ${ textToScan } ` ;
2023-10-25 23:39:11 +02:00
}
}
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 ) {
2023-12-02 20:11:06 +01:00
textToScan = ` ${ anPrompt } \n ${ textToScan } ` ;
2023-10-12 05:44:52 +02:00
}
}
// 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
2023-11-14 22:54:08 +01:00
if ( entry . characterFilter && entry . characterFilter ? . names ? . length > 0 ) {
2023-09-23 21:35:51 +02:00
const nameIncluded = entry . characterFilter . names . includes ( getCharaFilename ( ) ) ;
2023-12-02 20:11:06 +01:00
const filtered = entry . characterFilter . isExclude ? nameIncluded : ! nameIncluded ;
2023-09-23 21:35:51 +02:00
if ( filtered ) {
2023-11-14 22:54:08 +01:00
console . debug ( ` WI entry ${ entry . uid } filtered out by character ` ) ;
2023-09-23 21:35:51 +02:00
continue ;
}
}
2023-11-14 22:54:08 +01:00
if ( entry . characterFilter && entry . characterFilter ? . tags ? . length > 0 ) {
const tagKey = getTagKeyForCharacter ( this _chid ) ;
if ( tagKey ) {
const tagMapEntry = context . tagMap [ tagKey ] ;
if ( Array . isArray ( tagMapEntry ) ) {
// If tag map intersects with the tag exclusion list, skip
const includesTag = tagMapEntry . some ( ( tag ) => entry . characterFilter . tags . includes ( tag ) ) ;
const filtered = entry . characterFilter . isExclude ? includesTag : ! includesTag ;
if ( filtered ) {
console . debug ( ` WI entry ${ entry . uid } filtered out by tag ` ) ;
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 ) {
2023-12-02 20:11:06 +01:00
entry . content = substituteParams ( entry . content ) ;
2023-07-20 19:32:15 +02:00
activatedNow . add ( entry ) ;
continue ;
}
if ( Array . isArray ( entry . key ) && entry . key . length ) { //check for keywords existing
2023-11-15 12:11:09 +01:00
// If selectiveLogic isn't found, assume it's AND, only do this once per entry
const selectiveLogic = entry . selectiveLogic ? ? 0 ;
2023-12-04 22:57:04 +01:00
2023-07-20 19:32:15 +02:00
primary : for ( let key of entry . key ) {
const substituted = substituteParams ( key ) ;
2023-12-04 22:57:04 +01:00
2023-12-02 20:11:06 +01:00
console . debug ( ` ${ entry . uid } : ${ substituted } ` ) ;
2023-12-04 22:57:04 +01:00
2023-07-20 19:32:15 +02:00
if ( substituted && matchKeys ( textToScan , substituted . trim ( ) ) ) {
2023-12-04 22:57:04 +01:00
console . debug ( ` WI UID ${ entry . uid } found by primary match: ${ substituted } . ` ) ;
2023-07-20 19:32:15 +02:00
//selective logic begins
if (
entry . selective && //all entries are selective now
Array . isArray ( entry . keysecondary ) && //always true
entry . keysecondary . length //ignore empties
) {
2023-12-04 22:57:04 +01:00
console . debug ( ` WI UID: ${ entry . uid } found. Checking logic: ${ entry . selectiveLogic } ` ) ;
2023-12-05 09:56:52 +01:00
let hasAnyMatch = false ;
2023-07-20 19:32:15 +02:00
secondary : for ( let keysecondary of entry . keysecondary ) {
const secondarySubstituted = substituteParams ( keysecondary ) ;
2023-12-05 09:56:52 +01:00
const hasSecondaryMatch = secondarySubstituted && matchKeys ( textToScan , secondarySubstituted . trim ( ) ) ;
2023-12-04 22:57:04 +01:00
console . debug ( ` WI UID: ${ entry . uid } : Filtering for secondary keyword - " ${ secondarySubstituted } ". ` ) ;
2023-12-05 09:56:52 +01:00
if ( hasSecondaryMatch ) {
hasAnyMatch = true ;
}
2023-12-05 11:04:27 +01:00
// Simplified AND ANY / NOT ALL if statement. (Proper fix for PR#1356 by Bronya)
// If AND ANY logic and the main checks pass OR if NOT ALL logic and the main checks do not pass
if ( ( selectiveLogic === world _info _logic . AND _ANY && hasSecondaryMatch ) || ( selectiveLogic === world _info _logic . NOT _ALL && ! hasSecondaryMatch ) ) {
2023-12-04 23:05:05 +01:00
// Differ both logic statements in the debugger
2023-12-05 11:04:27 +01:00
if ( selectiveLogic === world _info _logic . AND _ANY ) {
console . debug ( ` (AND ANY Check) Activating WI Entry ${ entry . uid } . Found match for word: ${ substituted } ${ secondarySubstituted } ` ) ;
2023-12-04 22:57:04 +01:00
} else {
2023-12-05 10:00:26 +01:00
console . debug ( ` (NOT ALL Check) Activating WI Entry ${ entry . uid } . Found match for word " ${ substituted } " without secondary keyword: ${ secondarySubstituted } ` ) ;
2023-07-20 19:32:15 +02:00
}
2023-12-04 22:57:04 +01:00
activatedNow . add ( entry ) ;
break secondary ;
2023-07-20 19:32:15 +02:00
}
}
2023-12-05 09:56:52 +01:00
2023-12-05 11:04:27 +01:00
// Handle NOT ANY logic
if ( selectiveLogic === world _info _logic . NOT _ANY && ! hasAnyMatch ) {
console . debug ( ` (NOT ANY Check) Activating WI Entry ${ entry . uid } , no secondary keywords found. ` ) ;
2023-12-05 09:56:52 +01:00
activatedNow . add ( entry ) ;
}
2023-07-20 19:32:15 +02:00
} else {
2023-12-07 19:06:06 +01:00
// Handle cases where secondary is empty
2023-12-04 22:57:04 +01:00
console . debug ( ` WI UID ${ entry . uid } : Activated without filter logic. ` ) ;
2023-07-20 19:32:15 +02:00
activatedNow . add ( entry ) ;
break primary ;
}
2023-12-04 22:57:04 +01:00
} else { console . debug ( ` No active entries for logic checks for word: ${ substituted } . ` ) ; }
2023-11-15 12:11:09 +01:00
}
2023-07-20 19:32:15 +02:00
}
}
needsToScan = world _info _recursive && activatedNow . size > 0 ;
const newEntries = [ ... activatedNow ]
. sort ( ( a , b ) => sortedEntries . indexOf ( a ) - sortedEntries . indexOf ( b ) ) ;
2023-12-02 19:04:51 +01:00
let newContent = '' ;
2023-07-20 19:32:15 +02:00
const textToScanTokens = getTokenCount ( allActivatedText ) ;
const probabilityChecksBefore = failedProbabilityChecks . size ;
2023-12-07 19:06:06 +01:00
filterByInclusionGroups ( newEntries , allActivatedEntries ) ;
2023-12-02 20:11:06 +01:00
console . debug ( '-- PROBABILITY CHECKS BEGIN --' ) ;
2023-07-20 19:32:15 +02:00
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 ;
2023-12-02 20:11:06 +01:00
} else { console . debug ( ` uid: ${ entry . uid } passed probability check, inserting to prompt ` ) ; }
2023-07-20 19:32:15 +02:00
newContent += ` ${ substituteParams ( entry . content ) } \n ` ;
if ( textToScanTokens + getTokenCount ( newContent ) >= budget ) {
2023-12-02 19:04:51 +01:00
console . debug ( 'WI budget reached, stopping' ) ;
2023-07-30 04:15:54 +02:00
if ( world _info _overflow _alert ) {
2023-12-02 19:04:51 +01:00
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 ) {
2023-12-02 19:04:51 +01:00
console . debug ( 'WI probability checks failed for all activated entries, stopping' ) ;
2023-07-20 19:32:15 +02:00
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-12-02 20:11:06 +01:00
let over _max = false ;
2023-11-01 18:02:38 +01:00
over _max = (
world _info _min _activations _depth _max > 0 &&
minActivationMsgIndex > world _info _min _activations _depth _max
2023-12-02 20:11:06 +01:00
) || ( minActivationMsgIndex >= chat . length ) ;
2023-11-01 18:02:38 +01:00
if ( ! over _max ) {
2023-12-02 20:11:06 +01:00
needsToScan = true ;
2023-12-02 19:04:51 +01:00
textToScan = transformString ( chat . slice ( minActivationMsgIndex , minActivationMsgIndex + 1 ) . join ( '' ) ) ;
2023-12-02 20:11:06 +01:00
minActivationMsgIndex += 1 ;
2023-10-30 22:55:32 +01:00
}
}
}
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-12-02 16:14:06 +01: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 ,
2023-12-02 21:06:57 +01:00
entries : [ entry . content ] ,
2023-09-24 13:41:56 +02:00
} ) ;
}
2023-12-02 15:07:08 +01:00
break ;
2023-12-02 16:14:06 +01:00
}
2023-07-20 19:32:15 +02:00
default :
break ;
}
} ) ;
2023-12-02 19:04:51 +01: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 ;
2023-12-02 20:11:06 +01:00
const ANWithWI = ` ${ ANTopEntries . join ( '\n' ) } \n ${ originalAN } \n ${ ANBottomEntries . join ( '\n' ) } ` ;
2023-07-20 19:32:15 +02:00
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
}
2023-12-07 19:06:06 +01:00
/ * *
* Filters entries by inclusion groups .
* @ param { object [ ] } newEntries Entries activated on current recursion level
* @ param { Set < object > } allActivatedEntries Set of all activated entries
* /
function filterByInclusionGroups ( newEntries , allActivatedEntries ) {
console . debug ( '-- INCLUSION GROUP CHECKS BEGIN --' ) ;
const grouped = newEntries . filter ( x => x . group ) . reduce ( ( acc , item ) => {
if ( ! acc [ item . group ] ) {
acc [ item . group ] = [ ] ;
}
acc [ item . group ] . push ( item ) ;
return acc ;
} , { } ) ;
if ( Object . keys ( grouped ) . length === 0 ) {
console . debug ( 'No inclusion groups found' ) ;
return ;
}
for ( const [ key , group ] of Object . entries ( grouped ) ) {
console . debug ( ` Checking inclusion group ' ${ key } ' with ${ group . length } entries ` , group ) ;
if ( ! Array . isArray ( group ) || group . length <= 1 ) {
console . debug ( 'Skipping inclusion group check, only one entry' ) ;
continue ;
}
if ( Array . from ( allActivatedEntries ) . some ( x => x . group === key ) ) {
console . debug ( ` Skipping inclusion group check, group already activated ' ${ key } ' ` ) ;
continue ;
}
// Do weighted random using probability of entry as weight
const totalWeight = group . reduce ( ( acc , item ) => acc + item . probability , 0 ) ;
const rollValue = Math . random ( ) * totalWeight ;
let currentWeight = 0 ;
let winner = null ;
for ( const entry of group ) {
currentWeight += entry . probability ;
if ( rollValue <= currentWeight ) {
console . debug ( ` Activated inclusion group ' ${ key } ' with entry ' ${ entry . uid } ' ` , entry ) ;
winner = entry ;
break ;
}
}
if ( ! winner ) {
console . debug ( ` Failed to activate inclusion group ' ${ key } ', no winner found ` ) ;
continue ;
}
// Remove every group item from newEntries but the winner
for ( const entry of group ) {
if ( entry === winner ) {
continue ;
}
console . debug ( ` Removing loser from inclusion group ' ${ key } ' entry ' ${ entry . uid } ' ` , entry ) ;
newEntries . splice ( newEntries . indexOf ( entry ) , 1 ) ;
}
}
}
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 ,
2023-12-05 11:04:27 +01:00
selectiveLogic : world _info _logic . AND _ANY ,
2023-07-20 19:32:15 +02:00
order : entry . weight ,
position : 0 ,
disable : ! entry . enabled ,
addMemo : ! ! entry . name ,
excludeRecursion : false ,
displayIndex : index ,
probability : null ,
useProbability : false ,
2023-12-04 17:36:05 +01:00
group : '' ,
2023-07-20 19:32:15 +02:00
} ;
} ) ;
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 ,
2023-12-05 11:04:27 +01:00
selectiveLogic : world _info _logic . AND _ANY ,
2023-07-20 19:32:15 +02:00
order : entry . insertorder ,
position : world _info _position . before ,
disable : false ,
addMemo : true ,
excludeRecursion : false ,
displayIndex : index ,
probability : entry . activationPercent ? ? null ,
useProbability : entry . activationPercent ? ? false ,
2023-12-04 17:36:05 +01:00
group : '' ,
2023-07-20 19:32:15 +02:00
} ;
} ) ;
return outputObj ;
}
function convertNovelLorebook ( inputObj ) {
const outputObj = {
2023-12-02 21:06:57 +01:00
entries : { } ,
2023-07-20 19:32:15 +02:00
} ;
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 ,
2023-12-05 11:04:27 +01:00
selectiveLogic : world _info _logic . AND _ANY ,
2023-07-20 19:32:15 +02:00
order : entry . contextConfig ? . budgetPriority ? ? 0 ,
position : 0 ,
disable : ! entry . enabled ,
addMemo : addMemo ,
excludeRecursion : false ,
displayIndex : index ,
probability : null ,
useProbability : false ,
2023-12-04 17:36:05 +01:00
group : '' ,
2023-07-20 19:32:15 +02:00
} ;
} ) ;
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 || [ ] ,
2023-12-02 19:04:51 +01:00
comment : entry . comment || '' ,
2023-07-20 19:32:15 +02:00
content : entry . content ,
constant : entry . constant || false ,
selective : entry . selective || false ,
order : entry . insertion _order ,
2023-12-02 19:04:51 +01:00
position : entry . extensions ? . position ? ? ( entry . position === 'before_char' ? world _info _position . before : world _info _position . after ) ,
2023-07-20 19:32:15 +02:00
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-12-05 11:04:27 +01:00
selectiveLogic : entry . extensions ? . selectiveLogic ? ? world _info _logic . AND _ANY ,
2023-12-04 17:36:05 +01:00
group : entry . extensions ? . group ? ? '' ,
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-12-02 20:56:16 +01:00
const html = ` <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 > ` ;
const checkResult = ( result ) => {
if ( result ) {
importEmbeddedWorldInfo ( true ) ;
}
} ;
2023-12-02 21:06:57 +01:00
callPopup ( html , 'confirm' , '' , { okButton : 'Yes' } ) . then ( checkResult ) ;
2023-10-10 17:08:08 +02:00
}
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..
2023-12-02 19:04:51 +01:00
$ ( '#WIDrawerIcon' ) . trigger ( 'click' ) ;
2023-10-08 16:04:53 +02:00
//..auto-opening the new imported WI
2023-12-02 19:04:51 +01:00
$ ( '#world_editor_select' ) . val ( newIndex ) . trigger ( 'change' ) ;
2023-07-20 19:32:15 +02:00
}
setWorldInfoButtonClass ( chid , true ) ;
}
function onWorldInfoChange ( _ , text ) {
if ( _ !== '__notSlashCommand__' ) { // if it's a slash command
2023-11-25 21:26:41 +01:00
if ( text . trim ( ) !== '' ) { // and args are provided
2023-12-02 19:04:51 +01:00
const slashInputSplitText = text . trim ( ) . toLowerCase ( ) . split ( ',' ) ;
2023-07-20 19:32:15 +02:00
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-12-02 19:04:51 +01:00
wiElement . prop ( 'selected' , true ) ;
2023-07-20 19:32:15 +02:00
toastr . success ( ` Activated world: ${ wiElement . text ( ) } ` ) ;
} else {
toastr . error ( ` No world found named: ${ worldName } ` ) ;
}
2023-08-13 18:20:00 +02:00
} ) ;
2023-12-02 19:04:51 +01: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 = [ ] ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . val ( null ) . trigger ( 'change' ) ;
2023-07-20 19:32:15 +02:00
}
} else { //if it's a pointer selection
let tempWorldInfo = [ ] ;
2023-12-02 19:04:51 +01:00
let selectedWorlds = $ ( '#world_info' ) . val ( ) . map ( ( e ) => Number ( e ) ) . filter ( ( e ) => ! isNaN ( e ) ) ;
2023-07-20 19:32:15 +02:00
if ( selectedWorlds . length > 0 ) {
selectedWorlds . forEach ( ( worldIndex ) => {
const existingWorldName = world _names [ worldIndex ] ;
if ( existingWorldName ) {
tempWorldInfo . push ( existingWorldName ) ;
} else {
const wiElement = getWIElement ( existingWorldName ) ;
2023-12-02 19:04:51 +01:00
wiElement . prop ( 'selected' , false ) ;
2023-07-20 19:32:15 +02:00
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 ( {
2023-12-02 19:04:51 +01:00
type : 'POST' ,
2023-12-06 23:09:48 +01:00
url : '/api/worldinfo/import' ,
2023-07-20 19:32:15 +02:00
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 ) {
2023-12-02 19:04:51 +01:00
$ ( '#world_editor_select' ) . val ( newIndex ) . trigger ( 'change' ) ;
2023-07-20 19:32:15 +02:00
}
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-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info' ) . on ( 'mousedown change' , async function ( e ) {
2023-07-20 19:32:15 +02:00
// If there's no world names, don't do anything
if ( world _names . length === 0 ) {
e . preventDefault ( ) ;
return ;
}
onWorldInfoChange ( '__notSlashCommand__' ) ;
} ) ;
//**************************WORLD INFO IMPORT EXPORT*************************//
2023-12-02 19:04:51 +01:00
$ ( '#world_import_button' ) . on ( 'click' , function ( ) {
$ ( '#world_import_file' ) . trigger ( 'click' ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_import_file' ) . on ( 'change' , async function ( e ) {
2023-07-20 19:32:15 +02:00
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
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_create_button' ) . on ( 'click' , async ( ) => {
2023-07-20 19:32:15 +02:00
const tempName = getFreeWorldName ( ) ;
2023-12-02 19:04:51 +01:00
const finalName = await callPopup ( '<h3>Create a new World Info?</h3>Enter a name for the new file:' , 'input' , tempName ) ;
2023-07-20 19:32:15 +02:00
if ( finalName ) {
await createNewWorldInfo ( finalName ) ;
}
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_editor_select' ) . on ( 'change' , async ( ) => {
$ ( '#world_info_search' ) . val ( '' ) ;
2023-08-22 00:51:31 +02:00
worldInfoFilter . setFilterData ( FILTER _TYPES . WORLD _INFO _SEARCH , '' , true ) ;
2023-12-02 19:04:51 +01:00
const selectedIndex = String ( $ ( '#world_editor_select' ) . find ( ':selected' ) . val ( ) ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
if ( selectedIndex === '' ) {
2023-07-20 19:32:15 +02:00
hideWorldEditor ( ) ;
} else {
const worldName = world _names [ selectedIndex ] ;
showWorldEditor ( worldName ) ;
}
} ) ;
2023-07-03 19:06:18 +02:00
const saveSettings = ( ) => {
2023-12-02 20:11:06 +01:00
saveSettingsDebounced ( ) ;
2023-07-03 19:06:18 +02:00
eventSource . emit ( event _types . WORLDINFO _SETTINGS _UPDATED ) ;
2023-12-02 20:11:06 +01:00
} ;
2023-07-03 19:06:18 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#world_info_depth' ) . on ( 'input' , function ( ) {
2023-07-20 19:32:15 +02:00
world _info _depth = Number ( $ ( this ) . val ( ) ) ;
2023-12-02 19:04:51 +01: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-12-02 19:04:51 +01:00
$ ( '#world_info_min_activations' ) . on ( 'input' , function ( ) {
2023-11-01 18:02:38 +01:00
world _info _min _activations = Number ( $ ( this ) . val ( ) ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info_min_activations_counter' ) . val ( $ ( this ) . val ( ) ) ;
2023-11-01 18:02:38 +01:00
saveSettings ( ) ;
} ) ;
2023-12-02 19:04:51 +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 ( ) ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info_min_activations_depth_max_counter' ) . val ( $ ( this ) . val ( ) ) ;
2023-11-01 18:02:38 +01:00
saveSettings ( ) ;
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#world_info_budget' ) . on ( 'input' , function ( ) {
2023-07-20 19:32:15 +02:00
world _info _budget = Number ( $ ( this ) . val ( ) ) ;
2023-12-02 19:04:51 +01: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-12-02 19:04:51 +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-12-02 19:04:51 +01: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 ) ;
2023-12-02 19:04:51 +01:00
$ ( '#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 ( ) {
2023-12-02 19:04:51 +01:00
const value = String ( $ ( this ) . find ( ':selected' ) . val ( ) ) ;
2023-11-11 19:16:57 +01:00
localStorage . setItem ( SORT _ORDER _KEY , value ) ;
2023-10-05 22:30:18 +02:00
updateEditor ( navigation _option . none ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-10-05 22:30:18 +02:00
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 ,
} ) ;
}
2023-12-05 11:04:27 +01:00
} ) ;