Work on tl

7f9023d3c21cb026914efd5489add59b71e21297

Yokayo <nonameutubecomm@gmail.com>

13 files changed, +286 -145Showing whitespace changes
public/locales/ru-ru.json+142 -5
@@ -451,8 +451,8 @@
451451 "Creator's Notes": "Заметки создателя",
452452 "A-Z": "A-Z",
453453 "Z-A": "Z-A",
454454 "Newest": "НовейшиеСначала новые",
455455 "Oldest": "СтарейшиеСначала старые",
456456 "Favorites": "Избранные",
457457 "Recent": "Последние",
458458 "Most chats": "Больше всего чатов",
@@ -564,7 +564,7 @@
564564 "Advanced Definition": "Расширенное описание",
565565 "Character Lore": "Лор персонажа",
566566 "Export and Download": "Экспортировать и скачать",
567567 "Duplicate Character": "ДублироватьКлонировать персонажа",
568568 "Create Character": "Создать персонажа",
569569 "Delete Character": "Удалить персонажа",
570570 "View all tags": "Показать все тэги",
@@ -1690,7 +1690,7 @@
16901690 "Delete Message": "Удалить сообщение",
16911691 "Delete Swipe": "Удалить свайп",
16921692 "Could not get a reply from API. Check your connection settings / API key and try again.": "Не удалось получить ответ от API. Проверьте настройки соединения и API-ключ и повторите попытку.",
16931693 "Connecting To Proxy": "ПодключениеПодключиться к прокси",
16941694 "Are you sure you want to connect to the following proxy URL?": "Вы точно хотите соединиться с прокси по этому адресу?",
16951695 "API connection successful!": "Соединение с API установлено!",
16961696 "Proxy Saved": "Прокси сохранена",
@@ -1747,5 +1747,142 @@
17471747 "Markdown Hotkeys": "Горячие клавиши для разметки",
17481748 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
17491749 "Save and Update": "Сохранить и обновить",
17501750 "Profile name:": "Название профиля:",
1751+ "API returned an error": "API вернуло ошибку",
1752+ "Failed to save preset": "Не удалось сохранить пресет",
1753+ "Preset name should be unique.": "Название пресета должно быть уникальным.",
1754+ "Invalid file": "Невалидный файл",
1755+ "No preset selected": "Пресет не выбран",
1756+ "Invalid logit bias preset file.": "Файл пресета невалиден.",
1757+ "Preset was not deleted from server": "Пресет не удалён с сервера",
1758+ "Preset deleted": "Пресет удалён",
1759+ "Delete the preset?": "Удалить пресет?",
1760+ "Preset updated": "Пресет обновлён",
1761+ "Entered reverse proxy address is not a valid URL": "Введённый адрес прокси невалиден",
1762+ "An error occurred while counting tokens: Token budget exceeded.": "Ошибка при подсчёте токенов: Превышен бюджет токенов",
1763+ "An error occurred while counting tokens: Invalid character name": "Ошибка при подсчёте токенов: Невалидное имя персонажа",
1764+ "Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.": "Недостаточно токенов для всех выбранных промптов. Повысьте лимит токенов или отключите часть промптов.",
1765+ "The name of at least one character contained whitespaces or special characters. Please check your user and character name.": "В имени одного из персонажей содержится пробел или иной спецсимвол. Проверьте имена пользователя и персонажа.",
1766+ "An unknown error occurred while counting tokens. Further information may be available in console.": "Неизвестная ошибка при подсчёте токенов. Проверьте консоль, возможно, подробная информация есть там.",
1767+ "quota_error_1": "При обработке вашего запроса возникла ошибка.",
1768+ "quota_error_2": "Убедитесь, что на вашем",
1769+ "quota_error_3": "аккаунте OpenAI",
1770+ "quota_error_4": "имеется достаточно кредитов.",
1771+ "quota_error_5": "Если кредитов достаточно, то повторите попытку позднее.",
1772+ "Proxy preset '${0}' not found": "Пресет '${0}' не найден",
1773+ "Window.ai returned an error": "Window.ai вернул ошибку",
1774+ "Get it here:": "Загрузите здесь:",
1775+ "Extension is not installed": "Расширение не установлено",
1776+ "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
1777+ "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
1778+ "Could not import prompts. Export failed validation.": "Не удалось импортировать промпты. Не пройдена валидация при экспорте.",
1779+ "Prompt import complete.": "Импорт завершён.",
1780+ "Are you sure you want to delete this prompt?": "Вы точно хотите удалить этот промпт?",
1781+ "Existing prompts with the same ID will be overridden. Do you want to proceed?": "Имеющиеся промпты с совпадающими идентификаторами будут перезаписаны. Продолжить?",
1782+ "This will reset the prompt order for this character. You will not lose any prompts.": "Будет сброшен порядок промптов для этого персонажа. Сами промпты вы не потеряете.",
1783+ "assistant_note_1": "Примечание:",
1784+ "assistant_note_2": "это временный чат, он будет удалён, как только вы из него выйдете.",
1785+ "help_hotkeys_20": "Горячие клавиши для разметки",
1786+ "help_hotkeys_21": "Работают в окне ввода чата, а также в полях, отмеченных этим значком:",
1787+ "help_hotkeys_22": "**полужирный**",
1788+ "help_hotkeys_23": "*курсив*",
1789+ "help_hotkeys_24": "__подчёркивание__",
1790+ "help_hotkeys_25": "`inline-код`",
1791+ "help_hotkeys_26": "~~зачёркнутый~~",
1792+ "ext_regex_only_format_visual_desc": "Содержимое файла с историей чата останется нетронутым, изменения будут лишь визуальными (в UI).",
1793+ "Could not convert file": "Не удалось сконвертировать файл",
1794+ "Could not upload file": "Не удалось загрузить файл",
1795+ "Could not download file": "Не удалось скачать файл",
1796+ "File is too big. Maximum size is ${0}.": "Слишком большой файл. Максимальный размер: ${0}.",
1797+ "Binary files are not supported. Select a text file or image.": "Бинарные файлы не поддерживаются. Выберите текстовый файл или изображение.",
1798+ "No character or group selected": "Не выбрано ни одного персонажа или группы",
1799+ "Could not delete file": "Не удалось удалить файл",
1800+ "No attachments selected.": "Вложение не выбрано.",
1801+ "Data Bank": "Банк данных",
1802+ "No files were scraped.": "Не соскраплено ни одного файла.",
1803+ "Scraped ${0} files from ${1} to ${2}.": "Соскраплено ${0} файлов из ${1} в ${2}.",
1804+ "Check browser console for details.": "Подробности см. в консоли браузера.",
1805+ "Scraping failed": "Ошибка скрапинга",
1806+ "External media has been blocked": "Внешние медиа отключены",
1807+ "Use the 'Ext. Media' button to allow it. Click on this message to dismiss.": "Разрешить можно с помощью кнопки 'Внешн. медиа'. Нажмите на это сообщение, чтобы его скрыть.",
1808+ "Couldn't get CSRF token. Please refresh the page.": "Не удалось получить CSRF токен. Попробуйте перезагрузить страницу.",
1809+ "Error": "Ошибка",
1810+ "API Error": "Ошибка API",
1811+ "Please wait until the chat is saved before switching characters.": "Пожалуйста, дождитесь сохранения чата, прежде чем переключать персонажа.",
1812+ "Your chat is still saving...": "Чат всё ещё сохраняется...",
1813+ "Character ${0} not found in the list": "Персонаж ${0} не найден в списке",
1814+ "Streaming is enabled, but the version of Kobold used does not support token streaming.": "Включён стриминг текста, но ваша версия Kobold не поддерживает стриминг токенов.",
1815+ "Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.": "Для устаревшего API стриминг недоступен. Обновите oobaboga и используйте новый API, чтобы включить стриминг.",
1816+ "Verify that the server is running and accessible.": "Убедитесь, что сервер запущен и доступен по сети.",
1817+ "ST Server cannot be reached": "Не удалось соединиться с ST Server",
1818+ "You must first select a character to duplicate!": "Вы не выбрали персонажа, которого хотите клонировать!",
1819+ "Character Duplicated": "Персонаж склонирован",
1820+ "No character name provided.": "Вы не ввели имя персонажа.",
1821+ "Rename Character": "Переименование",
1822+ "No character selected.": "Вы не выбрали персонажа.",
1823+ "New name:": "Новое имя:",
1824+ "Same character name provided, so name did not change.": "Введено то же самое имя, ничего не изменилось.",
1825+ "Character renamed and past chats updated!": "Персонаж переименован, а чаты обновлены!",
1826+ "Character renamed!": "Персонаж переименован!",
1827+ "Something went wrong. The page will be reloaded.": "Что-то пошло не так. Страница будет перезагружена.",
1828+ "Past chat could not be updated: ${0}": "Не удалось обновить чат ${0}",
1829+ "Trying to save group chat with regular saveChat function. Aborting to prevent corruption.": "Произошла попытка сохранения группового чата функцией saveChat. Откатываем изменения, чтобы предотвратить потерю данных.",
1830+ "Check the server connection and reload the page to prevent data loss.": "Проверьте связь с сервером и перезагрузите страницу, чтобы избежать потери данных.",
1831+ "Chat could not be saved": "Не удалось сохранить чат",
1832+ "Settings could not be loaded after multiple attempts. Please try again later.": "Не удалось загрузить настройки за несколько попыток. Попробуйте позднее.",
1833+ "Settings could not be saved": "Не удалось сохранить настройки",
1834+ "Could not load chat data. Try reloading the page.": "Не удалось загрузить чат. Попробуйте обновить страницу.",
1835+ "Invalid process (no 'type')": "Невалидный процесс (нет параметра 'type')",
1836+ "Character Deleted: ${0}": "Персонаж удалён: ${0}",
1837+ "Character Created: ${0}": "Персонаж создан: ${0}",
1838+ "Group Created": "Группа создана",
1839+ "Group Deleted": "Группа удалена",
1840+ "Character Imported: ${0}": "Персонаж импортирован: ${0}",
1841+ "Invalid swipe ID: ${0}": "Некорректный идентификатор свайпа: ${0}",
1842+ "No messages to delete swipes from.": "Сообщение, из которого требуется удалить свайп, не найдено.",
1843+ "Can't delete the last swipe.": "Невозможно удалить единственный свайп.",
1844+ "GUI Settings preset is not supported for Horde. Please select another preset.": "Для Horde не поддерживаются пресеты настроек GUI. Пожалуйста, выберите другой пресет.",
1845+ "Embedded lorebook will be removed from this character.": "Встроенный лорбук будет удалён из персонажа.",
1846+ "Name is required": "Введите имя",
1847+ "Cannot create characters while generating. Stop the request and try again.": "Во время генерации ответа создать персонажа невозможно. Остановите запрос и повторите попытку.",
1848+ "Creation aborted": "Процесс создания прерван",
1849+ "Failed to create character": "Не удалось создать персонажа",
1850+ "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.",
1851+ "Context template '${0}' not found": "Шаблон контекста '${0}' не найден",
1852+ "Instruct template '${0}' not found": "Шаблон Instruct-режима '${0}' не найден",
1853+ "Error: ${0} is not a valid API": "Ошибка: ${0} не является валидным API",
1854+ "API set to ${0}, trying to connect..": "Установлено API ${0}, пробуем подключиться...",
1855+ "Unsupported file type: ": "Неподдерживаемый тип файла: ",
1856+ "Cannot import characters while generating. Stop the request and try again.": "Во время генерации ответа импорт персонажа невозможен. Остановите запрос и повторите попытку.",
1857+ "Import aborted": "Процесс импорта прерван",
1858+ "The file is likely invalid or corrupted.": "Вероятно, файл невалиден или повреждён.",
1859+ "Could not import character": "Не удалось импортировать персонажа",
1860+ "Cannot run /impersonate command while the reply is being generated.": "Во время генерации ответа выполнить /impersonate невозможно.",
1861+ "Name must be provided as an argument to rename this chat.": "Для переименования чата необходимо предоставить новое имя в качестве аргумента.",
1862+ "No chat selected that can be renamed.": "Чат, который требуется переименовать, не найден.",
1863+ "Successfully renamed chat to: ${0}": "Чат успешно переименован в: ${0}",
1864+ "Character ${0} not found. Skipping deletion.": "Персонаж ${0} не найден. Удаление пропускается.",
1865+ "Failed to delete character": "При удалении персонажа произошло ошибка",
1866+ "duplicate_confirm_1": "Вы точно хотите клонировать этого персонажа?",
1867+ "duplicate_confirm_2": "Если вы хотите просто создать новый чат, воспользуйтесь кнопкой \"Начать новый чат\" в меню слева внизу.",
1868+ "delete_confirm_1": "ОТМЕНИТЬ БУДЕТ НЕВОЗМОЖНО!",
1869+ "delete_confirm_2": "Также удалить файлы чатов",
1870+ "Delete the character?": "Удалить персонажа?",
1871+ "Not a valid number": "Некорректное число",
1872+ "Author's Note depth updated": "Глубина заметок автора обновлена",
1873+ "Author's Note frequency updated": "Частота заметок автора обновлена",
1874+ "Not a valid position": "Некорректная позиция",
1875+ "Author's Note position updated": "Позиция заметок автора обновлена",
1876+ "Something went wrong. Could not save character's author's note.": "Что-то пошло не так. Не удалось сохранить заметки автора для этого персонажа.",
1877+ "Select a character before trying to use Author's Note": "Сначала необходимо выбрать персонажа",
1878+ "Author's Note text updated": "Текст заметок автора обновлён",
1879+ "Group Validation": "Валидация группы",
1880+ "Warning: Listed member ${0} does not exist as a character. It will be removed from the group.": "Предупреждение: персонаж ${0} не существует в виде карточки. Он будет удалён из группы.",
1881+ "Group Chat could not be saved": "Не удалось сохранить групповой чат",
1882+ "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
1883+ "Currently no group selected.": "В данный момент не выбрано ни одной группы.",
1884+ "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",
1885+ "Delete the group?": "Удалить группу?",
1886+ "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.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
1887+ "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа"
17511888}
public/script.js+59 -64
@@ -383,8 +383,8 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
383383
384384 if (localStorage.getItem(warningShownKey) === null) {
385385 const warningToast = toastr.warning(
386386 't`Use the "'Ext. Media"' button to allow it. Click on this message to dismiss.'`,
387387 't`External media has been blocked'`,
388388 {
389389 timeOut: 0,
390390 preventDuplicates: true,
@@ -924,7 +924,7 @@ async function firstLoadInit() {
924924 token = tokenData.token;
925925 } catch {
926926 hideLoader();
927927 toastr.error('t`Couldn\'t get CSRF token. Please refresh the page.'`, 't`Error'`, { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
928928 throw new Error('Initialization failed');
929929 }
930930
@@ -1140,7 +1140,7 @@ async function getStatusKobold() {
11401140
11411141 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
11421142 if (online_status === 'no_connection' && data.response) {
11431143 toastr.error(data.response, 't`API Error'`, { timeOut: 5000, preventDuplicates: true });
11441144 }
11451145 } catch (err) {
11461146 console.error('Error getting status', err);
@@ -1226,7 +1226,7 @@ async function getStatusTextgen() {
12261226
12271227 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
12281228 if (online_status === 'no_connection' && data.response) {
12291229 toastr.error(data.response, 't`API Error'`, { timeOut: 5000, preventDuplicates: true });
12301230 }
12311231 } catch (err) {
12321232 if (err instanceof AbortReason) {
@@ -1278,7 +1278,7 @@ export async function selectCharacterById(id) {
12781278 }
12791279
12801280 if (isChatSaving) {
12811281 toastr.info('t`Please wait until the chat is saved before switching characters.'`, 't`Your chat is still saving...'`);
12821282 return;
12831283 }
12841284
@@ -1632,7 +1632,7 @@ export async function getOneCharacter(avatarUrl) {
16321632 if (indexOf !== -1) {
16331633 characters[indexOf] = getData;
16341634 } else {
16351635 toastr.error(t`Character ${avatarUrl} not found in the list`, 't`Error'`, { timeOut: 5000, preventDuplicates: true });
16361636 }
16371637 }
16381638}
@@ -3374,7 +3374,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
33743374 await eventSource.emit(event_types.GENERATION_AFTER_COMMANDS, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
33753375
33763376 if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
33773377 toastr.error('t`Streaming is enabled, but the version of Kobold used does not support token streaming.'`, undefined, { timeOut: 10000, preventDuplicates: true });
33783378 unblockGeneration(type);
33793379 return Promise.resolve();
33803380 }
@@ -3383,7 +3383,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
33833383 textgen_settings.streaming &&
33843384 textgen_settings.legacy_api &&
33853385 textgen_settings.type === OOBA) {
33863386 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 });
33873387 unblockGeneration(type);
33883388 return Promise.resolve();
33893389 }
@@ -3399,7 +3399,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
33993399
34003400 if (!pingResult) {
34013401 unblockGeneration(type);
34023402 toastr.error('t`Verify that the server is running and accessible.'`, 't`ST Server cannot be reached'`);
34033403 throw new Error('Server unreachable');
34043404 }
34053405
@@ -4427,7 +4427,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44274427 generatedPromptCache = '';
44284428
44294429 if (data?.response) {
44304430 toastr.error(data.response, 't`API Error'`, { preventDuplicates: true });
44314431 }
44324432 throw new Error(data?.response);
44334433 }
@@ -4518,7 +4518,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
45184518
45194519 function onError(exception) {
45204520 if (typeof exception?.error?.message === 'string') {
45214521 toastr.error(exception.error.message, 't`Error'`, { timeOut: 10000, extendedTimeOut: 20000 });
45224522 }
45234523
45244524 generatedPromptCache = '';
@@ -4920,7 +4920,7 @@ function addChatsSeparator(mesSendString) {
49204920
49214921async function duplicateCharacter() {
49224922 if (!this_chid) {
49234923 toastr.warning('t`You must first select a character to duplicate!'`);
49244924 return '';
49254925 }
49264926
@@ -4939,7 +4939,7 @@ async function duplicateCharacter() {
49394939 body: JSON.stringify(body),
49404940 });
49414941 if (response.ok) {
49424942 toastr.success('t`Character Duplicated'`);
49434943 const data = await response.json();
49444944 await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
49454945 await getCharacters();
@@ -5786,23 +5786,23 @@ export function setSendButtonState(value) {
57865786
57875787export async function renameCharacter(name = null, { silent = false, renameChats = null } = {}) {
57885788 if (!name && silent) {
57895789 toastr.warning('t`No character name provided.'`, 't`Rename Character'`);
57905790 return false;
57915791 }
57925792 if (this_chid === undefined) {
57935793 toastr.warning('t`No character selected.'`, 't`Rename Character'`);
57945794 return false;
57955795 }
57965796
57975797 const oldAvatar = characters[this_chid].avatar;
57985798 const newValue = name || await callGenericPopup('<h3>' + t`New name:` + '</h3>', POPUP_TYPE.INPUT, characters[this_chid].name);
57995799
58005800 if (!newValue) {
58015801 toastr.warning('t`No character name provided.'`, 't`Rename Character'`);
58025802 return false;
58035803 }
58045804 if (newValue === characters[this_chid].name) {
58055805 toastr.info('t`Same character name provided, so name did not change.'`, 't`Rename Character'`);
58065806 return false;
58075807 }
58085808
@@ -5849,9 +5849,9 @@ export async function renameCharacter(name = null, { silent = false, renameChats
58495849 if (renamePastChatsConfirm) {
58505850 await renamePastChats(newAvatar, newValue);
58515851 await reloadCurrentChat();
58525852 toastr.success('t`Character renamed and past chats updated!'`, 't`Rename Character'`);
58535853 } else {
58545854 toastr.success('t`Character renamed!'`, 't`Rename Character'`);
58555855 }
58565856 }
58575857 else {
@@ -5864,8 +5864,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
58645864 }
58655865 catch (error) {
58665866 // Reloading to prevent data corruption
58675867 if (!silent) await callPopup('t`Something went wrong. The page will be reloaded.'`, 'text');
58685868 else toastr.error('t`Something went wrong. The page will be reloaded.'`, 't`Rename Character'`);
58695869
58705870 console.log('Renaming character error:', error);
58715871 location.reload();
@@ -5922,7 +5922,7 @@ async function renamePastChats(newAvatar, newValue) {
59225922 }
59235923 }
59245924 } catch (error) {
59255925 toastr.error(t`Past chat could not be updated: ${file_name}`);
59265926 console.error(error);
59275927 }
59285928 }
@@ -5971,7 +5971,7 @@ export async function saveChat(chatName, withMetadata, mesId) {
59715971 characters[this_chid]['date_last_chat'] = Date.now();
59725972 chat.forEach(function (item, i) {
59735973 if (item['is_group']) {
59745974 toastr.error('t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.'`);
59755975 throw new Error('Group chat saved from saveChat');
59765976 }
59775977 /*
@@ -6016,7 +6016,7 @@ export async function saveChat(chatName, withMetadata, mesId) {
60166016 contentType: 'application/json',
60176017 success: function (data) { },
60186018 error: function (jqXHR, exception) {
60196019 toastr.error('t`Check the server connection and reload the page to prevent data loss.'`, 't`Chat could not be saved'`);
60206020 console.log(exception);
60216021 console.log(jqXHR);
60226022 },
@@ -6413,7 +6413,7 @@ export async function getSettings() {
64136413
64146414 if (!response.ok) {
64156415 reloadLoop();
64166416 toastr.error('t`Settings could not be loaded after multiple attempts. Please try again later.'`);
64176417 throw new Error('Error getting settings');
64186418 }
64196419
@@ -6640,7 +6640,7 @@ export async function saveSettings(type) {
66406640 eventSource.emit(event_types.SETTINGS_UPDATED);
66416641 },
66426642 error: function (jqXHR, exception) {
66436643 toastr.error('t`Check the server connection and reload the page to prevent data loss.'t`, 't`Settings could not be saved'`);
66446644 console.log(exception);
66456645 console.log(jqXHR);
66466646 },
@@ -6906,7 +6906,7 @@ export async function displayPastChats() {
69066906 const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats());
69076907
69086908 if (!data) {
69096909 toastr.error('t`Could not load chat data. Try reloading the page.'`);
69106910 return;
69116911 }
69126912
@@ -7039,7 +7039,7 @@ export function selectRightMenuWithAnimation(selectedMenuId) {
70397039
70407040export function select_rm_info(type, charId, previousCharId = null) {
70417041 if (!type) {
70427042 toastr.error('t`Invalid process (no \'type\')'`);
70437043 return;
70447044 }
70457045 if (type !== 'group_create') {
@@ -7047,20 +7047,20 @@ export function select_rm_info(type, charId, previousCharId = null) {
70477047 }
70487048
70497049 if (type === 'char_delete') {
70507050 toastr.warning(t`Character Deleted: ${displayName}`);
70517051 }
70527052 if (type === 'char_create') {
70537053 toastr.success(t`Character Created: ${displayName}`);
70547054 }
70557055 if (type === 'group_create') {
70567056 toastr.success('t`Group Created'`);
70577057 }
70587058 if (type === 'group_delete') {
70597059 toastr.warning('t`Group Deleted'`);
70607060 }
70617061
70627062 if (type === 'char_import') {
70637063 toastr.success(t`Character Imported: ${displayName}`);
70647064 }
70657065
70667066 selectRightMenuWithAnimation('rm_characters_block');
@@ -7515,25 +7515,25 @@ export function hideSwipeButtons() {
75157515 */
75167516export async function deleteSwipe(swipeId = null) {
75177517 if (swipeId && (isNaN(swipeId) || swipeId < 0)) {
75187518 toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`);
75197519 return;
75207520 }
75217521
75227522 const lastMessage = chat[chat.length - 1];
75237523 if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) {
75247524 toastr.warning('t`No messages to delete swipes from.'`);
75257525 return;
75267526 }
75277527
75287528 if (lastMessage.swipes.length <= 1) {
75297529 toastr.warning('t`Can\'t delete the last swipe.'`);
75307530 return;
75317531 }
75327532
75337533 swipeId = swipeId ?? lastMessage.swipe_id;
75347534
75357535 if (swipeId < 0 || swipeId >= lastMessage.swipes.length) {
75367536 toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`);
75377537 return;
75387538 }
75397539
@@ -7672,7 +7672,7 @@ export function setGenerationProgress(progress) {
76727672
76737673function isHordeGenerationNotAllowed() {
76747674 if (main_api == 'koboldhorde' && preset_settings == 'gui') {
76757675 toastr.error('t`GUI Settings preset is not supported for Horde. Please select another preset.'`);
76767676 return true;
76777677 }
76787678
@@ -7721,7 +7721,7 @@ function openCharacterWorldPopup() {
77217721 }
77227722
77237723 $('#character_json_data').val(JSON.stringify(data));
77247724 toastr.info('t`Embedded lorebook will be removed from this character.'`);
77257725 } catch {
77267726 console.error('Failed to parse character JSON data.');
77277727 }
@@ -7912,11 +7912,11 @@ async function createOrEditCharacter(e) {
79127912
79137913 if ($('#form_create').attr('actiontype') == 'createcharacter') {
79147914 if (String($('#character_name_pole').val()).length === 0) {
79157915 toastr.error('t`Name is required'`);
79167916 return;
79177917 }
79187918 if (is_group_generating || is_send_press) {
79197919 toastr.error('t`Cannot create characters while generating. Stop the request and try again.'`, 't`Creation aborted'`);
79207920 return;
79217921 }
79227922 try {
@@ -8000,7 +8000,7 @@ async function createOrEditCharacter(e) {
80008000
80018001 } catch (error) {
80028002 console.error('Error creating character', error);
80038003 toastr.error('t`Failed to create character'`);
80048004 }
80058005 } else {
80068006 try {
@@ -8059,7 +8059,7 @@ async function createOrEditCharacter(e) {
80598059 }
80608060 } catch (error) {
80618061 console.log(error);
80628062 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.'`);
80638063 }
80648064 }
80658065}
@@ -8551,7 +8551,7 @@ async function selectContextCallback(args, name) {
85518551 const result = fuse.search(name);
85528552
85538553 if (result.length === 0) {
85548554 !quiet && toastr.warning(t`Context template "'${name}"' not found`);
85558555 return '';
85568556 }
85578557
@@ -8571,7 +8571,7 @@ async function selectInstructCallback(args, name) {
85718571 const result = fuse.search(name);
85728572
85738573 if (result.length === 0) {
85748574 !quiet && toastr.warning(t`Instruct template "'${name}"' not found`);
85758575 return '';
85768576 }
85778577
@@ -8623,7 +8623,7 @@ async function connectAPISlash(args, text) {
86238623
86248624 const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
86258625 if (!apiConfig) {
86268626 toastr.error(t`Error: ${text} is not a valid API`);
86278627 return '';
86288628 }
86298629
@@ -8652,7 +8652,7 @@ async function connectAPISlash(args, text) {
86528652 }
86538653
86548654 const quiet = isTrueBoolean(args?.quiet);
86558655 const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`);
86568656
86578657 try {
86588658 await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100);
@@ -8691,7 +8691,7 @@ export async function processDroppedFiles(files, data = new Map()) {
86918691 const preservedName = data instanceof Map && data.get(file);
86928692 await importCharacter(file, preservedName);
86938693 } else {
86948694 toastr.warning('t`Unsupported file type: '` + file.name);
86958695 }
86968696 }
86978697}
@@ -8704,7 +8704,7 @@ export async function processDroppedFiles(files, data = new Map()) {
87048704 */
87058705async function importCharacter(file, preserveFileName = '') {
87068706 if (is_group_generating || is_send_press) {
87078707 toastr.error('t`Cannot import characters while generating. Stop the request and try again.'`, 't`Import aborted'`);
87088708 throw new Error('Cannot import character while generating');
87098709 }
87108710
@@ -8731,7 +8731,7 @@ async function importCharacter(file, preserveFileName = '') {
87318731 });
87328732
87338733 if (data.error) {
87348734 toastr.error('t`The file is likely invalid or corrupted.'`, 't`Could not import character'`);
87358735 return;
87368736 }
87378737
@@ -8784,7 +8784,7 @@ async function doImpersonate(args, prompt) {
87848784 await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
87858785 } catch {
87868786 console.warn('Timeout waiting for generation unlock');
87878787 toastr.warning('t`Cannot run /impersonate command while the reply is being generated.'`);
87888788 return '';
87898789 }
87908790
@@ -8846,19 +8846,19 @@ async function doDeleteChat() {
88468846
88478847async function doRenameChat(_, chatName) {
88488848 if (!chatName) {
88498849 toastr.warning('t`Name must be provided as an argument to rename this chat.'`);
88508850 return '';
88518851 }
88528852
88538853 const currentChatName = getCurrentChatId();
88548854 if (!currentChatName) {
88558855 toastr.warning('t`No chat selected that can be renamed.'`);
88568856 return '';
88578857 }
88588858
88598859 await renameChat(currentChatName, chatName);
88608860
88618861 toastr.success(t`Successfully renamed chat to: ${chatName}`);
88628862 return '';
88638863}
88648864
@@ -8973,7 +8973,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
89738973 for (const key of characterKey) {
89748974 const character = characters.find(x => x.avatar == key);
89758975 if (!character) {
89768976 toastr.warning(t`Character ${key} not found. Skipping deletion.`);
89778977 continue;
89788978 }
89798979
@@ -8990,7 +8990,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
89908990 });
89918991
89928992 if (!response.ok) {
89938993 toastr.error(`${response.status} ${response.statusText}`, 't`Failed to delete character'`);
89948994 continue;
89958995 }
89968996
@@ -9665,12 +9665,7 @@ jQuery(async function () {
96659665
96669666 let deleteChats = false;
96679667
96689668 const confirm = await Popup.show.confirm('t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), `{
9669- <b>THIS IS PERMANENT!<br><br>
9670- <label for="del_char_checkbox" class="checkbox_label justifyCenter">
9671- <input type="checkbox" id="del_char_checkbox" />
9672- <small>Also delete the chat files</small>
9673- </label></b>`, {
96749669 onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),
96759670 });
96769671 if (!confirm) {
public/scripts/PromptManager.js+7 -6
@@ -8,6 +8,7 @@ import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
88import { debounce_timeout } from './constants.js';
99import { renderTemplateAsync } from './templates.js';
1010import { Popup } from './popup.js';
11+import { t } from './i18n.js';
1112
1213function debouncePromise(func, delay) {
1314 let timeoutId;
@@ -455,7 +456,7 @@ class PromptManager {
455456
456457 // Delete selected prompt from list form and close edit form
457458 this.handleDeletePrompt = async (event) => {
458459 Popup.show.confirm('t`Are you sure you want to delete this prompt?'`, null).then((userChoice) => {
459460 if (!userChoice) return;
460461 const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
461462 const prompt = this.getPromptById(promptID);
@@ -531,7 +532,7 @@ class PromptManager {
531532
532533 // Import prompts for the selected character
533534 this.handleImport = () => {
534535 Popup.show.confirm('t`Existing prompts with the same ID will be overridden. Do you want to proceed?'`, null)
535536 .then(userChoice => {
536537 if (!userChoice) return;
537538
@@ -552,7 +553,7 @@ class PromptManager {
552553 const data = JSON.parse(fileContent);
553554 this.import(data);
554555 } catch (err) {
555556 toastr.error('t`An error occurred while importing prompts. More info available in console.'`);
556557 console.log('An error occurred while importing prompts');
557558 console.log(err.toString());
558559 }
@@ -567,7 +568,7 @@ class PromptManager {
567568
568569 // Restore default state of a characters prompt order
569570 this.handleCharacterReset = () => {
570571 Popup.show.confirm('t`This will reset the prompt order for this character. You will not lose any prompts.'`, null)
571572 .then(userChoice => {
572573 if (!userChoice) return;
573574
@@ -1649,7 +1650,7 @@ class PromptManager {
16491650 };
16501651
16511652 if (false === this.validateObject(controlObj, importData)) {
16521653 toastr.warning('t`Could not import prompts. Export failed validation.'`);
16531654 return;
16541655 }
16551656
@@ -1672,7 +1673,7 @@ class PromptManager {
16721673 throw new Error('Prompt order strategy not supported.');
16731674 }
16741675
16751676 toastr.success('t`Prompt import complete.'`);
16761677 this.saveServiceSettings().then(() => this.render());
16771678 }
16781679
public/scripts/authors-note.js+10 -9
@@ -16,6 +16,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1616import { SlashCommand } from './slash-commands/SlashCommand.js';
1717import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
1818export { MODULE_NAME as NOTE_MODULE_NAME };
19+import { t } from './i18n.js';
1920
2021const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
2122
@@ -37,7 +38,7 @@ const chara_note_position = {
3738
3839function setNoteTextCommand(_, text) {
3940 $('#extension_floating_prompt').val(text).trigger('input');
4041 toastr.success('t`Author\'s Note text updated'`);
4142 return '';
4243}
4344
@@ -45,12 +46,12 @@ function setNoteDepthCommand(_, text) {
4546 const value = Number(text);
4647
4748 if (Number.isNaN(value)) {
4849 toastr.error('t`Not a valid number'`);
4950 return;
5051 }
5152
5253 $('#extension_floating_depth').val(Math.abs(value)).trigger('input');
5354 toastr.success('t`Author\'s Note depth updated'`);
5455 return '';
5556}
5657
@@ -58,12 +59,12 @@ function setNoteIntervalCommand(_, text) {
5859 const value = Number(text);
5960
6061 if (Number.isNaN(value)) {
6162 toastr.error('t`Not a valid number'`);
6263 return;
6364 }
6465
6566 $('#extension_floating_interval').val(Math.abs(value)).trigger('input');
6667 toastr.success('t`Author\'s Note frequency updated'`);
6768 return '';
6869}
6970
@@ -76,12 +77,12 @@ function setNotePositionCommand(_, text) {
7677 const position = validPositions[text?.trim()];
7778
7879 if (Number.isNaN(position)) {
7980 toastr.error('t`Not a valid position'`);
8081 return;
8182 }
8283
8384 $(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
8485 toastr.info('t`Author\'s Note position updated'`);
8586 return '';
8687}
8788
@@ -206,7 +207,7 @@ function onExtensionFloatingCharaPromptInput() {
206207 extension_settings.note.chara.push(tempCharaNote);
207208 } else {
208209 console.log('Character author\'s note error: No avatar name key could be found.');
209210 toastr.error('t`Something went wrong. Could not save character\'s author\'s note.'`);
210211
211212 // Don't save settings if something went wrong
212213 return;
@@ -397,7 +398,7 @@ function onANMenuItemClick() {
397398 //because this listener takes priority
398399 $('#options').stop().fadeOut(animation_duration);
399400 } else {
400401 toastr.warning('t`Select a character before trying to use Author\'s Note'`, '', { timeOut: 2000 });
401402 }
402403}
403404
public/scripts/chats.js+13 -12
@@ -40,6 +40,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
4040import { ScraperManager } from './scrapers.js';
4141import { DragAndDropHandler } from './dragdrop.js';
4242import { renderTemplateAsync } from './templates.js';
43+import { t } from './i18n.js';
4344
4445/**
4546 * @typedef {Object} FileAttachment
@@ -206,7 +207,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
206207 const fileText = await converter(file);
207208 base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
208209 } catch (error) {
209210 toastr.error(String(error), 't`Could not convert file'`);
210211 console.error('Could not convert file', error);
211212 }
212213 }
@@ -257,7 +258,7 @@ export async function uploadFileAttachment(fileName, base64Data) {
257258 const responseData = await result.json();
258259 return responseData.path;
259260 } catch (error) {
260261 toastr.error(String(error), 't`Could not upload file'`);
261262 console.error('Could not upload file', error);
262263 }
263264}
@@ -283,7 +284,7 @@ export async function getFileAttachment(url) {
283284 const text = await result.text();
284285 return text;
285286 } catch (error) {
286287 toastr.error(error, 't`Could not download file'`);
287288 console.error('Could not download file', error);
288289 }
289290}
@@ -299,13 +300,13 @@ async function validateFile(file) {
299300 const isBinary = /^[\x00-\x08\x0E-\x1F\x7F-\xFF]*$/.test(fileText);
300301
301302 if (!isImage && file.size > fileSizeLimit) {
302303 toastr.error(t`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`);
303304 return false;
304305 }
305306
306307 // If file is binary
307308 if (isBinary && !isImage && !isConvertible(file.type)) {
308309 toastr.error('t`Binary files are not supported. Select a text file or image.'`);
309310 return false;
310311 }
311312
@@ -521,7 +522,7 @@ async function openExternalMediaOverridesDialog() {
521522 const entityId = getCurrentEntityId();
522523
523524 if (!entityId) {
524525 toastr.info('t`No character or group selected'`);
525526 return;
526527 }
527528
@@ -646,7 +647,7 @@ async function deleteFileFromServer(url, silent = false) {
646647 await eventSource.emit(event_types.FILE_ATTACHMENT_DELETED, url);
647648 return true;
648649 } catch (error) {
649650 toastr.error(String(error), 't`Could not delete file'`);
650651 console.error('Could not delete file', error);
651652 return false;
652653 }
@@ -1054,7 +1055,7 @@ async function openAttachmentManager() {
10541055 const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
10551056
10561057 if (selectedAttachments.length === 0) {
10571058 toastr.info('t`No attachments selected.'`, 't`Data Bank'`);
10581059 return;
10591060 }
10601061
@@ -1168,7 +1169,7 @@ async function runScraper(scraperId, target, callback) {
11681169
11691170 if (files.length === 0) {
11701171 console.warn('Scraping returned no files');
11711172 toastr.info('t`No files were scraped.'`, 't`Data Bank'`);
11721173 return;
11731174 }
11741175
@@ -1176,12 +1177,12 @@ async function runScraper(scraperId, target, callback) {
11761177 await uploadFileAttachmentToServer(file, target);
11771178 }
11781179
11791180 toastr.success(t`Scraped ${files.length} files from ${scraperId} to ${target}.`, 't`Data Bank'`);
11801181 callback();
11811182 }
11821183 catch (error) {
11831184 console.error('Scraping failed', error);
11841185 toastr.error('t`Check browser console for details.'`, 't`Scraping failed'`);
11851186 }
11861187}
11871188
@@ -1208,7 +1209,7 @@ export async function uploadFileAttachmentToServer(file, target) {
12081209 const fileText = await converter(file);
12091210 base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
12101211 } catch (error) {
12111212 toastr.error(String(error), 't`Could not convert file'`);
12121213 console.error('Could not convert file', error);
12131214 }
12141215 } else {
public/scripts/extensions/regex/editor.html+1 -1
@@ -132,7 +132,7 @@
132132 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
133133 <span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
134134 </span>
135135 <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.">
136136 <input type="checkbox" name="only_format_display" />
137137 <span data-i18n="Only Format Display">Alter Chat Display</span>
138138 </label>
public/scripts/group-chats.js+10 -10
@@ -189,8 +189,8 @@ async function validateGroup(group) {
189189 group.members = group.members.filter(member => {
190190 const character = characters.find(x => x.avatar === member || x.name === member);
191191 if (!character) {
192192 const msg = t`Warning: Listed member ${member} does not exist as a character. It will be removed from the group.`;
193193 toastr.warning(msg, 't`Group Validation'`);
194194 console.warn(msg);
195195 dirty = true;
196196 }
@@ -522,7 +522,7 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
522522 });
523523
524524 if (!response.ok) {
525525 toastr.error('t`Check the server connection and reload the page to prevent data loss.'`, 't`Group Chat could not be saved'`);
526526 console.error('Group chat could not be saved', response);
527527 return;
528528 }
@@ -837,7 +837,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
837837 activatedMembers = activateSwipe(group.members);
838838
839839 if (activatedMembers.length === 0) {
840840 toastr.warning('t`Deleted group member swiped. To get a reply, add them back to the group.'`);
841841 throw new Error('Deleted group member swiped');
842842 }
843843 }
@@ -1368,15 +1368,15 @@ function isGroupMemberDisabled(avatarId) {
13681368
13691369async function onDeleteGroupClick() {
13701370 if (!openGroupId) {
13711371 toastr.warning('t`Currently no group selected.'`);
13721372 return;
13731373 }
13741374 if (is_group_generating) {
13751375 toastr.warning('t`Not so fast! Wait for the characters to stop typing before deleting the group.'`);
13761376 return;
13771377 }
13781378
13791379 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>');
13801380 if (confirm) {
13811381 deleteGroup(openGroupId);
13821382 }
@@ -1630,7 +1630,7 @@ function updateFavButtonState(state) {
16301630
16311631export async function openGroupById(groupId) {
16321632 if (isChatSaving) {
16331633 toastr.info('t`Please wait until the chat is saved before switching characters.'`, 't`Your chat is still saving...'`);
16341634 return;
16351635 }
16361636
@@ -1659,7 +1659,7 @@ export async function openGroupById(groupId) {
16591659
16601660function openCharacterDefinition(characterSelect) {
16611661 if (is_group_generating) {
16621662 toastr.warning('t`Can\'t peek a character while group reply is being generated'`);
16631663 console.warn('Can\'t peek a character def while group reply is being generated');
16641664 return;
16651665 }
@@ -1908,7 +1908,7 @@ export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
19081908 });
19091909
19101910 if (!response.ok) {
19111911 toastr.error('t`Check the server connection and reload the page to prevent data loss.'`, 't`Group chat could not be saved'`);
19121912 console.error('Group chat could not be saved', response);
19131913 }
19141914}
public/scripts/openai.js+25 -28
@@ -388,7 +388,7 @@ async function validateReverseProxy() {
388388 new URL(oai_settings.reverse_proxy);
389389 }
390390 catch (err) {
391391 toastr.error('t`Entered reverse proxy address is not a valid URL'`);
392392 setOnlineStatus('no_connection');
393393 resultCheckStatus();
394394 throw err;
@@ -399,7 +399,7 @@ async function validateReverseProxy() {
399399 const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) }));
400400
401401 if (!confirmation) {
402402 toastr.error('t`Update or remove your reverse proxy settings.'`);
403403 setOnlineStatus('no_connection');
404404 resultCheckStatus();
405405 throw new Error('Proxy connection denied.');
@@ -1225,15 +1225,15 @@ export async function prepareOpenAIMessages({
12251225 await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
12261226 } catch (error) {
12271227 if (error instanceof TokenBudgetExceededError) {
12281228 toastr.error('t`An error occurred while counting tokens: Token budget exceeded.'`);
12291229 chatCompletion.log('Token budget exceeded.');
12301230 promptManager.error = 't`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.'`;
12311231 } else if (error instanceof InvalidCharacterNameError) {
12321232 toastr.warning('t`An error occurred while counting tokens: Invalid character name'`);
12331233 chatCompletion.log('Invalid character name');
12341234 promptManager.error = 't`The name of at least one character contained whitespaces or special characters. Please check your user and character name.'`;
12351235 } else {
12361236 toastr.error('t`An unknown error occurred while counting tokens. Further information may be available in console.'`);
12371237 chatCompletion.log('----- Unexpected error while preparing prompts -----');
12381238 chatCompletion.log(error);
12391239 chatCompletion.log(error.stack);
@@ -1287,11 +1287,8 @@ function tryParseStreamingError(response, decoded) {
12871287 }
12881288}
12891289
12901290async function checkQuotaError(data) {
1291- const errorText = `<h3>Encountered an error while processing your request.<br>
1291+ const errorText = await renderTemplateAsync('quotaError');
1292- Check you have credits available on your
1293- <a href="https://platform.openai.com/account/usage" target="_blank">OpenAI account</a>.<br>
1294- If you have sufficient credits, please try again later.</h3>`;
12951292
12961293 if (!data) {
12971294 return;
@@ -1927,11 +1924,11 @@ async function sendOpenAIRequest(type, messages, signal) {
19271924 else {
19281925 const data = await response.json();
19291926
19301927 await checkQuotaError(data);
19311928 checkModerationError(data);
19321929
19331930 if (data.error) {
19341931 toastr.error(data.error.message || response.statusText, 't`API returned an error'`);
19351932 throw new Error(data);
19361933 }
19371934
@@ -2178,7 +2175,7 @@ function parseOpenAITextLogprobs(logprobs) {
21782175
21792176function handleWindowError(err) {
21802177 const text = parseWindowError(err);
21812178 toastr.error(text, 't`Window.ai returned an error'`);
21822179 throw err;
21832180}
21842181
@@ -3251,7 +3248,7 @@ async function getStatusOpen() {
32513248}
32523249
32533250function showWindowExtensionError() {
32543251 toastr.error('t`Get it here:` + ' <a href="https://windowai.io/" target="_blank">windowai.io</a>', 't`Extension is not installed'`, {
32553252 escapeHtml: false,
32563253 timeOut: 0,
32573254 extendedTimeOut: 0,
@@ -3370,7 +3367,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
33703367 if (triggerUi) $('#settings_preset_openai').append(option).trigger('change');
33713368 }
33723369 } else {
33733370 toastr.error('t`Failed to save preset'`);
33743371 throw new Error('Failed to save preset');
33753372 }
33763373}
@@ -3449,7 +3446,7 @@ async function createNewLogitBiasPreset() {
34493446 }
34503447
34513448 if (name in oai_settings.bias_presets) {
34523449 toastr.error('t`Preset name should be unique.'`);
34533450 return;
34543451 }
34553452
@@ -3493,7 +3490,7 @@ async function onPresetImportFileChange(e) {
34933490 try {
34943491 presetBody = JSON.parse(importedFile);
34953492 } catch (err) {
34963493 toastr.error('t`Invalid file'`);
34973494 return;
34983495 }
34993496
@@ -3534,7 +3531,7 @@ async function onPresetImportFileChange(e) {
35343531 });
35353532
35363533 if (!savePresetSettings.ok) {
35373534 toastr.error('t`Failed to save preset'`);
35383535 return;
35393536 }
35403537
@@ -3559,7 +3556,7 @@ async function onPresetImportFileChange(e) {
35593556
35603557async function onExportPresetClick() {
35613558 if (!oai_settings.preset_settings_openai) {
35623559 toastr.error('t`No preset selected'`);
35633560 return;
35643561 }
35653562
@@ -3600,12 +3597,12 @@ async function onLogitBiasPresetImportFileChange(e) {
36003597 e.target.value = '';
36013598
36023599 if (name in oai_settings.bias_presets) {
36033600 toastr.error('t`Preset name should be unique.'`);
36043601 return;
36053602 }
36063603
36073604 if (!Array.isArray(importedFile)) {
36083605 toastr.error('t`Invalid logit bias preset file.'`);
36093606 return;
36103607 }
36113608
@@ -3664,16 +3661,16 @@ async function onDeletePresetClick() {
36643661 });
36653662
36663663 if (!response.ok) {
36673664 toastr.warning('t`Preset was not deleted from server'`);
36683665 } else {
36693666 toastr.success('t`Preset deleted'`);
36703667 }
36713668
36723669 saveSettingsDebounced();
36733670}
36743671
36753672async function onLogitBiasPresetDeleteClick() {
36763673 const value = await callPopup('t`Delete the preset?'`, 'confirm');
36773674
36783675 if (!value) {
36793676 return;
@@ -4808,7 +4805,7 @@ function runProxyCallback(_, value) {
48084805 const result = fuse.search(value);
48094806
48104807 if (result.length === 0) {
48114808 toastr.warning(t`Proxy preset "'${value}"' not found`);
48124809 return '';
48134810 }
48144811
@@ -4982,7 +4979,7 @@ export function initOpenAI() {
49824979 $('#update_oai_preset').on('click', async function () {
49834980 const name = oai_settings.preset_settings_openai;
49844981 await saveOpenAIPreset(name, oai_settings);
49854982 toastr.success('t`Preset updated'`);
49864983 });
49874984
49884985 $('#impersonation_prompt_restore').on('click', function () {
public/scripts/templates/assistantNote.html+1 -1
@@ -1,3 +1,3 @@
11<div>
22 <b data-i18n="assistant_note_1">Note:</b> <span data-i18n="assistant_note_2">this chat is temporary and will be deleted as soon as you leave it.</span>
33</div>
public/scripts/templates/deleteConfirm.html+5 -0
@@ -0,0 +1,5 @@
1+<b><span data-i18n="delete_confirm_1">THIS IS PERMANENT!</span><br><br>
2+<label for="del_char_checkbox" class="checkbox_label justifyCenter">
3+ <input type="checkbox" id="del_char_checkbox" />
4+ <small data-i18n="delete_confirm_2">Also delete the chat files</small>
5+</label></b>
5 \ No newline at end of file
public/scripts/templates/duplicateConfirm.html+2 -2
@@ -1,5 +1,5 @@
11<div>
22 <h3 data-i18n="duplicate_confirm_1">Are you sure you want to duplicate this character?</h3>
33 <span data-i18n="duplicate_confirm_2">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>
44 <br>
55</div>
public/scripts/templates/hotkeys.html+7 -7
@@ -14,18 +14,18 @@
1414 <li><kbd data-i18n="help_hotkeys_19">Ctrl+Shift+Down</kbd> = <span data-i18n="help_hotkeys_20">Scroll chat to bottom</span></li>
1515</ul>
1616<div>
1717 <strong data-i18n="help_hotkeys_20">Markdown Hotkeys</strong>
1818</div>
1919<div>
2020 <small>
2121 <span data-i18n="help_hotkeys_21">Works in the chatbar and textareas marked with this icon:</span>
2222 <code><i class="fa-brands fa-markdown"></i></code>
2323 </small>
2424</div>
2525<ul>
2626 <li><kbd>Ctrl+B</kbd> = <span data-i18n="help_hotkeys_22">**bold**</span></li>
2727 <li><kbd>Ctrl+I</kbd> = <span data-i18n="help_hotkeys_23">*italic*</span></li>
2828 <li><kbd>Ctrl+U</kbd> = <span data-i18n="help_hotkeys_24">__underline__</span></li>
2929 <li><kbd>Ctrl+K</kbd> = <span data-i18n="help_hotkeys_25">`inline code`</span></li>
3030 <li><kbd>Ctrl+Shift+~</kbd> = <span data-i18n="help_hotkeys_26">~~strikethrough~~</span></li>
3131</ul>
public/scripts/templates/quotaError.html+4 -0
@@ -0,0 +1,4 @@
1+<h3><span data-i18n="quota_error_1">Encountered an error while processing your request.</span><br>
2+ <span data-i18n="quota_error_2">Check you have credits available on your</span>
3+ <a href="https://platform.openai.com/account/usage" target="_blank" data-i18n="quota_error_3">OpenAI account</a><span data-i18n="quota_error_4">.</span><br>
4+ <span data-i18n="quota_error_5">If you have sufficient credits, please try again later.</span></h3>
4 \ No newline at end of file