2023-12-15 11:43:27 +01:00
import { callPopup , chat _metadata , eventSource , event _types , generateQuietPrompt , getCurrentChatId , getRequestHeaders , getThumbnailUrl , saveSettingsDebounced } from '../script.js' ;
2023-12-02 19:04:51 +01:00
import { saveMetadataDebounced } from './extensions.js' ;
2024-05-12 21:15:05 +02:00
import { SlashCommand } from './slash-commands/SlashCommand.js' ;
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js' ;
2024-04-27 10:26:01 +02:00
import { flashHighlight , stringFormat } from './utils.js' ;
2023-10-21 00:40:58 +02:00
2023-10-21 16:43:25 +02:00
const BG _METADATA _KEY = 'custom_background' ;
const LIST _METADATA _KEY = 'chat_backgrounds' ;
2023-10-21 00:40:58 +02:00
2023-12-15 11:43:27 +01:00
export let background _settings = {
name : '__transparent.png' ,
url : generateUrlParameter ( '__transparent.png' , false ) ,
} ;
export function loadBackgroundSettings ( settings ) {
let backgroundSettings = settings . background ;
if ( ! backgroundSettings || ! backgroundSettings . name || ! backgroundSettings . url ) {
backgroundSettings = background _settings ;
}
setBackground ( backgroundSettings . name , backgroundSettings . url ) ;
}
2023-10-21 00:40:58 +02:00
/ * *
2023-10-21 16:43:25 +02:00
* Sets the background for the current chat and adds it to the list of custom backgrounds .
* @ param { { url : string , path : string } } backgroundInfo
2023-10-21 00:40:58 +02:00
* /
2023-10-21 16:43:25 +02:00
function forceSetBackground ( backgroundInfo ) {
saveBackgroundMetadata ( backgroundInfo . url ) ;
2023-10-21 00:40:58 +02:00
setCustomBackground ( ) ;
2023-10-21 16:43:25 +02:00
const list = chat _metadata [ LIST _METADATA _KEY ] || [ ] ;
const bg = backgroundInfo . path ;
list . push ( bg ) ;
chat _metadata [ LIST _METADATA _KEY ] = list ;
saveMetadataDebounced ( ) ;
getChatBackgroundsList ( ) ;
highlightNewBackground ( bg ) ;
2023-10-21 00:40:58 +02:00
highlightLockedBackground ( ) ;
}
async function onChatChanged ( ) {
if ( hasCustomBackground ( ) ) {
setCustomBackground ( ) ;
}
else {
unsetCustomBackground ( ) ;
}
2023-10-21 16:43:25 +02:00
getChatBackgroundsList ( ) ;
2023-10-21 00:40:58 +02:00
highlightLockedBackground ( ) ;
}
2023-10-21 16:43:25 +02:00
function getChatBackgroundsList ( ) {
const list = chat _metadata [ LIST _METADATA _KEY ] ;
const listEmpty = ! Array . isArray ( list ) || list . length === 0 ;
$ ( '#bg_custom_content' ) . empty ( ) ;
$ ( '#bg_chat_hint' ) . toggle ( listEmpty ) ;
if ( listEmpty ) {
return ;
}
for ( const bg of list ) {
const template = getBackgroundFromTemplate ( bg , true ) ;
$ ( '#bg_custom_content' ) . append ( template ) ;
}
}
2023-10-21 00:40:58 +02:00
function getBackgroundPath ( fileUrl ) {
return ` backgrounds/ ${ fileUrl } ` ;
}
function highlightLockedBackground ( ) {
$ ( '.bg_example' ) . removeClass ( 'locked' ) ;
2023-10-21 16:43:25 +02:00
const lockedBackground = chat _metadata [ BG _METADATA _KEY ] ;
2023-10-21 00:40:58 +02:00
if ( ! lockedBackground ) {
return ;
}
2023-12-02 19:04:51 +01:00
$ ( '.bg_example' ) . each ( function ( ) {
2023-10-21 00:40:58 +02:00
const url = $ ( this ) . data ( 'url' ) ;
if ( url === lockedBackground ) {
$ ( this ) . addClass ( 'locked' ) ;
}
} ) ;
}
function onLockBackgroundClick ( e ) {
e . stopPropagation ( ) ;
const chatName = getCurrentChatId ( ) ;
if ( ! chatName ) {
toastr . warning ( 'Select a chat to lock the background for it' ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
const relativeBgImage = getUrlParameter ( this ) ;
saveBackgroundMetadata ( relativeBgImage ) ;
setCustomBackground ( ) ;
highlightLockedBackground ( ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
function onUnlockBackgroundClick ( e ) {
e . stopPropagation ( ) ;
removeBackgroundMetadata ( ) ;
unsetCustomBackground ( ) ;
highlightLockedBackground ( ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
function hasCustomBackground ( ) {
2023-10-21 16:43:25 +02:00
return chat _metadata [ BG _METADATA _KEY ] ;
2023-10-21 00:40:58 +02:00
}
function saveBackgroundMetadata ( file ) {
2023-10-21 16:43:25 +02:00
chat _metadata [ BG _METADATA _KEY ] = file ;
2023-10-21 00:40:58 +02:00
saveMetadataDebounced ( ) ;
}
function removeBackgroundMetadata ( ) {
2023-10-21 16:43:25 +02:00
delete chat _metadata [ BG _METADATA _KEY ] ;
2023-10-21 00:40:58 +02:00
saveMetadataDebounced ( ) ;
}
function setCustomBackground ( ) {
2023-10-21 16:43:25 +02:00
const file = chat _metadata [ BG _METADATA _KEY ] ;
2023-10-21 00:40:58 +02:00
// bg already set
2023-12-02 19:04:51 +01:00
if ( document . getElementById ( 'bg_custom' ) . style . backgroundImage == file ) {
2023-10-21 00:40:58 +02:00
return ;
}
2023-12-02 19:04:51 +01:00
$ ( '#bg_custom' ) . css ( 'background-image' , file ) ;
2023-10-21 00:40:58 +02:00
}
function unsetCustomBackground ( ) {
2023-12-02 19:04:51 +01:00
$ ( '#bg_custom' ) . css ( 'background-image' , 'none' ) ;
2023-10-21 00:40:58 +02:00
}
function onSelectBackgroundClick ( ) {
2023-10-21 16:43:25 +02:00
const isCustom = $ ( this ) . attr ( 'custom' ) === 'true' ;
2023-10-21 00:40:58 +02:00
const relativeBgImage = getUrlParameter ( this ) ;
// if clicked on upload button
if ( ! relativeBgImage ) {
return ;
}
2023-10-21 16:43:25 +02:00
// Automatically lock the background if it's custom or other background is locked
if ( hasCustomBackground ( ) || isCustom ) {
2023-10-21 00:40:58 +02:00
saveBackgroundMetadata ( relativeBgImage ) ;
setCustomBackground ( ) ;
2023-10-21 16:43:25 +02:00
highlightLockedBackground ( ) ;
2023-10-21 00:40:58 +02:00
}
2023-12-15 11:43:27 +01:00
highlightLockedBackground ( ) ;
2023-10-21 00:40:58 +02:00
const customBg = window . getComputedStyle ( document . getElementById ( 'bg_custom' ) ) . backgroundImage ;
2023-10-21 16:43:25 +02:00
// Custom background is set. Do not override the layer below
2023-10-21 00:40:58 +02:00
if ( customBg !== 'none' ) {
return ;
}
2023-12-02 19:04:51 +01:00
const bgFile = $ ( this ) . attr ( 'bgfile' ) ;
2023-10-21 00:40:58 +02:00
const backgroundUrl = getBackgroundPath ( bgFile ) ;
2023-10-21 16:43:25 +02:00
// Fetching to browser memory to reduce flicker
2023-10-21 00:40:58 +02:00
fetch ( backgroundUrl ) . then ( ( ) => {
2023-12-15 11:43:27 +01:00
setBackground ( bgFile , relativeBgImage ) ;
2023-10-21 00:40:58 +02:00
} ) . catch ( ( ) => {
console . log ( 'Background could not be set: ' + backgroundUrl ) ;
} ) ;
}
2023-10-21 16:43:25 +02:00
async function onCopyToSystemBackgroundClick ( e ) {
2023-10-21 00:40:58 +02:00
e . stopPropagation ( ) ;
2023-10-21 16:43:25 +02:00
const bgNames = await getNewBackgroundName ( this ) ;
if ( ! bgNames ) {
return ;
}
const bgFile = await fetch ( bgNames . oldBg ) ;
if ( ! bgFile . ok ) {
toastr . warning ( 'Failed to copy background' ) ;
return ;
}
const blob = await bgFile . blob ( ) ;
const file = new File ( [ blob ] , bgNames . newBg ) ;
const formData = new FormData ( ) ;
formData . set ( 'avatar' , file ) ;
uploadBackground ( formData ) ;
2023-10-21 00:40:58 +02:00
2023-10-21 16:43:25 +02:00
const list = chat _metadata [ LIST _METADATA _KEY ] || [ ] ;
const index = list . indexOf ( bgNames . oldBg ) ;
list . splice ( index , 1 ) ;
saveMetadataDebounced ( ) ;
getChatBackgroundsList ( ) ;
}
/ * *
* Gets the new background name from the user .
* @ param { Element } referenceElement
* @ returns { Promise < { oldBg : string , newBg : string } > }
* * /
async function getNewBackgroundName ( referenceElement ) {
const exampleBlock = $ ( referenceElement ) . closest ( '.bg_example' ) ;
const isCustom = exampleBlock . attr ( 'custom' ) === 'true' ;
const oldBg = exampleBlock . attr ( 'bgfile' ) ;
if ( ! oldBg ) {
2023-10-21 00:40:58 +02:00
console . debug ( 'no bgfile' ) ;
return ;
}
2023-10-21 16:43:25 +02:00
const fileExtension = oldBg . split ( '.' ) . pop ( ) ;
const fileNameBase = isCustom ? oldBg . split ( '/' ) . pop ( ) : oldBg ;
const oldBgExtensionless = fileNameBase . replace ( ` . ${ fileExtension } ` , '' ) ;
const newBgExtensionless = await callPopup ( '<h3>Enter new background name:</h3>' , 'input' , oldBgExtensionless ) ;
2023-10-21 00:40:58 +02:00
2023-10-21 16:43:25 +02:00
if ( ! newBgExtensionless ) {
2023-10-21 00:40:58 +02:00
console . debug ( 'no new_bg_extensionless' ) ;
return ;
}
2023-10-21 16:43:25 +02:00
const newBg = ` ${ newBgExtensionless } . ${ fileExtension } ` ;
2023-10-21 00:40:58 +02:00
2023-10-21 16:43:25 +02:00
if ( oldBgExtensionless === newBgExtensionless ) {
2023-10-21 00:40:58 +02:00
console . debug ( 'new_bg === old_bg' ) ;
return ;
}
2023-10-21 16:43:25 +02:00
return { oldBg , newBg } ;
}
async function onRenameBackgroundClick ( e ) {
e . stopPropagation ( ) ;
const bgNames = await getNewBackgroundName ( this ) ;
if ( ! bgNames ) {
return ;
}
const data = { old _bg : bgNames . oldBg , new _bg : bgNames . newBg } ;
2023-12-07 21:17:19 +01:00
const response = await fetch ( '/api/backgrounds/rename' , {
2023-10-21 00:40:58 +02:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( data ) ,
cache : 'no-cache' ,
} ) ;
if ( response . ok ) {
await getBackgrounds ( ) ;
2023-10-21 16:43:25 +02:00
highlightNewBackground ( bgNames . newBg ) ;
2023-10-21 00:40:58 +02:00
} else {
toastr . warning ( 'Failed to rename background' ) ;
}
}
async function onDeleteBackgroundClick ( e ) {
e . stopPropagation ( ) ;
const bgToDelete = $ ( this ) . closest ( '.bg_example' ) ;
const url = bgToDelete . data ( 'url' ) ;
2023-10-21 16:43:25 +02:00
const isCustom = bgToDelete . attr ( 'custom' ) === 'true' ;
2023-12-02 19:04:51 +01:00
const confirm = await callPopup ( '<h3>Delete the background?</h3>' , 'confirm' ) ;
2023-10-21 16:43:25 +02:00
const bg = bgToDelete . attr ( 'bgfile' ) ;
2023-10-21 00:40:58 +02:00
if ( confirm ) {
2023-10-21 16:43:25 +02:00
// If it's not custom, it's a built-in background. Delete it from the server
if ( ! isCustom ) {
delBackground ( bg ) ;
} else {
const list = chat _metadata [ LIST _METADATA _KEY ] || [ ] ;
const index = list . indexOf ( bg ) ;
list . splice ( index , 1 ) ;
}
2023-10-21 00:40:58 +02:00
const siblingSelector = '.bg_example:not(#form_bg_download)' ;
const nextBg = bgToDelete . next ( siblingSelector ) ;
const prevBg = bgToDelete . prev ( siblingSelector ) ;
2023-10-21 16:43:25 +02:00
const anyBg = $ ( siblingSelector ) ;
2023-10-21 00:40:58 +02:00
if ( nextBg . length > 0 ) {
nextBg . trigger ( 'click' ) ;
2023-10-21 16:43:25 +02:00
} else if ( prevBg . length > 0 ) {
2023-10-21 00:40:58 +02:00
prevBg . trigger ( 'click' ) ;
2023-10-21 16:43:25 +02:00
} else {
$ ( anyBg [ Math . floor ( Math . random ( ) * anyBg . length ) ] ) . trigger ( 'click' ) ;
2023-10-21 00:40:58 +02:00
}
bgToDelete . remove ( ) ;
2023-10-21 16:43:25 +02:00
if ( url === chat _metadata [ BG _METADATA _KEY ] ) {
2023-10-21 00:40:58 +02:00
removeBackgroundMetadata ( ) ;
unsetCustomBackground ( ) ;
highlightLockedBackground ( ) ;
}
2023-10-21 16:43:25 +02:00
if ( isCustom ) {
getChatBackgroundsList ( ) ;
saveMetadataDebounced ( ) ;
}
2023-10-21 00:40:58 +02:00
}
}
2023-12-02 19:04:51 +01:00
const autoBgPrompt = 'Pause your roleplay and choose a location ONLY from the provided list that is the most suitable for the current scene. Do not output any other text:\n{0}' ;
2023-10-21 00:40:58 +02:00
async function autoBackgroundCommand ( ) {
2023-10-21 19:41:19 +02:00
/** @type {HTMLElement[]} */
const bgTitles = Array . from ( document . querySelectorAll ( '#bg_menu_content .BGSampleTitle' ) ) ;
const options = bgTitles . map ( x => ( { element : x , text : x . innerText . trim ( ) } ) ) . filter ( x => x . text . length > 0 ) ;
2023-10-21 00:40:58 +02:00
if ( options . length == 0 ) {
toastr . warning ( 'No backgrounds to choose from. Please upload some images to the "backgrounds" folder.' ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
const list = options . map ( option => ` - ${ option . text } ` ) . join ( '\n' ) ;
const prompt = stringFormat ( autoBgPrompt , list ) ;
const reply = await generateQuietPrompt ( prompt , false , false ) ;
const fuse = new Fuse ( options , { keys : [ 'text' ] } ) ;
const bestMatch = fuse . search ( reply , { limit : 1 } ) ;
if ( bestMatch . length == 0 ) {
toastr . warning ( 'No match found. Please try again.' ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
console . debug ( 'Automatically choosing background:' , bestMatch ) ;
bestMatch [ 0 ] . item . element . click ( ) ;
2024-06-17 03:30:52 +02:00
return '' ;
2023-10-21 00:40:58 +02:00
}
export async function getBackgrounds ( ) {
2023-12-07 21:17:19 +01:00
const response = await fetch ( '/api/backgrounds/all' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-10-21 00:40:58 +02:00
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
2023-12-02 19:04:51 +01:00
'' : '' ,
2023-10-21 00:40:58 +02:00
} ) ,
} ) ;
2023-12-15 11:43:27 +01:00
if ( response . ok ) {
2023-10-21 00:40:58 +02:00
const getData = await response . json ( ) ;
//background = getData;
//console.log(getData.length);
2023-12-02 19:04:51 +01:00
$ ( '#bg_menu_content' ) . children ( 'div' ) . remove ( ) ;
2023-10-21 00:40:58 +02:00
for ( const bg of getData ) {
const template = getBackgroundFromTemplate ( bg , false ) ;
2023-12-02 19:04:51 +01:00
$ ( '#bg_menu_content' ) . append ( template ) ;
2023-10-21 00:40:58 +02:00
}
}
}
/ * *
2023-12-15 11:43:27 +01:00
* Gets the CSS URL of the background
2023-10-21 00:40:58 +02:00
* @ param { Element } block
* @ returns { string } URL of the background
* /
function getUrlParameter ( block ) {
2023-12-02 19:04:51 +01:00
return $ ( block ) . closest ( '.bg_example' ) . data ( 'url' ) ;
2023-10-21 00:40:58 +02:00
}
2023-12-15 11:43:27 +01:00
function generateUrlParameter ( bg , isCustom ) {
return isCustom ? ` url(" ${ encodeURI ( bg ) } ") ` : ` url(" ${ getBackgroundPath ( bg ) } ") ` ;
}
2023-10-21 00:40:58 +02:00
/ * *
* Instantiates a background template
* @ param { string } bg Path to background
* @ param { boolean } isCustom Whether the background is custom
2023-10-21 16:43:25 +02:00
* @ returns { JQuery < HTMLElement > } Background template
2023-10-21 00:40:58 +02:00
* /
function getBackgroundFromTemplate ( bg , isCustom ) {
const template = $ ( '#background_template .bg_example' ) . clone ( ) ;
2023-10-21 16:43:25 +02:00
const thumbPath = isCustom ? bg : getThumbnailUrl ( 'bg' , bg ) ;
2023-12-15 11:43:27 +01:00
const url = generateUrlParameter ( bg , isCustom ) ;
2023-10-21 16:43:25 +02:00
const title = isCustom ? bg . split ( '/' ) . pop ( ) : bg ;
const friendlyTitle = title . slice ( 0 , title . lastIndexOf ( '.' ) ) ;
template . attr ( 'title' , title ) ;
2023-10-21 00:40:58 +02:00
template . attr ( 'bgfile' , bg ) ;
template . attr ( 'custom' , String ( isCustom ) ) ;
template . data ( 'url' , url ) ;
template . css ( 'background-image' , ` url(' ${ thumbPath } ') ` ) ;
2023-10-21 16:43:25 +02:00
template . find ( '.BGSampleTitle' ) . text ( friendlyTitle ) ;
2023-10-21 00:40:58 +02:00
return template ;
}
2023-12-15 11:43:27 +01:00
async function setBackground ( bg , url ) {
$ ( '#bg1' ) . css ( 'background-image' , url ) ;
background _settings . name = bg ;
background _settings . url = url ;
saveSettingsDebounced ( ) ;
2023-10-21 00:40:58 +02:00
}
async function delBackground ( bg ) {
2023-12-07 21:17:19 +01:00
await fetch ( '/api/backgrounds/delete' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-10-21 00:40:58 +02:00
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
bg : bg ,
} ) ,
} ) ;
}
function onBackgroundUploadSelected ( ) {
2023-12-02 19:04:51 +01:00
const form = $ ( '#form_bg_download' ) . get ( 0 ) ;
2023-10-21 16:43:25 +02:00
if ( ! ( form instanceof HTMLFormElement ) ) {
console . error ( 'form_bg_download is not a form' ) ;
return ;
2023-10-21 00:40:58 +02:00
}
2023-10-21 16:43:25 +02:00
const formData = new FormData ( form ) ;
uploadBackground ( formData ) ;
form . reset ( ) ;
}
/ * *
* Uploads a background to the server
* @ param { FormData } formData
* /
function uploadBackground ( formData ) {
jQuery . ajax ( {
2023-12-02 19:04:51 +01:00
type : 'POST' ,
2023-12-07 21:17:19 +01:00
url : '/api/backgrounds/upload' ,
2023-10-21 16:43:25 +02:00
data : formData ,
beforeSend : function ( ) {
} ,
cache : false ,
contentType : false ,
processData : false ,
success : async function ( bg ) {
2023-12-15 11:43:27 +01:00
setBackground ( bg , generateUrlParameter ( bg , false ) ) ;
2023-10-21 16:43:25 +02:00
await getBackgrounds ( ) ;
highlightNewBackground ( bg ) ;
} ,
error : function ( jqXHR , exception ) {
console . log ( exception ) ;
console . log ( jqXHR ) ;
} ,
} ) ;
2023-10-21 00:40:58 +02:00
}
/ * *
* @ param { string } bg
* /
function highlightNewBackground ( bg ) {
const newBg = $ ( ` .bg_example[bgfile=" ${ bg } "] ` ) ;
const scrollOffset = newBg . offset ( ) . top - newBg . parent ( ) . offset ( ) . top ;
$ ( '#Backgrounds' ) . scrollTop ( scrollOffset ) ;
2024-04-27 10:26:01 +02:00
flashHighlight ( newBg ) ;
2023-10-21 00:40:58 +02:00
}
function onBackgroundFilterInput ( ) {
const filterValue = String ( $ ( this ) . val ( ) ) . toLowerCase ( ) ;
2023-12-02 19:04:51 +01:00
$ ( '#bg_menu_content > div' ) . each ( function ( ) {
2023-10-21 00:40:58 +02:00
const $bgContent = $ ( this ) ;
2023-12-02 19:04:51 +01:00
if ( $bgContent . attr ( 'title' ) . toLowerCase ( ) . includes ( filterValue ) ) {
2023-10-21 00:40:58 +02:00
$bgContent . show ( ) ;
} else {
$bgContent . hide ( ) ;
}
} ) ;
}
export function initBackgrounds ( ) {
eventSource . on ( event _types . CHAT _CHANGED , onChatChanged ) ;
eventSource . on ( event _types . FORCE _SET _BACKGROUND , forceSetBackground ) ;
2023-12-02 19:04:51 +01:00
$ ( document ) . on ( 'click' , '.bg_example' , onSelectBackgroundClick ) ;
2023-10-21 00:40:58 +02:00
$ ( document ) . on ( 'click' , '.bg_example_lock' , onLockBackgroundClick ) ;
$ ( document ) . on ( 'click' , '.bg_example_unlock' , onUnlockBackgroundClick ) ;
$ ( document ) . on ( 'click' , '.bg_example_edit' , onRenameBackgroundClick ) ;
2023-12-02 19:04:51 +01:00
$ ( document ) . on ( 'click' , '.bg_example_cross' , onDeleteBackgroundClick ) ;
$ ( document ) . on ( 'click' , '.bg_example_copy' , onCopyToSystemBackgroundClick ) ;
$ ( '#auto_background' ) . on ( 'click' , autoBackgroundCommand ) ;
$ ( '#add_bg_button' ) . on ( 'change' , onBackgroundUploadSelected ) ;
$ ( '#bg-filter' ) . on ( 'input' , onBackgroundFilterInput ) ;
2024-05-12 21:15:05 +02:00
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( { name : 'lockbg' ,
callback : onLockBackgroundClick ,
aliases : [ 'bglock' ] ,
helpString : 'Locks a background for the currently selected chat' ,
} ) ) ;
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( { name : 'unlockbg' ,
callback : onUnlockBackgroundClick ,
aliases : [ 'bgunlock' ] ,
helpString : 'Unlocks a background for the currently selected chat' ,
} ) ) ;
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( { name : 'autobg' ,
callback : autoBackgroundCommand ,
aliases : [ 'bgauto' ] ,
helpString : 'Automatically changes the background based on the chat context using the AI request prompt' ,
} ) ) ;
2023-10-21 00:40:58 +02:00
}