mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 02:47:52 +01:00
Merge branch 'staging' into tool-calling
This commit is contained in:
commit
6369882be3
@ -6641,7 +6641,7 @@
|
||||
<script src="lib/popper.js"></script>
|
||||
<script src="lib/purify.min.js"></script>
|
||||
<script src="lib/highlight.min.js"></script>
|
||||
<script src="lib/moment.min.js"></script>
|
||||
<script src="lib/moment-with-locales.min.js"></script>
|
||||
<script src="lib/cropper.min.js"></script>
|
||||
<script src="lib/jquery-cropper.min.js"></script>
|
||||
<script src="lib/toastr.min.js"></script>
|
||||
|
2
public/lib/moment-with-locales.min.js
vendored
Normal file
2
public/lib/moment-with-locales.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/lib/moment-with-locales.min.js.map
Normal file
1
public/lib/moment-with-locales.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
public/lib/moment.min.js
vendored
2
public/lib/moment.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -56,7 +56,7 @@
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Top K": "Top K",
|
||||
"Top P": "Top P",
|
||||
"Do Sample": "Сделать образец",
|
||||
"Do Sample": "Включить сэмплинг",
|
||||
"Add BOS Token": "Добавлять BOS-токен",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
|
||||
"Ban EOS Token": "Запретить EOS-токен",
|
||||
@ -451,8 +451,8 @@
|
||||
"Creator's Notes": "Заметки создателя",
|
||||
"A-Z": "A-Z",
|
||||
"Z-A": "Z-A",
|
||||
"Newest": "Новейшие",
|
||||
"Oldest": "Старейшие",
|
||||
"Newest": "Сначала новые",
|
||||
"Oldest": "Сначала старые",
|
||||
"Favorites": "Избранные",
|
||||
"Recent": "Последние",
|
||||
"Most chats": "Больше всего чатов",
|
||||
@ -564,7 +564,7 @@
|
||||
"Advanced Definition": "Расширенное описание",
|
||||
"Character Lore": "Лор персонажа",
|
||||
"Export and Download": "Экспортировать и скачать",
|
||||
"Duplicate Character": "Дублировать персонажа",
|
||||
"Duplicate Character": "Клонировать персонажа",
|
||||
"Create Character": "Создать персонажа",
|
||||
"Delete Character": "Удалить персонажа",
|
||||
"View all tags": "Показать все тэги",
|
||||
@ -1690,7 +1690,7 @@
|
||||
"Delete Message": "Удалить сообщение",
|
||||
"Delete Swipe": "Удалить свайп",
|
||||
"Could not get a reply from API. Check your connection settings / API key and try again.": "Не удалось получить ответ от API. Проверьте настройки соединения и API-ключ и повторите попытку.",
|
||||
"Connecting To Proxy": "Подключение к прокси",
|
||||
"Connecting To Proxy": "Подключиться к прокси",
|
||||
"Are you sure you want to connect to the following proxy URL?": "Вы точно хотите соединиться с прокси по этому адресу?",
|
||||
"API connection successful!": "Соединение с API установлено!",
|
||||
"Proxy Saved": "Прокси сохранена",
|
||||
@ -1747,5 +1747,151 @@
|
||||
"Markdown Hotkeys": "Горячие клавиши для разметки",
|
||||
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
|
||||
"Save and Update": "Сохранить и обновить",
|
||||
"Profile name:": "Название профиля:"
|
||||
"Profile name:": "Название профиля:",
|
||||
"API returned an error": "API вернуло ошибку",
|
||||
"Failed to save preset": "Не удалось сохранить пресет",
|
||||
"Preset name should be unique.": "Название пресета должно быть уникальным.",
|
||||
"Invalid file": "Невалидный файл",
|
||||
"No preset selected": "Пресет не выбран",
|
||||
"Invalid logit bias preset file.": "Файл пресета невалиден.",
|
||||
"Preset was not deleted from server": "Пресет не удалён с сервера",
|
||||
"Preset deleted": "Пресет удалён",
|
||||
"Delete the preset?": "Удалить пресет?",
|
||||
"Preset updated": "Пресет обновлён",
|
||||
"Entered reverse proxy address is not a valid URL": "Введённый адрес прокси невалиден",
|
||||
"An error occurred while counting tokens: Token budget exceeded.": "Ошибка при подсчёте токенов: Превышен бюджет токенов",
|
||||
"An error occurred while counting tokens: Invalid character name": "Ошибка при подсчёте токенов: Невалидное имя персонажа",
|
||||
"Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.": "Недостаточно токенов для всех выбранных промптов. Повысьте лимит токенов или отключите часть промптов.",
|
||||
"The name of at least one character contained whitespaces or special characters. Please check your user and character name.": "В имени одного из персонажей содержится пробел или иной спецсимвол. Проверьте имена пользователя и персонажа.",
|
||||
"An unknown error occurred while counting tokens. Further information may be available in console.": "Неизвестная ошибка при подсчёте токенов. Проверьте консоль, возможно, подробная информация есть там.",
|
||||
"Encountered an error while processing your request.": "При обработке вашего запроса возникла ошибка.",
|
||||
"Check you have credits available on your": "Убедитесь, что на вашем",
|
||||
"OpenAI account quora_error": "аккаунте OpenAI",
|
||||
"dot quota_error": "имеется достаточно кредитов.",
|
||||
"If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
|
||||
"Proxy preset '${0}' not found": "Пресет '${0}' не найден",
|
||||
"Window.ai returned an error": "Window.ai вернул ошибку",
|
||||
"Get it here:": "Загрузите здесь:",
|
||||
"Extension is not installed": "Расширение не установлено",
|
||||
"Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
|
||||
"An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
|
||||
"Could not import prompts. Export failed validation.": "Не удалось импортировать промпты. Не пройдена валидация при экспорте.",
|
||||
"Prompt import complete.": "Импорт завершён.",
|
||||
"Are you sure you want to delete this prompt?": "Вы точно хотите удалить этот промпт?",
|
||||
"Existing prompts with the same ID will be overridden. Do you want to proceed?": "Имеющиеся промпты с совпадающими идентификаторами будут перезаписаны. Продолжить?",
|
||||
"This will reset the prompt order for this character. You will not lose any prompts.": "Будет сброшен порядок промптов для этого персонажа. Сами промпты вы не потеряете.",
|
||||
"Note:": "Примечание:",
|
||||
"this chat is temporary and will be deleted as soon as you leave it.": "это временный чат, он будет удалён, как только вы из него выйдете.",
|
||||
"help_hotkeys_20": "Горячие клавиши для разметки",
|
||||
"help_hotkeys_21": "Работают в окне ввода чата, а также в полях, отмеченных этим значком:",
|
||||
"help_hotkeys_22": "**полужирный**",
|
||||
"help_hotkeys_23": "*курсив*",
|
||||
"help_hotkeys_24": "__подчёркивание__",
|
||||
"help_hotkeys_25": "`inline-код`",
|
||||
"help_hotkeys_26": "~~зачёркнутый~~",
|
||||
"ext_regex_only_format_visual_desc": "Содержимое файла с историей чата останется нетронутым, изменения будут лишь визуальными (в UI).",
|
||||
"Could not convert file": "Не удалось сконвертировать файл",
|
||||
"Could not upload file": "Не удалось загрузить файл",
|
||||
"Could not download file": "Не удалось скачать файл",
|
||||
"File is too big. Maximum size is ${0}.": "Слишком большой файл. Максимальный размер: ${0}.",
|
||||
"Binary files are not supported. Select a text file or image.": "Бинарные файлы не поддерживаются. Выберите текстовый файл или изображение.",
|
||||
"No character or group selected": "Не выбрано ни одного персонажа или группы",
|
||||
"Could not delete file": "Не удалось удалить файл",
|
||||
"No attachments selected.": "Вложение не выбрано.",
|
||||
"Data Bank": "Банк данных",
|
||||
"No files were scraped.": "Скрапинг не выполнен.",
|
||||
"Scraped ${0} files from ${1} to ${2}.": "Соскраплено из ${0} файлов из ${1} в ${2}.",
|
||||
"Check browser console for details.": "Подробности см. в консоли браузера.",
|
||||
"Scraping failed": "Ошибка скрапинга",
|
||||
"External media has been blocked": "Внешние медиа отключены",
|
||||
"Use the 'Ext. Media' button to allow it. Click on this message to dismiss.": "Разрешить можно с помощью кнопки 'Внешн. медиа'. Нажмите на это сообщение, чтобы его скрыть.",
|
||||
"Couldn't get CSRF token. Please refresh the page.": "Не удалось получить CSRF токен. Попробуйте перезагрузить страницу.",
|
||||
"Error": "Ошибка",
|
||||
"API Error": "Ошибка API",
|
||||
"Please wait until the chat is saved before switching characters.": "Пожалуйста, дождитесь сохранения чата, прежде чем переключать персонажа.",
|
||||
"Your chat is still saving...": "Чат всё ещё сохраняется...",
|
||||
"Character ${0} not found in the list": "Персонаж ${0} не найден в списке",
|
||||
"Streaming is enabled, but the version of Kobold used does not support token streaming.": "Включён стриминг текста, но ваша версия Kobold не поддерживает стриминг токенов.",
|
||||
"Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.": "Для устаревшего API стриминг недоступен. Обновите oobaboga и используйте новый API, чтобы включить стриминг.",
|
||||
"Verify that the server is running and accessible.": "Убедитесь, что сервер запущен и доступен по сети.",
|
||||
"ST Server cannot be reached": "Не удалось соединиться с сервером ST",
|
||||
"You must first select a character to duplicate!": "Вы не выбрали персонажа, которого хотите клонировать!",
|
||||
"Character Duplicated": "Персонаж склонирован",
|
||||
"No character name provided.": "Вы не ввели имя персонажа.",
|
||||
"Rename Character": "Переименование",
|
||||
"No character selected.": "Вы не выбрали персонажа.",
|
||||
"New name:": "Новое имя:",
|
||||
"Same character name provided, so name did not change.": "Введено то же самое имя, ничего не изменилось.",
|
||||
"Character renamed and past chats updated!": "Персонаж переименован, а чаты обновлены!",
|
||||
"Character renamed!": "Персонаж переименован!",
|
||||
"Something went wrong. The page will be reloaded.": "Что-то пошло не так. Страница будет перезагружена.",
|
||||
"Past chat could not be updated: ${0}": "Не удалось обновить чат ${0}",
|
||||
"Trying to save group chat with regular saveChat function. Aborting to prevent corruption.": "Произошла попытка сохранения группового чата функцией saveChat. Откатываем изменения, чтобы предотвратить потерю данных.",
|
||||
"Check the server connection and reload the page to prevent data loss.": "Проверьте связь с сервером и перезагрузите страницу, чтобы избежать потери данных.",
|
||||
"Chat could not be saved": "Не удалось сохранить чат",
|
||||
"Settings could not be loaded after multiple attempts. Please try again later.": "Не удалось загрузить настройки за несколько попыток. Попробуйте позднее.",
|
||||
"Settings could not be saved": "Не удалось сохранить настройки",
|
||||
"Could not load chat data. Try reloading the page.": "Не удалось загрузить чат. Попробуйте обновить страницу.",
|
||||
"Invalid process (no 'type')": "Невалидный процесс (нет параметра 'type')",
|
||||
"Character Deleted: ${0}": "Персонаж удалён: ${0}",
|
||||
"Character Created: ${0}": "Персонаж создан: ${0}",
|
||||
"Group Created": "Группа создана",
|
||||
"Group Deleted": "Группа удалена",
|
||||
"Character Imported: ${0}": "Персонаж импортирован: ${0}",
|
||||
"Invalid swipe ID: ${0}": "Некорректный идентификатор свайпа: ${0}",
|
||||
"No messages to delete swipes from.": "Сообщение, из которого требуется удалить свайп, не найдено.",
|
||||
"Can't delete the last swipe.": "Невозможно удалить единственный свайп.",
|
||||
"GUI Settings preset is not supported for Horde. Please select another preset.": "Для Horde не поддерживаются пресеты настроек GUI. Пожалуйста, выберите другой пресет.",
|
||||
"Embedded lorebook will be removed from this character.": "Встроенный лорбук будет удалён из персонажа.",
|
||||
"Name is required": "Введите имя",
|
||||
"Cannot create characters while generating. Stop the request and try again.": "Во время генерации ответа создать персонажа невозможно. Остановите запрос и повторите попытку.",
|
||||
"Creation aborted": "Процесс создания прерван",
|
||||
"Failed to create character": "Не удалось создать персонажа",
|
||||
"Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.": "Что-то пошло не так в процессе сохранения персонажа, либо вы загрузили файл некорректного формата. Проверьте, что изображение точно не в формате webp.",
|
||||
"Context template '${0}' not found": "Шаблон контекста '${0}' не найден",
|
||||
"Instruct template '${0}' not found": "Шаблон Instruct-режима '${0}' не найден",
|
||||
"Error: ${0} is not a valid API": "Ошибка: ${0} не является валидным API",
|
||||
"API set to ${0}, trying to connect..": "Установлено API ${0}, пробуем подключиться...",
|
||||
"Unsupported file type: ": "Неподдерживаемый тип файла: ",
|
||||
"Cannot import characters while generating. Stop the request and try again.": "Во время генерации ответа импорт персонажа невозможен. Остановите запрос и повторите попытку.",
|
||||
"Import aborted": "Процесс импорта прерван",
|
||||
"The file is likely invalid or corrupted.": "Вероятно, файл невалиден или повреждён.",
|
||||
"Could not import character": "Не удалось импортировать персонажа",
|
||||
"Cannot run /impersonate command while the reply is being generated.": "Во время генерации ответа выполнить /impersonate невозможно.",
|
||||
"Name must be provided as an argument to rename this chat.": "Для переименования чата необходимо предоставить новое имя в качестве аргумента.",
|
||||
"No chat selected that can be renamed.": "Чат, который требуется переименовать, не найден.",
|
||||
"Successfully renamed chat to: ${0}": "Чат успешно переименован в: ${0}",
|
||||
"Character ${0} not found. Skipping deletion.": "Персонаж ${0} не найден. Удаление пропускается.",
|
||||
"Failed to delete character": "При удалении персонажа произошло ошибка",
|
||||
"Are you sure you want to duplicate this character?": "Вы точно хотите клонировать этого персонажа?",
|
||||
"If you just want to start a new chat with the same character...": "Если вы хотите просто создать новый чат, воспользуйтесь кнопкой \"Начать новый чат\" в меню слева внизу.",
|
||||
"THIS IS PERMANENT!": "ОТМЕНИТЬ БУДЕТ НЕВОЗМОЖНО!",
|
||||
"Also delete the chat files": "Также удалить файлы чатов",
|
||||
"Delete the character?": "Удалить персонажа?",
|
||||
"Not a valid number": "Некорректное число",
|
||||
"Author's Note depth updated": "Глубина заметок автора обновлена",
|
||||
"Author's Note frequency updated": "Частота заметок автора обновлена",
|
||||
"Not a valid position": "Некорректная позиция",
|
||||
"Author's Note position updated": "Позиция заметок автора обновлена",
|
||||
"Something went wrong. Could not save character's author's note.": "Что-то пошло не так. Не удалось сохранить заметки автора для этого персонажа.",
|
||||
"Select a character before trying to use Author's Note": "Сначала необходимо выбрать персонажа",
|
||||
"Author's Note text updated": "Текст заметок автора обновлён",
|
||||
"Group Validation": "Валидация группы",
|
||||
"Warning: Listed member ${0} does not exist as a character. It will be removed from the group.": "Предупреждение: персонаж ${0} не существует в виде карточки. Он будет удалён из группы.",
|
||||
"Group Chat could not be saved": "Не удалось сохранить групповой чат",
|
||||
"Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
|
||||
"Currently no group selected.": "В данный момент не выбрано ни одной группы.",
|
||||
"Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",
|
||||
"Delete the group?": "Удалить группу?",
|
||||
"This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
|
||||
"Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
|
||||
"Threshold": "Порог",
|
||||
"DRY Repetition Penalty": "DRY Штраф за повтор",
|
||||
"Multiplier": "Множитель",
|
||||
"DRY_Multiplier_desc": "Поставьте в положение > 0, чтобы включить DRY. Определяет величину штрафа для кратчайшей \"штрафуемой\" строки.",
|
||||
"DRY_Repetition_Penalty_desc": "DRY налагает штраф на токены, генерация которых приведёт к появлению строки, которая уже была в тексте раньше. Установите множитель = 0, чтобы отключить.",
|
||||
"Base": "Основание",
|
||||
"DRY_Base_desc": "Определяет, насколько быстро возрастает штраф с увеличением длины строки.",
|
||||
"Allowed Length": "Допустимая длина",
|
||||
"DRY_Allowed_Length_desc": "Длина повторяющейся строки, при превышении которой DRY начинает налагать штраф."
|
||||
}
|
||||
|
345
public/script.js
345
public/script.js
@ -294,10 +294,17 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) {
|
||||
}
|
||||
});
|
||||
|
||||
DOMPurify.addHook('uponSanitizeAttribute', (_, data, config) => {
|
||||
DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => {
|
||||
if (!config['MESSAGE_SANITIZE']) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Retain the classes on UI elements of messages that interact with the main UI */
|
||||
const permittedNodeTypes = ['BUTTON', 'DIV'];
|
||||
if (config['MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.attrName) {
|
||||
case 'class': {
|
||||
if (data.attrValue) {
|
||||
@ -385,8 +392,8 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
|
||||
|
||||
if (localStorage.getItem(warningShownKey) === null) {
|
||||
const warningToast = toastr.warning(
|
||||
'Use the "Ext. Media" button to allow it. Click on this message to dismiss.',
|
||||
'External media has been blocked',
|
||||
t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`,
|
||||
t`External media has been blocked`,
|
||||
{
|
||||
timeOut: 0,
|
||||
preventDuplicates: true,
|
||||
@ -651,6 +658,7 @@ async function getSystemMessages() {
|
||||
force_avatar: system_avatar,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
uses_system_ui: true,
|
||||
mes: await renderTemplateAsync('welcome', { displayVersion }),
|
||||
},
|
||||
group: {
|
||||
@ -926,7 +934,7 @@ async function firstLoadInit() {
|
||||
token = tokenData.token;
|
||||
} catch {
|
||||
hideLoader();
|
||||
toastr.error('Couldn\'t get CSRF token. Please refresh the page.', 'Error', { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
|
||||
toastr.error(t`Couldn't get CSRF token. Please refresh the page.`, t`Error`, { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
|
||||
throw new Error('Initialization failed');
|
||||
}
|
||||
|
||||
@ -1142,7 +1150,7 @@ async function getStatusKobold() {
|
||||
|
||||
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
|
||||
if (online_status === 'no_connection' && data.response) {
|
||||
toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });
|
||||
toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error getting status', err);
|
||||
@ -1228,7 +1236,7 @@ async function getStatusTextgen() {
|
||||
|
||||
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
|
||||
if (online_status === 'no_connection' && data.response) {
|
||||
toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });
|
||||
toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof AbortReason) {
|
||||
@ -1280,7 +1288,7 @@ export async function selectCharacterById(id) {
|
||||
}
|
||||
|
||||
if (isChatSaving) {
|
||||
toastr.info('Please wait until the chat is saved before switching characters.', 'Your chat is still saving...');
|
||||
toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1634,7 +1642,7 @@ export async function getOneCharacter(avatarUrl) {
|
||||
if (indexOf !== -1) {
|
||||
characters[indexOf] = getData;
|
||||
} else {
|
||||
toastr.error(`Character ${avatarUrl} not found in the list`, 'Error', { timeOut: 5000, preventDuplicates: true });
|
||||
toastr.error(t`Character ${avatarUrl} not found in the list`, t`Error`, { timeOut: 5000, preventDuplicates: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1917,9 +1925,10 @@ export async function sendTextareaMessage() {
|
||||
* @param {boolean} isSystem If the message was sent by the system
|
||||
* @param {boolean} isUser If the message was sent by the user
|
||||
* @param {number} messageId Message index in chat array
|
||||
* @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
||||
export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) {
|
||||
if (!mes) {
|
||||
return '';
|
||||
}
|
||||
@ -1994,15 +2003,33 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
||||
});
|
||||
}
|
||||
|
||||
mes = mes.replace(/```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(".+?")|(\u201C.+?\u201D)/gm, function (match, p1, p2) {
|
||||
if (p1) {
|
||||
return '<q>"' + p1.replace(/"/g, '') + '"</q>';
|
||||
} else if (p2) {
|
||||
return '<q>“' + p2.replace(/\u201C|\u201D/g, '') + '”</q>';
|
||||
} else {
|
||||
return match;
|
||||
mes = mes.replace(
|
||||
/```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(".*?")|(\u201C.*?\u201D)|(\u00AB.*?\u00BB)|(\u300C.*?\u300D)|(\u300E.*?\u300F)|(\uFF02.*?\uFF02)/gm,
|
||||
function (match, p1, p2, p3, p4, p5, p6) {
|
||||
if (p1) {
|
||||
// English double quotes
|
||||
return `<q>"${p1.slice(1, -1)}"</q>`;
|
||||
} else if (p2) {
|
||||
// Curly double quotes “ ”
|
||||
return `<q>“${p2.slice(1, -1)}”</q>`;
|
||||
} else if (p3) {
|
||||
// Guillemets « »
|
||||
return `<q>«${p3.slice(1, -1)}»</q>`;
|
||||
} else if (p4) {
|
||||
// Corner brackets 「 」
|
||||
return `<q>「${p4.slice(1, -1)}」</q>`;
|
||||
} else if (p5) {
|
||||
// White corner brackets 『 』
|
||||
return `<q>『${p5.slice(1, -1)}』</q>`;
|
||||
} else if (p6) {
|
||||
// Fullwidth quotes " "
|
||||
return `<q>"${p6.slice(1, -1)}"</q>`;
|
||||
} else {
|
||||
// Return the original match if no quotes are found
|
||||
return match;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Restore double quotes in tags
|
||||
if (!power_user.encode_tags) {
|
||||
@ -2030,7 +2057,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'] };
|
||||
const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'], ...sanitizerOverrides };
|
||||
mes = encodeStyleTags(mes);
|
||||
mes = DOMPurify.sanitize(mes, config);
|
||||
mes = decodeStyleTags(mes);
|
||||
@ -2235,6 +2262,18 @@ export function addCopyToCodeBlocks(messageElement) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a single message to the chat.
|
||||
* @param {object} mes Message object
|
||||
* @param {object} [options] Options
|
||||
* @param {string} [options.type='normal'] Message type
|
||||
* @param {number} [options.insertAfter=null] Message ID to insert the new message after
|
||||
* @param {boolean} [options.scroll=true] Whether to scroll to the new message
|
||||
* @param {number} [options.insertBefore=null] Message ID to insert the new message before
|
||||
* @param {number} [options.forceId=null] Force the message ID
|
||||
* @param {boolean} [options.showSwipes=true] Whether to show swipe buttons
|
||||
* @returns {void}
|
||||
*/
|
||||
export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true } = {}) {
|
||||
let messageText = mes['mes'];
|
||||
const momentDate = timestampToMoment(mes.send_date);
|
||||
@ -2263,7 +2302,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
} else if (this_chid === undefined) {
|
||||
avatarImg = system_avatar;
|
||||
} else {
|
||||
if (characters[this_chid].avatar != 'none') {
|
||||
if (characters[this_chid].avatar !== 'none') {
|
||||
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
} else {
|
||||
avatarImg = default_avatar;
|
||||
@ -2278,12 +2317,16 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
avatarImg = mes['force_avatar'];
|
||||
}
|
||||
|
||||
// if mes.uses_system_ui is true, set an override on the sanitizer options
|
||||
const sanitizerOverrides = mes.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {};
|
||||
|
||||
messageText = messageFormatting(
|
||||
messageText,
|
||||
mes.name,
|
||||
isSystem,
|
||||
mes.is_user,
|
||||
chat.indexOf(mes),
|
||||
sanitizerOverrides,
|
||||
);
|
||||
const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);
|
||||
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
@ -2335,7 +2378,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
||||
}
|
||||
|
||||
//shows or hides the Prompt display button
|
||||
let mesIdToFind = type == 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
|
||||
let mesIdToFind = type === 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
|
||||
|
||||
//if we have itemized messages, and the array isn't null..
|
||||
if (params.isUser === false && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {
|
||||
@ -2656,7 +2699,7 @@ export function sendSystemMessage(type, text, extra = {}) {
|
||||
newMessage.mes = text;
|
||||
}
|
||||
|
||||
if (type == system_message_types.SLASH_COMMANDS) {
|
||||
if (type === system_message_types.SLASH_COMMANDS) {
|
||||
newMessage.mes = getSlashCommandsHelp();
|
||||
}
|
||||
|
||||
@ -2670,7 +2713,7 @@ export function sendSystemMessage(type, text, extra = {}) {
|
||||
chat.push(newMessage);
|
||||
addOneMessage(newMessage);
|
||||
is_send_press = false;
|
||||
if (type == system_message_types.SLASH_COMMANDS) {
|
||||
if (type === system_message_types.SLASH_COMMANDS) {
|
||||
const browser = new SlashCommandBrowser();
|
||||
const spinner = document.querySelector('#chat .last_mes .custom-slashHelp');
|
||||
const parent = spinner.parentElement;
|
||||
@ -3398,7 +3441,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
await eventSource.emit(event_types.GENERATION_AFTER_COMMANDS, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
|
||||
|
||||
if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
|
||||
toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true });
|
||||
toastr.error(t`Streaming is enabled, but the version of Kobold used does not support token streaming.`, undefined, { timeOut: 10000, preventDuplicates: true });
|
||||
unblockGeneration(type);
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -3407,7 +3450,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
textgen_settings.streaming &&
|
||||
textgen_settings.legacy_api &&
|
||||
textgen_settings.type === OOBA) {
|
||||
toastr.error('Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.', undefined, { timeOut: 10000, preventDuplicates: true });
|
||||
toastr.error(t`Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.`, undefined, { timeOut: 10000, preventDuplicates: true });
|
||||
unblockGeneration(type);
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -3423,7 +3466,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
|
||||
if (!pingResult) {
|
||||
unblockGeneration(type);
|
||||
toastr.error('Verify that the server is running and accessible.', 'ST Server cannot be reached');
|
||||
toastr.error(t`Verify that the server is running and accessible.`, t`ST Server cannot be reached`);
|
||||
throw new Error('Server unreachable');
|
||||
}
|
||||
|
||||
@ -4476,7 +4519,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
generatedPromptCache = '';
|
||||
|
||||
if (data?.response) {
|
||||
toastr.error(data.response, 'API Error', { preventDuplicates: true });
|
||||
toastr.error(data.response, t`API Error`, { preventDuplicates: true });
|
||||
}
|
||||
throw new Error(data?.response);
|
||||
}
|
||||
@ -4585,7 +4628,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
|
||||
function onError(exception) {
|
||||
if (typeof exception?.error?.message === 'string') {
|
||||
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
|
||||
toastr.error(exception.error.message, t`Error`, { timeOut: 10000, extendedTimeOut: 20000 });
|
||||
}
|
||||
|
||||
generatedPromptCache = '';
|
||||
@ -4989,7 +5032,7 @@ function addChatsSeparator(mesSendString) {
|
||||
|
||||
async function duplicateCharacter() {
|
||||
if (!this_chid) {
|
||||
toastr.warning('You must first select a character to duplicate!');
|
||||
toastr.warning(t`You must first select a character to duplicate!`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -5008,7 +5051,7 @@ async function duplicateCharacter() {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) {
|
||||
toastr.success('Character Duplicated');
|
||||
toastr.success(t`Character Duplicated`);
|
||||
const data = await response.json();
|
||||
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
|
||||
await getCharacters();
|
||||
@ -5855,23 +5898,23 @@ export function setSendButtonState(value) {
|
||||
|
||||
export async function renameCharacter(name = null, { silent = false, renameChats = null } = {}) {
|
||||
if (!name && silent) {
|
||||
toastr.warning('No character name provided.', 'Rename Character');
|
||||
toastr.warning(t`No character name provided.`, t`Rename Character`);
|
||||
return false;
|
||||
}
|
||||
if (this_chid === undefined) {
|
||||
toastr.warning('No character selected.', 'Rename Character');
|
||||
toastr.warning(t`No character selected.`, t`Rename Character`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldAvatar = characters[this_chid].avatar;
|
||||
const newValue = name || await callGenericPopup('<h3>New name:</h3>', POPUP_TYPE.INPUT, characters[this_chid].name);
|
||||
const newValue = name || await callGenericPopup('<h3>' + t`New name:` + '</h3>', POPUP_TYPE.INPUT, characters[this_chid].name);
|
||||
|
||||
if (!newValue) {
|
||||
toastr.warning('No character name provided.', 'Rename Character');
|
||||
toastr.warning(t`No character name provided.`, t`Rename Character`);
|
||||
return false;
|
||||
}
|
||||
if (newValue === characters[this_chid].name) {
|
||||
toastr.info('Same character name provided, so name did not change.', 'Rename Character');
|
||||
toastr.info(t`Same character name provided, so name did not change.`, t`Rename Character`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -5918,9 +5961,9 @@ export async function renameCharacter(name = null, { silent = false, renameChats
|
||||
if (renamePastChatsConfirm) {
|
||||
await renamePastChats(newAvatar, newValue);
|
||||
await reloadCurrentChat();
|
||||
toastr.success('Character renamed and past chats updated!', 'Rename Character');
|
||||
toastr.success(t`Character renamed and past chats updated!`, t`Rename Character`);
|
||||
} else {
|
||||
toastr.success('Character renamed!', 'Rename Character');
|
||||
toastr.success(t`Character renamed!`, t`Rename Character`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -5933,8 +5976,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
|
||||
}
|
||||
catch (error) {
|
||||
// Reloading to prevent data corruption
|
||||
if (!silent) await callPopup('Something went wrong. The page will be reloaded.', 'text');
|
||||
else toastr.error('Something went wrong. The page will be reloaded.', 'Rename Character');
|
||||
if (!silent) await callPopup(t`Something went wrong. The page will be reloaded.`, 'text');
|
||||
else toastr.error(t`Something went wrong. The page will be reloaded.`, t`Rename Character`);
|
||||
|
||||
console.log('Renaming character error:', error);
|
||||
location.reload();
|
||||
@ -5991,7 +6034,7 @@ async function renamePastChats(newAvatar, newValue) {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
toastr.error(`Past chat could not be updated: ${file_name}`);
|
||||
toastr.error(t`Past chat could not be updated: ${file_name}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@ -6040,7 +6083,7 @@ export async function saveChat(chatName, withMetadata, mesId) {
|
||||
characters[this_chid]['date_last_chat'] = Date.now();
|
||||
chat.forEach(function (item, i) {
|
||||
if (item['is_group']) {
|
||||
toastr.error('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.');
|
||||
toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
|
||||
throw new Error('Group chat saved from saveChat');
|
||||
}
|
||||
/*
|
||||
@ -6085,7 +6128,7 @@ export async function saveChat(chatName, withMetadata, mesId) {
|
||||
contentType: 'application/json',
|
||||
success: function (data) { },
|
||||
error: function (jqXHR, exception) {
|
||||
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Chat could not be saved');
|
||||
toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`);
|
||||
console.log(exception);
|
||||
console.log(jqXHR);
|
||||
},
|
||||
@ -6482,7 +6525,7 @@ export async function getSettings() {
|
||||
|
||||
if (!response.ok) {
|
||||
reloadLoop();
|
||||
toastr.error('Settings could not be loaded after multiple attempts. Please try again later.');
|
||||
toastr.error(t`Settings could not be loaded after multiple attempts. Please try again later.`);
|
||||
throw new Error('Error getting settings');
|
||||
}
|
||||
|
||||
@ -6709,7 +6752,7 @@ export async function saveSettings(type) {
|
||||
eventSource.emit(event_types.SETTINGS_UPDATED);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Settings could not be saved');
|
||||
toastr.error(t`Check the server connection and reload the page to prevent data loss.t`, t`Settings could not be saved`);
|
||||
console.log(exception);
|
||||
console.log(jqXHR);
|
||||
},
|
||||
@ -6975,7 +7018,7 @@ export async function displayPastChats() {
|
||||
const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats());
|
||||
|
||||
if (!data) {
|
||||
toastr.error('Could not load chat data. Try reloading the page.');
|
||||
toastr.error(t`Could not load chat data. Try reloading the page.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -7108,7 +7151,7 @@ export function selectRightMenuWithAnimation(selectedMenuId) {
|
||||
|
||||
export function select_rm_info(type, charId, previousCharId = null) {
|
||||
if (!type) {
|
||||
toastr.error('Invalid process (no \'type\')');
|
||||
toastr.error(t`Invalid process (no 'type')`);
|
||||
return;
|
||||
}
|
||||
if (type !== 'group_create') {
|
||||
@ -7116,20 +7159,20 @@ export function select_rm_info(type, charId, previousCharId = null) {
|
||||
}
|
||||
|
||||
if (type === 'char_delete') {
|
||||
toastr.warning(`Character Deleted: ${displayName}`);
|
||||
toastr.warning(t`Character Deleted: ${displayName}`);
|
||||
}
|
||||
if (type === 'char_create') {
|
||||
toastr.success(`Character Created: ${displayName}`);
|
||||
toastr.success(t`Character Created: ${displayName}`);
|
||||
}
|
||||
if (type === 'group_create') {
|
||||
toastr.success('Group Created');
|
||||
toastr.success(t`Group Created`);
|
||||
}
|
||||
if (type === 'group_delete') {
|
||||
toastr.warning('Group Deleted');
|
||||
toastr.warning(t`Group Deleted`);
|
||||
}
|
||||
|
||||
if (type === 'char_import') {
|
||||
toastr.success(`Character Imported: ${displayName}`);
|
||||
toastr.success(t`Character Imported: ${displayName}`);
|
||||
}
|
||||
|
||||
selectRightMenuWithAnimation('rm_characters_block');
|
||||
@ -7587,25 +7630,25 @@ export function hideSwipeButtons() {
|
||||
*/
|
||||
export async function deleteSwipe(swipeId = null) {
|
||||
if (swipeId && (isNaN(swipeId) || swipeId < 0)) {
|
||||
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
|
||||
toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) {
|
||||
toastr.warning('No messages to delete swipes from.');
|
||||
toastr.warning(t`No messages to delete swipes from.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastMessage.swipes.length <= 1) {
|
||||
toastr.warning('Can\'t delete the last swipe.');
|
||||
toastr.warning(t`Can't delete the last swipe.`);
|
||||
return;
|
||||
}
|
||||
|
||||
swipeId = swipeId ?? lastMessage.swipe_id;
|
||||
|
||||
if (swipeId < 0 || swipeId >= lastMessage.swipes.length) {
|
||||
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
|
||||
toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -7744,7 +7787,7 @@ export function setGenerationProgress(progress) {
|
||||
|
||||
function isHordeGenerationNotAllowed() {
|
||||
if (main_api == 'koboldhorde' && preset_settings == 'gui') {
|
||||
toastr.error('GUI Settings preset is not supported for Horde. Please select another preset.');
|
||||
toastr.error(t`GUI Settings preset is not supported for Horde. Please select another preset.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -7793,7 +7836,7 @@ function openCharacterWorldPopup() {
|
||||
}
|
||||
|
||||
$('#character_json_data').val(JSON.stringify(data));
|
||||
toastr.info('Embedded lorebook will be removed from this character.');
|
||||
toastr.info(t`Embedded lorebook will be removed from this character.`);
|
||||
} catch {
|
||||
console.error('Failed to parse character JSON data.');
|
||||
}
|
||||
@ -7984,11 +8027,11 @@ async function createOrEditCharacter(e) {
|
||||
|
||||
if ($('#form_create').attr('actiontype') == 'createcharacter') {
|
||||
if (String($('#character_name_pole').val()).length === 0) {
|
||||
toastr.error('Name is required');
|
||||
toastr.error(t`Name is required`);
|
||||
return;
|
||||
}
|
||||
if (is_group_generating || is_send_press) {
|
||||
toastr.error('Cannot create characters while generating. Stop the request and try again.', 'Creation aborted');
|
||||
toastr.error(t`Cannot create characters while generating. Stop the request and try again.`, t`Creation aborted`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -8072,7 +8115,7 @@ async function createOrEditCharacter(e) {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating character', error);
|
||||
toastr.error('Failed to create character');
|
||||
toastr.error(t`Failed to create character`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@ -8131,7 +8174,7 @@ async function createOrEditCharacter(e) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error('Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.');
|
||||
toastr.error(t`Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8641,7 +8684,7 @@ async function selectContextCallback(args, name) {
|
||||
const result = fuse.search(name);
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(`Context template "${name}" not found`);
|
||||
!quiet && toastr.warning(t`Context template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -8661,7 +8704,7 @@ async function selectInstructCallback(args, name) {
|
||||
const result = fuse.search(name);
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(`Instruct template "${name}" not found`);
|
||||
!quiet && toastr.warning(t`Instruct template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -8713,7 +8756,7 @@ async function connectAPISlash(args, text) {
|
||||
|
||||
const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
|
||||
if (!apiConfig) {
|
||||
toastr.error(`Error: ${text} is not a valid API`);
|
||||
toastr.error(t`Error: ${text} is not a valid API`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -8742,7 +8785,7 @@ async function connectAPISlash(args, text) {
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet);
|
||||
const toast = quiet ? jQuery() : toastr.info(`API set to ${text}, trying to connect..`);
|
||||
const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`);
|
||||
|
||||
try {
|
||||
await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100);
|
||||
@ -8781,7 +8824,7 @@ export async function processDroppedFiles(files, data = new Map()) {
|
||||
const preservedName = data instanceof Map && data.get(file);
|
||||
await importCharacter(file, preservedName);
|
||||
} else {
|
||||
toastr.warning('Unsupported file type: ' + file.name);
|
||||
toastr.warning(t`Unsupported file type: ` + file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8794,7 +8837,7 @@ export async function processDroppedFiles(files, data = new Map()) {
|
||||
*/
|
||||
async function importCharacter(file, preserveFileName = '') {
|
||||
if (is_group_generating || is_send_press) {
|
||||
toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted');
|
||||
toastr.error(t`Cannot import characters while generating. Stop the request and try again.`, t`Import aborted`);
|
||||
throw new Error('Cannot import character while generating');
|
||||
}
|
||||
|
||||
@ -8821,7 +8864,7 @@ async function importCharacter(file, preserveFileName = '') {
|
||||
});
|
||||
|
||||
if (data.error) {
|
||||
toastr.error('The file is likely invalid or corrupted.', 'Could not import character');
|
||||
toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -8874,7 +8917,7 @@ async function doImpersonate(args, prompt) {
|
||||
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
|
||||
} catch {
|
||||
console.warn('Timeout waiting for generation unlock');
|
||||
toastr.warning('Cannot run /impersonate command while the reply is being generated.');
|
||||
toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -8936,19 +8979,19 @@ async function doDeleteChat() {
|
||||
|
||||
async function doRenameChat(_, chatName) {
|
||||
if (!chatName) {
|
||||
toastr.warning('Name must be provided as an argument to rename this chat.');
|
||||
toastr.warning(t`Name must be provided as an argument to rename this chat.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const currentChatName = getCurrentChatId();
|
||||
if (!currentChatName) {
|
||||
toastr.warning('No chat selected that can be renamed.');
|
||||
toastr.warning(t`No chat selected that can be renamed.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
await renameChat(currentChatName, chatName);
|
||||
|
||||
toastr.success(`Successfully renamed chat to: ${chatName}`);
|
||||
toastr.success(t`Successfully renamed chat to: ${chatName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -9063,7 +9106,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
|
||||
for (const key of characterKey) {
|
||||
const character = characters.find(x => x.avatar == key);
|
||||
if (!character) {
|
||||
toastr.warning(`Character ${key} not found. Skipping deletion.`);
|
||||
toastr.warning(t`Character ${key} not found. Skipping deletion.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -9080,7 +9123,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error(`${response.status} ${response.statusText}`, 'Failed to delete character');
|
||||
toastr.error(`${response.status} ${response.statusText}`, t`Failed to delete character`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -9132,6 +9175,90 @@ function doTogglePanels() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to open a navbar drawer when a drawer open button is clicked.
|
||||
* Handles click events on .drawer-opener elements.
|
||||
* Opens the drawer associated with the clicked button according to the data-target attribute.
|
||||
* @returns {void}
|
||||
*/
|
||||
function doDrawerOpenClick() {
|
||||
const targetDrawerID = $(this).attr('data-target');
|
||||
const drawer = $(`#${targetDrawerID}`);
|
||||
const drawerToggle = drawer.find('.drawer-toggle');
|
||||
const drawerWasOpenAlready = drawerToggle.parent().find('.drawer-content').hasClass('openDrawer');
|
||||
if (drawerWasOpenAlready || drawer.hasClass('resizing')) { return; }
|
||||
doNavbarIconClick.call(drawerToggle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to open or close a navbar drawer when a navbar icon is clicked.
|
||||
* Handles click events on .drawer-toggle elements.
|
||||
* @returns {void}
|
||||
*/
|
||||
function doNavbarIconClick() {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return; }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
drawer.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: 'swing',
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||||
},
|
||||
complete: async function () {
|
||||
favsToHotswap();
|
||||
await delay(50);
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {
|
||||
await resetScrollHeight($(this));
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
} else if (drawerWasOpenAlready) { //to close manually
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).removeClass('resizing');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
drawer.toggleClass('closedDrawer openDrawer');
|
||||
}
|
||||
}
|
||||
|
||||
function addDebugFunctions() {
|
||||
const doBackfill = async () => {
|
||||
for (const message of chat) {
|
||||
@ -9755,12 +9882,7 @@ jQuery(async function () {
|
||||
|
||||
let deleteChats = false;
|
||||
|
||||
const confirm = await Popup.show.confirm('Delete the character?', `
|
||||
<b>THIS IS PERMANENT!<br><br>
|
||||
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
|
||||
<input type="checkbox" id="del_char_checkbox" />
|
||||
<small>Also delete the chat files</small>
|
||||
</label></b>`, {
|
||||
const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), {
|
||||
onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),
|
||||
});
|
||||
if (!confirm) {
|
||||
@ -10721,69 +10843,8 @@ jQuery(async function () {
|
||||
stopScriptExecution();
|
||||
});
|
||||
|
||||
$('.drawer-toggle').on('click', function () {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return; }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
drawer.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: 'swing',
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||||
},
|
||||
complete: async function () {
|
||||
favsToHotswap();
|
||||
await delay(50);
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$('#rm_print_characters_block').trigger('scroll');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {
|
||||
await resetScrollHeight($(this));
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
} else if (drawerWasOpenAlready) { //to close manually
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).removeClass('resizing');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
|
||||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
drawer.toggleClass('closedDrawer openDrawer');
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.drawer-opener', doDrawerOpenClick);
|
||||
$('.drawer-toggle').on('click', doNavbarIconClick);
|
||||
|
||||
$('html').on('touchstart mousedown', function (e) {
|
||||
var clickTarget = $(e.target);
|
||||
|
@ -8,6 +8,7 @@ import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@ -455,7 +456,7 @@ class PromptManager {
|
||||
|
||||
// Delete selected prompt from list form and close edit form
|
||||
this.handleDeletePrompt = async (event) => {
|
||||
Popup.show.confirm('Are you sure you want to delete this prompt?', null).then((userChoice) => {
|
||||
Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => {
|
||||
if (!userChoice) return;
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
@ -531,7 +532,7 @@ class PromptManager {
|
||||
|
||||
// Import prompts for the selected character
|
||||
this.handleImport = () => {
|
||||
Popup.show.confirm('Existing prompts with the same ID will be overridden. Do you want to proceed?', null)
|
||||
Popup.show.confirm(t`Existing prompts with the same ID will be overridden. Do you want to proceed?`, null)
|
||||
.then(userChoice => {
|
||||
if (!userChoice) return;
|
||||
|
||||
@ -552,7 +553,7 @@ class PromptManager {
|
||||
const data = JSON.parse(fileContent);
|
||||
this.import(data);
|
||||
} catch (err) {
|
||||
toastr.error('An error occurred while importing prompts. More info available in console.');
|
||||
toastr.error(t`An error occurred while importing prompts. More info available in console.`);
|
||||
console.log('An error occurred while importing prompts');
|
||||
console.log(err.toString());
|
||||
}
|
||||
@ -567,7 +568,7 @@ class PromptManager {
|
||||
|
||||
// Restore default state of a characters prompt order
|
||||
this.handleCharacterReset = () => {
|
||||
Popup.show.confirm('This will reset the prompt order for this character. You will not lose any prompts.', null)
|
||||
Popup.show.confirm(t`This will reset the prompt order for this character. You will not lose any prompts.`, null)
|
||||
.then(userChoice => {
|
||||
if (!userChoice) return;
|
||||
|
||||
@ -1649,7 +1650,7 @@ class PromptManager {
|
||||
};
|
||||
|
||||
if (false === this.validateObject(controlObj, importData)) {
|
||||
toastr.warning('Could not import prompts. Export failed validation.');
|
||||
toastr.warning(t`Could not import prompts. Export failed validation.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1672,7 +1673,7 @@ class PromptManager {
|
||||
throw new Error('Prompt order strategy not supported.');
|
||||
}
|
||||
|
||||
toastr.success('Prompt import complete.');
|
||||
toastr.success(t`Prompt import complete.`);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||
import { t } from './i18n.js';
|
||||
|
||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||
|
||||
@ -37,7 +38,7 @@ const chara_note_position = {
|
||||
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success('Author\'s Note text updated');
|
||||
toastr.success(t`Author's Note text updated`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -45,12 +46,12 @@ function setNoteDepthCommand(_, text) {
|
||||
const value = Number(text);
|
||||
|
||||
if (Number.isNaN(value)) {
|
||||
toastr.error('Not a valid number');
|
||||
toastr.error(t`Not a valid number`);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note depth updated');
|
||||
toastr.success(t`Author's Note depth updated`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -58,12 +59,12 @@ function setNoteIntervalCommand(_, text) {
|
||||
const value = Number(text);
|
||||
|
||||
if (Number.isNaN(value)) {
|
||||
toastr.error('Not a valid number');
|
||||
toastr.error(t`Not a valid number`);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note frequency updated');
|
||||
toastr.success(t`Author's Note frequency updated`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -76,12 +77,12 @@ function setNotePositionCommand(_, text) {
|
||||
const position = validPositions[text?.trim()];
|
||||
|
||||
if (Number.isNaN(position)) {
|
||||
toastr.error('Not a valid position');
|
||||
toastr.error(t`Not a valid position`);
|
||||
return;
|
||||
}
|
||||
|
||||
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
|
||||
toastr.info('Author\'s Note position updated');
|
||||
toastr.info(t`Author's Note position updated`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -206,7 +207,7 @@ function onExtensionFloatingCharaPromptInput() {
|
||||
extension_settings.note.chara.push(tempCharaNote);
|
||||
} else {
|
||||
console.log('Character author\'s note error: No avatar name key could be found.');
|
||||
toastr.error('Something went wrong. Could not save character\'s author\'s note.');
|
||||
toastr.error(t`Something went wrong. Could not save character's author's note.`);
|
||||
|
||||
// Don't save settings if something went wrong
|
||||
return;
|
||||
@ -397,7 +398,7 @@ function onANMenuItemClick() {
|
||||
//because this listener takes priority
|
||||
$('#options').stop().fadeOut(animation_duration);
|
||||
} else {
|
||||
toastr.warning('Select a character before trying to use Author\'s Note', '', { timeOut: 2000 });
|
||||
toastr.warning(t`Select a character before trying to use Author's Note`, '', { timeOut: 2000 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { ScraperManager } from './scrapers.js';
|
||||
import { DragAndDropHandler } from './dragdrop.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileAttachment
|
||||
@ -206,7 +207,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
|
||||
const fileText = await converter(file);
|
||||
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
|
||||
} catch (error) {
|
||||
toastr.error(String(error), 'Could not convert file');
|
||||
toastr.error(String(error), t`Could not convert file`);
|
||||
console.error('Could not convert file', error);
|
||||
}
|
||||
}
|
||||
@ -257,7 +258,7 @@ export async function uploadFileAttachment(fileName, base64Data) {
|
||||
const responseData = await result.json();
|
||||
return responseData.path;
|
||||
} catch (error) {
|
||||
toastr.error(String(error), 'Could not upload file');
|
||||
toastr.error(String(error), t`Could not upload file`);
|
||||
console.error('Could not upload file', error);
|
||||
}
|
||||
}
|
||||
@ -283,7 +284,7 @@ export async function getFileAttachment(url) {
|
||||
const text = await result.text();
|
||||
return text;
|
||||
} catch (error) {
|
||||
toastr.error(error, 'Could not download file');
|
||||
toastr.error(error, t`Could not download file`);
|
||||
console.error('Could not download file', error);
|
||||
}
|
||||
}
|
||||
@ -299,13 +300,13 @@ async function validateFile(file) {
|
||||
const isBinary = /^[\x00-\x08\x0E-\x1F\x7F-\xFF]*$/.test(fileText);
|
||||
|
||||
if (!isImage && file.size > fileSizeLimit) {
|
||||
toastr.error(`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`);
|
||||
toastr.error(t`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If file is binary
|
||||
if (isBinary && !isImage && !isConvertible(file.type)) {
|
||||
toastr.error('Binary files are not supported. Select a text file or image.');
|
||||
toastr.error(t`Binary files are not supported. Select a text file or image.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -521,7 +522,7 @@ async function openExternalMediaOverridesDialog() {
|
||||
const entityId = getCurrentEntityId();
|
||||
|
||||
if (!entityId) {
|
||||
toastr.info('No character or group selected');
|
||||
toastr.info(t`No character or group selected`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -646,7 +647,7 @@ async function deleteFileFromServer(url, silent = false) {
|
||||
await eventSource.emit(event_types.FILE_ATTACHMENT_DELETED, url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
toastr.error(String(error), 'Could not delete file');
|
||||
toastr.error(String(error), t`Could not delete file`);
|
||||
console.error('Could not delete file', error);
|
||||
return false;
|
||||
}
|
||||
@ -1054,7 +1055,7 @@ async function openAttachmentManager() {
|
||||
const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
|
||||
|
||||
if (selectedAttachments.length === 0) {
|
||||
toastr.info('No attachments selected.', 'Data Bank');
|
||||
toastr.info(t`No attachments selected.`, t`Data Bank`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1168,7 +1169,7 @@ async function runScraper(scraperId, target, callback) {
|
||||
|
||||
if (files.length === 0) {
|
||||
console.warn('Scraping returned no files');
|
||||
toastr.info('No files were scraped.', 'Data Bank');
|
||||
toastr.info(t`No files were scraped.`, t`Data Bank`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1176,12 +1177,12 @@ async function runScraper(scraperId, target, callback) {
|
||||
await uploadFileAttachmentToServer(file, target);
|
||||
}
|
||||
|
||||
toastr.success(`Scraped ${files.length} files from ${scraperId} to ${target}.`, 'Data Bank');
|
||||
toastr.success(t`Scraped ${files.length} files from ${scraperId} to ${target}.`, t`Data Bank`);
|
||||
callback();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Scraping failed', error);
|
||||
toastr.error('Check browser console for details.', 'Scraping failed');
|
||||
toastr.error(t`Check browser console for details.`, t`Scraping failed`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1208,7 +1209,7 @@ export async function uploadFileAttachmentToServer(file, target) {
|
||||
const fileText = await converter(file);
|
||||
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
|
||||
} catch (error) {
|
||||
toastr.error(String(error), 'Could not convert file');
|
||||
toastr.error(String(error), t`Could not convert file`);
|
||||
console.error('Could not convert file', error);
|
||||
}
|
||||
} else {
|
||||
|
@ -132,7 +132,7 @@
|
||||
<small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
|
||||
<span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly. Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
|
||||
</span>
|
||||
<label class="checkbox flex-container" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
|
||||
<label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
|
||||
<input type="checkbox" name="only_format_display" />
|
||||
<span data-i18n="Only Format Display">Alter Chat Display</span>
|
||||
</label>
|
||||
|
@ -474,7 +474,7 @@ async function processTtsQueue() {
|
||||
}
|
||||
|
||||
if (extension_settings.tts.narrate_quoted_only) {
|
||||
const special_quotes = /[“”«»]/g; // Extend this regex to include other special quotes
|
||||
const special_quotes = /[“”«»「」『』""]/g; // Extend this regex to include other special quotes
|
||||
text = text.replace(special_quotes, '"');
|
||||
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
|
||||
const partJoiner = (ttsProvider?.separator || ' ... ');
|
||||
|
@ -189,8 +189,8 @@ async function validateGroup(group) {
|
||||
group.members = group.members.filter(member => {
|
||||
const character = characters.find(x => x.avatar === member || x.name === member);
|
||||
if (!character) {
|
||||
const msg = `Warning: Listed member ${member} does not exist as a character. It will be removed from the group.`;
|
||||
toastr.warning(msg, 'Group Validation');
|
||||
const msg = t`Warning: Listed member ${member} does not exist as a character. It will be removed from the group.`;
|
||||
toastr.warning(msg, t`Group Validation`);
|
||||
console.warn(msg);
|
||||
dirty = true;
|
||||
}
|
||||
@ -522,7 +522,7 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Group Chat could not be saved');
|
||||
toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Group Chat could not be saved`);
|
||||
console.error('Group chat could not be saved', response);
|
||||
return;
|
||||
}
|
||||
@ -837,7 +837,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
activatedMembers = activateSwipe(group.members);
|
||||
|
||||
if (activatedMembers.length === 0) {
|
||||
toastr.warning('Deleted group member swiped. To get a reply, add them back to the group.');
|
||||
toastr.warning(t`Deleted group member swiped. To get a reply, add them back to the group.`);
|
||||
throw new Error('Deleted group member swiped');
|
||||
}
|
||||
}
|
||||
@ -1368,15 +1368,15 @@ function isGroupMemberDisabled(avatarId) {
|
||||
|
||||
async function onDeleteGroupClick() {
|
||||
if (!openGroupId) {
|
||||
toastr.warning('Currently no group selected.');
|
||||
toastr.warning(t`Currently no group selected.`);
|
||||
return;
|
||||
}
|
||||
if (is_group_generating) {
|
||||
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
|
||||
toastr.warning(t`Not so fast! Wait for the characters to stop typing before deleting the group.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await Popup.show.confirm('Delete the group?', '<p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>');
|
||||
const confirm = await Popup.show.confirm(t`Delete the group?`, '<p>' + t`This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.` + '</p>');
|
||||
if (confirm) {
|
||||
deleteGroup(openGroupId);
|
||||
}
|
||||
@ -1630,7 +1630,7 @@ function updateFavButtonState(state) {
|
||||
|
||||
export async function openGroupById(groupId) {
|
||||
if (isChatSaving) {
|
||||
toastr.info('Please wait until the chat is saved before switching characters.', 'Your chat is still saving...');
|
||||
toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1659,7 +1659,7 @@ export async function openGroupById(groupId) {
|
||||
|
||||
function openCharacterDefinition(characterSelect) {
|
||||
if (is_group_generating) {
|
||||
toastr.warning('Can\'t peek a character while group reply is being generated');
|
||||
toastr.warning(t`Can't peek a character while group reply is being generated`);
|
||||
console.warn('Can\'t peek a character def while group reply is being generated');
|
||||
return;
|
||||
}
|
||||
@ -1908,7 +1908,7 @@ export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Group chat could not be saved');
|
||||
toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Group chat could not be saved`);
|
||||
console.error('Group chat could not be saved', response);
|
||||
}
|
||||
}
|
||||
|
@ -215,6 +215,7 @@ function addLanguagesToDropdown() {
|
||||
}
|
||||
|
||||
export async function initLocales() {
|
||||
moment.locale(localeFile);
|
||||
langs = await fetch('/locales/lang.json').then(response => response.json());
|
||||
localeData = await getLocaleData(localeFile);
|
||||
applyLocale();
|
||||
|
@ -392,7 +392,7 @@ async function validateReverseProxy() {
|
||||
new URL(oai_settings.reverse_proxy);
|
||||
}
|
||||
catch (err) {
|
||||
toastr.error('Entered reverse proxy address is not a valid URL');
|
||||
toastr.error(t`Entered reverse proxy address is not a valid URL`);
|
||||
setOnlineStatus('no_connection');
|
||||
resultCheckStatus();
|
||||
throw err;
|
||||
@ -403,7 +403,7 @@ async function validateReverseProxy() {
|
||||
const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) }));
|
||||
|
||||
if (!confirmation) {
|
||||
toastr.error('Update or remove your reverse proxy settings.');
|
||||
toastr.error(t`Update or remove your reverse proxy settings.`);
|
||||
setOnlineStatus('no_connection');
|
||||
resultCheckStatus();
|
||||
throw new Error('Proxy connection denied.');
|
||||
@ -1259,15 +1259,15 @@ export async function prepareOpenAIMessages({
|
||||
await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
|
||||
} catch (error) {
|
||||
if (error instanceof TokenBudgetExceededError) {
|
||||
toastr.error('An error occurred while counting tokens: Token budget exceeded.');
|
||||
toastr.error(t`An error occurred while counting tokens: Token budget exceeded.`);
|
||||
chatCompletion.log('Token budget exceeded.');
|
||||
promptManager.error = 'Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.';
|
||||
promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.`;
|
||||
} else if (error instanceof InvalidCharacterNameError) {
|
||||
toastr.warning('An error occurred while counting tokens: Invalid character name');
|
||||
toastr.warning(t`An error occurred while counting tokens: Invalid character name`);
|
||||
chatCompletion.log('Invalid character name');
|
||||
promptManager.error = 'The name of at least one character contained whitespaces or special characters. Please check your user and character name.';
|
||||
promptManager.error = t`The name of at least one character contained whitespaces or special characters. Please check your user and character name.`;
|
||||
} else {
|
||||
toastr.error('An unknown error occurred while counting tokens. Further information may be available in console.');
|
||||
toastr.error(t`An unknown error occurred while counting tokens. Further information may be available in console.`);
|
||||
chatCompletion.log('----- Unexpected error while preparing prompts -----');
|
||||
chatCompletion.log(error);
|
||||
chatCompletion.log(error.stack);
|
||||
@ -1321,11 +1321,8 @@ function tryParseStreamingError(response, decoded) {
|
||||
}
|
||||
}
|
||||
|
||||
function checkQuotaError(data) {
|
||||
const errorText = `<h3>Encountered an error while processing your request.<br>
|
||||
Check you have credits available on your
|
||||
<a href="https://platform.openai.com/account/usage" target="_blank">OpenAI account</a>.<br>
|
||||
If you have sufficient credits, please try again later.</h3>`;
|
||||
async function checkQuotaError(data) {
|
||||
const errorText = await renderTemplateAsync('quotaError');
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
@ -1964,11 +1961,11 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
else {
|
||||
const data = await response.json();
|
||||
|
||||
checkQuotaError(data);
|
||||
await checkQuotaError(data);
|
||||
checkModerationError(data);
|
||||
|
||||
if (data.error) {
|
||||
toastr.error(data.error.message || response.statusText, 'API returned an error');
|
||||
toastr.error(data.error.message || response.statusText, t`API returned an error`);
|
||||
throw new Error(data);
|
||||
}
|
||||
|
||||
@ -2078,7 +2075,7 @@ function parseOpenAITextLogprobs(logprobs) {
|
||||
|
||||
function handleWindowError(err) {
|
||||
const text = parseWindowError(err);
|
||||
toastr.error(text, 'Window.ai returned an error');
|
||||
toastr.error(text, t`Window.ai returned an error`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
@ -3186,7 +3183,7 @@ async function getStatusOpen() {
|
||||
}
|
||||
|
||||
function showWindowExtensionError() {
|
||||
toastr.error('Get it here: <a href="https://windowai.io/" target="_blank">windowai.io</a>', 'Extension is not installed', {
|
||||
toastr.error(t`Get it here:` + ' <a href="https://windowai.io/" target="_blank">windowai.io</a>', t`Extension is not installed`, {
|
||||
escapeHtml: false,
|
||||
timeOut: 0,
|
||||
extendedTimeOut: 0,
|
||||
@ -3305,7 +3302,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
if (triggerUi) $('#settings_preset_openai').append(option).trigger('change');
|
||||
}
|
||||
} else {
|
||||
toastr.error('Failed to save preset');
|
||||
toastr.error(t`Failed to save preset`);
|
||||
throw new Error('Failed to save preset');
|
||||
}
|
||||
}
|
||||
@ -3384,7 +3381,7 @@ async function createNewLogitBiasPreset() {
|
||||
}
|
||||
|
||||
if (name in oai_settings.bias_presets) {
|
||||
toastr.error('Preset name should be unique.');
|
||||
toastr.error(t`Preset name should be unique.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3428,7 +3425,7 @@ async function onPresetImportFileChange(e) {
|
||||
try {
|
||||
presetBody = JSON.parse(importedFile);
|
||||
} catch (err) {
|
||||
toastr.error('Invalid file');
|
||||
toastr.error(t`Invalid file`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3469,7 +3466,7 @@ async function onPresetImportFileChange(e) {
|
||||
});
|
||||
|
||||
if (!savePresetSettings.ok) {
|
||||
toastr.error('Failed to save preset');
|
||||
toastr.error(t`Failed to save preset`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3494,7 +3491,7 @@ async function onPresetImportFileChange(e) {
|
||||
|
||||
async function onExportPresetClick() {
|
||||
if (!oai_settings.preset_settings_openai) {
|
||||
toastr.error('No preset selected');
|
||||
toastr.error(t`No preset selected`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3535,12 +3532,12 @@ async function onLogitBiasPresetImportFileChange(e) {
|
||||
e.target.value = '';
|
||||
|
||||
if (name in oai_settings.bias_presets) {
|
||||
toastr.error('Preset name should be unique.');
|
||||
toastr.error(t`Preset name should be unique.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(importedFile)) {
|
||||
toastr.error('Invalid logit bias preset file.');
|
||||
toastr.error(t`Invalid logit bias preset file.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3599,16 +3596,16 @@ async function onDeletePresetClick() {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.warning('Preset was not deleted from server');
|
||||
toastr.warning(t`Preset was not deleted from server`);
|
||||
} else {
|
||||
toastr.success('Preset deleted');
|
||||
toastr.success(t`Preset deleted`);
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onLogitBiasPresetDeleteClick() {
|
||||
const value = await callPopup('Delete the preset?', 'confirm');
|
||||
const value = await callPopup(t`Delete the preset?`, 'confirm');
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
@ -4745,7 +4742,7 @@ function runProxyCallback(_, value) {
|
||||
const result = fuse.search(value);
|
||||
|
||||
if (result.length === 0) {
|
||||
toastr.warning(`Proxy preset "${value}" not found`);
|
||||
toastr.warning(t`Proxy preset '${value}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -4919,7 +4916,7 @@ export function initOpenAI() {
|
||||
$('#update_oai_preset').on('click', async function () {
|
||||
const name = oai_settings.preset_settings_openai;
|
||||
await saveOpenAIPreset(name, oai_settings);
|
||||
toastr.success('Preset updated');
|
||||
toastr.success(t`Preset updated`);
|
||||
});
|
||||
|
||||
$('#impersonation_prompt_restore').on('click', function () {
|
||||
|
@ -1,3 +1,3 @@
|
||||
<div>
|
||||
<b>Note:</b> this chat is temporary and will be deleted as soon as you leave it.
|
||||
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
|
||||
</div>
|
||||
|
5
public/scripts/templates/deleteConfirm.html
Normal file
5
public/scripts/templates/deleteConfirm.html
Normal file
@ -0,0 +1,5 @@
|
||||
<b><span data-i18n="THIS IS PERMANENT!">THIS IS PERMANENT!</span><br><br>
|
||||
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
|
||||
<input type="checkbox" id="del_char_checkbox" />
|
||||
<small data-i18n="Also delete the chat files">Also delete the chat files</small>
|
||||
</label></b>
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<h3>Are you sure you want to duplicate this character?</h3>
|
||||
<span>If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.</span>
|
||||
<h3 data-i18n="Are you sure you want to duplicate this character?">Are you sure you want to duplicate this character?</h3>
|
||||
<span data-i18n="If you just want to start a new chat with the same character...">If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.</span>
|
||||
<br>
|
||||
</div>
|
||||
|
@ -14,18 +14,18 @@
|
||||
<li><kbd data-i18n="help_hotkeys_19">Ctrl+Shift+Down</kbd> = <span data-i18n="help_hotkeys_20">Scroll chat to bottom</span></li>
|
||||
</ul>
|
||||
<div>
|
||||
<strong>Markdown Hotkeys</strong>
|
||||
<strong data-i18n="help_hotkeys_20">Markdown Hotkeys</strong>
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
<span>Works in the chatbar and textareas marked with this icon:</span>
|
||||
<span data-i18n="help_hotkeys_21">Works in the chatbar and textareas marked with this icon:</span>
|
||||
<code><i class="fa-brands fa-markdown"></i></code>
|
||||
</small>
|
||||
</div>
|
||||
<ul>
|
||||
<li><kbd>Ctrl+B</kbd> = <span>**bold**</span></li>
|
||||
<li><kbd>Ctrl+I</kbd> = <span>*italic*</span></li>
|
||||
<li><kbd>Ctrl+U</kbd> = <span>__underline__</span></li>
|
||||
<li><kbd>Ctrl+K</kbd> = <span>`inline code`</span></li>
|
||||
<li><kbd>Ctrl+Shift+~</kbd> = <span>~~strikethrough~~</span></li>
|
||||
<li><kbd>Ctrl+B</kbd> = <span data-i18n="help_hotkeys_22">**bold**</span></li>
|
||||
<li><kbd>Ctrl+I</kbd> = <span data-i18n="help_hotkeys_23">*italic*</span></li>
|
||||
<li><kbd>Ctrl+U</kbd> = <span data-i18n="help_hotkeys_24">__underline__</span></li>
|
||||
<li><kbd>Ctrl+K</kbd> = <span data-i18n="help_hotkeys_25">`inline code`</span></li>
|
||||
<li><kbd>Ctrl+Shift+~</kbd> = <span data-i18n="help_hotkeys_26">~~strikethrough~~</span></li>
|
||||
</ul>
|
||||
|
4
public/scripts/templates/quotaError.html
Normal file
4
public/scripts/templates/quotaError.html
Normal file
@ -0,0 +1,4 @@
|
||||
<h3><span data-i18n="Encountered an error while processing your request.">Encountered an error while processing your request.</span><br>
|
||||
<span data-i18n="Check you have credits available on your">Check you have credits available on your</span>
|
||||
<a href="https://platform.openai.com/account/usage" target="_blank" data-i18n="OpenAI account quora_error">OpenAI account</a><span data-i18n="dot quota_error">.</span><br>
|
||||
<span data-i18n="If you have sufficient credits, please try again later.">If you have sufficient credits, please try again later.</span></h3>
|
@ -4,30 +4,56 @@
|
||||
<a href="https://docs.sillytavern.app/usage/update/" target="_blank" data-i18n="Want to update?">
|
||||
Want to update?
|
||||
</a>
|
||||
<hr>
|
||||
<h3 data-i18n="How to start chatting?">How to start chatting?</h3>
|
||||
<ol>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
|
||||
<span data-i18n="Click _space">Click </span>
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="sys-settings-button">
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
<span data-i18n="[title]API Connections">API Connections</span>
|
||||
</button>
|
||||
<span data-i18n="and connect to an">and connect to an</span>
|
||||
<a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank">
|
||||
<span class="fa-solid fa-circle-question"></span>
|
||||
<span data-i18n="API">API</span></a>.
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
|
||||
<span data-i18n="Click _space">Click </span>
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="rightNavHolder">
|
||||
<i class="fa-solid fa-address-card"></i>
|
||||
<span data-i18n="[title]Character Management">Character Management</span>
|
||||
</button>
|
||||
<span data-i18n="and pick a character."> and pick a character.</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div>
|
||||
<span data-i18n="You can browse a list of bundled characters in the">
|
||||
You can browse a list of bundled characters in the
|
||||
</span>
|
||||
<i data-i18n="Download Extensions & Assets">
|
||||
Download Extensions & Assets
|
||||
</i>
|
||||
<span data-i18n="menu within">
|
||||
menu within
|
||||
</span>
|
||||
<code><i class="fa-solid fa-cubes"></i></code>
|
||||
<span>.</span>
|
||||
<span data-i18n="You can add more">You can add more</span>
|
||||
<button class="open_characters_library menu_button menu_button_icon inline-flex">
|
||||
<i class="fa-solid fa-image-portrait"></i>
|
||||
<span data-i18n="Sample characters">Sample characters</span>
|
||||
</button>
|
||||
<span data-i18n="or">or</span>
|
||||
<button class="external_import_button menu_button menu_button_icon inline-flex">
|
||||
<i class="fa-solid fa-cloud-arrow-down"></i>
|
||||
<span data-i18n="Import Characters">Import characters</span>
|
||||
</button>
|
||||
<span data-i18n="from other websites">from other websites.</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<span data-i18n="Go to the">Go to the</span>
|
||||
|
||||
<i data-i18n="Download Extensions & Assets">Download Extensions & Assets</i>
|
||||
<span data-i18n="menu within">menu within</span>
|
||||
|
||||
<button class="menu_button menu_button_icon drawer-opener inline-flex" data-target="extensions-settings-button">
|
||||
<i class="fa-solid fa-cubes"></i>
|
||||
<span data-i18n="[title]Extensions">Extensions</span>
|
||||
</button>
|
||||
|
||||
<span data-i18n="to install additional features.">to install additional features.</span>
|
||||
</div>
|
||||
|
||||
<h3 data-i18n="Confused or lost?">Confused or lost?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@ -43,7 +69,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h3 data-i18n="Still have questions?">Still have questions?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@ -62,4 +87,3 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -314,6 +314,12 @@ input[type='checkbox']:focus-visible {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mes_text ol,
|
||||
.mes_text ul {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.mes_text br,
|
||||
.mes_bias br {
|
||||
content: ' ';
|
||||
@ -1218,8 +1224,9 @@ textarea.autoSetHeight {
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
font-family: var(--mainFontFamily);
|
||||
select,
|
||||
button {
|
||||
font-family: var(--mainFontFamily), sans-serif;
|
||||
font-size: var(--mainFontSize);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user