Merge branch 'staging' into integrity

c92ca8dbfbab1bd1d221ebf86680f05cc2591efd

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

38 files changed, +632 -134Ignore whitespace
.github/readme.md+27 -12
@@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
192192
193193##### Volume Mappings
194194
195195* [config]`CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
196196* [data]`DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
197197* [plugins]`PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
198198* [extensions]`EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
199199
200200##### Port Mappings
201201
202202* [PublicPort]`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.
203203
204204##### Additional Settings
205205
206-* [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/).
206+* `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.
207-* [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.
208207
209208#### InstallRunning commandthe container
210209
2112101. Open your Command Line
212-2. Run the following command
211+2. Run the following command in a folder where you want to store the configuration and data files:
213212
214-`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]'`
213+```bash
214+SILLYTAVERN_VERSION="latest"
215+PUBLIC_PORT="8000"
216+CONFIG_PATH="./config"
217+DATA_PATH="./data"
218+PLUGINS_PATH="./plugins"
219+EXTENSIONS_PATH="./extensions"
220+
221+docker run \
222+ --name="sillytavern" \
223+ -p "$PUBLIC_PORT:8000/tcp" \
224+ -v "$CONFIG_PATH:/home/node/app/config:rw" \
225+ -v "$DATA_PATH:/home/node/app/data:rw" \
226+ -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
227+ -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
228+ ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
229+```
215230
216-> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
231+> 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.
217232
218233### Building the image yourself
219234
.github/workflows/pr-auto-manager.yml+1 -1
@@ -19,7 +19,7 @@ jobs:
1919 - name: Label PR Size
2020 # Pull Request Size Labeler
2121 # https://github.com/marketplace/actions/pull-request-size-labeler
2222 uses: codelytv/pr-size-labeler@v1.10.21
2323 with:
2424 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2525 xs_label: '🟩 ⬤○○○○'
default/content/index.json+8 -0
@@ -786,5 +786,13 @@
786786 {
787787 "filename": "presets/context/DeepSeek-V2.5.json",
788788 "type": "context"
789+ },
790+ {
791+ "filename": "presets/reasoning/DeepSeek.json",
792+ "type": "reasoning"
793+ },
794+ {
795+ "filename": "presets/reasoning/Blank.json",
796+ "type": "reasoning"
789797 }
790798]
default/content/presets/reasoning/Blank.json+6 -0
@@ -0,0 +1,6 @@
1+{
2+ "name": "Blank",
3+ "prefix": "",
4+ "suffix": "",
5+ "separator": ""
6+}
default/content/presets/reasoning/DeepSeek.json+6 -0
@@ -0,0 +1,6 @@
1+{
2+ "name": "DeepSeek",
3+ "prefix": "<think>\n",
4+ "suffix": "\n</think>",
5+ "separator": "\n\n"
6+}
public/index.html+16 -3
@@ -1957,7 +1957,7 @@
19571957 <span data-i18n="Enable web search">Enable web search</span>
19581958 </label>
19591959 <div class="flexBasis100p toggle-description justifyLeft">
1960- <span>
1960+ <span data-i18n="Use search capabilities provided by the backend.">
19611961 Use search capabilities provided by the backend.
19621962 </span>
19631963 </div>
@@ -2188,7 +2188,7 @@
21882188 <input id="horde_trusted_workers_only" type="checkbox" />
21892189 <span data-i18n="Trusted workers only">Trusted workers only</span>
21902190 </label>
21912191 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
21922192 <h4 data-i18n="API key">API key</h4>
21932193 <small>
21942194 <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 @@
39173917 <summary data-i18n="Reasoning Formatting">
39183918 Reasoning Formatting
39193919 </summary>
3920+ <div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
3921+ <select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
3922+ <div class="flex-container margin0 justifyCenter gap3px">
3923+ <input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
3924+ <i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3925+ <i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
3926+ <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>
3927+ <i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3928+ <i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3929+ <i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3930+ <i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3931+ </div>
3932+ </div>
39203933 <div class="flex-container">
39213934 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
39223935 <small data-i18n="Prefix">Prefix</small>
@@ -6563,7 +6576,7 @@
65636576 <div class="ch_name"></div>
65646577 <small class="ch_additional_info group_select_counter"></small>
65656578 </div>
65666579 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
65676580 <i class='group_fav_icon fa-solid fa-star'></i>
65686581 <input class="ch_fav" value="" hidden />
65696582 <div class="group_select_block_list ch_description"></div>
public/locales/ru-ru.json+157 -21
@@ -23,9 +23,8 @@
2323 "Mirostat Mode": "Режим",
2424 "Mirostat Tau": "Tau",
2525 "Mirostat Eta": "Eta",
2626 "Variability parameter for Mirostat outputs": "Параметр изменчивостиВариативность для выходных данных Mirostat.",
2727 "Learning rate of Mirostat": "Скорость обучения Mirostat.",
28- "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
2928 "Temperature Last": "Температура последней",
3029 "LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
3130 "Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
6059 "Add BOS Token": "Добавлять BOS-токен",
6160 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
6261 "Ban EOS Token": "Запретить EOS-токен",
6362 "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременносамостоятельно (только при достижении лимита токенов)",
6463 "Skip Special Tokens": "Пропускать спец. токены",
6564 "Beam search": "Поиск Beam Search",
66- "Number of Beams": "Количество Beam",
6765 "Length Penalty": "Штраф за длину",
6866 "Early Stopping": "ПреждевременнаяПрекращать остановкасразу",
69- "Contrastive search": "Контрастный поиск",
7067 "Penalty Alpha": "Penalty Alpha",
7168 "Seed": "Зерно",
7269 "Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
8986 "Text Completion presets": "Пресеты для Text Completion",
9087 "Documentation on sampling parameters": "Документация по параметрам сэмплеров",
9188 "Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
9289 "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаетепонимаете, что делаете.",
9390 "Wrap in Quotes": "Заключать в кавычки",
9491 "Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
9592 "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@@ -109,7 +106,7 @@
109106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
110107 "API key": "API-ключ",
111108 "Tabby API key": "Tabby API-ключ",
112109 "Get it here:": "ПолучитьПолучите здесь:",
113110 "Register": "Зарегистрироваться",
114111 "TogetherAI Model": "Модель TogetherAI",
115112 "Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
@@ -289,10 +286,10 @@
289286 "Author's Note": "Заметки автора",
290287 "Replace empty message": "Заменять пустые сообщения",
291288 "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
292289 "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаетепонимаете, что делаете",
293290 "Chat Completion Source": "Источник для Chat Completion",
294291 "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
295292 "Review the Privacy statement": "ОзнакомитьсяОзнакомьтесь с заявлением о конфиденциальности",
296293 "Trusted workers only": "Только доверенные рабочие машины",
297294 "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
298295 "-- Horde models not loaded --": "--Модель Horde не загружена--",
@@ -699,7 +696,7 @@
699696 "Aggressive": "Агрессивный",
700697 "Very aggressive": "Очень агрессивный",
701698 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
702699 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вкладиспользовать ввремя своипростоя свободныевашего GPU-циклы вдля ордупомощи Horde",
703700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
704701 "Load koboldcpp order": "Загрузить порядок из koboldcpp",
705702 "Use Google Tokenizer": "Использовать токенизатор Google",
@@ -744,7 +741,7 @@
744741 "Last Assistant Prefix": "Последний префикс ассистента",
745742 "System Instruction Prefix": "Префикс системной инструкции",
746743 "User Filler Message": "Принудительное сообщение пользователя",
747744 "Permanent": "перманентныхпостоянных",
748745 "Alt. Greetings": "Др. варианты",
749746 "Smooth Streaming": "Плавный стриминг",
750747 "Save checkpoint": "Сохранить чекпоинт",
@@ -1227,7 +1224,6 @@
12271224 "JSON-serialized array of strings.": "Список строк в формате JSON.",
12281225 "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.",
12291226 "Helpful tip coming soon.": "Подсказку скоро добавим.",
1230- "Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
12311227 "Speculative Ngram": "Speculative Ngram",
12321228 "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.",
12331229 "Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1734,7 +1730,7 @@
17341730 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
17351731 "Save and Update": "Сохранить и обновить",
17361732 "Profile name:": "Название профиля:",
17371733 "API returned an error": "API вернулоответило ошибкуошибкой",
17381734 "Failed to save preset": "Не удалось сохранить пресет",
17391735 "Preset name should be unique.": "Название пресета должно быть уникальным.",
17401736 "Invalid file": "Невалидный файл",
@@ -1756,8 +1752,7 @@
17561752 "dot quota_error": "имеется достаточно кредитов.",
17571753 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
17581754 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",
17591755 "Window.ai returned an error": "Window.ai вернулответил ошибкуошибкой",
1760- "Get it here:": "Загрузите здесь:",
17611756 "Extension is not installed": "Расширение не установлено",
17621757 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
17631758 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1861,7 @@
18661861 "Group Chat could not be saved": "Не удалось сохранить групповой чат",
18671862 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
18681863 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",
18691864 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажперсонажи закончитзакончат печатать.",
18701865 "Delete the group?": "Удалить группу?",
18711866 "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.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
18721867 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1992,7 @@
19971992 "Default persona deleted": "Удалена персона по умолчанию",
19981993 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
19991994 "Persona deleted": "Персона удалена",
20001995 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задатьприсвоить имя.",
20011996 "Persona name not set": "У персоны отсутствует имя",
20021997 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
20031998 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2203,5 +2198,146 @@
22032198 "Input:": "Входные данные:",
22042199 "Tokenized text:": "Токенизированный текст:",
22052200 "Token IDs:": "Идентификаторы токенов:",
22062201 "Tokens:": "Токенов:",
2202+ "Max prompt cost:": "Макс. стоимость промпта:",
2203+ "Reset custom sampler selection": "Сбросить подборку семплеров",
2204+ "Here you can toggle the display of individual samplers. (WIP)": "Здесь можно включить или выключить отображение каждого из сэмплеров отдельно. (WIP)",
2205+ "Request Model Reasoning": "Запрашивать цепочку рассуждений",
2206+ "Reasoning": "Рассуждения / Reasoning",
2207+ "Auto-Parse": "Авто-парсинг",
2208+ "reasoning_auto_parse": "Автоматически считывать блоки рассуждений, расположенные между префиксом и суффиксом рассуждений. Для работы должно быть указано и то, и другое.",
2209+ "Auto-Expand": "Разворачивать",
2210+ "reasoning_auto_expand": "Автоматически разворачивать блоки рассуждений.",
2211+ "Show Hidden": "Показывать время",
2212+ "reasoning_show_hidden": "Отображать затраченное на рассуждения время для моделей со скрытой цепочкой рассуждений",
2213+ "Add to Prompts": "Добавлять в промпт",
2214+ "reasoning_add_to_prompts": "Добавлять существующие блоки рассуждений в промпт. Для добавления новых используйте меню редактирования сообщений.",
2215+ "reasoning_max_additions": "Макс. кол-во блоков рассуждений в промпте, считается от последнего сообщения",
2216+ "Max": "Макс.",
2217+ "Reasoning Formatting": "Форматирование рассуждений",
2218+ "Prefix": "Префикс",
2219+ "Suffix": "Постфикс",
2220+ "Separator": "Разделитель",
2221+ "reasoning_separator": "Вставляется между рассуждениями и содержанием самого сообщения.",
2222+ "reasoning_prefix": "Вставляется перед рассуждениями.",
2223+ "reasoning_suffix": "Вставляется после рассуждений.",
2224+ "Seed_desc": "Фиксированное значение зерна позволяет получать предсказуемые, одинаковые результаты на одинаковых настройках. Поставьте -1 для рандомного зерна.",
2225+ "# of Beams": "Кол-во лучей",
2226+ "The number of sequences generated at each step with Beam Search.": "Кол-во вариантов, генерируемых Beam Search на каждом шаге работы.",
2227+ "Penalize sequences based on their length.": "Штрафует строки в зависимости от длины",
2228+ "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. Поставив галочку, вы укажете поиску остановиться тогда, когда будет достигнуто кол-во лучей из соответствующего поля. Если галочку не отмечать, то генерация остановится тогда, когда сочтёт, что дальше найти лучших кандидатов слишком маловероятно.",
2229+ "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-сэмплинга, подбирающий наиболее вероятную последовательность слов или токенов путём исследования и расширения сразу нескольких вариантов. На каждом шаге он удерживает фиксированное кол-во самых подходящих вариантов (ширина луча).",
2230+ "Smooth_Sampling_desc": "Изменяет распределение с помощью квадратичных и кубических преобразований. Снижение Коэффициента сглаживания даёт более креативные ответы, обычно идеальное значение находится в диапазоне 0.2-0.3 (при кривой сглаживания=1.0). Повышение значения Кривой сглаживания сделает кривую круче, что приведёт к более агрессивной фильтрации маловероятных вариантов. Установив Кривую сглаживания = 1.0, вы фактически нейтрализуете этот параметр и будете работать только с Коэффициентом",
2231+ "Temperature_Last_desc": "Применять сэмплер Температуры в последнюю очередь. Почти всегда оправдано.\nПри включении: сначала все токены семплируются, и затем температура регулирует распределение у оставшихся (технически, у оставшихся логитов).\nПри выключении: сначала температура настраивает распределение ВСЕХ токенов, и потом они семплируются уже с этим обновлённым распределением.\nПри отключении этой опции токены в хвосте получают больше шансов попасть в итоговую последовательность, что может привести к менее связным и логичным ответам.",
2232+ "Swipe # for All Messages": "Номер свайпа на всех сообщениях",
2233+ "Display swipe numbers for all messages, not just the last.": "Отображать номер свайпа для всех сообщений, а не только для последнего.",
2234+ "Penalty Range": "Окно для штрафа",
2235+ "Never": "Никогда",
2236+ "Groups and Past Personas": "Для групп и прошлых персон",
2237+ "Always": "Всегда",
2238+ "Request model reasoning": "Запрашивать рассуждения",
2239+ "Allows the model to return its thinking process.": "Позволяет модели высылать в ответе свою цепочку рассуждений.",
2240+ "Rename Persona": "Переименовать персону",
2241+ "Change Persona Image": "Изменить изображение персоны",
2242+ "Duplicate Persona": "Клонировать персону",
2243+ "Delete Persona": "Удалить персону",
2244+ "Enter a new name for this persona:": "Введите новое имя персоны:",
2245+ "Connections": "Связи",
2246+ "Click to select this as default persona for the new chats. Click again to remove it.": "Нажмите, чтобы установить эту персону стандартной для всех новых чатов. Нажмите ещё раз, чтобы отключить.",
2247+ "Character": "Персонаж",
2248+ "Click to lock your selected persona to the current character. Click again to remove the lock.": "Нажмите, чтобы закрепить эту персону для текущего персонажа. Нажмите ещё раз, чтобы открепить.",
2249+ "Chat": "Чат",
2250+ "[No character connections. Click one of the buttons above to connect this persona.]": "[Связи отсутствуют. Нажмите на одну из кнопок выше, чтобы создать.]",
2251+ "Global Settings": "Общие настройки",
2252+ "Allow multiple persona connections per character": "Разрешить привязывать несколько персон к одному персонажу",
2253+ "When multiple personas are connected to a character, a popup will appear to select which one to use": "При связывании нескольких персон с персонажем, будет появляться окошко с предложением выбрать нужную.",
2254+ "Auto-lock a chosen persona to the chat": "Автоматически привязывать выбранную персону к чату",
2255+ "Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "При выборе новой персоны она автоматически будет привязана к текущему чату, и будет выбираться при его открытии.",
2256+ "Current Persona": "Текущая персона",
2257+ "The chat has been successfully converted!": "Чат успешно преобразован!",
2258+ "Manual": "Когда вы скажете",
2259+ "Auto Mode delay": "Задержка авто-режима",
2260+ "Use tag as folder": "Тег-папка",
2261+ "All connections to ${0} have been removed.": "Все связи с персонажем ${0} были удалены.",
2262+ "Personas Unlocked": "Персоны отвязаны",
2263+ "Remove All Connections": "Удалить все связи",
2264+ "Persona ${0} selected and auto-locked to current chat": "Персона ${0} выбрана и автоматически закреплена за этим чатом",
2265+ "This persona is only temporarily chosen. Click for more info.": "Данная персона выбрана лишь временно. Нажмите, чтобы узнать больше.",
2266+ "Temporary Persona": "Временная персона",
2267+ "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.": "К этому чату уже привязана иная персона, либо у вас выбрана иная персона по-умолчанию. Выбранная в данный момент персона будет временной, и сбросится после перезагрузки. Если хотите всегда использовать её в этом чате, советуем её прикрепить.",
2268+ "Current Persona: ${0}": "Выбранная персона: ${0}",
2269+ "Chat persona: ${0}": "Персона для этого чата: ${0}",
2270+ "Default persona: ${0}": "Персона по умолчанию (стандартная): ${0}",
2271+ "Persona ${0} is now unlocked from this chat.": "Персона ${0} отвязана от этого чата.",
2272+ "Persona Unlocked": "Персона отвязана",
2273+ "Persona ${0} is now unlocked from character ${1}.": "Персона ${0} отвязана от персонажа ${1}.",
2274+ "Persona Not Found": "Персона не найдена",
2275+ "Persona Locked": "Персона закреплена",
2276+ "User persona ${0} is locked to character ${1}${2}": "Персона ${0} прикреплена к персонажу ${1}${2}",
2277+ "Persona Name Not Set": "У персоны отсутствует имя",
2278+ "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
2279+ "Default Persona Removed": "Персона по умолчанию снята",
2280+ "Persona is locked to the current character": "Персона закреплена за этим персонажем",
2281+ "Persona is locked to the current chat": "Персона закреплена за этим чатом",
2282+ "characters": "перс.",
2283+ "character": "персонаж",
2284+ "in this group": "в группе",
2285+ "Chatting Since": "Первая беседа",
2286+ "Context": "Контекст",
2287+ "Response": "Ответ",
2288+ "Connected": "Подключено",
2289+ "Enter new background name:": "Введите новое название для фона:",
2290+ "AI Horde Website": "Сайт AI Horde",
2291+ "Enable web search": "Включить поиск в Интернете",
2292+ "Use search capabilities provided by the backend.": "Разрешить использование предоставляемых бэкендом функций поиска.",
2293+ "Request inline images": "Запрашивать inline-изображения",
2294+ "Allows the model to return image attachments.": "Разрешить модели отправлять вложения в виде картинок.",
2295+ "Request inline images_desc_2": "Не совместимо со следующим функционалом: вызов функций, поиск в Интернете, системный промпт.",
2296+ "Connected Personas": "Связанные персоны",
2297+ "[Currently no personas connected]": "[Связанных персон нет]",
2298+ "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 + ЛКМ, чтобы её отвязать.",
2299+ "Persona Connections": "Связи с персонами",
2300+ "Pooled order": "Если уже давно не отвечали",
2301+ "Attach a File": "Приложить файл",
2302+ "Attach a file or image to a current chat.": "Приложить файл или изображение к текущему чату",
2303+ "Remove the file": "Удалить файл",
2304+ "Delete the Chat File?": "Удалить чат?",
2305+ "Forbidden": "Доступ запрещён",
2306+ "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.",
2307+ "Invalid endpoint URL. Requests may fail.": "Некорректный адрес эндпоинта. Запросы могут не проходить.",
2308+ "How to install extensions?": "Как устанавливать расширения?",
2309+ "Click the flashing button to install extensions.": "Чтобы их установить, нажмите на мигающую кнопку.",
2310+ "ext_regex_reasoning_desc": "Содержимое блоков рассуждений. При отмеченной галочке \"Только промпт\" будут также обработаны добавленные в промпт рассуждения.",
2311+ "Macro in Find Regex": "Макросы в рег. выражении",
2312+ "Don't substitute": "Не заменять",
2313+ "Substitute (raw)": "Заменять в \"чистом\" виде",
2314+ "Substitute (escaped)": "Заменять после экранирования",
2315+ "ext_regex_other_options_desc": "По умолчанию, расширение вносит изменения в сам файл чата.\nПри включении одной из опций (или обеих), файл чата останется нетронутым, при этом сами изменения по-прежнему будут действовать.",
2316+ "ext_regex_flags_help": "Нажмите, чтобы узнать больше о флагах в рег. выражениях.",
2317+ "Applies to all matches": "Заменяет все вхождения",
2318+ "Applies to the first match": "Заменяет первое вхождение",
2319+ "Case insensitive": "Не чувствительно к регистру",
2320+ "Case sensitive": "Чувствительно к регистру",
2321+ "Find Regex is empty": "Рег. выражение не указано",
2322+ "Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
2323+ "Export as JSONL": "Экспорт в формате JSONL",
2324+ "Thought for some time": "Какое-то время заняли размышления",
2325+ "Thinking...": "В раздумьях...",
2326+ "Thought for ${0}": "Размышления заняли ${0}",
2327+ "Hidden reasoning - Add reasoning block": "Рассуждения скрыты - Добавить блок рассуждений",
2328+ "Add reasoning block": "Добавить блок рассуждений",
2329+ "Edit reasoning": "Редактировать рассуждения",
2330+ "Copy reasoning": "Скопировать рассуждения",
2331+ "Confirm Edit": "Подтвердить",
2332+ "Remove reasoning": "Удалить рассуждения",
2333+ "Cancel edit": "Отменить редактирование",
2334+ "Remove Reasoning": "Удалить рассуждения",
2335+ "Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.": "Вы точно хотите удалить блок рассуждений?<br />Основное сообщение останется на месте.",
2336+ "Reasoning Parse": "Парсинг рассуждений",
2337+ "Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
2338+ "Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
2339+ "Reasoning already exists.": "Рассуждения уже присутствуют.",
2340+ "Edit Message": "Редактирование",
2341+ "Status check bypassed": "Проверка статуса отключена",
2342+ "Valid": "Работает"
22072343}
public/script.js+69 -9
@@ -1144,7 +1144,7 @@ export async function clearItemizedPrompts() {
11441144async function getStatusHorde() {
11451145 try {
11461146 const hordeStatus = await checkHordeStatus();
11471147 setOnlineStatus(hordeStatus ? 't`Connected'` : 'no_connection');
11481148 }
11491149 catch {
11501150 setOnlineStatus('no_connection');
@@ -1211,7 +1211,7 @@ async function getStatusTextgen() {
12111211 }
12121212
12131213 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
12141214 setOnlineStatus('t`Status check bypassed'`);
12151215 return resultCheckStatus();
12161216 }
12171217
@@ -1236,7 +1236,7 @@ async function getStatusTextgen() {
12361236 setOnlineStatus(textgen_settings.togetherai_model);
12371237 } else if (textgen_settings.type === textgen_types.OLLAMA) {
12381238 loadOllamaModels(data?.data);
12391239 setOnlineStatus(textgen_settings.ollama_model || 't`Connected'`);
12401240 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
12411241 loadInfermaticAIModels(data?.data);
12421242 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1260,7 +1260,7 @@ async function getStatusTextgen() {
12601260 setOnlineStatus(textgen_settings.tabby_model || data?.result);
12611261 } else if (textgen_settings.type === textgen_types.GENERIC) {
12621262 loadGenericModels(data?.data);
12631263 setOnlineStatus(textgen_settings.generic_model || data?.result || 't`Connected'`);
12641264 } else {
12651265 setOnlineStatus(data?.result);
12661266 }
@@ -6277,7 +6277,6 @@ export function syncMesToSwipe(messageId = null) {
62776277 }
62786278
62796279 const targetMessage = chat[targetMessageId];
6280-
62816280 if (!targetMessage) {
62826281 return false;
62836282 }
@@ -6311,6 +6310,68 @@ export function syncMesToSwipe(messageId = null) {
63116310}
63126311
63136312/**
6313+ * Syncs swipe data back to the message data at the given message ID (or the last message if no ID is given).
6314+ * If the swipe ID is not provided, the current swipe ID in the message object is used.
6315+ *
6316+ * If the swipe data is invalid in some way, this function will exit out without doing anything.
6317+ * @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.
6318+ * @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.
6319+ * @returns {boolean} Whether the swipe data was successfully synced to the message
6320+ */
6321+export function syncSwipeToMes(messageId = null, swipeId = null) {
6322+ if (!chat.length) {
6323+ return false;
6324+ }
6325+
6326+ const targetMessageId = messageId ?? chat.length - 1;
6327+ if (targetMessageId >= chat.length || targetMessageId < 0) {
6328+ console.warn(`[syncSwipeToMes] Invalid message ID: ${messageId}`);
6329+ return false;
6330+ }
6331+
6332+ const targetMessage = chat[targetMessageId];
6333+ if (!targetMessage) {
6334+ return false;
6335+ }
6336+
6337+ if (swipeId !== null) {
6338+ if (isNaN(swipeId) || swipeId < 0) {
6339+ console.warn(`[syncSwipeToMes] Invalid swipe ID: ${swipeId}`);
6340+ return false;
6341+ }
6342+ targetMessage.swipe_id = swipeId;
6343+ }
6344+
6345+ // No swipe data there yet, exit out
6346+ if (typeof targetMessage.swipe_id !== 'number') {
6347+ return false;
6348+ }
6349+ // If swipes structure is invalid, exit out
6350+ if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
6351+ return false;
6352+ }
6353+
6354+ const targetSwipeId = targetMessage.swipe_id;
6355+ if (!targetMessage.swipes[targetSwipeId] || !targetMessage.swipe_info[targetSwipeId]) {
6356+ console.warn(`[syncSwipeToMes] Invalid swipe ID: ${targetSwipeId}`);
6357+ return false;
6358+ }
6359+
6360+ const targetSwipeInfo = targetMessage.swipe_info[targetSwipeId];
6361+ if (typeof targetSwipeInfo !== 'object') {
6362+ return false;
6363+ }
6364+
6365+ targetMessage.mes = targetMessage.swipes[targetSwipeId];
6366+ targetMessage.send_date = targetSwipeInfo.send_date;
6367+ targetMessage.gen_started = targetSwipeInfo.gen_started;
6368+ targetMessage.gen_finished = targetSwipeInfo.gen_finished;
6369+ targetMessage.extra = structuredClone(targetSwipeInfo.extra);
6370+
6371+ return true;
6372+}
6373+
6374+/**
63146375 * Saves the image to the message object.
63156376 * @param {ParsedImage} img Image object
63166377 * @param {object} mes Chat message object
@@ -8342,10 +8403,9 @@ export async function deleteSwipe(swipeId = null) {
83428403 lastMessage.swipe_info.splice(swipeId, 1);
83438404 }
83448405
83458406 // Select the next swipswipe, or the one before if it was the last one
83468407 const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
8347- lastMessage.swipe_id = newSwipeId;
8408+ syncSwipeToMes(null, newSwipeId);
8348- lastMessage.mes = lastMessage.swipes[newSwipeId];
83498409
83508410 await saveChatConditional();
83518411 await reloadCurrentChat();
@@ -10477,7 +10537,7 @@ jQuery(async function () {
1047710537 e.stopPropagation();
1047810538 chat_file_for_del = $(this).attr('file_name');
1047910539 console.debug('detected cross click for' + chat_file_for_del);
1048010540 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
1048110541 });
1048210542
1048310543 $('#advanced_div').click(function () {
public/scripts/backgrounds.js+2 -1
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
55import { SlashCommand } from './slash-commands/SlashCommand.js';
66import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
77import { flashHighlight, stringFormat } from './utils.js';
8+import { t } from './i18n.js';
89
910const BG_METADATA_KEY = 'custom_background';
1011const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
243244 const fileExtension = oldBg.split('.').pop();
244245 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
245246 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
246247 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
247248
248249 if (!newBgExtensionless) {
249250 console.debug('no new_bg_extensionless');
public/scripts/bookmarks.js+1 -1
@@ -358,7 +358,7 @@ export async function convertSoloToGroupChat() {
358358 // Click on the freshly selected group to open it
359359 await openGroupById(group.id);
360360
361361 toastr.success('t`The chat has been successfully converted!'`);
362362}
363363
364364/**
public/scripts/custom-request.js+3 -1
@@ -41,6 +41,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types
4141 * @property {string} chat_completion_source - Source provider for chat completion
4242 * @property {number} max_tokens - Maximum number of tokens to generate
4343 * @property {number} [temperature] - Optional temperature parameter for response randomness
44+ * @property {string} [custom_url] - Optional custom URL for chat completion
4445 */
4546
4647/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
@@ -264,7 +265,7 @@ export class ChatCompletionService {
264265 * @param {ChatCompletionPayload} custom
265266 * @returns {ChatCompletionPayload}
266267 */
267268 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
268269 const payload = {
269270 ...props,
270271 messages,
@@ -272,6 +273,7 @@ export class ChatCompletionService {
272273 chat_completion_source,
273274 max_tokens,
274275 temperature,
276+ custom_url,
275277 stream: false,
276278 };
277279
public/scripts/extensions/assets/index.js+1 -1
@@ -424,7 +424,7 @@ jQuery(async () => {
424424 installHintButton.on('click', async function () {
425425 const installButton = $('#third_party_extension_button');
426426 flashHighlight(installButton, 5000);
427427 toastr.info('t`Click the flashing button to install extensions.'`, 't`How to install extensions?'`);
428428 });
429429
430430 const connectButton = windowHtml.find('#assets-connect-button');
public/scripts/extensions/attachments/attach-button.html+1 -1
@@ -1,4 +1,4 @@
11<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.">
22 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
33 <span data-i18n="Attach a File">Attach a File</span>
44</div>
public/scripts/extensions/connection-manager/index.js+4 -0
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
3939 'proxy',
4040 'stop-strings',
4141 'start-reply-with',
42+ 'reasoning-template',
4243];
4344
4445const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
5455 'tokenizer',
5556 'stop-strings',
5657 'start-reply-with',
58+ 'reasoning-template',
5759];
5860
5961const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
7072 'tokenizer': 'Tokenizer',
7173 'stop-strings': 'Custom Stopping Strings',
7274 'start-reply-with': 'Start Reply With',
75+ 'reasoning-template': 'Reasoning Template',
7376};
7477
7578/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154157 * @property {string} [tokenizer] Tokenizer
155158 * @property {string} [stop-strings] Custom Stopping Strings
156159 * @property {string} [start-reply-with] Start Reply With
160+ * @property {string} [reasoning-template] Reasoning Template
157161 * @property {string[]} [exclude] Commands to exclude
158162 */
159163
public/scripts/extensions/expressions/index.js+1 -1
@@ -2154,7 +2154,7 @@ function migrateSettings() {
21542154 imgElement.src = '';
21552155 }
21562156
2157- setExpressionOverrideHtml();
2157+ setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582158
21592159 if (isVisualNovelMode()) {
21602160 $('#visual-novel-wrapper').empty();
public/scripts/extensions/regex/editor.html+2 -2
@@ -19,7 +19,7 @@
1919 <div id="regex_info_block_wrapper">
2020 <div id="regex_info_block" class="info-block"></div>
2121 <a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
2222 <i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
2323 </a>
2424 </div>
2525
@@ -147,7 +147,7 @@
147147 </label>
148148 <span>
149149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
150150 <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>
151151 </span>
152152 <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.">
153153 <input type="checkbox" name="only_format_display" />
public/scripts/extensions/regex/index.js+1 -1
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
398398 for (const script of scripts) {
399399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
400400 if (script.disabled) {
401401 toastr.warning(t`Regex script "${scriptName}" is disabled.`);
402402 return value;
403403 }
404404
public/scripts/extensions/shared.js+1 -0
@@ -323,6 +323,7 @@ export class ConnectionManagerRequestService {
323323 max_tokens: maxTokens,
324324 model: profile.model,
325325 chat_completion_source: selectedApiMap.source,
326+ custom_url: profile['api-url'],
326327 }, {
327328 presetName: includePreset ? profile.preset : undefined,
328329 }, extractData);
public/scripts/extensions/tts/index.js+2 -2
@@ -1207,8 +1207,8 @@ jQuery(async function () {
12071207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
12081208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
12091209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
12101210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
12111211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
12121212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
12131213 name: 'speak',
12141214 callback: async (args, value) => {
public/scripts/group-chats.js+1 -1
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
696696 template.find('.group_fav_icon').css('display', 'none');
697697 template.addClass(group.fav ? 'is_fav' : '');
698698 template.find('.ch_fav').val(group.fav);
699699 template.find('.group_select_counter').text(`${count} ${+ ' ' + (count != 1 ? 't`characters'` : 't`character'}`));
700700 template.find('.group_select_block_list').text(namesList.join(', '));
701701
702702 // Display inline tags
public/scripts/horde.js+4 -3
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
1010import { delay } from './utils.js';
1111import { isMobile } from './RossAscends-mods.js';
1212import { autoSelectInstructPreset } from './instruct-mode.js';
13+import { t } from './i18n.js';
1314
1415export {
1516 horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
169170 }
170171 }
171172 console.log(maxContextLength, maxLength);
172173 $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
173174 return { maxContextLength, maxLength };
174175}
175176
@@ -177,7 +178,7 @@ function setContextSizePreview() {
177178 if (horde_settings.models.length) {
178179 adjustHordeGenerationParams(max_context, amount_gen);
179180 } else {
180181 $('#adjustedHordeParams').text('t`Context` + ': --, ' + t`Response` + ': --');
181182 }
182183}
183184
@@ -404,7 +405,7 @@ jQuery(function () {
404405 if (horde_settings.models.length) {
405406 adjustHordeGenerationParams(max_context, amount_gen);
406407 } else {
407408 $('#adjustedHordeParams').text('t`Context` + ': --, ' + t`Response` + ': --');
408409 }
409410
410411 saveSettingsDebounced();
public/scripts/instruct-mode.js+9 -6
@@ -398,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
398398/**
399399 * Formats instruct mode system prompt.
400400 * @param {string} systemPrompt System prompt string.
401+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
401402 * @returns {string} Formatted instruct mode system prompt.
402403 */
403404export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
404405 if (!systemPrompt) {
405406 return '';
406407 }
407408
408- const separator = power_user.instruct.wrap ? '\n' : '';
409+ const instruct = structuredClone(customInstruct ?? power_user.instruct);
410+
411+ const separator = instruct.wrap ? '\n' : '';
409412
410413 if (power_user.instruct.system_sequence_prefix) {
411414 // TODO: Replace with a proper 'System' prompt entity name input
412415 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
413416 systemPrompt = prefix + separator + systemPrompt;
414417 }
415418
416419 if (power_user.instruct.system_sequence_suffix) {
417420 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
418421 }
419422
420423 return systemPrompt;
public/scripts/openai.js+10 -8
@@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705705 return result;
706706}
707707
708-function formatWorldInfo(value) {
708+export function formatWorldInfo(value, { wiFormat = null } = {}) {
709709 if (!value) {
710710 return '';
711711 }
712712
713- if (!oai_settings.wi_format.trim()) {
713+ const format = wiFormat ?? oai_settings.wi_format;
714+
715+ if (!format.trim()) {
714716 return value;
715717 }
716718
717719 return stringFormat(oai_settings.wi_formatformat, value);
718720}
719721
720722/**
@@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952954 * @param {number} position - Prompt position in the extensions object.
953955 * @returns {string|false} - The prompt position for prompt collection.
954956 */
955957export function getPromptPosition(position) {
956958 if (position == extension_prompt_types.BEFORE_PROMPT) {
957959 return 'start';
958960 }
@@ -969,7 +971,7 @@ function getPromptPosition(position) {
969971 * @param {number} role Role of the prompt.
970972 * @returns {string} Mapped role.
971973 */
972974export function getPromptRole(role) {
973975 switch (role) {
974976 case extension_prompt_roles.SYSTEM:
975977 return 'system';
@@ -3476,7 +3478,7 @@ async function getStatusOpen() {
34763478 let status;
34773479
34783480 if ('ai' in window) {
34793481 status = 't`Valid'`;
34803482 }
34813483 else {
34823484 showWindowExtensionError();
@@ -3525,7 +3527,7 @@ async function getStatusOpen() {
35253527
35263528 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;
35273529 if (canBypass) {
35283530 setOnlineStatus('t`Status check bypassed'`);
35293531 }
35303532
35313533 try {
@@ -3547,7 +3549,7 @@ async function getStatusOpen() {
35473549 saveModelList(responseData.data);
35483550 }
35493551 if (!('error' in responseData)) {
35503552 setOnlineStatus('t`Valid'`);
35513553 }
35523554 } catch (error) {
35533555 console.error(error);
public/scripts/personas.js+1 -1
@@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800800 chat_metadata['persona'] = user_avatar;
801801 console.log(`Auto locked persona to ${user_avatar}`);
802802 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803803 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804804 }
805805 saveMetadataDebounced();
806806 updatePersonaUIStates();
public/scripts/power-user.js+14 -6
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
5555import { loadSystemPrompts } from './sysprompt.js';
5656import { fuzzySearchCategories } from './filters.js';
5757import { accountStorage } from './util/AccountStorage.js';
58+import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
5960export {
6061 loadPowerUserSettings,
@@ -257,6 +258,7 @@ let power_user = {
257258 },
258259
259260 reasoning: {
261+ name: DEFAULT_REASONING_TEMPLATE,
260262 auto_parse: false,
261263 add_to_prompts: false,
262264 auto_expand: false,
@@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
16241626 await loadInstructMode(data);
16251627 await loadContextSettings();
16261628 await loadSystemPrompts(data);
1629+ await loadReasoningTemplates(data);
16271630 loadMaxContextUnlocked();
16281631 switchWaifuMode();
16291632 switchSpoilerMode();
@@ -1985,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
19851988/**
19861989 * Renders a story string template with the given parameters.
19871990 * @param {object} params Template parameters.
1991+ * @param {object} [options] Additional options.
1992+ * @param {string} [options.customStoryString] Custom story string template.
1993+ * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
19881994 * @returns {string} The rendered story string.
19891995 */
1990-export function renderStoryString(params) {
1996+export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
19911997 try {
1998+ const storyString = customStoryString ?? power_user.context.story_string;
1999+ const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
2000+
19922001 // Validate and log possible warnings/errors
19932002 validateStoryString(power_user.context.story_stringstoryString, params);
19942003
19952004 // compile the story string template into a function, with no HTML escaping
19962005 const compiledTemplate = Handlebars.compile(power_user.context.story_stringstoryString, { noEscape: true });
19972006
19982007 // render the story string template with the given params
19992008 let output = compiledTemplate(params);
@@ -2006,7 +2015,7 @@ export function renderStoryString(params) {
20062015
20072016 // add a newline to the end of the story string if it doesn't have one
20082017 if (output.length > 0 && !output.endsWith('\n')) {
20092018 if (!power_user.instructinstructSettings.enabled || power_user.instructinstructSettings.wrap) {
20102019 output += '\n';
20112020 }
20122021 }
@@ -4229,14 +4238,13 @@ $(document).ready(() => {
42294238 ],
42304239 callback: (args, value) => {
42314240 const force = isTrueBoolean(String(args?.force ?? false));
4232- value = String(value ?? '').trim();
42334241
42344242 // Skip processing if no value and not forced
42354243 if (!force && !value) {
42364244 return power_user.user_prompt_bias;
42374245 }
42384246
42394247 power_user.user_prompt_bias = String(value ?? '');
42404248 $('#start_reply_with').val(power_user.user_prompt_bias);
42414249 saveSettingsDebounced();
42424250
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
2121import { instruct_presets } from './instruct-mode.js';
2222import { kai_settings } from './kai-settings.js';
2323import { convertNovelPreset } from './nai-settings.js';
2424import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
2525import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2626import { context_presets, getContextSettings, power_user } from './power-user.js';
2727import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
3838} from './textgen-settings.js';
3939import { download, parseJsonFile, waitUntilCondition } from './utils.js';
4040import { t } from './i18n.js';
41+import { reasoning_templates } from './reasoning.js';
4142
4243const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168169 },
169170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170171 },
172+ 'reasoning': {
173+ name: 'Reasoning Formatting',
174+ getData: () => {
175+ const manager = getPresetManager('reasoning');
176+ const name = manager.getSelectedPresetName();
177+ return manager.getPresetSettings(name);
178+ },
179+ setData: (data) => {
180+ const manager = getPresetManager('reasoning');
181+ const name = data.name;
182+ return manager.savePreset(name, data);
183+ },
184+ isValid: (data) => PresetManager.isPossiblyReasoningData(data),
185+ },
171186 };
172187
173188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191206 }
192207
208+ static isPossiblyReasoningData(data) {
209+ const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
210+ return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
211+ }
212+
193213 /**
194214 * Imports master settings from JSON data.
195215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228248 }
229249
250+ // 5. Reasoning Template
251+ if (this.isPossiblyReasoningData(data)) {
252+ toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
253+ return await getPresetManager('reasoning').savePreset(data.name, data);
254+ }
255+
230256 const validSections = [];
231257 for (const [key, section] of Object.entries(this.masterSections)) {
232258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478504 presets = system_prompts;
479505 preset_names = system_prompts.map(x => x.name);
480506 break;
507+ case 'reasoning':
508+ presets = reasoning_templates;
509+ preset_names = reasoning_templates.map(x => x.name);
510+ break;
481511 default:
482512 console.warn(`Unknown API ID ${api}`);
483513 }
@@ -490,7 +520,7 @@ class PresetManager {
490520 }
491521
492522 isAdvancedFormatting() {
493- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
523+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494524 }
495525
496526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554584 return sysprompt_preset;
555585 }
586+ case 'reasoning': {
587+ const reasoning_preset = structuredClone(power_user.reasoning);
588+ reasoning_preset['name'] = name || power_user.reasoning.preset;
589+ return reasoning_preset;
590+ }
556591 default:
557592 console.warn(`Unknown API ID ${apiId}`);
558593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599634 'include_reasoning',
600635 'global_banned_tokens',
601636 'send_banned_tokens',
637+
638+ // Reasoning exclusions
639+ 'auto_parse',
640+ 'add_to_prompts',
641+ 'auto_expand',
642+ 'show_hidden',
643+ 'max_additions',
602644 ];
603645 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604646
public/scripts/reasoning.js+179 -19
@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
77import { MacrosParser } from './macros.js';
88import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
99import { Popup } from './popup.js';
1010import { performFuzzySearch, power_user } from './power-user.js';
11+import { getPresetManager } from './preset-manager.js';
1112import { SlashCommand } from './slash-commands/SlashCommand.js';
1213import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
1314import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1415import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
1516import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1617import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
1718import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
19+
20+/**
21+ * @typedef {object} ReasoningTemplate
22+ * @property {string} name - The name of the template
23+ * @property {string} prefix - Reasoning prefix
24+ * @property {string} suffix - Reasoning suffix
25+ * @property {string} separator - Reasoning separator
26+ */
27+
28+/**
29+ * @type {ReasoningTemplate[]} List of reasoning templates
30+ */
31+export const reasoning_templates = [];
32+
33+export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34+
35+/**
36+ * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37+ * @readonly
38+ */
39+const UI = {
40+ $select: $('#reasoning_select'),
41+ $suffix: $('#reasoning_suffix'),
42+ $prefix: $('#reasoning_prefix'),
43+ $separator: $('#reasoning_separator'),
44+ $autoParse: $('#reasoning_auto_parse'),
45+ $autoExpand: $('#reasoning_auto_expand'),
46+ $showHidden: $('#reasoning_show_hidden'),
47+ $addToPrompts: $('#reasoning_add_to_prompts'),
48+ $maxAdditions: $('#reasoning_max_additions'),
49+};
1850
1951/**
2052 * Enum representing the type of the reasoning for a message (where it came from)
@@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
6193 mainApi = null,
6294 ignoreShowThoughts = false,
6395 textGenType = null,
6496 chatCompletionSource = null,
6597} = {}) {
6698 switch (mainApi ?? main_api) {
6799 case 'textgenerationwebui':
@@ -669,57 +701,102 @@ export class PromptReasoning {
669701}
670702
671703function loadReasoningSettings() {
672704 UI.$('#reasoning_add_to_prompts')addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
673705 UI.$('#reasoning_add_to_prompts')addToPrompts.on('change', function () {
674706 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
675707 saveSettingsDebounced();
676708 });
677709
678710 UI.$('#reasoning_prefix')prefix.val(power_user.reasoning.prefix);
679711 UI.$('#reasoning_prefix')prefix.on('input', function () {
680712 power_user.reasoning.prefix = String($(this).val());
681713 saveSettingsDebounced();
682714 });
683715
684716 UI.$('#reasoning_suffix')suffix.val(power_user.reasoning.suffix);
685717 UI.$('#reasoning_suffix')suffix.on('input', function () {
686718 power_user.reasoning.suffix = String($(this).val());
687719 saveSettingsDebounced();
688720 });
689721
690722 UI.$('#reasoning_separator')separator.val(power_user.reasoning.separator);
691723 UI.$('#reasoning_separator')separator.on('input', function () {
692724 power_user.reasoning.separator = String($(this).val());
693725 saveSettingsDebounced();
694726 });
695727
696728 UI.$('#reasoning_max_additions')maxAdditions.val(power_user.reasoning.max_additions);
697729 UI.$('#reasoning_max_additions')maxAdditions.on('input', function () {
698730 power_user.reasoning.max_additions = Number($(this).val());
699731 saveSettingsDebounced();
700732 });
701733
702734 UI.$('#reasoning_auto_parse')autoParse.prop('checked', power_user.reasoning.auto_parse);
703735 UI.$('#reasoning_auto_parse')autoParse.on('change', function () {
704736 power_user.reasoning.auto_parse = !!$(this).prop('checked');
705737 saveSettingsDebounced();
706738 });
707739
708740 UI.$('#reasoning_auto_expand')autoExpand.prop('checked', power_user.reasoning.auto_expand);
709741 UI.$('#reasoning_auto_expand')autoExpand.on('change', function () {
710742 power_user.reasoning.auto_expand = !!$(this).prop('checked');
711743 toggleReasoningAutoExpand();
712744 saveSettingsDebounced();
713745 });
714746 toggleReasoningAutoExpand();
715747
716748 UI.$('#reasoning_show_hidden')showHidden.prop('checked', power_user.reasoning.show_hidden);
717749 UI.$('#reasoning_show_hidden')showHidden.on('change', function () {
718750 power_user.reasoning.show_hidden = !!$(this).prop('checked');
719751 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
720752 saveSettingsDebounced();
721753 });
722754 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
755+
756+ UI.$select.on('change', async function () {
757+ const name = String($(this).val());
758+ const template = reasoning_templates.find(p => p.name === name);
759+ if (!template) {
760+ return;
761+ }
762+
763+ UI.$prefix.val(template.prefix);
764+ UI.$suffix.val(template.suffix);
765+ UI.$separator.val(template.separator);
766+
767+ power_user.reasoning.name = name;
768+ power_user.reasoning.prefix = template.prefix;
769+ power_user.reasoning.suffix = template.suffix;
770+ power_user.reasoning.separator = template.separator;
771+
772+ saveSettingsDebounced();
773+ });
774+}
775+
776+function selectReasoningTemplateCallback(args, name) {
777+ if (!name) {
778+ return power_user.reasoning.name ?? '';
779+ }
780+
781+ const quiet = isTrueBoolean(args?.quiet);
782+ const templateNames = reasoning_templates.map(preset => preset.name);
783+ let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
784+
785+ if (!foundName) {
786+ const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
787+
788+ if (result.length === 0) {
789+ !quiet && toastr.warning(`Reasoning template "${name}" not found`);
790+ return '';
791+ }
792+
793+ foundName = result[0].item;
794+ }
795+
796+ UI.$select.val(foundName).trigger('change');
797+ !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
798+ return foundName;
799+
723800}
724801
725802function registerReasoningSlashCommands() {
@@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
853930 : parsedReasoning.reasoning;
854931 },
855932 }));
933+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
934+ name: 'reasoning-template',
935+ aliases: ['reasoning-formatting', 'reasoning-preset'],
936+ callback: selectReasoningTemplateCallback,
937+ returns: 'template name',
938+ namedArgumentList: [
939+ SlashCommandNamedArgument.fromProps({
940+ name: 'quiet',
941+ description: 'Suppress the toast message on template change',
942+ typeList: [ARGUMENT_TYPE.BOOLEAN],
943+ defaultValue: 'false',
944+ enumList: commonEnumProviders.boolean('trueFalse')(),
945+ }),
946+ ],
947+ unnamedArgumentList: [
948+ SlashCommandArgument.fromProps({
949+ description: 'reasoning template name',
950+ typeList: [ARGUMENT_TYPE.STRING],
951+ enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
952+ }),
953+ ],
954+ helpString: `
955+ <div>
956+ Selects a reasoning template by name, using fuzzy search to find the closest match.
957+ Gets the current template if no name is provided.
958+ </div>
959+ <div>
960+ <strong>Example:</strong>
961+ <ul>
962+ <li>
963+ <pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
964+ </li>
965+ </ul>
966+ </div>
967+ `,
968+ }));
856969}
857970
858971function registerReasoningMacros() {
@@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
12121325 }
12131326}
12141327
1328+/**
1329+ * Loads reasoning templates from the settings data.
1330+ * @param {object} data Settings data
1331+ * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
1332+ * @returns {Promise<void>}
1333+ */
1334+export async function loadReasoningTemplates(data) {
1335+ if (data.reasoning !== undefined) {
1336+ reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
1337+ }
1338+
1339+ for (const template of reasoning_templates) {
1340+ $('<option>').val(template.name).text(template.name).appendTo(UI.$select);
1341+ }
1342+
1343+ // No template name, need to migrate
1344+ if (power_user.reasoning.name === undefined) {
1345+ const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
1346+ if (defaultTemplate) {
1347+ // If the reasoning settings were modified - migrate them to a custom template
1348+ if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
1349+ /** @type {ReasoningTemplate} */
1350+ const data = {
1351+ name: '[Migrated] Custom',
1352+ prefix: power_user.reasoning.prefix,
1353+ suffix: power_user.reasoning.suffix,
1354+ separator: power_user.reasoning.separator,
1355+ };
1356+ await getPresetManager('reasoning')?.savePreset(data.name, data);
1357+ power_user.reasoning.name = data.name;
1358+ } else {
1359+ power_user.reasoning.name = defaultTemplate.name;
1360+ }
1361+ } else {
1362+ // Template not found (deleted or content check skipped - leave blank)
1363+ power_user.reasoning.name = '';
1364+ }
1365+
1366+ saveSettingsDebounced();
1367+ }
1368+
1369+ UI.$select.val(power_user.reasoning.name);
1370+}
1371+
1372+/**
1373+ * Initializes reasoning settings and event handlers.
1374+ */
12151375export function initReasoning() {
12161376 loadReasoningSettings();
12171377 setReasoningEventHandlers();
public/scripts/samplerSelect.js+2 -19
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
99//import { getSortableDelay, onlyUnique } from './utils.js';
1010//import { getCfgPrompt } from './cfg-scale.js';
1111import { setting_names } from './textgen-settings.js';
12+import { renderTemplateAsync } from './templates.js';
1213
1314
1415const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
2526 const html = $(document.createElement('div'));
2627 html.attr('id', 'sampler_view_list')
2728 .addClass('flex-container flexFlowColumn');
28- html.append(`
29+ html.append(await renderTemplateAsync('samplerSelector'));
29- <div class="title_restorable flexFlowColumn alignItemsBaseline">
30- <div class="flex-container justifyCenter">
31- <h3>Sampler Select</h3>
32- <div class="flex-container alignItemsBaseline">
33- <div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
34- <i class="fa-solid fa-recycle"></i>
35- </div>
36- </div>
37- <!--<div class="flex-container alignItemsBaseline">
38- <div class="menu_button menu_button_icon" title="Create a new sampler">
39- <i class="fa-solid fa-plus"></i>
40- <span data-i18n="Create">Create</span>
41- </div>
42- </div>-->
43- </div>
44- <small>Here you can toggle the display of individual samplers. (WIP)</small>
45- </div>
46- <hr>`);
4730
4831 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
4932 const APISamplers = await listSamplers(main_api);
public/scripts/secrets.js+2 -1
@@ -1,5 +1,6 @@
11import { DOMPurify } from '../lib.js';
22import { callPopup, getRequestHeaders } from '../script.js';
3+import { t } from './i18n.js';
34
45export const SECRET_KEYS = {
56 HORDE: 'api_key_horde',
@@ -104,7 +105,7 @@ async function viewSecrets() {
104105 });
105106
106107 if (response.status == 403) {
107108 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');
108109 return;
109110 }
110111
public/scripts/st-context.js+4 -1
@@ -49,6 +49,7 @@ import {
4949 clearChat,
5050 unshallowCharacter,
5151 deleteLastMessage,
52+ getCharacterCardFields,
5253} from '../script.js';
5354import {
5455 extension_settings,
@@ -78,7 +79,7 @@ import { ToolManager } from './tool-calling.js';
7879import { accountStorage } from './util/AccountStorage.js';
7980import { timestampToMoment, uuidv4 } from './utils.js';
8081import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
8182import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
8283import { ChatCompletionService, TextCompletionService } from './custom-request.js';
8384import { ConnectionManagerRequestService } from './extensions/shared.js';
8485import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
@@ -189,6 +190,7 @@ export function getContext() {
189190 textCompletionSettings: textgenerationwebui_settings,
190191 powerUserSettings: power_user,
191192 getCharacters,
193+ getCharacterCardFields,
192194 uuidv4,
193195 humanizedDateTime,
194196 updateMessageBlock,
@@ -207,6 +209,7 @@ export function getContext() {
207209 saveWorldInfo,
208210 updateWorldInfoList,
209211 convertCharacterBook,
212+ getWorldInfoPrompt,
210213 CONNECT_API_MAP,
211214 getTextGenServer,
212215 extractMessageFromData,
public/scripts/templates/assistantNote.html+2 -2
@@ -1,9 +1,9 @@
11<div data-type="assistant_note">
22 <div>
33 <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>
44 <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
55 </div>
66 <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
77 <i class="fa-solid fa-file-export"></i>
88 </div>
99</div>
public/scripts/templates/samplerSelector.html+18 -0
@@ -0,0 +1,18 @@
1+<div class="title_restorable flexFlowColumn alignItemsBaseline">
2+ <div class="flex-container justifyCenter">
3+ <h3 data-i18n="Sampler Select">Sampler Select</h3>
4+ <div class="flex-container alignItemsBaseline">
5+ <div id="resetSelectedSamplers" class="menu_button menu_button_icon" data-i18n="[title]Reset custom sampler selection" title="Reset custom sampler selection">
6+ <i class="fa-solid fa-recycle"></i>
7+ </div>
8+ </div>
9+ <!--<div class="flex-container alignItemsBaseline">
10+ <div class="menu_button menu_button_icon" title="Create a new sampler">
11+ <i class="fa-solid fa-plus"></i>
12+ <span data-i18n="Create">Create</span>
13+ </div>
14+ </div>-->
15+ </div>
16+ <small data-i18n="Here you can toggle the display of individual samplers. (WIP)">Here you can toggle the display of individual samplers. (WIP)</small>
17+</div>
18+<hr>
18 \ No newline at end of file
public/scripts/world-info.js+23 -7
@@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
753753
754754/**
755755 * Gets the world info based on chat messages.
756756 * @param {string[]} chat - The chat messages to scan, in reverse order.
757757 * @param {number} maxContext - The maximum context size of the generation.
758758 * @param {boolean} isDryRun - If true, the function will not emit any events.
759- * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
759+ * @typedef {object} WIPromptResult
760+ * @property {string} worldInfoString - Complete world info string
761+ * @property {string} worldInfoBefore - World info that goes before the prompt
762+ * @property {string} worldInfoAfter - World info that goes after the prompt
763+ * @property {Array} worldInfoExamples - Array of example entries
764+ * @property {Array} worldInfoDepth - Array of depth entries
765+ * @property {Array} anBefore - Array of entries before Author's Note
766+ * @property {Array} anAfter - Array of entries after Author's Note
760767 * @returns {Promise<WIPromptResult>} The world info string and depth.
761768 */
762769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
778785 worldInfoAfter,
779786 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
780787 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
788+ anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
789+ anAfter: activatedWorldInfo.ANAfterEntries ?? [],
781790 };
782791}
783792
@@ -3862,7 +3871,14 @@ function parseDecorators(content) {
38623871 * @param {string[]} chat The chat messages to scan, in reverse order.
38633872 * @param {number} maxContext The maximum context size of the generation.
38643873 * @param {boolean} isDryRun Whether to perform a dry run.
3865- * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
3874+ * @typedef {object} WIActivated
3875+ * @property {string} worldInfoBefore The world info before the chat.
3876+ * @property {string} worldInfoAfter The world info after the chat.
3877+ * @property {any[]} EMEntries The entries for examples.
3878+ * @property {any[]} WIDepthEntries The depth entries.
3879+ * @property {any[]} ANBeforeEntries The entries before Author's Note.
3880+ * @property {any[]} ANAfterEntries The entries after Author's Note.
3881+ * @property {Set<any>} allActivatedEntries All entries.
38663882 * @returns {Promise<WIActivated>} The world info activated.
38673883 */
38683884export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +3922,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39063922 timedEffects.checkTimedEffects();
39073923
39083924 if (sortedEntries.length === 0) {
39093925 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
39103926 }
39113927
39123928 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4371,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
43554371 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
43564372 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574373
43584374 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
43594375}
43604376
43614377/**
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
4343 vectors: 'vectors',
4444 backups: 'backups',
4545 sysprompt: 'sysprompt',
46+ reasoning: 'reasoning',
4647});
4748
4849/**
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
4848 MOVING_UI: 'moving_ui',
4949 QUICK_REPLIES: 'quick_replies',
5050 SYSPROMPT: 'sysprompt',
51+ REASONING: 'reasoning',
5152};
5253
5354/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
6162 const presets = [];
6263
6364 for (const contentItem of contentIndex) {
6465 if (contentItem.type.endsWith('_preset') || contentItem.type === ['instruct' || contentItem.type ===, 'context', ||'sysprompt', 'reasoning'].includes(contentItem.type === 'sysprompt')) {
6566 contentItem.name = path.parse(contentItem.filename).name;
6667 contentItem.folder = getTargetByType(contentItem.type, directories);
6768 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299300 return directories.quickreplies;
300301 case CONTENT_TYPES.SYSPROMPT:
301302 return directories.sysprompt;
303+ case CONTENT_TYPES.REASONING:
304+ return directories.reasoning;
302305 default:
303306 return null;
304307 }
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
3030 return { folder: directories.context, extension: '.json' };
3131 case 'sysprompt':
3232 return { folder: directories.sysprompt, extension: '.json' };
33+ case 'reasoning':
34+ return { folder: directories.reasoning, extension: '.json' };
3335 default:
3436 return { folder: null, extension: null };
3537 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255255 const context = readAndParseFromDirectory(request.user.directories.context);
256256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257+ const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258259 response.send({
259260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272273 instruct,
273274 context,
274275 sysprompt,
276+ reasoning,
275277 enable_extensions: ENABLE_EXTENSIONS,
276278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277279 enable_accounts: ENABLE_ACCOUNTS,
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
9595 * @property {string} vectors - The directory where the vectors are stored
9696 * @property {string} backups - The directory where the backups are stored
9797 * @property {string} sysprompt - The directory where the system prompt data is stored
98+ * @property {string} reasoning - The directory where the reasoning templates are stored
9899 */
99100
100101/**