',
- ].join('')
- },
- group: {
- name: systemUserName,
- force_avatar: system_avatar,
- is_user: false,
- is_system: true,
- is_name: true,
- is_group: true,
- mes: "Group chat created. Say 'Hi' to lovely people!",
- },
- empty: {
- name: systemUserName,
- force_avatar: system_avatar,
- is_user: false,
- is_system: true,
- is_name: true,
- mes: "No one hears you. Hint: add more members to the group!",
- },
- generic: {
- name: systemUserName,
- force_avatar: system_avatar,
- is_user: false,
- is_system: true,
- is_name: true,
- mes: "Generic system message. User `text` parameter to override the contents",
- },
- bookmark_created: {
- name: systemUserName,
- force_avatar: system_avatar,
- is_user: false,
- is_system: true,
- is_name: true,
- mes: `Bookmark created! Click here to open the bookmark chat: {1}`,
- },
- bookmark_back: {
- name: systemUserName,
- force_avatar: system_avatar,
- is_user: false,
- is_system: true,
- is_name: true,
- mes: `Click here to return to the previous chat: Return`,
- },
-};
+function getSystemMessages() {
+ system_messages = {
+ help: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: renderTemplate("help"),
+ },
+ slash_commands: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: '',
+ },
+ hotkeys: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: renderTemplate("hotkeys"),
+ },
+ formatting: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: renderTemplate("formatting"),
+ },
+ macros: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: renderTemplate("macros"),
+ },
+ welcome:
+ {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: renderTemplate("welcome"),
+ },
+ group: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ is_group: true,
+ mes: "Group chat created. Say 'Hi' to lovely people!",
+ },
+ empty: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: "No one hears you. Hint: add more members to the group!",
+ },
+ generic: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: "Generic system message. User `text` parameter to override the contents",
+ },
+ bookmark_created: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: `Bookmark created! Click here to open the bookmark chat: {1}`,
+ },
+ bookmark_back: {
+ name: systemUserName,
+ force_avatar: system_avatar,
+ is_user: false,
+ is_system: true,
+ is_name: true,
+ mes: `Click here to return to the previous chat: Return`,
+ },
+ };
+}
// Register configuration migrations
registerPromptManagerMigration();
@@ -551,6 +476,36 @@ $(document).ajaxError(function myErrorHandler(_, xhr) {
}
});
+function getUrlSync(url, cache = true) {
+ return $.ajax({
+ type: "GET",
+ url: url,
+ cache: cache,
+ async: false
+ }).responseText;
+}
+
+function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true) {
+ try {
+ const templateContent = getUrlSync(`/scripts/templates/${templateId}.html`);
+ const template = Handlebars.compile(templateContent);
+ 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() {
try {
const response = await fetch('/version');
@@ -837,6 +792,7 @@ $.ajaxPrefilter((options, originalOptions, xhr) => {
///// initialization protocol ////////
$.get("/csrf-token").then(async (data) => {
token = data.token;
+ getSystemMessages();
sendSystemMessage(system_message_types.WELCOME);
await readSecretState();
await getClientVersion();
@@ -1399,17 +1355,6 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
mes = mes.replace(new RegExp(`(^|\n)${ch_name}:`, 'g'), "$1");
}
- //function to hide any from AI response output
- /* if (power_user.removeXML && ch_name && !isUser && !isSystem) {
- //console.log('incoming mes')
- //console.log(mes)
- mes = mes.replaceAll(/</g, "<");
- mes = mes.replaceAll(/>/g, ">");
- mes = mes.replaceAll(/<<[^>>]+>>/g, "");
- //console.log('mes after removed ')
- //console.log(mes)
- } */
-
mes = DOMPurify.sanitize(mes);
return mes;
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index 9a8d3597b..73aa98021 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -33,7 +33,7 @@ import {
SECRET_KEYS,
secret_state,
} from "./secrets.js";
-import { debounce, delay, getStringHash } from "./utils.js";
+import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
var NavToggle = document.getElementById("nav-toggle");
@@ -717,7 +717,12 @@ export async function initMovingUI() {
// ---------------------------------------------------
-$("document").ready(function () {
+jQuery(async function () {
+ try {
+ await waitUntilCondition(() => online_status !== undefined, 1000, 10);
+ } catch {
+ console.log('Timeout waiting for online_status');
+ }
// initial status check
setTimeout(() => {
diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js
index 36fb38ff0..9beb88a5f 100644
--- a/public/scripts/extensions/quick-reply/index.js
+++ b/public/scripts/extensions/quick-reply/index.js
@@ -28,7 +28,7 @@ async function updateQuickReplyPresetList() {
if (result.ok) {
var data = await result.json();
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
- console.log(presets)
+ console.debug('Quick Reply presets', presets);
$("#quickReplyPresets").find('option[value!=""]').remove();
@@ -284,7 +284,7 @@ async function doQR(_, text) {
}
text = Number(text)
- //use scale starting with 0
+ //use scale starting with 0
//ex: user inputs "/qr 2" >> qr with data-index 1 (but 2nd item displayed) gets triggered
let QRnum = Number(text - 1)
if (QRnum <= 0) { QRnum = 0 }
diff --git a/public/scripts/i18n.js b/public/scripts/i18n.js
new file mode 100644
index 000000000..d5ac1d115
--- /dev/null
+++ b/public/scripts/i18n.js
@@ -0,0 +1,75 @@
+import { waitUntilCondition } from "./utils.js";
+
+const storageKey = "language";
+export const localeData = await fetch("i18n.json").then(response => response.json());
+
+export function applyLocale(root = document) {
+ const overrideLanguage = localStorage.getItem("language");
+ var language = overrideLanguage || navigator.language || navigator.userLanguage;
+ language = language.toLowerCase();
+ //load the appropriate language file
+ if (localeData.lang.indexOf(language) < 0) language = "en";
+
+ const $root = root instanceof Document ? $(root) : $(new DOMParser().parseFromString(root, "text/html"));
+
+ //find all the elements with `data-i18n` attribute
+ $root.find("[data-i18n]").each(function () {
+ //read the translation from the language data
+ const keys = $(this).data("i18n").split(';'); // Multi-key entries are ; delimited
+ for (const key of keys) {
+ const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
+ if (attributeMatch) { // attribute-tagged key
+ const localizedValue = localeData?.[language]?.[attributeMatch[2]];
+ if (localizedValue) {
+ $(this).attr(attributeMatch[1], localizedValue);
+ }
+ } else { // No attribute tag, treat as 'text'
+ const localizedValue = localeData?.[language]?.[key];
+ if (localizedValue) {
+ $(this).text(localizedValue);
+ }
+ }
+ }
+ });
+
+ if (root !== document) {
+ return $root.get(0).body.innerHTML;
+ }
+}
+
+function addLanguagesToDropdown() {
+ if (!Array.isArray(localeData?.lang)) {
+ return;
+ }
+
+ for (const lang of localeData.lang) {
+ const option = document.createElement('option');
+ option.value = lang;
+ option.innerText = lang;
+ $('#ui_language_select').append(option);
+ }
+
+ const selectedLanguage = localStorage.getItem(storageKey);
+ if (selectedLanguage) {
+ $('#ui_language_select').val(selectedLanguage);
+ }
+}
+
+jQuery(async () => {
+ waitUntilCondition(() => !!localeData);
+ window["applyLocale"] = applyLocale;
+ applyLocale();
+ addLanguagesToDropdown();
+
+ $('#ui_language_select').on('change', async function () {
+ const language = $(this).val();
+
+ if (language) {
+ localStorage.setItem(storageKey, language);
+ } else {
+ localStorage.removeItem(storageKey);
+ }
+
+ location.reload();
+ });
+});
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 3d4832bb1..973796e6b 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -207,7 +207,6 @@ let movingUIPresets = [];
let context_presets = [];
const storage_keys = {
- ui_language: "language",
fast_ui_mode: "TavernAI_fast_ui_mode",
avatar_style: "TavernAI_avatar_style",
chat_display: "TavernAI_chat_display",
@@ -1277,26 +1276,6 @@ function doResetPanels() {
$("#movingUIreset").trigger('click');
}
-function addLanguagesToDropdown() {
- $.getJSON('i18n.json', function (data) {
- if (!Array.isArray(data?.lang)) {
- return;
- }
-
- for (const lang of data.lang) {
- const option = document.createElement('option');
- option.value = lang;
- option.innerText = lang;
- $('#ui_language_select').append(option);
- }
-
- const selectedLanguage = localStorage.getItem(storage_keys.ui_language);
- if (selectedLanguage) {
- $('#ui_language_select').val(selectedLanguage);
- }
- });
-}
-
function setAvgBG() {
const bgimg = new Image();
bgimg.src = $('#bg1')
@@ -2025,18 +2004,6 @@ $(document).ready(() => {
saveSettingsDebounced();
});
- $('#ui_language_select').on('change', async function () {
- const language = $(this).val();
-
- if (language) {
- localStorage.setItem(storage_keys.ui_language, language);
- } else {
- localStorage.removeItem(storage_keys.ui_language);
- }
-
- location.reload();
- });
-
$(window).on('focus', function () {
browser_has_focus = true;
});
@@ -2052,5 +2019,4 @@ $(document).ready(() => {
registerSlashCommand('cut', doMesCut, [], ' (requred number) – cuts the specified message from the chat', true, true);
registerSlashCommand('resetpanels', doResetPanels, ['resetui'], ' – resets UI panels to original state.', true, true);
registerSlashCommand('bgcol', setAvgBG, [], ' – WIP test of auto-bg avg coloring', true, true);
- addLanguagesToDropdown();
});
diff --git a/public/scripts/templates/formatting.html b/public/scripts/templates/formatting.html
new file mode 100644
index 000000000..71671a3b0
--- /dev/null
+++ b/public/scripts/templates/formatting.html
@@ -0,0 +1,21 @@
+Text formatting commands:
+
+
*text* - displays as italics
+
**text** - displays as bold
+
***text*** - displays as bold italics
+
```text``` - displays as a code block (new lines allowed between the backticks)
+
+
like this
+
+
`text` - displays as inline code
+
text - displays as a blockquote (note the space after >)
+
like this
+
# text - displays as a large header (note the space)
+
like this
+
## text - displays as a medium header (note the space)
+
like this
+
### text - displays as a small header (note the space)
+
like this
+
$$ text $$ - renders a LaTeX formula (if enabled)
+
$ text $ - renders an AsciiMath formula (if enabled)
+
diff --git a/public/scripts/templates/help.html b/public/scripts/templates/help.html
new file mode 100644
index 000000000..66b858f48
--- /dev/null
+++ b/public/scripts/templates/help.html
@@ -0,0 +1,11 @@
+Hello there! Please select the help topic you would like to learn more about:
+
+
+
+ Still got questions left? The Official SillyTavern Documentation Website has much more information!
+
diff --git a/public/scripts/templates/hotkeys.html b/public/scripts/templates/hotkeys.html
new file mode 100644
index 000000000..18e751bd9
--- /dev/null
+++ b/public/scripts/templates/hotkeys.html
@@ -0,0 +1,13 @@
+Hotkeys/Keybinds:
+
+
Up = Edit last message in chat
+
Ctrl+Up = Edit last USER message in chat
+
Left = swipe left
+
Right = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)
+
Ctrl+Left = view locally stored variables (in the browser console window)
+
Enter (with chat bar selected) = send your message to AI
{{user}} - your current Persona username
+
{{char}} - the Character's name
+
{{input}} - the user input
+
{{time}} - the current time
+
{{date}} - the current date
+
{{idle_duration}} - the time since the last user message was sent
+
{{random:(args)}} - returns a random item from the list. (ex: {{random:1,2,3,4}} will return 1 of the 4 numbers at random. Works with text lists too.
+
{{roll:(formula)}} - rolls a dice. (ex: {{roll:1d6}} will roll a 6-sided dice and return a number between 1 and 6)
+
diff --git a/public/scripts/templates/welcome.html b/public/scripts/templates/welcome.html
new file mode 100644
index 000000000..7c80d19ab
--- /dev/null
+++ b/public/scripts/templates/welcome.html
@@ -0,0 +1,72 @@
+