mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 10:57:45 +01:00
Add async template renderer
This commit is contained in:
parent
6290dff3d9
commit
369c3512c0
@ -153,7 +153,7 @@ import {
|
|||||||
ensureImageFormatSupported,
|
ensureImageFormatSupported,
|
||||||
} from './scripts/utils.js';
|
} from './scripts/utils.js';
|
||||||
|
|
||||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
||||||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, processChatSlashCommands, registerSlashCommand } from './scripts/slash-commands.js';
|
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, processChatSlashCommands, registerSlashCommand } from './scripts/slash-commands.js';
|
||||||
import {
|
import {
|
||||||
tag_map,
|
tag_map,
|
||||||
@ -212,6 +212,7 @@ import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, de
|
|||||||
import { initPresetManager } from './scripts/preset-manager.js';
|
import { initPresetManager } from './scripts/preset-manager.js';
|
||||||
import { evaluateMacros } from './scripts/macros.js';
|
import { evaluateMacros } from './scripts/macros.js';
|
||||||
import { callGenericPopup } from './scripts/popup.js';
|
import { callGenericPopup } from './scripts/popup.js';
|
||||||
|
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
||||||
|
|
||||||
//exporting functions and vars for mods
|
//exporting functions and vars for mods
|
||||||
export {
|
export {
|
||||||
@ -286,6 +287,7 @@ export {
|
|||||||
printCharactersDebounced,
|
printCharactersDebounced,
|
||||||
isOdd,
|
isOdd,
|
||||||
countOccurrences,
|
countOccurrences,
|
||||||
|
renderTemplate,
|
||||||
};
|
};
|
||||||
|
|
||||||
showLoader();
|
showLoader();
|
||||||
@ -575,14 +577,14 @@ export const MAX_INJECTION_DEPTH = 1000;
|
|||||||
|
|
||||||
let system_messages = {};
|
let system_messages = {};
|
||||||
|
|
||||||
function getSystemMessages() {
|
async function getSystemMessages() {
|
||||||
system_messages = {
|
system_messages = {
|
||||||
help: {
|
help: {
|
||||||
name: systemUserName,
|
name: systemUserName,
|
||||||
force_avatar: system_avatar,
|
force_avatar: system_avatar,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
is_system: true,
|
is_system: true,
|
||||||
mes: renderTemplate('help'),
|
mes: await renderTemplateAsync('help'),
|
||||||
},
|
},
|
||||||
slash_commands: {
|
slash_commands: {
|
||||||
name: systemUserName,
|
name: systemUserName,
|
||||||
@ -596,21 +598,21 @@ function getSystemMessages() {
|
|||||||
force_avatar: system_avatar,
|
force_avatar: system_avatar,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
is_system: true,
|
is_system: true,
|
||||||
mes: renderTemplate('hotkeys'),
|
mes: await renderTemplateAsync('hotkeys'),
|
||||||
},
|
},
|
||||||
formatting: {
|
formatting: {
|
||||||
name: systemUserName,
|
name: systemUserName,
|
||||||
force_avatar: system_avatar,
|
force_avatar: system_avatar,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
is_system: true,
|
is_system: true,
|
||||||
mes: renderTemplate('formatting'),
|
mes: await renderTemplateAsync('formatting'),
|
||||||
},
|
},
|
||||||
macros: {
|
macros: {
|
||||||
name: systemUserName,
|
name: systemUserName,
|
||||||
force_avatar: system_avatar,
|
force_avatar: system_avatar,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
is_system: true,
|
is_system: true,
|
||||||
mes: renderTemplate('macros'),
|
mes: await renderTemplateAsync('macros'),
|
||||||
},
|
},
|
||||||
welcome:
|
welcome:
|
||||||
{
|
{
|
||||||
@ -618,7 +620,7 @@ function getSystemMessages() {
|
|||||||
force_avatar: system_avatar,
|
force_avatar: system_avatar,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
is_system: true,
|
is_system: true,
|
||||||
mes: renderTemplate('welcome'),
|
mes: await renderTemplateAsync('welcome'),
|
||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
name: systemUserName,
|
name: systemUserName,
|
||||||
@ -672,52 +674,6 @@ $(document).ajaxError(function myErrorHandler(_, xhr) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a URL content using XMLHttpRequest synchronously.
|
|
||||||
* @param {string} url URL to load synchronously
|
|
||||||
* @returns {string} Response text
|
|
||||||
*/
|
|
||||||
function getUrlSync(url) {
|
|
||||||
console.debug('Loading URL synchronously', url);
|
|
||||||
const request = new XMLHttpRequest();
|
|
||||||
request.open('GET', url, false); // `false` makes the request synchronous
|
|
||||||
request.send();
|
|
||||||
|
|
||||||
if (request.status >= 200 && request.status < 300) {
|
|
||||||
return request.responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Error loading ${url}: ${request.status} ${request.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateCache = new Map();
|
|
||||||
|
|
||||||
export function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) {
|
|
||||||
try {
|
|
||||||
const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`;
|
|
||||||
let template = templateCache.get(pathToTemplate);
|
|
||||||
if (!template) {
|
|
||||||
const templateContent = getUrlSync(pathToTemplate);
|
|
||||||
template = Handlebars.compile(templateContent);
|
|
||||||
templateCache.set(pathToTemplate, template);
|
|
||||||
}
|
|
||||||
let result = template(templateData);
|
|
||||||
|
|
||||||
if (sanitize) {
|
|
||||||
result = DOMPurify.sanitize(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localize) {
|
|
||||||
result = applyLocale(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error rendering template', templateId, templateData, err);
|
|
||||||
toastr.error('Check the DevTools console for more information.', 'Error rendering template');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getClientVersion() {
|
async function getClientVersion() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/version');
|
const response = await fetch('/version');
|
||||||
@ -901,7 +857,7 @@ async function firstLoadInit() {
|
|||||||
await getClientVersion();
|
await getClientVersion();
|
||||||
await readSecretState();
|
await readSecretState();
|
||||||
await getSettings();
|
await getSettings();
|
||||||
getSystemMessages();
|
await getSystemMessages();
|
||||||
sendSystemMessage(system_message_types.WELCOME);
|
sendSystemMessage(system_message_types.WELCOME);
|
||||||
initLocales();
|
initLocales();
|
||||||
initTags();
|
initTags();
|
||||||
@ -4614,7 +4570,7 @@ async function DupeChar() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function promptItemize(itemizedPrompts, requestedMesId) {
|
async function promptItemize(itemizedPrompts, requestedMesId) {
|
||||||
console.log('PROMPT ITEMIZE ENTERED');
|
console.log('PROMPT ITEMIZE ENTERED');
|
||||||
var incomingMesId = Number(requestedMesId);
|
var incomingMesId = Number(requestedMesId);
|
||||||
console.debug(`looking for MesId ${incomingMesId}`);
|
console.debug(`looking for MesId ${incomingMesId}`);
|
||||||
@ -4655,7 +4611,7 @@ function promptItemize(itemizedPrompts, requestedMesId) {
|
|||||||
chatInjects: getTokenCount(itemizedPrompts[thisPromptSet].chatInjects),
|
chatInjects: getTokenCount(itemizedPrompts[thisPromptSet].chatInjects),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (params.chatInjects){
|
if (params.chatInjects) {
|
||||||
params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects;
|
params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4737,10 +4693,12 @@ function promptItemize(itemizedPrompts, requestedMesId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.this_main_api == 'openai') {
|
if (params.this_main_api == 'openai') {
|
||||||
callPopup(renderTemplate('itemizationChat', params), 'text');
|
const template = await renderTemplateAsync('itemizationChat', params);
|
||||||
|
callPopup(template, 'text');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
callPopup(renderTemplate('itemizationText', params), 'text');
|
const template = await renderTemplateAsync('itemizationText', params);
|
||||||
|
callPopup(template, 'text');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7814,7 +7772,11 @@ window['SillyTavern'].getContext = function () {
|
|||||||
*/
|
*/
|
||||||
registerHelper: () => { },
|
registerHelper: () => { },
|
||||||
registedDebugFunction: registerDebugFunction,
|
registedDebugFunction: registerDebugFunction,
|
||||||
|
/**
|
||||||
|
* @deprecated Use renderExtensionTemplateAsync instead.
|
||||||
|
*/
|
||||||
renderExtensionTemplate: renderExtensionTemplate,
|
renderExtensionTemplate: renderExtensionTemplate,
|
||||||
|
renderExtensionTemplateAsync: renderExtensionTemplateAsync,
|
||||||
callPopup: callPopup,
|
callPopup: callPopup,
|
||||||
callGenericPopup: callGenericPopup,
|
callGenericPopup: callGenericPopup,
|
||||||
mainApi: main_api,
|
mainApi: main_api,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, substituteParams, renderTemplate, animation_duration } from '../script.js';
|
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
||||||
import { hideLoader, showLoader } from './loader.js';
|
import { hideLoader, showLoader } from './loader.js';
|
||||||
|
import { renderTemplate, renderTemplateAsync } from '../script.js';
|
||||||
import { isSubsetOf, setValueByPath } from './utils.js';
|
import { isSubsetOf, setValueByPath } from './utils.js';
|
||||||
export {
|
export {
|
||||||
getContext,
|
getContext,
|
||||||
@ -50,17 +51,31 @@ export function saveMetadataDebounced() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an ability for extensions to render HTML templates.
|
* Provides an ability for extensions to render HTML templates synchronously.
|
||||||
* Templates sanitation and localization is forced.
|
* Templates sanitation and localization is forced.
|
||||||
* @param {string} extensionName Extension name
|
* @param {string} extensionName Extension name
|
||||||
* @param {string} templateId Template ID
|
* @param {string} templateId Template ID
|
||||||
* @param {object} templateData Additional data to pass to the template
|
* @param {object} templateData Additional data to pass to the template
|
||||||
* @returns {string} Rendered HTML
|
* @returns {string} Rendered HTML
|
||||||
|
*
|
||||||
|
* @deprecated Use renderExtensionTemplateAsync instead.
|
||||||
*/
|
*/
|
||||||
export function renderExtensionTemplate(extensionName, templateId, templateData = {}, sanitize = true, localize = true) {
|
export function renderExtensionTemplate(extensionName, templateId, templateData = {}, sanitize = true, localize = true) {
|
||||||
return renderTemplate(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
return renderTemplate(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an ability for extensions to render HTML templates asynchronously.
|
||||||
|
* Templates sanitation and localization is forced.
|
||||||
|
* @param {string} extensionName Extension name
|
||||||
|
* @param {string} templateId Template ID
|
||||||
|
* @param {object} templateData Additional data to pass to the template
|
||||||
|
* @returns {Promise<string>} Rendered HTML
|
||||||
|
*/
|
||||||
|
export function renderExtensionTemplateAsync(extensionName, templateId, templateData = {}, sanitize = true, localize = true) {
|
||||||
|
return renderTemplateAsync(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Disables parallel updates
|
// Disables parallel updates
|
||||||
class ModuleWorkerWrapper {
|
class ModuleWorkerWrapper {
|
||||||
constructor(callback) {
|
constructor(callback) {
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
printCharactersDebounced,
|
printCharactersDebounced,
|
||||||
setCharacterId,
|
setCharacterId,
|
||||||
setEditedMessageId,
|
setEditedMessageId,
|
||||||
renderTemplate,
|
|
||||||
chat,
|
chat,
|
||||||
getFirstDisplayedMessageId,
|
getFirstDisplayedMessageId,
|
||||||
showMoreMessages,
|
showMoreMessages,
|
||||||
@ -23,6 +22,7 @@ import {
|
|||||||
ANIMATION_DURATION_DEFAULT,
|
ANIMATION_DURATION_DEFAULT,
|
||||||
setActiveGroup,
|
setActiveGroup,
|
||||||
setActiveCharacter,
|
setActiveCharacter,
|
||||||
|
renderTemplateAsync,
|
||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js';
|
import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js';
|
||||||
import {
|
import {
|
||||||
@ -1363,8 +1363,8 @@ export function registerDebugFunction(functionId, name, description, func) {
|
|||||||
debug_functions.push({ functionId, name, description, func });
|
debug_functions.push({ functionId, name, description, func });
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDebugMenu() {
|
async function showDebugMenu() {
|
||||||
const template = renderTemplate('debug', { functions: debug_functions });
|
const template = await renderTemplateAsync('debug', { functions: debug_functions });
|
||||||
callPopup(template, 'text', '', { wide: true, large: true });
|
callPopup(template, 'text', '', { wide: true, large: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
130
public/scripts/templates.js
Normal file
130
public/scripts/templates.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { applyLocale } from './i18n.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<string, function>}
|
||||||
|
* @description Cache for Handlebars templates.
|
||||||
|
*/
|
||||||
|
const TEMPLATE_CACHE = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a URL content using XMLHttpRequest synchronously.
|
||||||
|
* @param {string} url URL to load synchronously
|
||||||
|
* @returns {string} Response text
|
||||||
|
*/
|
||||||
|
function getUrlSync(url) {
|
||||||
|
console.debug('Loading URL synchronously', url);
|
||||||
|
const request = new XMLHttpRequest();
|
||||||
|
request.open('GET', url, false); // `false` makes the request synchronous
|
||||||
|
request.send();
|
||||||
|
|
||||||
|
if (request.status >= 200 && request.status < 300) {
|
||||||
|
return request.responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Error loading ${url}: ${request.status} ${request.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a URL content using XMLHttpRequest asynchronously.
|
||||||
|
* @param {string} url URL to load asynchronously
|
||||||
|
* @returns {Promise<string>} Response text
|
||||||
|
*/
|
||||||
|
function getUrlAsync(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = new XMLHttpRequest();
|
||||||
|
request.open('GET', url, true);
|
||||||
|
request.onload = () => {
|
||||||
|
if (request.status >= 200 && request.status < 300) {
|
||||||
|
resolve(request.responseText);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Error loading ${url}: ${request.status} ${request.statusText}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onerror = () => {
|
||||||
|
reject(new Error(`Error loading ${url}: ${request.status} ${request.statusText}`));
|
||||||
|
};
|
||||||
|
request.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a Handlebars template asynchronously.
|
||||||
|
* @param {string} templateId ID of the template to render
|
||||||
|
* @param {Record<string, any>} templateData The data to pass to the template
|
||||||
|
* @param {boolean} sanitize Should the template be sanitized with DOMPurify
|
||||||
|
* @param {boolean} localize Should the template be localized
|
||||||
|
* @param {boolean} fullPath Should the template ID be treated as a full path or a relative path
|
||||||
|
* @returns {Promise<string>} Rendered template
|
||||||
|
*/
|
||||||
|
export async function renderTemplateAsync(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) {
|
||||||
|
async function fetchTemplateAsync(pathToTemplate) {
|
||||||
|
let template = TEMPLATE_CACHE.get(pathToTemplate);
|
||||||
|
if (!template) {
|
||||||
|
const templateContent = await getUrlAsync(pathToTemplate);
|
||||||
|
template = Handlebars.compile(templateContent);
|
||||||
|
TEMPLATE_CACHE.set(pathToTemplate, template);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`;
|
||||||
|
const template = await fetchTemplateAsync(pathToTemplate);
|
||||||
|
let result = template(templateData);
|
||||||
|
|
||||||
|
if (sanitize) {
|
||||||
|
result = DOMPurify.sanitize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localize) {
|
||||||
|
result = applyLocale(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error rendering template', templateId, templateData, err);
|
||||||
|
toastr.error('Check the DevTools console for more information.', 'Error rendering template');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a Handlebars template synchronously.
|
||||||
|
* @param {string} templateId ID of the template to render
|
||||||
|
* @param {Record<string, any>} templateData The data to pass to the template
|
||||||
|
* @param {boolean} sanitize Should the template be sanitized with DOMPurify
|
||||||
|
* @param {boolean} localize Should the template be localized
|
||||||
|
* @param {boolean} fullPath Should the template ID be treated as a full path or a relative path
|
||||||
|
* @returns {string} Rendered template
|
||||||
|
*
|
||||||
|
* @deprecated Use renderTemplateAsync instead.
|
||||||
|
*/
|
||||||
|
export function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) {
|
||||||
|
function fetchTemplateSync(pathToTemplate) {
|
||||||
|
let template = TEMPLATE_CACHE.get(pathToTemplate);
|
||||||
|
if (!template) {
|
||||||
|
const templateContent = getUrlSync(pathToTemplate);
|
||||||
|
template = Handlebars.compile(templateContent);
|
||||||
|
TEMPLATE_CACHE.set(pathToTemplate, template);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`;
|
||||||
|
const template = fetchTemplateSync(pathToTemplate);
|
||||||
|
let result = template(templateData);
|
||||||
|
|
||||||
|
if (sanitize) {
|
||||||
|
result = DOMPurify.sanitize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localize) {
|
||||||
|
result = applyLocale(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error rendering template', templateId, templateData, err);
|
||||||
|
toastr.error('Check the DevTools console for more information.', 'Error rendering template');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user