Merge pull request #3930 from Yokayo/staging Update ru-ru translation

bb9fe64652e78a8d6768264ccbe80334c184e533

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
17 files changed, +257 -94Ignore whitespace
public/css/rm-groups.css+2 -2
@@ -87,7 +87,7 @@
8787}
8888
8989#rm_group_members:empty::before {
90- content: 'Group is empty';
90+ content: attr(group_empty_text);
9191
9292 font-weight: bolder;
9393 width: 100%;
@@ -115,7 +115,7 @@
115115}
116116
117117#rm_group_add_members:empty::before {
118- content: 'No characters available';
118+ content: attr(no_characters_text);
119119
120120 font-weight: bolder;
121121 width: 100%;
public/index.html+9 -9
@@ -2052,8 +2052,8 @@
20522052 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
20532053 <label for="openai_reasoning_effort">
20542054 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
20552055 <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i>
20562056 <i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i>
20572057 </label>
20582058 <select id="openai_reasoning_effort">
20592059 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>
@@ -2498,8 +2498,8 @@
24982498 <option value="search" data-i18n="Search" hidden>A-Z</option>
24992499 <option value="asc">A-Z</option>
25002500 <option value="desc">Z-A</option>
25012501 <option data-i18n="Date Asc" value="date_asc">Date Asc</option>
25022502 <option data-i18n="Date Desc" value="date_desc">Date Desc</option>
25032503 </select>
25042504 <select id="featherless_category_selection" class="text_pole">
25052505 <option value="" disabled selected data-i18n="category">category</option>
@@ -2509,7 +2509,7 @@
25092509 <option value="All" data-i18n="All">All</option>
25102510 </select>
25112511 <select id="featherless_class_selection" class="text_pole">
25122512 <option value="" selected data-i18n="classAll Classes">All Classes</option>
25132513 </select>
25142514 <div id="featherless_model_pagination_container" class="flex1"></div>
25152515 <i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i>
@@ -5531,7 +5531,7 @@
55315531 <div class="inline-drawer-content">
55325532 <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
55335533 <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
55345534 <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
55355535 </div>
55365536 </div>
55375537 </div>
@@ -5549,7 +5549,7 @@
55495549 <div class="tags rm_tag_filter"></div>
55505550 </div>
55515551 <div id="rm_group_add_members_pagination" class="group_pagination"></div>
55525552 <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
55535553 </div>
55545554 </div>
55555555 </div>
@@ -5969,7 +5969,7 @@
59695969 <div class="tag_view_color_picker" data-value="color"></div>
59705970 <div class="tag_view_color_picker" data-value="color2"></div>
59715971 <div class="tag_view_name" contenteditable="true"></div>
59725972 <div class="tag_view_counter"><span class="tag_view_counter_value"></span>&nbsp;<span data-i18n="tag_entries">entries</span></div>
59735973 <div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div>
59745974 </div>
59755975 </div>
@@ -6704,7 +6704,7 @@
67046704 </div>
67056705 <div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter">
67066706 <div class="wide100p character_name_block">
67076707 <span class="ch_name" data-i18n="Go back">Go back</span>
67086708 </div>
67096709 </div>
67106710 </div>
public/locales/ru-ru.json+105 -22
@@ -52,7 +52,7 @@
5252 "Presence Penalty": "Штраф за присутствие",
5353 "Top A": "Top А",
5454 "Tail Free Sampling": "Tail Free Sampling",
5555 "Rep. Pen. Slope": "Rep.Рост Pen.штрафа Slopeза повтор к концу промпта",
5656 "Top K": "Top K",
5757 "Top P": "Top P",
5858 "Do Sample": "Включить сэмплинг",
@@ -162,9 +162,9 @@
162162 "Story String": "Строка истории",
163163 "Example Separator": "Разделитель примеров сообщений",
164164 "Chat Start": "Начало чата",
165165 "Activation Regex": "RegexРег. выражение для активации",
166166 "Instruct Mode": "Режим Instruct",
167167 "Wrap Sequences with Newline": "ОтделятьКаждая строкистрока символомиз шаблона на новой строкистроке",
168168 "Include Names": "Добавлять имена",
169169 "Force for Groups and Personas": "Также для групп и персон",
170170 "System Prompt": "Системный промпт",
@@ -299,7 +299,7 @@
299299 "AI Horde": "AI Horde",
300300 "NovelAI": "NovelAI",
301301 "OpenAI API key": "Ключ для API OpenAI",
302302 "Trim spaces": "Обрезать пробелы в начале и конце",
303303 "Trim Incomplete Sentences": "Удалять неоконченные предложения",
304304 "Include Newline": "Добавлять новую строку",
305305 "Non-markdown strings": "Строки без разметки",
@@ -510,7 +510,7 @@
510510 "New preset": "Новый пресет",
511511 "Delete preset": "Удалить пресет",
512512 "API Connections": "Соединения с API",
513513 "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохимипри ответамиплохих ставяответах, вделая очередьзапросы только подтвержденныхк работниковдоверенным рабочим машинам. Может замедлить время ответа.",
514514 "Clear your API key": "Стереть ключ от API",
515515 "Refresh models": "Обновить модели",
516516 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
@@ -551,7 +551,7 @@
551551 "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным, используйте как ориентир",
552552 "Click to select a new avatar for this character": "Нажмите чтобы выбрать новый аватар для этого персонажа",
553553 "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Пример:\n [{{user}} is a 28-year-old Romanian cat girl.]",
554554 "Toggle grid view": "ПереключитьСменить вид сетки",
555555 "Add to Favorites": "Добавить в Избранное",
556556 "Advanced Definition": "Расширенное описание",
557557 "Character Lore": "Лор персонажа",
@@ -624,7 +624,7 @@
624624 "UI Theme": "Тема UI",
625625 "This message is invisible for the AI": "Это сообщение невидимо для ИИ",
626626 "Sampler Priority": "Приоритет сэмплеров",
627627 "Ooba only. Determines the order of samplers.": "Только для oobabooga. Определяет порядок сэмплеров.",
628628 "Load default order": "Загрузить стандартный порядок",
629629 "Max Tokens Second": "Макс. кол-во токенов в секунду",
630630 "CFG": "CFG",
@@ -695,7 +695,7 @@
695695 "Medium": "Средний",
696696 "Aggressive": "Агрессивный",
697697 "Very aggressive": "Очень агрессивный",
698698 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В\nВ единицах 1e-4; разумное значение - 3.&#13;Установите\nУстановите в 0, чтобы отключить.&#13;См\nСм. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
699699 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
700700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
701701 "Load koboldcpp order": "Загрузить порядок из koboldcpp",
@@ -964,7 +964,7 @@
964964 "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)",
965965 "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)",
966966 "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",
967967 "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, заглянитенаходится в",
968968 "char_import_7": ")",
969969 "Grammar String": "Грамматика",
970970 "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
@@ -1016,7 +1016,7 @@
10161016 "prompt_manager_relative": "Относительная",
10171017 "prompt_manager_depth": "Глубина",
10181018 "Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.",
10191019 "The prompt to be sent.": "Отправляемый ИИТекст промптпромпта.",
10201020 "prompt_manager_forbid_overrides": "Запретить перезапись",
10211021 "This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.",
10221022 "image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие",
@@ -1232,7 +1232,7 @@
12321232 "Top P & Min P": "Top P & Min P",
12331233 "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.",
12341234 "Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.",
12351235 "character_names_default": "ExceptДобавлять forпрефиксы groupsдля andгрупповых pastчатов personasи предыдущих персон. Otherwise,В makeостальных sureслучаях youуказывайте provideимена namesв inпромпте theиными promptспособами.",
12361236 "Completion": "Completion Object",
12371237 "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.",
12381238 "Use AI21 Tokenizer": "Использовать токенайзер AI21",
@@ -1277,7 +1277,7 @@
12771277 "Will be inserted as a last prompt line when using system/neutral generation.": "Will be inserted as a last prompt line when using system/neutral generation.",
12781278 "If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "Если ИИ генерирует стоп-строку, то всё после неё будет вырезано из ответа (включая и саму стоп-строку).",
12791279 "Will be inserted at the start of the chat history if it doesn't start with a User message.": "Вставляется в начале истории чата, если она начинается не с сообщения пользователя.",
12801280 "Global World Info/Lorebook activation settings": "НастройкиГлобальные активациинастройки глобальногоактивации лорбука / Информации о мире",
12811281 "Click to expand": "Щёлкните, чтобы развернуть",
12821282 "Insertion Strategy": "Как инжектить",
12831283 "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "Only the entries with the most number of key matches will be selected for Inclusion Group filtering",
@@ -1646,8 +1646,8 @@
16461646 "mui_reset": "Сброс",
16471647 "Quick 'Impersonate' button": "Быстрое перевоплощение",
16481648 "Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.",
16491649 "Separators as Stop Strings": "Разделители какв качестве стоп-строкистрок",
16501650 "Names as Stop Strings": "Имена какв качестве стоп-строкистрок",
16511651 "Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.",
16521652 "context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.",
16531653 "First User Prefix": "Первый префикс пользователя",
@@ -1914,8 +1914,8 @@
19141914 "Cannot restore GUI preset": "Пресет для Gui восстановить нельзя",
19151915 "Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию",
19161916 "Default template cannot be restored": "Невозможно восстановить шаблон по умолчанию",
19171917 "Resetting a <b>default preset</b> will restore the default settings.": "Сброс <b>стандартногокомплектного пресета</b> восстановит настройки по умолчанию.",
19181918 "Resetting a <b>default template</b> will restore the default settings.": "Сброс <b>стандартногокомплектного шаблона</b> восстановит настройки по умолчанию.",
19191919 "Are you sure?": "Вы уверены?",
19201920 "Default preset restored": "Стандартный пресет восстановлен",
19211921 "Default template restored": "Стандартный шаблон восстановлен",
@@ -2046,11 +2046,11 @@
20462046 "prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью",
20472047 "prompt_post_processing_semi": "Semi-strict (чередовать роли)",
20482048 "prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)",
20492049 "Select Horde models": "ВыбратьВыберите модель из Horde",
20502050 "Model ID (optional)": "Идентификатор модели (необязательно)",
20512051 "Derive context size from backend": "Использовать бэкенд для определения размера контекста",
20522052 "Rename current preset": "Переименовать пресет",
20532053 "No Worlds active. Click here to select.": "Нет активныхАктивных миров. Нажмитенет, чтобыЛКМ выбратьдля выбора.",
20542054 "Title/Memo": "Название",
20552055 "Strategy": "Статус",
20562056 "Position": "Позиция",
@@ -2169,7 +2169,7 @@
21692169 "instruct_derived": "Считывать из метаданных модели (по возможности)",
21702170 "Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте",
21712171 "Reasoning Effort": "Рассуждения",
21722172 "Constrains effort on reasoning for reasoning models.": "Регулирует объём внутренних рассуждений модели (reasoning), для моделей, которые поддерживают эту возможность.\nНа данный момент поддерживаются три значения: Подробные, Обычные, Поверхностные.\nПри менее подробном рассуждении ответ получается быстрее, а также экономятся токены, уходящие на рассуждения.",
21732173 "openai_reasoning_effort_low": "Поверхностные",
21742174 "openai_reasoning_effort_medium": "Обычные",
21752175 "openai_reasoning_effort_high": "Подробные",
@@ -2274,8 +2274,8 @@
22742274 "Persona Name Not Set": "У персоны отсутствует имя",
22752275 "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
22762276 "Default Persona Removed": "Персона по умолчанию снята",
22772277 "Persona is locked to the current character": "Персона закреплена за этимтекущим персонажем",
22782278 "Persona is locked to the current chat": "Персона закреплена за этимтекущим чатом",
22792279 "characters": "перс.",
22802280 "character": "персонаж",
22812281 "in this group": "в группе",
@@ -2336,5 +2336,88 @@
23362336 "Reasoning already exists.": "Рассуждения уже присутствуют.",
23372337 "Edit Message": "Редактирование",
23382338 "Status check bypassed": "Проверка статуса отключена",
23392339 "Valid": "Работает",
2340+ "Use Group Scoring": "Использовать Group Scoring",
2341+ "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "До групповых фильтров будут допущены только записи с наибольшим кол-вом совпадений",
2342+ "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)",
2343+ "( None )": "(Отсутствует)",
2344+ "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами",
2345+ "Move Entry to Another Lorebook": "Переместить запись в другой лорбук",
2346+ "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.",
2347+ "Select Target Lorebook": "Выберите куда переместить",
2348+ "Move '${0}' to:": "Переместить '${0}' в:",
2349+ "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.",
2350+ "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной",
2351+ "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}",
2352+ "Select your current Reasoning Template": "Выберите текущий Шаблон рассуждений",
2353+ "Delete template": "Удалить шаблон",
2354+ "Reasoning Template": "Шаблон рассуждений",
2355+ "openai_reasoning_effort_auto": "Авто",
2356+ "openai_reasoning_effort_minimum": "Минимальные",
2357+ "openai_reasoning_effort_maximum": "Максимальные",
2358+ "OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.": "OpenAI принимает следующее: low (Поверхностные), medium (Обычные), high (Подробные). Minimum (Минимальные) - то же самое, что low. Maximum (Максимальные) - то же самое, что high. При выборе Auto (Авто) значение не отсылается вообще.",
2359+ "Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "Резервирует часть ответа для рассуждений (Поверхностные: 10% ответа, Обычные: 25%, Подробные: 50%). Остальные значения зависят от конкретной модели.",
2360+ "xAI Model": "Модель xAI",
2361+ "xAI API Key": "Ключ от API xAI",
2362+ "HuggingFace Token": "Токен HuggingFace",
2363+ "Endpoint URL": "Адрес эндпоинта",
2364+ "Example: https://****.endpoints.huggingface.cloud": "Пример: https://****.endpoints.huggingface.cloud",
2365+ "Featherless Model Selection": "Выбор модели из Featherless",
2366+ "category": "категория",
2367+ "Top": "Топовые",
2368+ "All Classes": "Все классы",
2369+ "Date Asc": "Дата, возрастание",
2370+ "Date Desc": "Дата, убывание",
2371+ "Background Image": "Фоновое изображение",
2372+ "Delete the background?": "Удалить фон?",
2373+ "Tags_as_Folders_desc": "Чтобы тег отображался как папка, его нужно отметить таковым в меню управления тегами. Нажмите сюда, чтобы открыть его.",
2374+ "tag_entries": "раз исп.",
2375+ "Multiple personas are connected to this character.\nSelect a persona to use for this chat.": "К этому персонажу привязано несколько персон.\nВыберите персону, которую хотите использовать в этом чате.",
2376+ "Select Persona": "Выберите персону",
2377+ "Completion Object": "Как часть Completion Object",
2378+ "Move ${0} to:": "Переместить '${0}' в:",
2379+ "Chat Scenario Override": "Перезапись сценария чата",
2380+ "Unique to this chat.": "Действует только в рамках текущего чата.",
2381+ "All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать этот сценарий вместо того, который указан в карточке.",
2382+ "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.": "Чекпоинты наследуют сценарий родителя, после отделения его можно менять.",
2383+ "Delete Tag": "Удалить тег",
2384+ "Do you want to delete the tag": "Вы точно хотите удалить тег",
2385+ "If you want to merge all references to this tag into another tag, select it below:": "Если хотите заменить ссылки на этот тег на какой-то другой, то выберите из списка:",
2386+ "Open Folder (Show all characters even if not selected)": "Открытая папка (показать всех персонажей, включая невыбранных)",
2387+ "Closed Folder (Hide all characters unless selected)": "Закрытая папка (скрыть всех персонажей, кроме выбранных)",
2388+ "No Folder": "Не папка",
2389+ "Show only favorites": "Показать только избранных персонажей",
2390+ "Show only groups": "Показать только группы",
2391+ "Show only folders": "Показать только папки",
2392+ "Manage tags": "Панель управления тегами",
2393+ "Show Tag List": "Показать список тегов",
2394+ "Clear all filters": "Сбросить все фильтры",
2395+ "There are no items to display.": "Отображать абсолютно нечего.",
2396+ "Characters and groups hidden by filters or closed folders": "Персонажи и группы скрыты настройками фильтров либо закрытыми папками",
2397+ "Otterly empty": "Всё что можно, всё выдрано",
2398+ "Here be dragons": "Список настолько очистился, что в него вернулись драконы",
2399+ "Kiwibunga": "Настолько пусто, что киви прилетела посидеть",
2400+ "Pump-a-Rum": "Пу-пу-пу",
2401+ "Croak it": "Только кваканье лягушек и стрёкот сверчков",
2402+ "${0} character hidden.": "Персонажей скрыто: ${0}.",
2403+ "${0} characters hidden.": "Персонажей скрыто: ${0}.",
2404+ "/ page": "/ стр.",
2405+ "Context Length": "Размер контекста",
2406+ "Added On": "Добавлена",
2407+ "Class": "Класс",
2408+ "Bulk_edit_characters": "Массовое редактирование персонажей\n\nЛКМ, чтобы выделить либо отменить выделение персонажа\nShift+ЛКМ, чтобы массово выделить либо отменить выделение персонажей\nПКМ, чтобы выбрать действие",
2409+ "Bulk select all characters": "Выбрать всех персонажей",
2410+ "Duplicate": "Клонировать",
2411+ "Next page": "След. страница",
2412+ "Previous page": "Пред. страница",
2413+ "Group: ${0}": "Группа: ${0}",
2414+ "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "Вы удалили персонажа или чат, и мы из соображений безопасности перенесли вас на эту страницу! Выберите другого персонажа!",
2415+ "Group is empty.": "Группа пуста.",
2416+ "No characters available": "Персонажей нет",
2417+ "Choose what to export": "Выберите, что экспортировать",
2418+ "Text Completion Preset": "Пресет для режима Text Completion",
2419+ "Update enabled": "Обновить включенные",
2420+ "Could not connect to API": "Не удалось подключиться к API",
2421+ "Connected to API": "Соединение с API установлено",
2422+ "Go back": "Назад"
23402423}
public/script.js+33 -26
@@ -143,6 +143,7 @@ import {
143143 getHordeModels,
144144 adjustHordeGenerationParams,
145145 MIN_LENGTH,
146+ initHorde,
146147} from './scripts/horde.js';
147148
148149import {
@@ -175,6 +176,9 @@ import {
175176 saveBase64AsFile,
176177 uuidv4,
177178 equalsIgnoreCaseAndAccents,
179+ localizePagination,
180+ renderPaginationDropdown,
181+ paginationDropdownChangeHandler,
178182} from './scripts/utils.js';
179183import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
180184
@@ -994,6 +998,7 @@ async function firstLoadInit() {
994998 initAuthorsNote();
995999 await initPersonas();
9961000 initWorldInfo();
1001+ initHorde();
9971002 initRossMods();
9981003 initStats();
9991004 initCfg();
@@ -1423,30 +1428,26 @@ function getBackBlock() {
14231428 return template;
14241429}
14251430
14261431async function getEmptyBlock() {
14271432 const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog'];
14281433 const texts = ['t`Here be dragons'`, 't`Otterly empty'`, 't`Kiwibunga'`, 't`Pump-a-Rum'`, 't`Croak it'`];
14291434 const roll = new Date().getMinutes() % icons.length;
14301435 const emptyBlockparams = `{
1431- <div class="text_block empty_block">
1436+ text: texts[roll],
1432- <i class="fa-solid ${icons[roll]} fa-4x"></i>
1437+ icon: icons[roll],
1433- <h1>${texts[roll]}</h1>
1438+ };
1434- <p>There are no items to display.</p>
1439+ const emptyBlock = await renderTemplateAsync('emptyBlock', params);
1435- </div>`;
14361440 return $(emptyBlock);
14371441}
14381442
14391443/**
14401444 * @param {number} hidden Number of hidden characters
14411445 */
14421446async function getHiddenBlock(hidden) {
14431447 const hiddenBlockparams = `{
1444- <div class="text_block hidden_block">
1448+ text: (hidden > 1 ? t`${hidden} characters hidden.` : t`${hidden} character hidden.`),
1445- <small>
1449+ };
1446- <p>${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.</p>
1450+ const hiddenBlock = await renderTemplateAsync('hiddenBlock', params);
1447- <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
1448- </small>
1449- </div>`;
14501451 return $(hiddenBlock);
14511452}
14521453
@@ -1526,10 +1527,11 @@ export async function printCharacters(fullRefresh = false) {
15261527
15271528 const entities = getEntitiesList({ doFilter: true });
15281529
1530+ const pageSize = Number(accountStorage.getItem(storageKey)) || per_page_default;
1531+ const sizeChangerOptions = [10, 25, 50, 100, 250, 500, 1000];
15291532 $('#rm_print_characters_pagination').pagination({
15301533 dataSource: entities,
1531- pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default,
1534+ pageSize,
1532- sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
15331535 pageRange: 1,
15341536 pageNumber: saveCharactersPage || 1,
15351537 position: 'top',
@@ -1538,14 +1540,16 @@ export async function printCharacters(fullRefresh = false) {
15381540 prevText: '<',
15391541 nextText: '>',
15401542 formatNavigator: PAGINATION_TEMPLATE,
1543+ formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
15411544 showNavigator: true,
15421545 callback: async function (/** @type {Entity[]} */ data) {
15431546 $(listId).empty();
15441547 if (power_user.bogus_folders && isBogusFolderOpen()) {
15451548 $(listId).append(getBackBlock());
15461549 }
15471550 if (!data.length) {
1548- $(listId).append(getEmptyBlock());
1551+ const emptyBlock = await getEmptyBlock();
1552+ $(listId).append(emptyBlock);
15491553 }
15501554 let displayCount = 0;
15511555 for (const i of data) {
@@ -1566,13 +1570,16 @@ export async function printCharacters(fullRefresh = false) {
15661570
15671571 const hidden = (characters.length + groups.length) - displayCount;
15681572 if (hidden > 0 && entitiesFilter.hasAnyFilter()) {
1569- $(listId).append(getHiddenBlock(hidden));
1573+ const hiddenBlock = await getHiddenBlock(hidden);
1574+ $(listId).append(hiddenBlock);
15701575 }
1576+ localizePagination($('#rm_print_characters_pagination'));
15711577
15721578 eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
15731579 },
15741580 afterSizeSelectorChange: function (e, size) {
15751581 accountStorage.setItem(storageKey, e.target.value);
1582+ paginationDropdownChangeHandler(e, size);
15761583 },
15771584 afterPaging: function (e) {
15781585 saveCharactersPage = e;
@@ -8430,15 +8437,15 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w
84308437 function getOkButtonText() {
84318438 if (['text', 'char_not_selected'].includes(popup_type)) {
84328439 $dialoguePopupCancel.css('display', 'none');
84338440 return okButton ?? 't`Ok'`;
84348441 } else if (['delete_extension'].includes(popup_type)) {
84358442 return okButton ?? 't`Ok'`;
84368443 } else if (['new_chat', 'confirm'].includes(popup_type)) {
84378444 return okButton ?? 't`Yes'`;
84388445 } else if (['input'].includes(popup_type)) {
84398446 return okButton ?? t`Save`;
84408447 }
84418448 return okButton ?? 't`Delete'`;
84428449 }
84438450
84448451 dialogueCloseStop = true;
public/scripts/backgrounds.js+2 -1
@@ -6,6 +6,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
66import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
77import { flashHighlight, stringFormat } from './utils.js';
88import { t } from './i18n.js';
9+import { Popup } from './popup.js';
910
1011const BG_METADATA_KEY = 'custom_background';
1112const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -291,7 +292,7 @@ async function onDeleteBackgroundClick(e) {
291292 const bgToDelete = $(this).closest('.bg_example');
292293 const url = bgToDelete.data('url');
293294 const isCustom = bgToDelete.attr('custom') === 'true';
294295 const confirm = await callPopupPopup.show.confirm('<h3>t`Delete the background?</h3>'`, 'confirm'null);
295296 const bg = bgToDelete.attr('bgfile');
296297
297298 if (confirm) {
public/scripts/group-chats.js+19 -7
@@ -13,6 +13,9 @@ import {
1313 getBase64Async,
1414 resetScrollHeight,
1515 initScrollHeight,
16+ localizePagination,
17+ renderPaginationDropdown,
18+ paginationDropdownChangeHandler,
1619} from './utils.js';
1720import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js';
1821import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js';
@@ -1374,6 +1377,8 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
13741377
13751378function printGroupCandidates() {
13761379 const storageKey = 'GroupCandidates_PerPage';
1380+ const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
1381+ const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
13771382 $('#rm_group_add_members_pagination').pagination({
13781383 dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
13791384 pageRange: 1,
@@ -1382,18 +1387,20 @@ function printGroupCandidates() {
13821387 prevText: '<',
13831388 nextText: '>',
13841389 formatNavigator: PAGINATION_TEMPLATE,
1390+ formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
13851391 showNavigator: true,
13861392 showSizeChanger: true,
1387- pageSize: Number(accountStorage.getItem(storageKey)) || 5,
1393+ pageSize,
1388- sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
1394+ afterSizeSelectorChange: function (e, size) {
1389- afterSizeSelectorChange: function (e) {
13901395 accountStorage.setItem(storageKey, e.target.value);
1396+ paginationDropdownChangeHandler(e, size);
13911397 },
13921398 callback: function (data) {
13931399 $('#rm_group_add_members').empty();
13941400 for (const i of data) {
13951401 $('#rm_group_add_members').append(getGroupCharacterBlock(i.item));
13961402 }
1403+ localizePagination($('#rm_group_add_members_pagination'));
13971404 },
13981405 });
13991406}
@@ -1401,6 +1408,9 @@ function printGroupCandidates() {
14011408function printGroupMembers() {
14021409 const storageKey = 'GroupMembers_PerPage';
14031410 $('.rm_group_members_pagination').each(function () {
1411+ let that = this;
1412+ const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
1413+ const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
14041414 $(this).pagination({
14051415 dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
14061416 pageRange: 1,
@@ -1411,16 +1421,18 @@ function printGroupMembers() {
14111421 formatNavigator: PAGINATION_TEMPLATE,
14121422 showNavigator: true,
14131423 showSizeChanger: true,
1414- pageSize: Number(accountStorage.getItem(storageKey)) || 5,
1424+ formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
1415- sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
1425+ pageSize,
14161426 afterSizeSelectorChange: function (e, size) {
14171427 accountStorage.setItem(storageKey, e.target.value);
1428+ paginationDropdownChangeHandler(e, size);
14181429 },
14191430 callback: function (data) {
14201431 $('.rm_group_members').empty();
14211432 for (const i of data) {
14221433 $('.rm_group_members').append(getGroupCharacterBlock(i.item));
14231434 }
1435+ localizePagination($(that));
14241436 },
14251437 });
14261438 });
@@ -1804,7 +1816,7 @@ async function createGroup() {
18041816 const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(', ');
18051817
18061818 if (!name) {
18071819 name = t`Group: ${memberNames}`;
18081820 }
18091821
18101822 const avatar_url = $('#group_avatar_preview img').attr('src');
public/scripts/horde.js+3 -3
@@ -394,7 +394,7 @@ function getHordeModelTemplate(option) {
394394 `));
395395}
396396
397397jQuery(export function initHorde () {
398398 $('#horde_model').on('mousedown change', async function (e) {
399399 console.log('Horde model change', e);
400400 horde_settings.models = $('#horde_model').val();
@@ -441,7 +441,7 @@ jQuery(function () {
441441 if (!isMobile()) {
442442 $('#horde_model').select2({
443443 width: '100%',
444444 placeholder: 't`Select Horde models'`,
445445 allowClear: true,
446446 closeOnSelect: false,
447447 templateSelection: function (data) {
@@ -451,5 +451,5 @@ jQuery(function () {
451451 templateResult: getHordeModelTemplate,
452452 });
453453 }
454454});
455455
public/scripts/openai.js+1 -1
@@ -3851,7 +3851,7 @@ function createLogitBiasListItem(entry) {
38513851}
38523852
38533853async function createNewLogitBiasPreset() {
38543854 const name = await callPopupPopup.show.input('t`Preset name:'`, 'input'null);
38553855
38563856 if (!name) {
38573857 return;
public/scripts/personas.js+7 -3
@@ -22,7 +22,7 @@ import {
2222} from '../script.js';
2323import { persona_description_positions, power_user } from './power-user.js';
2424import { getTokenCountAsync } from './tokenizers.js';
2525import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler } from './utils.js';
2626import { debounce_timeout } from './constants.js';
2727import { FILTER_TYPES, FilterHelper } from './filters.js';
2828import { groups, selected_group } from './group-chats.js';
@@ -250,16 +250,18 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
250250 const storageKey = 'Personas_PerPage';
251251 const listId = '#user_avatar_block';
252252 const perPage = Number(accountStorage.getItem(storageKey)) || 5;
253+ const sizeChangerOptions = [5, 10, 25, 50, 100, 250, 500, 1000];
253254
254255 $('#persona_pagination_container').pagination({
255256 dataSource: entities,
256257 pageSize: perPage,
257- sizeChangerOptions: [5, 10, 25, 50, 100, 250, 500, 1000],
258+ sizeChangerOptions,
258259 pageRange: 1,
259260 pageNumber: savePersonasPage || 1,
260261 position: 'top',
261262 showPageNumbers: false,
262263 showSizeChanger: true,
264+ formatSizeChanger: renderPaginationDropdown(perPage, sizeChangerOptions),
263265 prevText: '<',
264266 nextText: '>',
265267 formatNavigator: PAGINATION_TEMPLATE,
@@ -270,9 +272,11 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
270272 $(listId).append(getUserAvatarBlock(item));
271273 }
272274 updatePersonaUIStates();
275+ localizePagination($('#persona_pagination_container'));
273276 },
274277 afterSizeSelectorChange: function (e, size) {
275278 accountStorage.setItem(storageKey, e.target.value);
279+ paginationDropdownChangeHandler(e, size);
276280 },
277281 afterPaging: function (e) {
278282 savePersonasPage = e;
public/scripts/tags.js+5 -11
@@ -27,7 +27,7 @@ import { debounce_timeout } from './constants.js';
2727import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
2828import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
2929import { renderTemplateAsync } from './templates.js';
3030import { t, translate } from './i18n.js';
3131
3232export {
3333 TAG_FOLDER_TYPES,
@@ -318,7 +318,7 @@ function getTagBlock(tag, entities, hidden = 0, isUseless = false) {
318318 template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
319319 template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`);
320320 template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
321321 template.find('.bogus_folder_counter').text(`${count} ${` + (count != 1 ? 't`characters'` : 't`character'}`));
322322 template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
323323 if (isUseless) template.addClass('useless');
324324
@@ -1057,7 +1057,7 @@ function appendTagToList(listElement, tag, { removable = false, isFilter = false
10571057 tagElement.attr('title', tag.title);
10581058 }
10591059 if (tag.icon) {
10601060 tagElement.find('.tag_name').text('').attr('title', `${translate(tag.name)} ${tag.title || ''}`.trim()).addClass(tag.icon);
10611061 tagElement.addClass('actionable');
10621062 }
10631063
@@ -1644,6 +1644,7 @@ function updateDrawTagFolder(element, tag) {
16441644
16451645 // Draw/update css attributes for this class
16461646 folderElement.attr('title', tagFolder.tooltip);
1647+ folderElement.attr('data-i18n', '[title]' + tagFolder.tooltip);
16471648 const indicator = folderElement.find('.tag_folder_indicator');
16481649 indicator.text(tagFolder.icon);
16491650 indicator.css('color', tagFolder.color);
@@ -1655,14 +1656,7 @@ async function onTagDeleteClick() {
16551656 const tag = tags.find(x => x.id === id);
16561657 const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name })));
16571658
16581659 const popupContent = $(`await renderTemplateAsync('deleteTag', { otherTags }));
1659- <h3>Delete Tag</h3>
1660- <div>Do you want to delete the tag <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
1661- <div class="m-t-2 marginBot5">If you want to merge all references to this tag into another tag, select it below:</div>
1662- <select id="merge_tag_select">
1663- <option value="">--- None ---</option>
1664- ${otherTags.map(x => `<option value="${x.id}">${x.name}</option>`).join('')}
1665- </select>`);
16661660
16671661 appendTagToList(popupContent.find('#tag_to_delete'), tag);
16681662
public/scripts/templates/deleteTag.html+9 -0
@@ -0,0 +1,9 @@
1+<h3 data-i18n="Delete Tag">Delete Tag</h3>
2+<div><span data-i18n="Do you want to delete the tag">Do you want to delete the tag</span> <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
3+<div class="m-t-2 marginBot5" data-i18n="If you want to merge all references to this tag into another tag, select it below:">If you want to merge all references to this tag into another tag, select it below:</div>
4+<select id="merge_tag_select">
5+ <option value="">--- None ---</option>
6+ {{#each otherTags}}
7+ <option value="{{this.id}}">{{this.name}}</option>
8+ {{/each}}
9+</select>
9 \ No newline at end of file
public/scripts/templates/emptyBlock.html+7 -0
@@ -0,0 +1,7 @@
1+<div class="text_block empty_block">
2+ <i class="fa-solid {{icon}} fa-4x"></i>
3+ <h1>{{text}}</h1>
4+ <p data-i18n="There are no items to display.">
5+ There are no items to display.
6+ </p>
7+</div>
public/scripts/templates/hiddenBlock.html+6 -0
@@ -0,0 +1,6 @@
1+<div class="text_block hidden_block">
2+ <small>
3+ <p>{{text}}</p>
4+ <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
5+ </small>
6+</div>
public/scripts/textgen-models.js+6 -6
@@ -7,6 +7,7 @@ import { renderTemplateAsync } from './templates.js';
77import { POPUP_TYPE, callGenericPopup } from './popup.js';
88import { t } from './i18n.js';
99import { accountStorage } from './util/AccountStorage.js';
10+import { localizePagination, PAGINATION_TEMPLATE } from './utils.js';
1011
1112let mancerModels = [];
1213let togetherModels = [];
@@ -361,9 +362,7 @@ export async function loadFeatherlessModels(data) {
361362 showSizeChanger: false,
362363 prevText: '<',
363364 nextText: '>',
364365 formatNavigator: function (currentPagePAGINATION_TEMPLATE, totalPage) {
365- return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
366- },
367366 showNavigator: true,
368367 callback: function (modelsOnPage, pagination) {
369368 modelCardBlock.innerHTML = '';
@@ -385,15 +384,15 @@ export async function loadFeatherlessModels(data) {
385384
386385 const modelClassDiv = document.createElement('div');
387386 modelClassDiv.classList.add('model-class');
388387 modelClassDiv.textContent = t`Class` + `: ${model.model_class || 'N/A'}`;
389388
390389 const contextLengthDiv = document.createElement('div');
391390 contextLengthDiv.classList.add('model-context-length');
392391 contextLengthDiv.textContent = t`Context Length` + `: ${model.context_length}`;
393392
394393 const dateAddedDiv = document.createElement('div');
395394 dateAddedDiv.classList.add('model-date-added');
396395 dateAddedDiv.textContent = t`Added On` + `: ${new Date(model.created * 1000).toLocaleDateString()}`;
397396
398397 detailsContainer.appendChild(modelClassDiv);
399398 detailsContainer.appendChild(contextLengthDiv);
@@ -417,6 +416,7 @@ export async function loadFeatherlessModels(data) {
417416
418417 // Update the current page value whenever the page changes
419418 featherlessCurrentPage = pagination.pageNumber;
419+ localizePagination(paginationContainer);
420420 },
421421 afterSizeSelectorChange: function (e) {
422422 const newPerPage = e.target.value;
public/scripts/utils.js+40 -1
@@ -20,7 +20,46 @@ import { getCurrentLocale, t } from './i18n.js';
2020 * Pagination status string template.
2121 * @type {string}
2222 */
2323export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of.. <%= totalNumber %>';
24+
25+export const localizePagination = function(container) {
26+ container.find('[title="Next page"]').attr('title', t`Next page`);
27+ container.find('[title="Previous page"]').attr('title', t`Previous page`);
28+};
29+
30+/**
31+ * Renders a dropdown for selecting page size in pagination.
32+ * @param {number} pageSize Page size
33+ * @param {number[]} sizeChangerOptions Array of page size options
34+ * @returns {string} The rendered dropdown element as a string
35+ */
36+export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) {
37+ const sizeSelect = document.createElement('select');
38+ sizeSelect.classList.add('J-paginationjs-size-select');
39+
40+ if (sizeChangerOptions.indexOf(pageSize) === -1) {
41+ sizeChangerOptions.unshift(pageSize);
42+ sizeChangerOptions.sort((a, b) => a - b);
43+ }
44+
45+ for (let i = 0; i < sizeChangerOptions.length; i++) {
46+ const option = document.createElement('option');
47+ option.value = `${sizeChangerOptions[i]}`;
48+ option.textContent = `${sizeChangerOptions[i]} ${t`/ page`}`;
49+ if (sizeChangerOptions[i] === pageSize) {
50+ option.setAttribute('selected', 'selected');
51+ }
52+ sizeSelect.appendChild(option);
53+ }
54+
55+ return sizeSelect.outerHTML;
56+};
57+
58+export const paginationDropdownChangeHandler = function(event, size) {
59+ let dropdown = $(event?.originalEvent?.currentTarget || event.delegateTarget).find('select');
60+ dropdown.find('[selected]').removeAttr('selected');
61+ dropdown.find(`[value=${size}]`).attr('selected', '');
62+};
2463
2564/**
2665 * Navigation options for pagination.
public/scripts/world-info.js+2 -2
@@ -2658,7 +2658,7 @@ export async function getWorldEntry(name, data, entry) {
26582658 if (!isMobile()) {
26592659 $(characterFilter).select2({
26602660 width: '100%',
26612661 placeholder: 't`Tie this entry to specific characters or characters with specific tags'`,
26622662 allowClear: true,
26632663 closeOnSelect: false,
26642664 });
@@ -3258,7 +3258,7 @@ export async function getWorldEntry(name, data, entry) {
32583258
32593259 // Create wrapper div
32603260 const wrapper = document.createElement('div');
32613261 wrapper.textContent = t`Move "'${sourceName}"' to:`;
32623262
32633263 // Create container and append elements
32643264 const container = document.createElement('div');
public/style.css+1 -0
@@ -5715,6 +5715,7 @@ body:not(.movingUI) .drawer-content.maximized {
57155715 width: unset;
57165716 margin: 0;
57175717 font-size: calc(var(--mainFontSize) * 0.85);
5718+ padding-right: 20px;
57185719}
57195720
57205721.paginationjs-pages ul li a {