Spaces:
Running
Running
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js'; | |
import { saveMetadataDebounced } from './extensions.js'; | |
import { SlashCommand } from './slash-commands/SlashCommand.js'; | |
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | |
import { flashHighlight, stringFormat } from './utils.js'; | |
const BG_METADATA_KEY = 'custom_background'; | |
const LIST_METADATA_KEY = 'chat_backgrounds'; | |
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); | |
} | |
/** | |
* Sets the background for the current chat and adds it to the list of custom backgrounds. | |
* @param {{url: string, path:string}} backgroundInfo | |
*/ | |
function forceSetBackground(backgroundInfo) { | |
saveBackgroundMetadata(backgroundInfo.url); | |
setCustomBackground(); | |
const list = chat_metadata[LIST_METADATA_KEY] || []; | |
const bg = backgroundInfo.path; | |
list.push(bg); | |
chat_metadata[LIST_METADATA_KEY] = list; | |
saveMetadataDebounced(); | |
getChatBackgroundsList(); | |
highlightNewBackground(bg); | |
highlightLockedBackground(); | |
} | |
async function onChatChanged() { | |
if (hasCustomBackground()) { | |
setCustomBackground(); | |
} | |
else { | |
unsetCustomBackground(); | |
} | |
getChatBackgroundsList(); | |
highlightLockedBackground(); | |
} | |
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); | |
} | |
} | |
function getBackgroundPath(fileUrl) { | |
return `backgrounds/${fileUrl}`; | |
} | |
function highlightLockedBackground() { | |
$('.bg_example').removeClass('locked'); | |
const lockedBackground = chat_metadata[BG_METADATA_KEY]; | |
if (!lockedBackground) { | |
return; | |
} | |
$('.bg_example').each(function () { | |
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'); | |
return ''; | |
} | |
const relativeBgImage = getUrlParameter(this); | |
saveBackgroundMetadata(relativeBgImage); | |
setCustomBackground(); | |
highlightLockedBackground(); | |
return ''; | |
} | |
function onUnlockBackgroundClick(e) { | |
e.stopPropagation(); | |
removeBackgroundMetadata(); | |
unsetCustomBackground(); | |
highlightLockedBackground(); | |
return ''; | |
} | |
function hasCustomBackground() { | |
return chat_metadata[BG_METADATA_KEY]; | |
} | |
function saveBackgroundMetadata(file) { | |
chat_metadata[BG_METADATA_KEY] = file; | |
saveMetadataDebounced(); | |
} | |
function removeBackgroundMetadata() { | |
delete chat_metadata[BG_METADATA_KEY]; | |
saveMetadataDebounced(); | |
} | |
function setCustomBackground() { | |
const file = chat_metadata[BG_METADATA_KEY]; | |
// bg already set | |
if (document.getElementById('bg_custom').style.backgroundImage == file) { | |
return; | |
} | |
$('#bg_custom').css('background-image', file); | |
} | |
function unsetCustomBackground() { | |
$('#bg_custom').css('background-image', 'none'); | |
} | |
function onSelectBackgroundClick() { | |
const isCustom = $(this).attr('custom') === 'true'; | |
const relativeBgImage = getUrlParameter(this); | |
// if clicked on upload button | |
if (!relativeBgImage) { | |
return; | |
} | |
// Automatically lock the background if it's custom or other background is locked | |
if (hasCustomBackground() || isCustom) { | |
saveBackgroundMetadata(relativeBgImage); | |
setCustomBackground(); | |
highlightLockedBackground(); | |
} | |
highlightLockedBackground(); | |
const customBg = window.getComputedStyle(document.getElementById('bg_custom')).backgroundImage; | |
// Custom background is set. Do not override the layer below | |
if (customBg !== 'none') { | |
return; | |
} | |
const bgFile = $(this).attr('bgfile'); | |
const backgroundUrl = getBackgroundPath(bgFile); | |
// Fetching to browser memory to reduce flicker | |
fetch(backgroundUrl).then(() => { | |
setBackground(bgFile, relativeBgImage); | |
}).catch(() => { | |
console.log('Background could not be set: ' + backgroundUrl); | |
}); | |
} | |
async function onCopyToSystemBackgroundClick(e) { | |
e.stopPropagation(); | |
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); | |
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) { | |
console.debug('no bgfile'); | |
return; | |
} | |
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); | |
if (!newBgExtensionless) { | |
console.debug('no new_bg_extensionless'); | |
return; | |
} | |
const newBg = `${newBgExtensionless}.${fileExtension}`; | |
if (oldBgExtensionless === newBgExtensionless) { | |
console.debug('new_bg === old_bg'); | |
return; | |
} | |
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 }; | |
const response = await fetch('/api/backgrounds/rename', { | |
method: 'POST', | |
headers: getRequestHeaders(), | |
body: JSON.stringify(data), | |
cache: 'no-cache', | |
}); | |
if (response.ok) { | |
await getBackgrounds(); | |
highlightNewBackground(bgNames.newBg); | |
} else { | |
toastr.warning('Failed to rename background'); | |
} | |
} | |
async function onDeleteBackgroundClick(e) { | |
e.stopPropagation(); | |
const bgToDelete = $(this).closest('.bg_example'); | |
const url = bgToDelete.data('url'); | |
const isCustom = bgToDelete.attr('custom') === 'true'; | |
const confirm = await callPopup('<h3>Delete the background?</h3>', 'confirm'); | |
const bg = bgToDelete.attr('bgfile'); | |
if (confirm) { | |
// 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); | |
} | |
const siblingSelector = '.bg_example:not(#form_bg_download)'; | |
const nextBg = bgToDelete.next(siblingSelector); | |
const prevBg = bgToDelete.prev(siblingSelector); | |
const anyBg = $(siblingSelector); | |
if (nextBg.length > 0) { | |
nextBg.trigger('click'); | |
} else if (prevBg.length > 0) { | |
prevBg.trigger('click'); | |
} else { | |
$(anyBg[Math.floor(Math.random() * anyBg.length)]).trigger('click'); | |
} | |
bgToDelete.remove(); | |
if (url === chat_metadata[BG_METADATA_KEY]) { | |
removeBackgroundMetadata(); | |
unsetCustomBackground(); | |
highlightLockedBackground(); | |
} | |
if (isCustom) { | |
getChatBackgroundsList(); | |
saveMetadataDebounced(); | |
} | |
} | |
} | |
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}'; | |
async function autoBackgroundCommand() { | |
/** @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); | |
if (options.length == 0) { | |
toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.'); | |
return ''; | |
} | |
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.'); | |
return ''; | |
} | |
console.debug('Automatically choosing background:', bestMatch); | |
bestMatch[0].item.element.click(); | |
return ''; | |
} | |
export async function getBackgrounds() { | |
const response = await fetch('/api/backgrounds/all', { | |
method: 'POST', | |
headers: getRequestHeaders(), | |
body: JSON.stringify({ | |
'': '', | |
}), | |
}); | |
if (response.ok) { | |
const getData = await response.json(); | |
//background = getData; | |
//console.log(getData.length); | |
$('#bg_menu_content').children('div').remove(); | |
for (const bg of getData) { | |
const template = getBackgroundFromTemplate(bg, false); | |
$('#bg_menu_content').append(template); | |
} | |
} | |
} | |
/** | |
* Gets the CSS URL of the background | |
* @param {Element} block | |
* @returns {string} URL of the background | |
*/ | |
function getUrlParameter(block) { | |
return $(block).closest('.bg_example').data('url'); | |
} | |
function generateUrlParameter(bg, isCustom) { | |
return isCustom ? `url("${encodeURI(bg)}")` : `url("${getBackgroundPath(bg)}")`; | |
} | |
/** | |
* Instantiates a background template | |
* @param {string} bg Path to background | |
* @param {boolean} isCustom Whether the background is custom | |
* @returns {JQuery<HTMLElement>} Background template | |
*/ | |
function getBackgroundFromTemplate(bg, isCustom) { | |
const template = $('#background_template .bg_example').clone(); | |
const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg); | |
const url = generateUrlParameter(bg, isCustom); | |
const title = isCustom ? bg.split('/').pop() : bg; | |
const friendlyTitle = title.slice(0, title.lastIndexOf('.')); | |
template.attr('title', title); | |
template.attr('bgfile', bg); | |
template.attr('custom', String(isCustom)); | |
template.data('url', url); | |
template.css('background-image', `url('${thumbPath}')`); | |
template.find('.BGSampleTitle').text(friendlyTitle); | |
return template; | |
} | |
async function setBackground(bg, url) { | |
$('#bg1').css('background-image', url); | |
background_settings.name = bg; | |
background_settings.url = url; | |
saveSettingsDebounced(); | |
} | |
async function delBackground(bg) { | |
await fetch('/api/backgrounds/delete', { | |
method: 'POST', | |
headers: getRequestHeaders(), | |
body: JSON.stringify({ | |
bg: bg, | |
}), | |
}); | |
} | |
function onBackgroundUploadSelected() { | |
const form = $('#form_bg_download').get(0); | |
if (!(form instanceof HTMLFormElement)) { | |
console.error('form_bg_download is not a form'); | |
return; | |
} | |
const formData = new FormData(form); | |
uploadBackground(formData); | |
form.reset(); | |
} | |
/** | |
* Uploads a background to the server | |
* @param {FormData} formData | |
*/ | |
function uploadBackground(formData) { | |
jQuery.ajax({ | |
type: 'POST', | |
url: '/api/backgrounds/upload', | |
data: formData, | |
beforeSend: function () { | |
}, | |
cache: false, | |
contentType: false, | |
processData: false, | |
success: async function (bg) { | |
setBackground(bg, generateUrlParameter(bg, false)); | |
await getBackgrounds(); | |
highlightNewBackground(bg); | |
}, | |
error: function (jqXHR, exception) { | |
console.log(exception); | |
console.log(jqXHR); | |
}, | |
}); | |
} | |
/** | |
* @param {string} bg | |
*/ | |
function highlightNewBackground(bg) { | |
const newBg = $(`.bg_example[bgfile="${bg}"]`); | |
const scrollOffset = newBg.offset().top - newBg.parent().offset().top; | |
$('#Backgrounds').scrollTop(scrollOffset); | |
flashHighlight(newBg); | |
} | |
function onBackgroundFilterInput() { | |
const filterValue = String($(this).val()).toLowerCase(); | |
$('#bg_menu_content > div').each(function () { | |
const $bgContent = $(this); | |
if ($bgContent.attr('title').toLowerCase().includes(filterValue)) { | |
$bgContent.show(); | |
} else { | |
$bgContent.hide(); | |
} | |
}); | |
} | |
export function initBackgrounds() { | |
eventSource.on(event_types.CHAT_CHANGED, onChatChanged); | |
eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground); | |
$(document).on('click', '.bg_example', onSelectBackgroundClick); | |
$(document).on('click', '.bg_example_lock', onLockBackgroundClick); | |
$(document).on('click', '.bg_example_unlock', onUnlockBackgroundClick); | |
$(document).on('click', '.bg_example_edit', onRenameBackgroundClick); | |
$(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); | |
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', | |
})); | |
} | |