Merge branch 'staging' into integrity

This commit is contained in:
Cohee
2025-03-18 01:51:07 +02:00
38 changed files with 631 additions and 133 deletions

37
.github/readme.md vendored
View File

@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
##### Volume Mappings
* [config] - The directory where SillyTavern configuration files will be stored on your host machine
* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
* [extensions] - (optional) The directory where global UI extensions will be stored on your host machine
* `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
* `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
* `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
* `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
##### Port Mappings
* [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
* `PUBLIC_PORT` - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
##### Additional Settings
* [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).
* [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
* `SILLYTAVERN_VERSION` - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" that points to the nightly image the respective branch.
#### Install command
#### Running the container
1. Open your Command Line
2. Run the following command
2. Run the following command in a folder where you want to store the configuration and data files:
`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
```bash
SILLYTAVERN_VERSION="latest"
PUBLIC_PORT="8000"
CONFIG_PATH="./config"
DATA_PATH="./data"
PLUGINS_PATH="./plugins"
EXTENSIONS_PATH="./extensions"
> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
docker run \
--name="sillytavern" \
-p "$PUBLIC_PORT:8000/tcp" \
-v "$CONFIG_PATH:/home/node/app/config:rw" \
-v "$DATA_PATH:/home/node/app/data:rw" \
-v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
-v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
```
> By default the container will run in the foreground. If you want to run it in the background, add the `-d` flag to the `docker run` command.
### Building the image yourself

View File

@ -19,7 +19,7 @@ jobs:
- name: Label PR Size
# Pull Request Size Labeler
# https://github.com/marketplace/actions/pull-request-size-labeler
uses: codelytv/pr-size-labeler@v1.10.2
uses: codelytv/pr-size-labeler@v1.10.1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
xs_label: '🟩 ⬤○○○○'

View File

@ -786,5 +786,13 @@
{
"filename": "presets/context/DeepSeek-V2.5.json",
"type": "context"
},
{
"filename": "presets/reasoning/DeepSeek.json",
"type": "reasoning"
},
{
"filename": "presets/reasoning/Blank.json",
"type": "reasoning"
}
]

View File

@ -0,0 +1,6 @@
{
"name": "Blank",
"prefix": "",
"suffix": "",
"separator": ""
}

View File

@ -0,0 +1,6 @@
{
"name": "DeepSeek",
"prefix": "<think>\n",
"suffix": "\n</think>",
"separator": "\n\n"
}

View File

@ -1957,7 +1957,7 @@
<span data-i18n="Enable web search">Enable web search</span>
</label>
<div class="flexBasis100p toggle-description justifyLeft">
<span>
<span data-i18n="Use search capabilities provided by the backend.">
Use search capabilities provided by the backend.
</span>
</div>
@ -2188,7 +2188,7 @@
<input id="horde_trusted_workers_only" type="checkbox" />
<span data-i18n="Trusted workers only">Trusted workers only</span>
</label>
<small id="adjustedHordeParams">Context: --, Response: --</small>
<small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
<h4 data-i18n="API key">API key</h4>
<small>
<span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://aihorde.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
@ -3917,6 +3917,19 @@
<summary data-i18n="Reasoning Formatting">
Reasoning Formatting
</summary>
<div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
<select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
<div class="flex-container margin0 justifyCenter gap3px">
<input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
<i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
<i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
<i data-preset-manager-new="reasoning" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
<i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
<i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
<i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
<i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
<small data-i18n="Prefix">Prefix</small>
@ -6563,7 +6576,7 @@
<div class="ch_name"></div>
<small class="ch_additional_info group_select_counter"></small>
</div>
<small class="character_name_block_sub_line">in this group</small>
<small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
<i class='group_fav_icon fa-solid fa-star'></i>
<input class="ch_fav" value="" hidden />
<div class="group_select_block_list ch_description"></div>

View File

@ -23,9 +23,8 @@
"Mirostat Mode": "Режим",
"Mirostat Tau": "Tau",
"Mirostat Eta": "Eta",
"Variability parameter for Mirostat outputs": "Параметр изменчивости для выходных данных Mirostat.",
"Variability parameter for Mirostat outputs": "Вариативность для выходных данных Mirostat.",
"Learning rate of Mirostat": "Скорость обучения Mirostat.",
"Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
"Temperature Last": "Температура последней",
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@ -60,13 +59,11 @@
"Add BOS Token": "Добавлять BOS-токен",
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
"Ban EOS Token": "Запретить EOS-токен",
"Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременно",
"Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию самостоятельно (только при достижении лимита токенов)",
"Skip Special Tokens": "Пропускать спец. токены",
"Beam search": "Поиск Beam",
"Number of Beams": "Количество Beam",
"Beam search": "Beam Search",
"Length Penalty": "Штраф за длину",
"Early Stopping": "Преждевременная остановка",
"Contrastive search": "Контрастный поиск",
"Early Stopping": "Прекращать сразу",
"Penalty Alpha": "Penalty Alpha",
"Seed": "Зерно",
"Epsilon Cutoff": "Epsilon Cutoff",
@ -89,7 +86,7 @@
"Text Completion presets": "Пресеты для Text Completion",
"Documentation on sampling parameters": "Документация по параметрам сэмплеров",
"Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
"Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаете, что делаете.",
"Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы понимаете, что делаете.",
"Wrap in Quotes": "Заключать в кавычки",
"Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
"Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@ -109,7 +106,7 @@
"Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
"API key": "API-ключ",
"Tabby API key": "Tabby API-ключ",
"Get it here:": "Получить здесь:",
"Get it here:": "Получите здесь:",
"Register": "Зарегистрироваться",
"TogetherAI Model": "Модель TogetherAI",
"Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
@ -289,10 +286,10 @@
"Author's Note": "Заметки автора",
"Replace empty message": "Заменять пустые сообщения",
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
"Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаете, что делаете",
"Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно понимаете, что делаете",
"Chat Completion Source": "Источник для Chat Completion",
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
"Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
"Review the Privacy statement": "Ознакомьтесь с заявлением о конфиденциальности",
"Trusted workers only": "Только доверенные рабочие машины",
"For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
"-- Horde models not loaded --": "--Модель Horde не загружена--",
@ -699,7 +696,7 @@
"Aggressive": "Агрессивный",
"Very aggressive": "Очень агрессивный",
"Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
"Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вклад в свои свободные GPU-циклы в орду",
"Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
"Load koboldcpp order": "Загрузить порядок из koboldcpp",
"Use Google Tokenizer": "Использовать токенизатор Google",
@ -744,7 +741,7 @@
"Last Assistant Prefix": "Последний префикс ассистента",
"System Instruction Prefix": "Префикс системной инструкции",
"User Filler Message": "Принудительное сообщение пользователя",
"Permanent": "перманентных",
"Permanent": "постоянных",
"Alt. Greetings": "Др. варианты",
"Smooth Streaming": "Плавный стриминг",
"Save checkpoint": "Сохранить чекпоинт",
@ -1227,7 +1224,6 @@
"JSON-serialized array of strings.": "Список строк в формате JSON.",
"Mirostat_desc": "Mirostat - своего рода термометр, измеряющий перплексию для выводимого текста.\nMirostat подгоняет перплексию генерируемого текста к перплексии входного текста, что позволяет избежать повторов.\n(когда по мере генерации текста авторегрессионным инференсом, перплексия всё больше приближается к нулю)\n а также ловушки перплексии (когда перплексия начинает уходить в сторону)\nБолее подробное описание в статье Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nРежим выбирает версию Mirostat. 0=отключить, 1=Mirostat 1.0 (только llama.cpp), 2=Mirostat 2.0.",
"Helpful tip coming soon.": "Подсказку скоро добавим.",
"Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
"Speculative Ngram": "Speculative Ngram",
"Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",
"Spaces Between Special Tokens": "Spaces Between Special Tokens",
@ -1734,7 +1730,7 @@
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
"Save and Update": "Сохранить и обновить",
"Profile name:": "Название профиля:",
"API returned an error": "API вернуло ошибку",
"API returned an error": "API ответило ошибкой",
"Failed to save preset": "Не удалось сохранить пресет",
"Preset name should be unique.": "Название пресета должно быть уникальным.",
"Invalid file": "Невалидный файл",
@ -1756,8 +1752,7 @@
"dot quota_error": "имеется достаточно кредитов.",
"If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
"Proxy preset '${0}' not found": "Пресет '${0}' не найден",
"Window.ai returned an error": "Window.ai вернул ошибку",
"Get it here:": "Загрузите здесь:",
"Window.ai returned an error": "Window.ai ответил ошибкой",
"Extension is not installed": "Расширение не установлено",
"Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
"An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@ -1866,7 +1861,7 @@
"Group Chat could not be saved": "Не удалось сохранить групповой чат",
"Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
"Currently no group selected.": "В данный момент не выбрано ни одной группы.",
"Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",
"Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажи закончат печатать.",
"Delete the group?": "Удалить группу?",
"This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
"Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@ -1997,7 +1992,7 @@
"Default persona deleted": "Удалена персона по умолчанию",
"The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
"Persona deleted": "Персона удалена",
"You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",
"You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо присвоить имя.",
"Persona name not set": "У персоны отсутствует имя",
"Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
"This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@ -2203,5 +2198,146 @@
"Input:": "Входные данные:",
"Tokenized text:": "Токенизированный текст:",
"Token IDs:": "Идентификаторы токенов:",
"Tokens:": "Токенов:"
"Tokens:": "Токенов:",
"Max prompt cost:": "Макс. стоимость промпта:",
"Reset custom sampler selection": "Сбросить подборку семплеров",
"Here you can toggle the display of individual samplers. (WIP)": "Здесь можно включить или выключить отображение каждого из сэмплеров отдельно. (WIP)",
"Request Model Reasoning": "Запрашивать цепочку рассуждений",
"Reasoning": "Рассуждения / Reasoning",
"Auto-Parse": "Авто-парсинг",
"reasoning_auto_parse": "Автоматически считывать блоки рассуждений, расположенные между префиксом и суффиксом рассуждений. Для работы должно быть указано и то, и другое.",
"Auto-Expand": "Разворачивать",
"reasoning_auto_expand": "Автоматически разворачивать блоки рассуждений.",
"Show Hidden": "Показывать время",
"reasoning_show_hidden": "Отображать затраченное на рассуждения время для моделей со скрытой цепочкой рассуждений",
"Add to Prompts": "Добавлять в промпт",
"reasoning_add_to_prompts": "Добавлять существующие блоки рассуждений в промпт. Для добавления новых используйте меню редактирования сообщений.",
"reasoning_max_additions": "Макс. кол-во блоков рассуждений в промпте, считается от последнего сообщения",
"Max": "Макс.",
"Reasoning Formatting": "Форматирование рассуждений",
"Prefix": "Префикс",
"Suffix": "Постфикс",
"Separator": "Разделитель",
"reasoning_separator": "Вставляется между рассуждениями и содержанием самого сообщения.",
"reasoning_prefix": "Вставляется перед рассуждениями.",
"reasoning_suffix": "Вставляется после рассуждений.",
"Seed_desc": "Фиксированное значение зерна позволяет получать предсказуемые, одинаковые результаты на одинаковых настройках. Поставьте -1 для рандомного зерна.",
"# of Beams": "Кол-во лучей",
"The number of sequences generated at each step with Beam Search.": "Кол-во вариантов, генерируемых Beam Search на каждом шаге работы.",
"Penalize sequences based on their length.": "Штрафует строки в зависимости от длины",
"Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates.": "Определяет, когда останавливать работу Beam Search. Поставив галочку, вы укажете поиску остановиться тогда, когда будет достигнуто кол-во лучей из соответствующего поля. Если галочку не отмечать, то генерация остановится тогда, когда сочтёт, что дальше найти лучших кандидатов слишком маловероятно.",
"A greedy, brute-force algorithm used in LLM sampling to find the most likely sequence of words or tokens. It expands multiple candidate sequences at once, maintaining a fixed number (beam width) of top sequences at each step.": "Жадный алгоритм LLM-сэмплинга, подбирающий наиболее вероятную последовательность слов или токенов путём исследования и расширения сразу нескольких вариантов. На каждом шаге он удерживает фиксированное кол-во самых подходящих вариантов (ширина луча).",
"Smooth_Sampling_desc": "Изменяет распределение с помощью квадратичных и кубических преобразований. Снижение Коэффициента сглаживания даёт более креативные ответы, обычно идеальное значение находится в диапазоне 0.2-0.3 (при кривой сглаживания=1.0). Повышение значения Кривой сглаживания сделает кривую круче, что приведёт к более агрессивной фильтрации маловероятных вариантов. Установив Кривую сглаживания = 1.0, вы фактически нейтрализуете этот параметр и будете работать только с Коэффициентом",
"Temperature_Last_desc": "Применять сэмплер Температуры в последнюю очередь. Почти всегда оправдано.\nПри включении: сначала все токены семплируются, и затем температура регулирует распределение у оставшихся (технически, у оставшихся логитов).\nПри выключении: сначала температура настраивает распределение ВСЕХ токенов, и потом они семплируются уже с этим обновлённым распределением.\nПри отключении этой опции токены в хвосте получают больше шансов попасть в итоговую последовательность, что может привести к менее связным и логичным ответам.",
"Swipe # for All Messages": "Номер свайпа на всех сообщениях",
"Display swipe numbers for all messages, not just the last.": "Отображать номер свайпа для всех сообщений, а не только для последнего.",
"Penalty Range": "Окно для штрафа",
"Never": "Никогда",
"Groups and Past Personas": "Для групп и прошлых персон",
"Always": "Всегда",
"Request model reasoning": "Запрашивать рассуждения",
"Allows the model to return its thinking process.": "Позволяет модели высылать в ответе свою цепочку рассуждений.",
"Rename Persona": "Переименовать персону",
"Change Persona Image": "Изменить изображение персоны",
"Duplicate Persona": "Клонировать персону",
"Delete Persona": "Удалить персону",
"Enter a new name for this persona:": "Введите новое имя персоны:",
"Connections": "Связи",
"Click to select this as default persona for the new chats. Click again to remove it.": "Нажмите, чтобы установить эту персону стандартной для всех новых чатов. Нажмите ещё раз, чтобы отключить.",
"Character": "Персонаж",
"Click to lock your selected persona to the current character. Click again to remove the lock.": "Нажмите, чтобы закрепить эту персону для текущего персонажа. Нажмите ещё раз, чтобы открепить.",
"Chat": "Чат",
"[No character connections. Click one of the buttons above to connect this persona.]": "[Связи отсутствуют. Нажмите на одну из кнопок выше, чтобы создать.]",
"Global Settings": "Общие настройки",
"Allow multiple persona connections per character": "Разрешить привязывать несколько персон к одному персонажу",
"When multiple personas are connected to a character, a popup will appear to select which one to use": "При связывании нескольких персон с персонажем, будет появляться окошко с предложением выбрать нужную.",
"Auto-lock a chosen persona to the chat": "Автоматически привязывать выбранную персону к чату",
"Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "При выборе новой персоны она автоматически будет привязана к текущему чату, и будет выбираться при его открытии.",
"Current Persona": "Текущая персона",
"The chat has been successfully converted!": "Чат успешно преобразован!",
"Manual": "Когда вы скажете",
"Auto Mode delay": "Задержка авто-режима",
"Use tag as folder": "Тег-папка",
"All connections to ${0} have been removed.": "Все связи с персонажем ${0} были удалены.",
"Personas Unlocked": "Персоны отвязаны",
"Remove All Connections": "Удалить все связи",
"Persona ${0} selected and auto-locked to current chat": "Персона ${0} выбрана и автоматически закреплена за этим чатом",
"This persona is only temporarily chosen. Click for more info.": "Данная персона выбрана лишь временно. Нажмите, чтобы узнать больше.",
"Temporary Persona": "Временная персона",
"A different persona is locked to this chat, or you have a different default persona set. The currently selected persona will only be temporary, and resets on reload. Consider locking this persona to the chat if you want to permanently use it.": "К этому чату уже привязана иная персона, либо у вас выбрана иная персона по-умолчанию. Выбранная в данный момент персона будет временной, и сбросится после перезагрузки. Если хотите всегда использовать её в этом чате, советуем её прикрепить.",
"Current Persona: ${0}": "Выбранная персона: ${0}",
"Chat persona: ${0}": "Персона для этого чата: ${0}",
"Default persona: ${0}": "Персона по умолчанию (стандартная): ${0}",
"Persona ${0} is now unlocked from this chat.": "Персона ${0} отвязана от этого чата.",
"Persona Unlocked": "Персона отвязана",
"Persona ${0} is now unlocked from character ${1}.": "Персона ${0} отвязана от персонажа ${1}.",
"Persona Not Found": "Персона не найдена",
"Persona Locked": "Персона закреплена",
"User persona ${0} is locked to character ${1}${2}": "Персона ${0} прикреплена к персонажу ${1}${2}",
"Persona Name Not Set": "У персоны отсутствует имя",
"You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
"Default Persona Removed": "Персона по умолчанию снята",
"Persona is locked to the current character": "Персона закреплена за этим персонажем",
"Persona is locked to the current chat": "Персона закреплена за этим чатом",
"characters": "перс.",
"character": "персонаж",
"in this group": "в группе",
"Chatting Since": "Первая беседа",
"Context": "Контекст",
"Response": "Ответ",
"Connected": "Подключено",
"Enter new background name:": "Введите новое название для фона:",
"AI Horde Website": "Сайт AI Horde",
"Enable web search": "Включить поиск в Интернете",
"Use search capabilities provided by the backend.": "Разрешить использование предоставляемых бэкендом функций поиска.",
"Request inline images": "Запрашивать inline-изображения",
"Allows the model to return image attachments.": "Разрешить модели отправлять вложения в виде картинок.",
"Request inline images_desc_2": "Не совместимо со следующим функционалом: вызов функций, поиск в Интернете, системный промпт.",
"Connected Personas": "Связанные персоны",
"[Currently no personas connected]": "[Связанных персон нет]",
"The following personas are connected to the current character.\n\nClick on a persona to select it for the current character.\nShift + Click to unlink the persona from the character.": "С этим персонажем связаны следующие персоны.\n\nНажмите на персону, чтобы выбрать её для данного персонажа.\nShift + ЛКМ, чтобы её отвязать.",
"Persona Connections": "Связи с персонами",
"Pooled order": "Если уже давно не отвечали",
"Attach a File": "Приложить файл",
"Attach a file or image to a current chat.": "Приложить файл или изображение к текущему чату",
"Remove the file": "Удалить файл",
"Delete the Chat File?": "Удалить чат?",
"Forbidden": "Доступ запрещён",
"To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.": "Чтобы видеть здесь ваши API-ключи, установите параметр allowKeysExposure в config.yaml в положение true, после чего перезапустите сервер SillyTavern.",
"Invalid endpoint URL. Requests may fail.": "Некорректный адрес эндпоинта. Запросы могут не проходить.",
"How to install extensions?": "Как устанавливать расширения?",
"Click the flashing button to install extensions.": "Чтобы их установить, нажмите на мигающую кнопку.",
"ext_regex_reasoning_desc": "Содержимое блоков рассуждений. При отмеченной галочке \"Только промпт\" будут также обработаны добавленные в промпт рассуждения.",
"Macro in Find Regex": "Макросы в рег. выражении",
"Don't substitute": "Не заменять",
"Substitute (raw)": "Заменять в \"чистом\" виде",
"Substitute (escaped)": "Заменять после экранирования",
"ext_regex_other_options_desc": "По умолчанию, расширение вносит изменения в сам файл чата.\nПри включении одной из опций (или обеих), файл чата останется нетронутым, при этом сами изменения по-прежнему будут действовать.",
"ext_regex_flags_help": "Нажмите, чтобы узнать больше о флагах в рег. выражениях.",
"Applies to all matches": "Заменяет все вхождения",
"Applies to the first match": "Заменяет первое вхождение",
"Case insensitive": "Не чувствительно к регистру",
"Case sensitive": "Чувствительно к регистру",
"Find Regex is empty": "Рег. выражение не указано",
"Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
"Export as JSONL": "Экспорт в формате JSONL",
"Thought for some time": "Какое-то время заняли размышления",
"Thinking...": "В раздумьях...",
"Thought for ${0}": "Размышления заняли ${0}",
"Hidden reasoning - Add reasoning block": "Рассуждения скрыты - Добавить блок рассуждений",
"Add reasoning block": "Добавить блок рассуждений",
"Edit reasoning": "Редактировать рассуждения",
"Copy reasoning": "Скопировать рассуждения",
"Confirm Edit": "Подтвердить",
"Remove reasoning": "Удалить рассуждения",
"Cancel edit": "Отменить редактирование",
"Remove Reasoning": "Удалить рассуждения",
"Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.": "Вы точно хотите удалить блок рассуждений?<br />Основное сообщение останется на месте.",
"Reasoning Parse": "Парсинг рассуждений",
"Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
"Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
"Reasoning already exists.": "Рассуждения уже присутствуют.",
"Edit Message": "Редактирование",
"Status check bypassed": "Проверка статуса отключена",
"Valid": "Работает"
}

View File

@ -1144,7 +1144,7 @@ export async function clearItemizedPrompts() {
async function getStatusHorde() {
try {
const hordeStatus = await checkHordeStatus();
setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');
setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection');
}
catch {
setOnlineStatus('no_connection');
@ -1211,7 +1211,7 @@ async function getStatusTextgen() {
}
if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
setOnlineStatus('Status check bypassed');
setOnlineStatus(t`Status check bypassed`);
return resultCheckStatus();
}
@ -1236,7 +1236,7 @@ async function getStatusTextgen() {
setOnlineStatus(textgen_settings.togetherai_model);
} else if (textgen_settings.type === textgen_types.OLLAMA) {
loadOllamaModels(data?.data);
setOnlineStatus(textgen_settings.ollama_model || 'Connected');
setOnlineStatus(textgen_settings.ollama_model || t`Connected`);
} else if (textgen_settings.type === textgen_types.INFERMATICAI) {
loadInfermaticAIModels(data?.data);
setOnlineStatus(textgen_settings.infermaticai_model);
@ -1260,7 +1260,7 @@ async function getStatusTextgen() {
setOnlineStatus(textgen_settings.tabby_model || data?.result);
} else if (textgen_settings.type === textgen_types.GENERIC) {
loadGenericModels(data?.data);
setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');
setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`);
} else {
setOnlineStatus(data?.result);
}
@ -6277,7 +6277,6 @@ export function syncMesToSwipe(messageId = null) {
}
const targetMessage = chat[targetMessageId];
if (!targetMessage) {
return false;
}
@ -6310,6 +6309,68 @@ export function syncMesToSwipe(messageId = null) {
return true;
}
/**
* Syncs swipe data back to the message data at the given message ID (or the last message if no ID is given).
* If the swipe ID is not provided, the current swipe ID in the message object is used.
*
* If the swipe data is invalid in some way, this function will exit out without doing anything.
* @param {number?} [messageId=null] - The ID of the message to sync with the swipe data. If no ID is given, the last message is used.
* @param {number?} [swipeId=null] - The ID of the swipe to sync. If no ID is given, the current swipe ID in the message object is used.
* @returns {boolean} Whether the swipe data was successfully synced to the message
*/
export function syncSwipeToMes(messageId = null, swipeId = null) {
if (!chat.length) {
return false;
}
const targetMessageId = messageId ?? chat.length - 1;
if (targetMessageId >= chat.length || targetMessageId < 0) {
console.warn(`[syncSwipeToMes] Invalid message ID: ${messageId}`);
return false;
}
const targetMessage = chat[targetMessageId];
if (!targetMessage) {
return false;
}
if (swipeId !== null) {
if (isNaN(swipeId) || swipeId < 0) {
console.warn(`[syncSwipeToMes] Invalid swipe ID: ${swipeId}`);
return false;
}
targetMessage.swipe_id = swipeId;
}
// No swipe data there yet, exit out
if (typeof targetMessage.swipe_id !== 'number') {
return false;
}
// If swipes structure is invalid, exit out
if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
return false;
}
const targetSwipeId = targetMessage.swipe_id;
if (!targetMessage.swipes[targetSwipeId] || !targetMessage.swipe_info[targetSwipeId]) {
console.warn(`[syncSwipeToMes] Invalid swipe ID: ${targetSwipeId}`);
return false;
}
const targetSwipeInfo = targetMessage.swipe_info[targetSwipeId];
if (typeof targetSwipeInfo !== 'object') {
return false;
}
targetMessage.mes = targetMessage.swipes[targetSwipeId];
targetMessage.send_date = targetSwipeInfo.send_date;
targetMessage.gen_started = targetSwipeInfo.gen_started;
targetMessage.gen_finished = targetSwipeInfo.gen_finished;
targetMessage.extra = structuredClone(targetSwipeInfo.extra);
return true;
}
/**
* Saves the image to the message object.
* @param {ParsedImage} img Image object
@ -8342,10 +8403,9 @@ export async function deleteSwipe(swipeId = null) {
lastMessage.swipe_info.splice(swipeId, 1);
}
// Select the next swip, or the one before if it was the last one
// Select the next swipe, or the one before if it was the last one
const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
lastMessage.swipe_id = newSwipeId;
lastMessage.mes = lastMessage.swipes[newSwipeId];
syncSwipeToMes(null, newSwipeId);
await saveChatConditional();
await reloadCurrentChat();
@ -10477,7 +10537,7 @@ jQuery(async function () {
e.stopPropagation();
chat_file_for_del = $(this).attr('file_name');
console.debug('detected cross click for' + chat_file_for_del);
callPopup('<h3>Delete the Chat File?</h3>', 'del_chat');
callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
});
$('#advanced_div').click(function () {

View File

@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { flashHighlight, stringFormat } from './utils.js';
import { t } from './i18n.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
const fileExtension = oldBg.split('.').pop();
const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
const newBgExtensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', oldBgExtensionless);
const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
if (!newBgExtensionless) {
console.debug('no new_bg_extensionless');

View File

@ -358,7 +358,7 @@ export async function convertSoloToGroupChat() {
// Click on the freshly selected group to open it
await openGroupById(group.id);
toastr.success('The chat has been successfully converted!');
toastr.success(t`The chat has been successfully converted!`);
}
/**

View File

@ -41,6 +41,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types
* @property {string} chat_completion_source - Source provider for chat completion
* @property {number} max_tokens - Maximum number of tokens to generate
* @property {number} [temperature] - Optional temperature parameter for response randomness
* @property {string} [custom_url] - Optional custom URL for chat completion
*/
/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
@ -264,7 +265,7 @@ export class ChatCompletionService {
* @param {ChatCompletionPayload} custom
* @returns {ChatCompletionPayload}
*/
static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {
static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
const payload = {
...props,
messages,
@ -272,6 +273,7 @@ export class ChatCompletionService {
chat_completion_source,
max_tokens,
temperature,
custom_url,
stream: false,
};

View File

@ -424,7 +424,7 @@ jQuery(async () => {
installHintButton.on('click', async function () {
const installButton = $('#third_party_extension_button');
flashHighlight(installButton, 5000);
toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');
toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
});
const connectButton = windowHtml.find('#assets-connect-button');

View File

@ -1,4 +1,4 @@
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
<div id="attachFile" class="list-group-item flex-container flexGap5" data-i18n="[title]Attach a file or image to a current chat." title="Attach a file or image to a current chat.">
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
<span data-i18n="Attach a File">Attach a File</span>
</div>

View File

@ -39,6 +39,7 @@ const CC_COMMANDS = [
'proxy',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const TC_COMMANDS = [
@ -54,6 +55,7 @@ const TC_COMMANDS = [
'tokenizer',
'stop-strings',
'start-reply-with',
'reasoning-template',
];
const FANCY_NAMES = {
@ -70,6 +72,7 @@ const FANCY_NAMES = {
'tokenizer': 'Tokenizer',
'stop-strings': 'Custom Stopping Strings',
'start-reply-with': 'Start Reply With',
'reasoning-template': 'Reasoning Template',
};
/**
@ -154,6 +157,7 @@ const profilesProvider = () => [
* @property {string} [tokenizer] Tokenizer
* @property {string} [stop-strings] Custom Stopping Strings
* @property {string} [start-reply-with] Start Reply With
* @property {string} [reasoning-template] Reasoning Template
* @property {string[]} [exclude] Commands to exclude
*/

View File

@ -2154,7 +2154,7 @@ function migrateSettings() {
imgElement.src = '';
}
setExpressionOverrideHtml();
setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
if (isVisualNovelMode()) {
$('#visual-novel-wrapper').empty();

View File

@ -19,7 +19,7 @@
<div id="regex_info_block_wrapper">
<div id="regex_info_block" class="info-block"></div>
<a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
<i class="fa-solid fa-circle-info" title="Click here to learn more about regex flags."></i>
<i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
</a>
</div>
@ -147,7 +147,7 @@
</label>
<span>
<small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
<span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
<span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" 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>
</span>
<label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
<input type="checkbox" name="only_format_display" />

View File

@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
for (const script of scripts) {
if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
if (script.disabled) {
toastr.warning(`Regex script "${scriptName}" is disabled.`);
toastr.warning(t`Regex script "${scriptName}" is disabled.`);
return value;
}

View File

@ -323,6 +323,7 @@ export class ConnectionManagerRequestService {
max_tokens: maxTokens,
model: profile.model,
chat_completion_source: selectedApiMap.source,
custom_url: profile['api-url'],
}, {
presetName: includePreset ? profile.preset : undefined,
}, extractData);

View File

@ -1207,8 +1207,8 @@ jQuery(async function () {
eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'speak',
callback: async (args, value) => {

View File

@ -696,7 +696,7 @@ export function getGroupBlock(group) {
template.find('.group_fav_icon').css('display', 'none');
template.addClass(group.fav ? 'is_fav' : '');
template.find('.ch_fav').val(group.fav);
template.find('.group_select_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
template.find('.group_select_counter').text(count + ' ' + (count != 1 ? t`characters` : t`character`));
template.find('.group_select_block_list').text(namesList.join(', '));
// Display inline tags

View File

@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
import { delay } from './utils.js';
import { isMobile } from './RossAscends-mods.js';
import { autoSelectInstructPreset } from './instruct-mode.js';
import { t } from './i18n.js';
export {
horde_settings,
@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
}
}
console.log(maxContextLength, maxLength);
$('#adjustedHordeParams').text(`Context: ${maxContextLength}, Response: ${maxLength}`);
$('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
return { maxContextLength, maxLength };
}
@ -177,7 +178,7 @@ function setContextSizePreview() {
if (horde_settings.models.length) {
adjustHordeGenerationParams(max_context, amount_gen);
} else {
$('#adjustedHordeParams').text('Context: --, Response: --');
$('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
}
}
@ -404,7 +405,7 @@ jQuery(function () {
if (horde_settings.models.length) {
adjustHordeGenerationParams(max_context, amount_gen);
} else {
$('#adjustedHordeParams').text('Context: --, Response: --');
$('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
}
saveSettingsDebounced();

View File

@ -398,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
/**
* Formats instruct mode system prompt.
* @param {string} systemPrompt System prompt string.
* @param {InstructSettings} customInstruct Custom instruct mode settings.
* @returns {string} Formatted instruct mode system prompt.
*/
export function formatInstructModeSystemPrompt(systemPrompt) {
export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
if (!systemPrompt) {
return '';
}
const separator = power_user.instruct.wrap ? '\n' : '';
const instruct = structuredClone(customInstruct ?? power_user.instruct);
if (power_user.instruct.system_sequence_prefix) {
const separator = instruct.wrap ? '\n' : '';
if (instruct.system_sequence_prefix) {
// TODO: Replace with a proper 'System' prompt entity name input
const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
const prefix = instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
systemPrompt = prefix + separator + systemPrompt;
}
if (power_user.instruct.system_sequence_suffix) {
systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
if (instruct.system_sequence_suffix) {
systemPrompt = systemPrompt + separator + instruct.system_sequence_suffix;
}
return systemPrompt;

View File

@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
return result;
}
function formatWorldInfo(value) {
export function formatWorldInfo(value, { wiFormat = null } = {}) {
if (!value) {
return '';
}
if (!oai_settings.wi_format.trim()) {
const format = wiFormat ?? oai_settings.wi_format;
if (!format.trim()) {
return value;
}
return stringFormat(oai_settings.wi_format, value);
return stringFormat(format, value);
}
/**
@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
* @param {number} position - Prompt position in the extensions object.
* @returns {string|false} - The prompt position for prompt collection.
*/
function getPromptPosition(position) {
export function getPromptPosition(position) {
if (position == extension_prompt_types.BEFORE_PROMPT) {
return 'start';
}
@ -969,7 +971,7 @@ function getPromptPosition(position) {
* @param {number} role Role of the prompt.
* @returns {string} Mapped role.
*/
function getPromptRole(role) {
export function getPromptRole(role) {
switch (role) {
case extension_prompt_roles.SYSTEM:
return 'system';
@ -3476,7 +3478,7 @@ async function getStatusOpen() {
let status;
if ('ai' in window) {
status = 'Valid';
status = t`Valid`;
}
else {
showWindowExtensionError();
@ -3525,7 +3527,7 @@ async function getStatusOpen() {
const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
if (canBypass) {
setOnlineStatus('Status check bypassed');
setOnlineStatus(t`Status check bypassed`);
}
try {
@ -3547,7 +3549,7 @@ async function getStatusOpen() {
saveModelList(responseData.data);
}
if (!('error' in responseData)) {
setOnlineStatus('Valid');
setOnlineStatus(t`Valid`);
}
} catch (error) {
console.error(error);

View File

@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
chat_metadata['persona'] = user_avatar;
console.log(`Auto locked persona to ${user_avatar}`);
if (toastPersonaNameChange && power_user.persona_show_notifications) {
toastr.success(`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
}
saveMetadataDebounced();
updatePersonaUIStates();

View File

@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
export {
loadPowerUserSettings,
@ -257,6 +258,7 @@ let power_user = {
},
reasoning: {
name: DEFAULT_REASONING_TEMPLATE,
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
await loadInstructMode(data);
await loadContextSettings();
await loadSystemPrompts(data);
await loadReasoningTemplates(data);
loadMaxContextUnlocked();
switchWaifuMode();
switchSpoilerMode();
@ -1985,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
/**
* Renders a story string template with the given parameters.
* @param {object} params Template parameters.
* @param {object} [options] Additional options.
* @param {string} [options.customStoryString] Custom story string template.
* @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
* @returns {string} The rendered story string.
*/
export function renderStoryString(params) {
export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
try {
const storyString = customStoryString ?? power_user.context.story_string;
const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
// Validate and log possible warnings/errors
validateStoryString(power_user.context.story_string, params);
validateStoryString(storyString, params);
// compile the story string template into a function, with no HTML escaping
const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });
const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
// render the story string template with the given params
let output = compiledTemplate(params);
@ -2006,7 +2015,7 @@ export function renderStoryString(params) {
// add a newline to the end of the story string if it doesn't have one
if (output.length > 0 && !output.endsWith('\n')) {
if (!power_user.instruct.enabled || power_user.instruct.wrap) {
if (!instructSettings.enabled || instructSettings.wrap) {
output += '\n';
}
}
@ -4229,14 +4238,13 @@ $(document).ready(() => {
],
callback: (args, value) => {
const force = isTrueBoolean(String(args?.force ?? false));
value = String(value ?? '').trim();
// Skip processing if no value and not forced
if (!force && !value) {
return power_user.user_prompt_bias;
}
power_user.user_prompt_bias = value;
power_user.user_prompt_bias = String(value ?? '');
$('#start_reply_with').val(power_user.user_prompt_bias);
saveSettingsDebounced();

View File

@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js';
import { convertNovelPreset } from './nai-settings.js';
import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
import { openai_settings, openai_setting_names } from './openai.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
@ -38,6 +38,7 @@ import {
} from './textgen-settings.js';
import { download, parseJsonFile, waitUntilCondition } from './utils.js';
import { t } from './i18n.js';
import { reasoning_templates } from './reasoning.js';
const presetManagers = {};
@ -168,6 +169,20 @@ class PresetManager {
},
isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
},
'reasoning': {
name: 'Reasoning Formatting',
getData: () => {
const manager = getPresetManager('reasoning');
const name = manager.getSelectedPresetName();
return manager.getPresetSettings(name);
},
setData: (data) => {
const manager = getPresetManager('reasoning');
const name = data.name;
return manager.savePreset(name, data);
},
isValid: (data) => PresetManager.isPossiblyReasoningData(data),
},
};
static isPossiblyInstructData(data) {
@ -190,6 +205,11 @@ class PresetManager {
return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
}
static isPossiblyReasoningData(data) {
const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
}
/**
* Imports master settings from JSON data.
* @param {object} data Data to import
@ -227,6 +247,12 @@ class PresetManager {
return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
}
// 5. Reasoning Template
if (this.isPossiblyReasoningData(data)) {
toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
return await getPresetManager('reasoning').savePreset(data.name, data);
}
const validSections = [];
for (const [key, section] of Object.entries(this.masterSections)) {
if (key in data && section.isValid(data[key])) {
@ -478,6 +504,10 @@ class PresetManager {
presets = system_prompts;
preset_names = system_prompts.map(x => x.name);
break;
case 'reasoning':
presets = reasoning_templates;
preset_names = reasoning_templates.map(x => x.name);
break;
default:
console.warn(`Unknown API ID ${api}`);
}
@ -490,7 +520,7 @@ class PresetManager {
}
isAdvancedFormatting() {
return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
}
updateList(name, preset) {
@ -553,6 +583,11 @@ class PresetManager {
sysprompt_preset['name'] = name || power_user.sysprompt.preset;
return sysprompt_preset;
}
case 'reasoning': {
const reasoning_preset = structuredClone(power_user.reasoning);
reasoning_preset['name'] = name || power_user.reasoning.preset;
return reasoning_preset;
}
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
@ -599,6 +634,13 @@ class PresetManager {
'include_reasoning',
'global_banned_tokens',
'send_banned_tokens',
// Reasoning exclusions
'auto_parse',
'add_to_prompts',
'auto_expand',
'show_hidden',
'max_additions',
];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));

View File

@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
import { power_user } from './power-user.js';
import { performFuzzySearch, power_user } from './power-user.js';
import { getPresetManager } from './preset-manager.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
/**
* @typedef {object} ReasoningTemplate
* @property {string} name - The name of the template
* @property {string} prefix - Reasoning prefix
* @property {string} suffix - Reasoning suffix
* @property {string} separator - Reasoning separator
*/
/**
* @type {ReasoningTemplate[]} List of reasoning templates
*/
export const reasoning_templates = [];
export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
/**
* @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
* @readonly
*/
const UI = {
$select: $('#reasoning_select'),
$suffix: $('#reasoning_suffix'),
$prefix: $('#reasoning_prefix'),
$separator: $('#reasoning_separator'),
$autoParse: $('#reasoning_auto_parse'),
$autoExpand: $('#reasoning_auto_expand'),
$showHidden: $('#reasoning_show_hidden'),
$addToPrompts: $('#reasoning_add_to_prompts'),
$maxAdditions: $('#reasoning_max_additions'),
};
/**
* Enum representing the type of the reasoning for a message (where it came from)
@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
mainApi = null,
ignoreShowThoughts = false,
textGenType = null,
chatCompletionSource = null
chatCompletionSource = null,
} = {}) {
switch (mainApi ?? main_api) {
case 'textgenerationwebui':
@ -669,57 +701,102 @@ export class PromptReasoning {
}
function loadReasoningSettings() {
$('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
$('#reasoning_add_to_prompts').on('change', function () {
UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
UI.$addToPrompts.on('change', function () {
power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#reasoning_prefix').val(power_user.reasoning.prefix);
$('#reasoning_prefix').on('input', function () {
UI.$prefix.val(power_user.reasoning.prefix);
UI.$prefix.on('input', function () {
power_user.reasoning.prefix = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_suffix').val(power_user.reasoning.suffix);
$('#reasoning_suffix').on('input', function () {
UI.$suffix.val(power_user.reasoning.suffix);
UI.$suffix.on('input', function () {
power_user.reasoning.suffix = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_separator').val(power_user.reasoning.separator);
$('#reasoning_separator').on('input', function () {
UI.$separator.val(power_user.reasoning.separator);
UI.$separator.on('input', function () {
power_user.reasoning.separator = String($(this).val());
saveSettingsDebounced();
});
$('#reasoning_max_additions').val(power_user.reasoning.max_additions);
$('#reasoning_max_additions').on('input', function () {
UI.$maxAdditions.val(power_user.reasoning.max_additions);
UI.$maxAdditions.on('input', function () {
power_user.reasoning.max_additions = Number($(this).val());
saveSettingsDebounced();
});
$('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);
$('#reasoning_auto_parse').on('change', function () {
UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
UI.$autoParse.on('change', function () {
power_user.reasoning.auto_parse = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
$('#reasoning_auto_expand').on('change', function () {
UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
UI.$autoExpand.on('change', function () {
power_user.reasoning.auto_expand = !!$(this).prop('checked');
toggleReasoningAutoExpand();
saveSettingsDebounced();
});
toggleReasoningAutoExpand();
$('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);
$('#reasoning_show_hidden').on('change', function () {
UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
UI.$showHidden.on('change', function () {
power_user.reasoning.show_hidden = !!$(this).prop('checked');
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
saveSettingsDebounced();
});
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
UI.$select.on('change', async function () {
const name = String($(this).val());
const template = reasoning_templates.find(p => p.name === name);
if (!template) {
return;
}
UI.$prefix.val(template.prefix);
UI.$suffix.val(template.suffix);
UI.$separator.val(template.separator);
power_user.reasoning.name = name;
power_user.reasoning.prefix = template.prefix;
power_user.reasoning.suffix = template.suffix;
power_user.reasoning.separator = template.separator;
saveSettingsDebounced();
});
}
function selectReasoningTemplateCallback(args, name) {
if (!name) {
return power_user.reasoning.name ?? '';
}
const quiet = isTrueBoolean(args?.quiet);
const templateNames = reasoning_templates.map(preset => preset.name);
let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
if (!foundName) {
const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
if (result.length === 0) {
!quiet && toastr.warning(`Reasoning template "${name}" not found`);
return '';
}
foundName = result[0].item;
}
UI.$select.val(foundName).trigger('change');
!quiet && toastr.success(`Reasoning template "${foundName}" selected`);
return foundName;
}
function registerReasoningSlashCommands() {
@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
: parsedReasoning.reasoning;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'reasoning-template',
aliases: ['reasoning-formatting', 'reasoning-preset'],
callback: selectReasoningTemplateCallback,
returns: 'template name',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'quiet',
description: 'Suppress the toast message on template change',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'false',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'reasoning template name',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
}),
],
helpString: `
<div>
Selects a reasoning template by name, using fuzzy search to find the closest match.
Gets the current template if no name is provided.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
</li>
</ul>
</div>
`,
}));
}
function registerReasoningMacros() {
@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
}
}
/**
* Loads reasoning templates from the settings data.
* @param {object} data Settings data
* @param {ReasoningTemplate[]} data.reasoning Reasoning templates
* @returns {Promise<void>}
*/
export async function loadReasoningTemplates(data) {
if (data.reasoning !== undefined) {
reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
}
for (const template of reasoning_templates) {
$('<option>').val(template.name).text(template.name).appendTo(UI.$select);
}
// No template name, need to migrate
if (power_user.reasoning.name === undefined) {
const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
if (defaultTemplate) {
// If the reasoning settings were modified - migrate them to a custom template
if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
/** @type {ReasoningTemplate} */
const data = {
name: '[Migrated] Custom',
prefix: power_user.reasoning.prefix,
suffix: power_user.reasoning.suffix,
separator: power_user.reasoning.separator,
};
await getPresetManager('reasoning')?.savePreset(data.name, data);
power_user.reasoning.name = data.name;
} else {
power_user.reasoning.name = defaultTemplate.name;
}
} else {
// Template not found (deleted or content check skipped - leave blank)
power_user.reasoning.name = '';
}
saveSettingsDebounced();
}
UI.$select.val(power_user.reasoning.name);
}
/**
* Initializes reasoning settings and event handlers.
*/
export function initReasoning() {
loadReasoningSettings();
setReasoningEventHandlers();

View File

@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
//import { getSortableDelay, onlyUnique } from './utils.js';
//import { getCfgPrompt } from './cfg-scale.js';
import { setting_names } from './textgen-settings.js';
import { renderTemplateAsync } from './templates.js';
const TGsamplerNames = setting_names;
@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
const html = $(document.createElement('div'));
html.attr('id', 'sampler_view_list')
.addClass('flex-container flexFlowColumn');
html.append(`
<div class="title_restorable flexFlowColumn alignItemsBaseline">
<div class="flex-container justifyCenter">
<h3>Sampler Select</h3>
<div class="flex-container alignItemsBaseline">
<div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
<i class="fa-solid fa-recycle"></i>
</div>
</div>
<!--<div class="flex-container alignItemsBaseline">
<div class="menu_button menu_button_icon" title="Create a new sampler">
<i class="fa-solid fa-plus"></i>
<span data-i18n="Create">Create</span>
</div>
</div>-->
</div>
<small>Here you can toggle the display of individual samplers. (WIP)</small>
</div>
<hr>`);
html.append(await renderTemplateAsync('samplerSelector'));
const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
const APISamplers = await listSamplers(main_api);

View File

@ -1,5 +1,6 @@
import { DOMPurify } from '../lib.js';
import { callPopup, getRequestHeaders } from '../script.js';
import { t } from './i18n.js';
export const SECRET_KEYS = {
HORDE: 'api_key_horde',
@ -104,7 +105,7 @@ async function viewSecrets() {
});
if (response.status == 403) {
callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.</p>', 'text');
callPopup('<h3>' + t`Forbidden` + '</h3><p>' + t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.` + '</p>', 'text');
return;
}

View File

@ -49,6 +49,7 @@ import {
clearChat,
unshallowCharacter,
deleteLastMessage,
getCharacterCardFields,
} from '../script.js';
import {
extension_settings,
@ -78,7 +79,7 @@ import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
import { timestampToMoment, uuidv4 } from './utils.js';
import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
import { ChatCompletionService, TextCompletionService } from './custom-request.js';
import { ConnectionManagerRequestService } from './extensions/shared.js';
import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
@ -189,6 +190,7 @@ export function getContext() {
textCompletionSettings: textgenerationwebui_settings,
powerUserSettings: power_user,
getCharacters,
getCharacterCardFields,
uuidv4,
humanizedDateTime,
updateMessageBlock,
@ -207,6 +209,7 @@ export function getContext() {
saveWorldInfo,
updateWorldInfoList,
convertCharacterBook,
getWorldInfoPrompt,
CONNECT_API_MAP,
getTextGenServer,
extractMessageFromData,

View File

@ -1,9 +1,9 @@
<div data-type="assistant_note">
<div>
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
<span>Click the button to save it as a file.</span>
<span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
</div>
<div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL">
<div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
<i class="fa-solid fa-file-export"></i>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="title_restorable flexFlowColumn alignItemsBaseline">
<div class="flex-container justifyCenter">
<h3 data-i18n="Sampler Select">Sampler Select</h3>
<div class="flex-container alignItemsBaseline">
<div id="resetSelectedSamplers" class="menu_button menu_button_icon" data-i18n="[title]Reset custom sampler selection" title="Reset custom sampler selection">
<i class="fa-solid fa-recycle"></i>
</div>
</div>
<!--<div class="flex-container alignItemsBaseline">
<div class="menu_button menu_button_icon" title="Create a new sampler">
<i class="fa-solid fa-plus"></i>
<span data-i18n="Create">Create</span>
</div>
</div>-->
</div>
<small data-i18n="Here you can toggle the display of individual samplers. (WIP)">Here you can toggle the display of individual samplers. (WIP)</small>
</div>
<hr>

View File

@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
/**
* Gets the world info based on chat messages.
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun If true, the function will not emit any events.
* @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
* @param {string[]} chat - The chat messages to scan, in reverse order.
* @param {number} maxContext - The maximum context size of the generation.
* @param {boolean} isDryRun - If true, the function will not emit any events.
* @typedef {object} WIPromptResult
* @property {string} worldInfoString - Complete world info string
* @property {string} worldInfoBefore - World info that goes before the prompt
* @property {string} worldInfoAfter - World info that goes after the prompt
* @property {Array} worldInfoExamples - Array of example entries
* @property {Array} worldInfoDepth - Array of depth entries
* @property {Array} anBefore - Array of entries before Author's Note
* @property {Array} anAfter - Array of entries after Author's Note
* @returns {Promise<WIPromptResult>} The world info string and depth.
*/
export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
worldInfoAfter,
worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
anAfter: activatedWorldInfo.ANAfterEntries ?? [],
};
}
@ -3862,7 +3871,14 @@ function parseDecorators(content) {
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun Whether to perform a dry run.
* @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
* @typedef {object} WIActivated
* @property {string} worldInfoBefore The world info before the chat.
* @property {string} worldInfoAfter The world info after the chat.
* @property {any[]} EMEntries The entries for examples.
* @property {any[]} WIDepthEntries The depth entries.
* @property {any[]} ANBeforeEntries The entries before Author's Note.
* @property {any[]} ANAfterEntries The entries after Author's Note.
* @property {Set<any>} allActivatedEntries All entries.
* @returns {Promise<WIActivated>} The world info activated.
*/
export async function checkWorldInfo(chat, maxContext, isDryRun) {
@ -3906,7 +3922,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
timedEffects.checkTimedEffects();
if (sortedEntries.length === 0) {
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
}
/** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@ -4355,7 +4371,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
}
/**

View File

@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
vectors: 'vectors',
backups: 'backups',
sysprompt: 'sysprompt',
reasoning: 'reasoning',
});
/**

View File

@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
MOVING_UI: 'moving_ui',
QUICK_REPLIES: 'quick_replies',
SYSPROMPT: 'sysprompt',
REASONING: 'reasoning',
};
/**
@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
const presets = [];
for (const contentItem of contentIndex) {
if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {
if (contentItem.type.endsWith('_preset') || ['instruct', 'context', 'sysprompt', 'reasoning'].includes(contentItem.type)) {
contentItem.name = path.parse(contentItem.filename).name;
contentItem.folder = getTargetByType(contentItem.type, directories);
presets.push(contentItem);
@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
return directories.quickreplies;
case CONTENT_TYPES.SYSPROMPT:
return directories.sysprompt;
case CONTENT_TYPES.REASONING:
return directories.reasoning;
default:
return null;
}

View File

@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
return { folder: directories.context, extension: '.json' };
case 'sysprompt':
return { folder: directories.sysprompt, extension: '.json' };
case 'reasoning':
return { folder: directories.reasoning, extension: '.json' };
default:
return { folder: null, extension: null };
}

View File

@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
const instruct = readAndParseFromDirectory(request.user.directories.instruct);
const context = readAndParseFromDirectory(request.user.directories.context);
const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
response.send({
settings,
@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
instruct,
context,
sysprompt,
reasoning,
enable_extensions: ENABLE_EXTENSIONS,
enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
enable_accounts: ENABLE_ACCOUNTS,

View File

@ -95,6 +95,7 @@ const STORAGE_KEYS = {
* @property {string} vectors - The directory where the vectors are stored
* @property {string} backups - The directory where the backups are stored
* @property {string} sysprompt - The directory where the system prompt data is stored
* @property {string} reasoning - The directory where the reasoning templates are stored
*/
/**