Merge branch 'staging' into integrity

c92ca8dbfbab1bd1d221ebf86680f05cc2591efd

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

38 files changed, +632 -134Showing whitespace changes
.github/readme.md+27 -12
@@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
192192
193##### Volume Mappings193##### Volume Mappings
194194
195* [config] - The directory where SillyTavern configuration files will be stored on your host machine195* `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
196* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine196* `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
197* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine197* `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
198* [extensions] - (optional) The directory where global UI extensions will be stored on your host machine198* `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
199199
200##### Port Mappings200##### Port Mappings
201201
202* [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.202* `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
204##### Additional Settings204##### 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
209#### Install command208#### Running the container
210209
2111. Open your Command Line2101. Open your Command Line
2122. Run the following command2112. 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
214SILLYTAVERN_VERSION="latest"
215PUBLIC_PORT="8000"
216CONFIG_PATH="./config"
217DATA_PATH="./data"
218PLUGINS_PATH="./plugins"
219EXTENSIONS_PATH="./extensions"
220
221docker 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
218### Building the image yourself233### Building the image yourself
219234
.github/workflows/pr-auto-manager.yml+1 -1
@@ -19,7 +19,7 @@ jobs:
19 - name: Label PR Size19 - name: Label PR Size
20 # Pull Request Size Labeler20 # Pull Request Size Labeler
21 # https://github.com/marketplace/actions/pull-request-size-labeler21 # https://github.com/marketplace/actions/pull-request-size-labeler
22 uses: codelytv/pr-size-labeler@v1.10.222 uses: codelytv/pr-size-labeler@v1.10.1
23 with:23 with:
24 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}24 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
25 xs_label: '🟩 ⬤○○○○'25 xs_label: '🟩 ⬤○○○○'
default/content/index.json+8 -0
@@ -786,5 +786,13 @@
786 {786 {
787 "filename": "presets/context/DeepSeek-V2.5.json",787 "filename": "presets/context/DeepSeek-V2.5.json",
788 "type": "context"788 "type": "context"
789 },
790 {
791 "filename": "presets/reasoning/DeepSeek.json",
792 "type": "reasoning"
793 },
794 {
795 "filename": "presets/reasoning/Blank.json",
796 "type": "reasoning"
789 }797 }
790]798]
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 @@
1957 <span data-i18n="Enable web search">Enable web search</span>1957 <span data-i18n="Enable web search">Enable web search</span>
1958 </label>1958 </label>
1959 <div class="flexBasis100p toggle-description justifyLeft">1959 <div class="flexBasis100p toggle-description justifyLeft">
1960 <span>1960 <span data-i18n="Use search capabilities provided by the backend.">
1961 Use search capabilities provided by the backend.1961 Use search capabilities provided by the backend.
1962 </span>1962 </span>
1963 </div>1963 </div>
@@ -2188,7 +2188,7 @@
2188 <input id="horde_trusted_workers_only" type="checkbox" />2188 <input id="horde_trusted_workers_only" type="checkbox" />
2189 <span data-i18n="Trusted workers only">Trusted workers only</span>2189 <span data-i18n="Trusted workers only">Trusted workers only</span>
2190 </label>2190 </label>
2191 <small id="adjustedHordeParams">Context: --, Response: --</small>2191 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
2192 <h4 data-i18n="API key">API key</h4>2192 <h4 data-i18n="API key">API key</h4>
2193 <small>2193 <small>
2194 <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>2194 <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 @@
3917 <summary data-i18n="Reasoning Formatting">3917 <summary data-i18n="Reasoning Formatting">
3918 Reasoning Formatting3918 Reasoning Formatting
3919 </summary>3919 </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>
3920 <div class="flex-container">3933 <div class="flex-container">
3921 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">3934 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
3922 <small data-i18n="Prefix">Prefix</small>3935 <small data-i18n="Prefix">Prefix</small>
@@ -6563,7 +6576,7 @@
6563 <div class="ch_name"></div>6576 <div class="ch_name"></div>
6564 <small class="ch_additional_info group_select_counter"></small>6577 <small class="ch_additional_info group_select_counter"></small>
6565 </div>6578 </div>
6566 <small class="character_name_block_sub_line">in this group</small>6579 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
6567 <i class='group_fav_icon fa-solid fa-star'></i>6580 <i class='group_fav_icon fa-solid fa-star'></i>
6568 <input class="ch_fav" value="" hidden />6581 <input class="ch_fav" value="" hidden />
6569 <div class="group_select_block_list ch_description"></div>6582 <div class="group_select_block_list ch_description"></div>
public/locales/ru-ru.json+157 -21
@@ -23,9 +23,8 @@
23 "Mirostat Mode": "Режим",23 "Mirostat Mode": "Режим",
24 "Mirostat Tau": "Tau",24 "Mirostat Tau": "Tau",
25 "Mirostat Eta": "Eta",25 "Mirostat Eta": "Eta",
26 "Variability parameter for Mirostat outputs": "Параметр изменчивости для выходных данных Mirostat.",26 "Variability parameter for Mirostat outputs": "Вариативность для выходных данных Mirostat.",
27 "Learning rate of Mirostat": "Скорость обучения Mirostat.",27 "Learning rate of Mirostat": "Скорость обучения Mirostat.",
28 "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
29 "Temperature Last": "Температура последней",28 "Temperature Last": "Температура последней",
30 "LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",29 "LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
31 "Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",30 "Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
60 "Add BOS Token": "Добавлять BOS-токен",59 "Add BOS Token": "Добавлять BOS-токен",
61 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",60 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
62 "Ban EOS Token": "Запретить EOS-токен",61 "Ban EOS Token": "Запретить EOS-токен",
63 "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременно",62 "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию самостоятельно (только при достижении лимита токенов)",
64 "Skip Special Tokens": "Пропускать спец. токены",63 "Skip Special Tokens": "Пропускать спец. токены",
65 "Beam search": "Поиск Beam",64 "Beam search": "Beam Search",
66 "Number of Beams": "Количество Beam",
67 "Length Penalty": "Штраф за длину",65 "Length Penalty": "Штраф за длину",
68 "Early Stopping": "Преждевременная остановка",66 "Early Stopping": "Прекращать сразу",
69 "Contrastive search": "Контрастный поиск",
70 "Penalty Alpha": "Penalty Alpha",67 "Penalty Alpha": "Penalty Alpha",
71 "Seed": "Зерно",68 "Seed": "Зерно",
72 "Epsilon Cutoff": "Epsilon Cutoff",69 "Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
89 "Text Completion presets": "Пресеты для Text Completion",86 "Text Completion presets": "Пресеты для Text Completion",
90 "Documentation on sampling parameters": "Документация по параметрам сэмплеров",87 "Documentation on sampling parameters": "Документация по параметрам сэмплеров",
91 "Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",88 "Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
92 "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаете, что делаете.",89 "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы понимаете, что делаете.",
93 "Wrap in Quotes": "Заключать в кавычки",90 "Wrap in Quotes": "Заключать в кавычки",
94 "Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",91 "Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
95 "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",92 "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@@ -109,7 +106,7 @@
109 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
110 "API key": "API-ключ",107 "API key": "API-ключ",
111 "Tabby API key": "Tabby API-ключ",108 "Tabby API key": "Tabby API-ключ",
112 "Get it here:": "Получить здесь:",109 "Get it here:": "Получите здесь:",
113 "Register": "Зарегистрироваться",110 "Register": "Зарегистрироваться",
114 "TogetherAI Model": "Модель TogetherAI",111 "TogetherAI Model": "Модель TogetherAI",
115 "Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",112 "Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
@@ -289,10 +286,10 @@
289 "Author's Note": "Заметки автора",286 "Author's Note": "Заметки автора",
290 "Replace empty message": "Заменять пустые сообщения",287 "Replace empty message": "Заменять пустые сообщения",
291 "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",288 "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
292 "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаете, что делаете",289 "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно понимаете, что делаете",
293 "Chat Completion Source": "Источник для Chat Completion",290 "Chat Completion Source": "Источник для Chat Completion",
294 "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",291 "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
295 "Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",292 "Review the Privacy statement": "Ознакомьтесь с заявлением о конфиденциальности",
296 "Trusted workers only": "Только доверенные рабочие машины",293 "Trusted workers only": "Только доверенные рабочие машины",
297 "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",294 "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
298 "-- Horde models not loaded --": "--Модель Horde не загружена--",295 "-- Horde models not loaded --": "--Модель Horde не загружена--",
@@ -699,7 +696,7 @@
699 "Aggressive": "Агрессивный",696 "Aggressive": "Агрессивный",
700 "Very aggressive": "Очень агрессивный",697 "Very aggressive": "Очень агрессивный",
701 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",698 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
702 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вклад в свои свободные GPU-циклы в орду",699 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
703 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
704 "Load koboldcpp order": "Загрузить порядок из koboldcpp",701 "Load koboldcpp order": "Загрузить порядок из koboldcpp",
705 "Use Google Tokenizer": "Использовать токенизатор Google",702 "Use Google Tokenizer": "Использовать токенизатор Google",
@@ -744,7 +741,7 @@
744 "Last Assistant Prefix": "Последний префикс ассистента",741 "Last Assistant Prefix": "Последний префикс ассистента",
745 "System Instruction Prefix": "Префикс системной инструкции",742 "System Instruction Prefix": "Префикс системной инструкции",
746 "User Filler Message": "Принудительное сообщение пользователя",743 "User Filler Message": "Принудительное сообщение пользователя",
747 "Permanent": "перманентных",744 "Permanent": "постоянных",
748 "Alt. Greetings": "Др. варианты",745 "Alt. Greetings": "Др. варианты",
749 "Smooth Streaming": "Плавный стриминг",746 "Smooth Streaming": "Плавный стриминг",
750 "Save checkpoint": "Сохранить чекпоинт",747 "Save checkpoint": "Сохранить чекпоинт",
@@ -1227,7 +1224,6 @@
1227 "JSON-serialized array of strings.": "Список строк в формате JSON.",1224 "JSON-serialized array of strings.": "Список строк в формате JSON.",
1228 "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.",1225 "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.",
1229 "Helpful tip coming soon.": "Подсказку скоро добавим.",1226 "Helpful tip coming soon.": "Подсказку скоро добавим.",
1230 "Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
1231 "Speculative Ngram": "Speculative Ngram",1227 "Speculative Ngram": "Speculative Ngram",
1232 "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.",1228 "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.",
1233 "Spaces Between Special Tokens": "Spaces Between Special Tokens",1229 "Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1734,7 +1730,7 @@
1734 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",1730 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
1735 "Save and Update": "Сохранить и обновить",1731 "Save and Update": "Сохранить и обновить",
1736 "Profile name:": "Название профиля:",1732 "Profile name:": "Название профиля:",
1737 "API returned an error": "API вернуло ошибку",1733 "API returned an error": "API ответило ошибкой",
1738 "Failed to save preset": "Не удалось сохранить пресет",1734 "Failed to save preset": "Не удалось сохранить пресет",
1739 "Preset name should be unique.": "Название пресета должно быть уникальным.",1735 "Preset name should be unique.": "Название пресета должно быть уникальным.",
1740 "Invalid file": "Невалидный файл",1736 "Invalid file": "Невалидный файл",
@@ -1756,8 +1752,7 @@
1756 "dot quota_error": "имеется достаточно кредитов.",1752 "dot quota_error": "имеется достаточно кредитов.",
1757 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",1753 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
1758 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",1754 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",
1759 "Window.ai returned an error": "Window.ai вернул ошибку",1755 "Window.ai returned an error": "Window.ai ответил ошибкой",
1760 "Get it here:": "Загрузите здесь:",
1761 "Extension is not installed": "Расширение не установлено",1756 "Extension is not installed": "Расширение не установлено",
1762 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",1757 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
1763 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",1758 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1861,7 @@
1866 "Group Chat could not be saved": "Не удалось сохранить групповой чат",1861 "Group Chat could not be saved": "Не удалось сохранить групповой чат",
1867 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",1862 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
1868 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",1863 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",
1869 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",1864 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажи закончат печатать.",
1870 "Delete the group?": "Удалить группу?",1865 "Delete the group?": "Удалить группу?",
1871 "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.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",1866 "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.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
1872 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",1867 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1992,7 @@
1997 "Default persona deleted": "Удалена персона по умолчанию",1992 "Default persona deleted": "Удалена персона по умолчанию",
1998 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",1993 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
1999 "Persona deleted": "Персона удалена",1994 "Persona deleted": "Персона удалена",
2000 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",1995 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо присвоить имя.",
2001 "Persona name not set": "У персоны отсутствует имя",1996 "Persona name not set": "У персоны отсутствует имя",
2002 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",1997 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
2003 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",1998 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2203,5 +2198,146 @@
2203 "Input:": "Входные данные:",2198 "Input:": "Входные данные:",
2204 "Tokenized text:": "Токенизированный текст:",2199 "Tokenized text:": "Токенизированный текст:",
2205 "Token IDs:": "Идентификаторы токенов:",2200 "Token IDs:": "Идентификаторы токенов:",
2206 "Tokens:": "Токенов:"2201 "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": "Работает"
2207}2343}
public/script.js+69 -9
@@ -1144,7 +1144,7 @@ export async function clearItemizedPrompts() {
1144async function getStatusHorde() {1144async function getStatusHorde() {
1145 try {1145 try {
1146 const hordeStatus = await checkHordeStatus();1146 const hordeStatus = await checkHordeStatus();
1147 setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');1147 setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection');
1148 }1148 }
1149 catch {1149 catch {
1150 setOnlineStatus('no_connection');1150 setOnlineStatus('no_connection');
@@ -1211,7 +1211,7 @@ async function getStatusTextgen() {
1211 }1211 }
12121212
1213 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {1213 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
1214 setOnlineStatus('Status check bypassed');1214 setOnlineStatus(t`Status check bypassed`);
1215 return resultCheckStatus();1215 return resultCheckStatus();
1216 }1216 }
12171217
@@ -1236,7 +1236,7 @@ async function getStatusTextgen() {
1236 setOnlineStatus(textgen_settings.togetherai_model);1236 setOnlineStatus(textgen_settings.togetherai_model);
1237 } else if (textgen_settings.type === textgen_types.OLLAMA) {1237 } else if (textgen_settings.type === textgen_types.OLLAMA) {
1238 loadOllamaModels(data?.data);1238 loadOllamaModels(data?.data);
1239 setOnlineStatus(textgen_settings.ollama_model || 'Connected');1239 setOnlineStatus(textgen_settings.ollama_model || t`Connected`);
1240 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {1240 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
1241 loadInfermaticAIModels(data?.data);1241 loadInfermaticAIModels(data?.data);
1242 setOnlineStatus(textgen_settings.infermaticai_model);1242 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1260,7 +1260,7 @@ async function getStatusTextgen() {
1260 setOnlineStatus(textgen_settings.tabby_model || data?.result);1260 setOnlineStatus(textgen_settings.tabby_model || data?.result);
1261 } else if (textgen_settings.type === textgen_types.GENERIC) {1261 } else if (textgen_settings.type === textgen_types.GENERIC) {
1262 loadGenericModels(data?.data);1262 loadGenericModels(data?.data);
1263 setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');1263 setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`);
1264 } else {1264 } else {
1265 setOnlineStatus(data?.result);1265 setOnlineStatus(data?.result);
1266 }1266 }
@@ -6277,7 +6277,6 @@ export function syncMesToSwipe(messageId = null) {
6277 }6277 }
62786278
6279 const targetMessage = chat[targetMessageId];6279 const targetMessage = chat[targetMessageId];
6280
6281 if (!targetMessage) {6280 if (!targetMessage) {
6282 return false;6281 return false;
6283 }6282 }
@@ -6311,6 +6310,68 @@ export function syncMesToSwipe(messageId = null) {
6311}6310}
63126311
6313/**6312/**
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 */
6321export 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/**
6314 * Saves the image to the message object.6375 * Saves the image to the message object.
6315 * @param {ParsedImage} img Image object6376 * @param {ParsedImage} img Image object
6316 * @param {object} mes Chat message object6377 * @param {object} mes Chat message object
@@ -8342,10 +8403,9 @@ export async function deleteSwipe(swipeId = null) {
8342 lastMessage.swipe_info.splice(swipeId, 1);8403 lastMessage.swipe_info.splice(swipeId, 1);
8343 }8404 }
83448405
8345 // Select the next swip, or the one before if it was the last one8406 // Select the next swipe, or the one before if it was the last one
8346 const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);8407 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
8350 await saveChatConditional();8410 await saveChatConditional();
8351 await reloadCurrentChat();8411 await reloadCurrentChat();
@@ -10477,7 +10537,7 @@ jQuery(async function () {
10477 e.stopPropagation();10537 e.stopPropagation();
10478 chat_file_for_del = $(this).attr('file_name');10538 chat_file_for_del = $(this).attr('file_name');
10479 console.debug('detected cross click for' + chat_file_for_del);10539 console.debug('detected cross click for' + chat_file_for_del);
10480 callPopup('<h3>Delete the Chat File?</h3>', 'del_chat');10540 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
10481 });10541 });
1048210542
10483 $('#advanced_div').click(function () {10543 $('#advanced_div').click(function () {
public/scripts/backgrounds.js+2 -1
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
5import { SlashCommand } from './slash-commands/SlashCommand.js';5import { SlashCommand } from './slash-commands/SlashCommand.js';
6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
7import { flashHighlight, stringFormat } from './utils.js';7import { flashHighlight, stringFormat } from './utils.js';
8import { t } from './i18n.js';
89
9const BG_METADATA_KEY = 'custom_background';10const BG_METADATA_KEY = 'custom_background';
10const LIST_METADATA_KEY = 'chat_backgrounds';11const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
243 const fileExtension = oldBg.split('.').pop();244 const fileExtension = oldBg.split('.').pop();
244 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;245 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
245 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');246 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
246 const newBgExtensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', oldBgExtensionless);247 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
247248
248 if (!newBgExtensionless) {249 if (!newBgExtensionless) {
249 console.debug('no new_bg_extensionless');250 console.debug('no new_bg_extensionless');
public/scripts/bookmarks.js+1 -1
@@ -358,7 +358,7 @@ export async function convertSoloToGroupChat() {
358 // Click on the freshly selected group to open it358 // Click on the freshly selected group to open it
359 await openGroupById(group.id);359 await openGroupById(group.id);
360360
361 toastr.success('The chat has been successfully converted!');361 toastr.success(t`The chat has been successfully converted!`);
362}362}
363363
364/**364/**
public/scripts/custom-request.js+3 -1
@@ -41,6 +41,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types
41 * @property {string} chat_completion_source - Source provider for chat completion41 * @property {string} chat_completion_source - Source provider for chat completion
42 * @property {number} max_tokens - Maximum number of tokens to generate42 * @property {number} max_tokens - Maximum number of tokens to generate
43 * @property {number} [temperature] - Optional temperature parameter for response randomness43 * @property {number} [temperature] - Optional temperature parameter for response randomness
44 * @property {string} [custom_url] - Optional custom URL for chat completion
44 */45 */
4546
46/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */47/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
@@ -264,7 +265,7 @@ export class ChatCompletionService {
264 * @param {ChatCompletionPayload} custom265 * @param {ChatCompletionPayload} custom
265 * @returns {ChatCompletionPayload}266 * @returns {ChatCompletionPayload}
266 */267 */
267 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {268 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
268 const payload = {269 const payload = {
269 ...props,270 ...props,
270 messages,271 messages,
@@ -272,6 +273,7 @@ export class ChatCompletionService {
272 chat_completion_source,273 chat_completion_source,
273 max_tokens,274 max_tokens,
274 temperature,275 temperature,
276 custom_url,
275 stream: false,277 stream: false,
276 };278 };
277279
public/scripts/extensions/assets/index.js+1 -1
@@ -424,7 +424,7 @@ jQuery(async () => {
424 installHintButton.on('click', async function () {424 installHintButton.on('click', async function () {
425 const installButton = $('#third_party_extension_button');425 const installButton = $('#third_party_extension_button');
426 flashHighlight(installButton, 5000);426 flashHighlight(installButton, 5000);
427 toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');427 toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
428 });428 });
429429
430 const connectButton = windowHtml.find('#assets-connect-button');430 const connectButton = windowHtml.find('#assets-connect-button');
public/scripts/extensions/attachments/attach-button.html+1 -1
@@ -1,4 +1,4 @@
1<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">1<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.">
2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
3 <span data-i18n="Attach a File">Attach a File</span>3 <span data-i18n="Attach a File">Attach a File</span>
4</div>4</div>
public/scripts/extensions/connection-manager/index.js+4 -0
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
39 'proxy',39 'proxy',
40 'stop-strings',40 'stop-strings',
41 'start-reply-with',41 'start-reply-with',
42 'reasoning-template',
42];43];
4344
44const TC_COMMANDS = [45const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
54 'tokenizer',55 'tokenizer',
55 'stop-strings',56 'stop-strings',
56 'start-reply-with',57 'start-reply-with',
58 'reasoning-template',
57];59];
5860
59const FANCY_NAMES = {61const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
70 'tokenizer': 'Tokenizer',72 'tokenizer': 'Tokenizer',
71 'stop-strings': 'Custom Stopping Strings',73 'stop-strings': 'Custom Stopping Strings',
72 'start-reply-with': 'Start Reply With',74 'start-reply-with': 'Start Reply With',
75 'reasoning-template': 'Reasoning Template',
73};76};
7477
75/**78/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154 * @property {string} [tokenizer] Tokenizer157 * @property {string} [tokenizer] Tokenizer
155 * @property {string} [stop-strings] Custom Stopping Strings158 * @property {string} [stop-strings] Custom Stopping Strings
156 * @property {string} [start-reply-with] Start Reply With159 * @property {string} [start-reply-with] Start Reply With
160 * @property {string} [reasoning-template] Reasoning Template
157 * @property {string[]} [exclude] Commands to exclude161 * @property {string[]} [exclude] Commands to exclude
158 */162 */
159163
public/scripts/extensions/expressions/index.js+1 -1
@@ -2154,7 +2154,7 @@ function migrateSettings() {
2154 imgElement.src = '';2154 imgElement.src = '';
2155 }2155 }
21562156
2157 setExpressionOverrideHtml();2157 setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582158
2159 if (isVisualNovelMode()) {2159 if (isVisualNovelMode()) {
2160 $('#visual-novel-wrapper').empty();2160 $('#visual-novel-wrapper').empty();
public/scripts/extensions/regex/editor.html+2 -2
@@ -19,7 +19,7 @@
19 <div id="regex_info_block_wrapper">19 <div id="regex_info_block_wrapper">
20 <div id="regex_info_block" class="info-block"></div>20 <div id="regex_info_block" class="info-block"></div>
21 <a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">21 <a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
22 <i class="fa-solid fa-circle-info" title="Click here to learn more about regex flags."></i>22 <i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
23 </a>23 </a>
24 </div>24 </div>
2525
@@ -147,7 +147,7 @@
147 </label>147 </label>
148 <span>148 <span>
149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
150 <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>150 <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>
151 </span>151 </span>
152 <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.">152 <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.">
153 <input type="checkbox" name="only_format_display" />153 <input type="checkbox" name="only_format_display" />
public/scripts/extensions/regex/index.js+1 -1
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
398 for (const script of scripts) {398 for (const script of scripts) {
399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
400 if (script.disabled) {400 if (script.disabled) {
401 toastr.warning(`Regex script "${scriptName}" is disabled.`);401 toastr.warning(t`Regex script "${scriptName}" is disabled.`);
402 return value;402 return value;
403 }403 }
404404
public/scripts/extensions/shared.js+1 -0
@@ -323,6 +323,7 @@ export class ConnectionManagerRequestService {
323 max_tokens: maxTokens,323 max_tokens: maxTokens,
324 model: profile.model,324 model: profile.model,
325 chat_completion_source: selectedApiMap.source,325 chat_completion_source: selectedApiMap.source,
326 custom_url: profile['api-url'],
326 }, {327 }, {
327 presetName: includePreset ? profile.preset : undefined,328 presetName: includePreset ? profile.preset : undefined,
328 }, extractData);329 }, extractData);
public/scripts/extensions/tts/index.js+2 -2
@@ -1207,8 +1207,8 @@ jQuery(async function () {
1207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);1207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
1208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);1208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
1209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);1209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
1210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);1210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
1211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);1211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
1212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1213 name: 'speak',1213 name: 'speak',
1214 callback: async (args, value) => {1214 callback: async (args, value) => {
public/scripts/group-chats.js+1 -1
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
696 template.find('.group_fav_icon').css('display', 'none');696 template.find('.group_fav_icon').css('display', 'none');
697 template.addClass(group.fav ? 'is_fav' : '');697 template.addClass(group.fav ? 'is_fav' : '');
698 template.find('.ch_fav').val(group.fav);698 template.find('.ch_fav').val(group.fav);
699 template.find('.group_select_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);699 template.find('.group_select_counter').text(count + ' ' + (count != 1 ? t`characters` : t`character`));
700 template.find('.group_select_block_list').text(namesList.join(', '));700 template.find('.group_select_block_list').text(namesList.join(', '));
701701
702 // Display inline tags702 // Display inline tags
public/scripts/horde.js+4 -3
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
10import { delay } from './utils.js';10import { delay } from './utils.js';
11import { isMobile } from './RossAscends-mods.js';11import { isMobile } from './RossAscends-mods.js';
12import { autoSelectInstructPreset } from './instruct-mode.js';12import { autoSelectInstructPreset } from './instruct-mode.js';
13import { t } from './i18n.js';
1314
14export {15export {
15 horde_settings,16 horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
169 }170 }
170 }171 }
171 console.log(maxContextLength, maxLength);172 console.log(maxContextLength, maxLength);
172 $('#adjustedHordeParams').text(`Context: ${maxContextLength}, Response: ${maxLength}`);173 $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
173 return { maxContextLength, maxLength };174 return { maxContextLength, maxLength };
174}175}
175176
@@ -177,7 +178,7 @@ function setContextSizePreview() {
177 if (horde_settings.models.length) {178 if (horde_settings.models.length) {
178 adjustHordeGenerationParams(max_context, amount_gen);179 adjustHordeGenerationParams(max_context, amount_gen);
179 } else {180 } else {
180 $('#adjustedHordeParams').text('Context: --, Response: --');181 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
181 }182 }
182}183}
183184
@@ -404,7 +405,7 @@ jQuery(function () {
404 if (horde_settings.models.length) {405 if (horde_settings.models.length) {
405 adjustHordeGenerationParams(max_context, amount_gen);406 adjustHordeGenerationParams(max_context, amount_gen);
406 } else {407 } else {
407 $('#adjustedHordeParams').text('Context: --, Response: --');408 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
408 }409 }
409410
410 saveSettingsDebounced();411 saveSettingsDebounced();
public/scripts/instruct-mode.js+9 -6
@@ -398,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
398/**398/**
399 * Formats instruct mode system prompt.399 * Formats instruct mode system prompt.
400 * @param {string} systemPrompt System prompt string.400 * @param {string} systemPrompt System prompt string.
401 * @param {InstructSettings} customInstruct Custom instruct mode settings.
401 * @returns {string} Formatted instruct mode system prompt.402 * @returns {string} Formatted instruct mode system prompt.
402 */403 */
403export function formatInstructModeSystemPrompt(systemPrompt) {404export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
404 if (!systemPrompt) {405 if (!systemPrompt) {
405 return '';406 return '';
406 }407 }
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
410 if (power_user.instruct.system_sequence_prefix) {413 if (instruct.system_sequence_prefix) {
411 // TODO: Replace with a proper 'System' prompt entity name input414 // TODO: Replace with a proper 'System' prompt entity name input
412 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');415 const prefix = instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
413 systemPrompt = prefix + separator + systemPrompt;416 systemPrompt = prefix + separator + systemPrompt;
414 }417 }
415418
416 if (power_user.instruct.system_sequence_suffix) {419 if (instruct.system_sequence_suffix) {
417 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;420 systemPrompt = systemPrompt + separator + instruct.system_sequence_suffix;
418 }421 }
419422
420 return systemPrompt;423 return systemPrompt;
public/scripts/openai.js+10 -8
@@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705 return result;705 return result;
706}706}
707707
708function formatWorldInfo(value) {708export function formatWorldInfo(value, { wiFormat = null } = {}) {
709 if (!value) {709 if (!value) {
710 return '';710 return '';
711 }711 }
712712
713 if (!oai_settings.wi_format.trim()) {713 const format = wiFormat ?? oai_settings.wi_format;
714
715 if (!format.trim()) {
714 return value;716 return value;
715 }717 }
716718
717 return stringFormat(oai_settings.wi_format, value);719 return stringFormat(format, value);
718}720}
719721
720/**722/**
@@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952 * @param {number} position - Prompt position in the extensions object.954 * @param {number} position - Prompt position in the extensions object.
953 * @returns {string|false} - The prompt position for prompt collection.955 * @returns {string|false} - The prompt position for prompt collection.
954 */956 */
955function getPromptPosition(position) {957export function getPromptPosition(position) {
956 if (position == extension_prompt_types.BEFORE_PROMPT) {958 if (position == extension_prompt_types.BEFORE_PROMPT) {
957 return 'start';959 return 'start';
958 }960 }
@@ -969,7 +971,7 @@ function getPromptPosition(position) {
969 * @param {number} role Role of the prompt.971 * @param {number} role Role of the prompt.
970 * @returns {string} Mapped role.972 * @returns {string} Mapped role.
971 */973 */
972function getPromptRole(role) {974export function getPromptRole(role) {
973 switch (role) {975 switch (role) {
974 case extension_prompt_roles.SYSTEM:976 case extension_prompt_roles.SYSTEM:
975 return 'system';977 return 'system';
@@ -3476,7 +3478,7 @@ async function getStatusOpen() {
3476 let status;3478 let status;
34773479
3478 if ('ai' in window) {3480 if ('ai' in window) {
3479 status = 'Valid';3481 status = t`Valid`;
3480 }3482 }
3481 else {3483 else {
3482 showWindowExtensionError();3484 showWindowExtensionError();
@@ -3525,7 +3527,7 @@ async function getStatusOpen() {
35253527
3526 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;3528 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;
3527 if (canBypass) {3529 if (canBypass) {
3528 setOnlineStatus('Status check bypassed');3530 setOnlineStatus(t`Status check bypassed`);
3529 }3531 }
35303532
3531 try {3533 try {
@@ -3547,7 +3549,7 @@ async function getStatusOpen() {
3547 saveModelList(responseData.data);3549 saveModelList(responseData.data);
3548 }3550 }
3549 if (!('error' in responseData)) {3551 if (!('error' in responseData)) {
3550 setOnlineStatus('Valid');3552 setOnlineStatus(t`Valid`);
3551 }3553 }
3552 } catch (error) {3554 } catch (error) {
3553 console.error(error);3555 console.error(error);
public/scripts/personas.js+1 -1
@@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800 chat_metadata['persona'] = user_avatar;800 chat_metadata['persona'] = user_avatar;
801 console.log(`Auto locked persona to ${user_avatar}`);801 console.log(`Auto locked persona to ${user_avatar}`);
802 if (toastPersonaNameChange && power_user.persona_show_notifications) {802 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803 toastr.success(`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);803 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804 }804 }
805 saveMetadataDebounced();805 saveMetadataDebounced();
806 updatePersonaUIStates();806 updatePersonaUIStates();
public/scripts/power-user.js+14 -6
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
55import { loadSystemPrompts } from './sysprompt.js';55import { loadSystemPrompts } from './sysprompt.js';
56import { fuzzySearchCategories } from './filters.js';56import { fuzzySearchCategories } from './filters.js';
57import { accountStorage } from './util/AccountStorage.js';57import { accountStorage } from './util/AccountStorage.js';
58import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
59export {60export {
60 loadPowerUserSettings,61 loadPowerUserSettings,
@@ -257,6 +258,7 @@ let power_user = {
257 },258 },
258259
259 reasoning: {260 reasoning: {
261 name: DEFAULT_REASONING_TEMPLATE,
260 auto_parse: false,262 auto_parse: false,
261 add_to_prompts: false,263 add_to_prompts: false,
262 auto_expand: false,264 auto_expand: false,
@@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
1624 await loadInstructMode(data);1626 await loadInstructMode(data);
1625 await loadContextSettings();1627 await loadContextSettings();
1626 await loadSystemPrompts(data);1628 await loadSystemPrompts(data);
1629 await loadReasoningTemplates(data);
1627 loadMaxContextUnlocked();1630 loadMaxContextUnlocked();
1628 switchWaifuMode();1631 switchWaifuMode();
1629 switchSpoilerMode();1632 switchSpoilerMode();
@@ -1985,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
1985/**1988/**
1986 * Renders a story string template with the given parameters.1989 * Renders a story string template with the given parameters.
1987 * @param {object} params Template parameters.1990 * @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.
1988 * @returns {string} The rendered story string.1994 * @returns {string} The rendered story string.
1989 */1995 */
1990export function renderStoryString(params) {1996export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
1991 try {1997 try {
1998 const storyString = customStoryString ?? power_user.context.story_string;
1999 const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
2000
1992 // Validate and log possible warnings/errors2001 // Validate and log possible warnings/errors
1993 validateStoryString(power_user.context.story_string, params);2002 validateStoryString(storyString, params);
19942003
1995 // compile the story string template into a function, with no HTML escaping2004 // compile the story string template into a function, with no HTML escaping
1996 const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });2005 const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
19972006
1998 // render the story string template with the given params2007 // render the story string template with the given params
1999 let output = compiledTemplate(params);2008 let output = compiledTemplate(params);
@@ -2006,7 +2015,7 @@ export function renderStoryString(params) {
20062015
2007 // add a newline to the end of the story string if it doesn't have one2016 // add a newline to the end of the story string if it doesn't have one
2008 if (output.length > 0 && !output.endsWith('\n')) {2017 if (output.length > 0 && !output.endsWith('\n')) {
2009 if (!power_user.instruct.enabled || power_user.instruct.wrap) {2018 if (!instructSettings.enabled || instructSettings.wrap) {
2010 output += '\n';2019 output += '\n';
2011 }2020 }
2012 }2021 }
@@ -4229,14 +4238,13 @@ $(document).ready(() => {
4229 ],4238 ],
4230 callback: (args, value) => {4239 callback: (args, value) => {
4231 const force = isTrueBoolean(String(args?.force ?? false));4240 const force = isTrueBoolean(String(args?.force ?? false));
4232 value = String(value ?? '').trim();
42334241
4234 // Skip processing if no value and not forced4242 // Skip processing if no value and not forced
4235 if (!force && !value) {4243 if (!force && !value) {
4236 return power_user.user_prompt_bias;4244 return power_user.user_prompt_bias;
4237 }4245 }
42384246
4239 power_user.user_prompt_bias = value;4247 power_user.user_prompt_bias = String(value ?? '');
4240 $('#start_reply_with').val(power_user.user_prompt_bias);4248 $('#start_reply_with').val(power_user.user_prompt_bias);
4241 saveSettingsDebounced();4249 saveSettingsDebounced();
42424250
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
21import { instruct_presets } from './instruct-mode.js';21import { instruct_presets } from './instruct-mode.js';
22import { kai_settings } from './kai-settings.js';22import { kai_settings } from './kai-settings.js';
23import { convertNovelPreset } from './nai-settings.js';23import { convertNovelPreset } from './nai-settings.js';
24import { openai_settings, openai_setting_names, oai_settings } from './openai.js';24import { openai_settings, openai_setting_names } from './openai.js';
25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
26import { context_presets, getContextSettings, power_user } from './power-user.js';26import { context_presets, getContextSettings, power_user } from './power-user.js';
27import { SlashCommand } from './slash-commands/SlashCommand.js';27import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
38} from './textgen-settings.js';38} from './textgen-settings.js';
39import { download, parseJsonFile, waitUntilCondition } from './utils.js';39import { download, parseJsonFile, waitUntilCondition } from './utils.js';
40import { t } from './i18n.js';40import { t } from './i18n.js';
41import { reasoning_templates } from './reasoning.js';
4142
42const presetManagers = {};43const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168 },169 },
169 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170 },171 },
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 },
171 };186 };
172187
173 static isPossiblyInstructData(data) {188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191 }206 }
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
193 /**213 /**
194 * Imports master settings from JSON data.214 * Imports master settings from JSON data.
195 * @param {object} data Data to import215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228 }248 }
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
230 const validSections = [];256 const validSections = [];
231 for (const [key, section] of Object.entries(this.masterSections)) {257 for (const [key, section] of Object.entries(this.masterSections)) {
232 if (key in data && section.isValid(data[key])) {258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478 presets = system_prompts;504 presets = system_prompts;
479 preset_names = system_prompts.map(x => x.name);505 preset_names = system_prompts.map(x => x.name);
480 break;506 break;
507 case 'reasoning':
508 presets = reasoning_templates;
509 preset_names = reasoning_templates.map(x => x.name);
510 break;
481 default:511 default:
482 console.warn(`Unknown API ID ${api}`);512 console.warn(`Unknown API ID ${api}`);
483 }513 }
@@ -490,7 +520,7 @@ class PresetManager {
490 }520 }
491521
492 isAdvancedFormatting() {522 isAdvancedFormatting() {
493 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';523 return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494 }524 }
495525
496 updateList(name, preset) {526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553 sysprompt_preset['name'] = name || power_user.sysprompt.preset;583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554 return sysprompt_preset;584 return sysprompt_preset;
555 }585 }
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 }
556 default:591 default:
557 console.warn(`Unknown API ID ${apiId}`);592 console.warn(`Unknown API ID ${apiId}`);
558 return {};593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599 'include_reasoning',634 'include_reasoning',
600 'global_banned_tokens',635 'global_banned_tokens',
601 'send_banned_tokens',636 'send_banned_tokens',
637
638 // Reasoning exclusions
639 'auto_parse',
640 'add_to_prompts',
641 'auto_expand',
642 'show_hidden',
643 'max_additions',
602 ];644 ];
603 const settings = Object.assign({}, getSettingsByApiId(this.apiId));645 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';
7import { MacrosParser } from './macros.js';7import { MacrosParser } from './macros.js';
8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
9import { Popup } from './popup.js';9import { Popup } from './popup.js';
10import { power_user } from './power-user.js';10import { performFuzzySearch, power_user } from './power-user.js';
11import { getPresetManager } from './preset-manager.js';
11import { SlashCommand } from './slash-commands/SlashCommand.js';12import { SlashCommand } from './slash-commands/SlashCommand.js';
12import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';13import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
13import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';14import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
14import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';15import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
15import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';16import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
16import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';17import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
17import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';18import { 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 */
31export const reasoning_templates = [];
32
33export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34
35/**
36 * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37 * @readonly
38 */
39const 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
19/**51/**
20 * Enum representing the type of the reasoning for a message (where it came from)52 * Enum representing the type of the reasoning for a message (where it came from)
@@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
61 mainApi = null,93 mainApi = null,
62 ignoreShowThoughts = false,94 ignoreShowThoughts = false,
63 textGenType = null,95 textGenType = null,
64 chatCompletionSource = null96 chatCompletionSource = null,
65} = {}) {97} = {}) {
66 switch (mainApi ?? main_api) {98 switch (mainApi ?? main_api) {
67 case 'textgenerationwebui':99 case 'textgenerationwebui':
@@ -669,57 +701,102 @@ export class PromptReasoning {
669}701}
670702
671function loadReasoningSettings() {703function loadReasoningSettings() {
672 $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);704 UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
673 $('#reasoning_add_to_prompts').on('change', function () {705 UI.$addToPrompts.on('change', function () {
674 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');706 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
675 saveSettingsDebounced();707 saveSettingsDebounced();
676 });708 });
677709
678 $('#reasoning_prefix').val(power_user.reasoning.prefix);710 UI.$prefix.val(power_user.reasoning.prefix);
679 $('#reasoning_prefix').on('input', function () {711 UI.$prefix.on('input', function () {
680 power_user.reasoning.prefix = String($(this).val());712 power_user.reasoning.prefix = String($(this).val());
681 saveSettingsDebounced();713 saveSettingsDebounced();
682 });714 });
683715
684 $('#reasoning_suffix').val(power_user.reasoning.suffix);716 UI.$suffix.val(power_user.reasoning.suffix);
685 $('#reasoning_suffix').on('input', function () {717 UI.$suffix.on('input', function () {
686 power_user.reasoning.suffix = String($(this).val());718 power_user.reasoning.suffix = String($(this).val());
687 saveSettingsDebounced();719 saveSettingsDebounced();
688 });720 });
689721
690 $('#reasoning_separator').val(power_user.reasoning.separator);722 UI.$separator.val(power_user.reasoning.separator);
691 $('#reasoning_separator').on('input', function () {723 UI.$separator.on('input', function () {
692 power_user.reasoning.separator = String($(this).val());724 power_user.reasoning.separator = String($(this).val());
693 saveSettingsDebounced();725 saveSettingsDebounced();
694 });726 });
695727
696 $('#reasoning_max_additions').val(power_user.reasoning.max_additions);728 UI.$maxAdditions.val(power_user.reasoning.max_additions);
697 $('#reasoning_max_additions').on('input', function () {729 UI.$maxAdditions.on('input', function () {
698 power_user.reasoning.max_additions = Number($(this).val());730 power_user.reasoning.max_additions = Number($(this).val());
699 saveSettingsDebounced();731 saveSettingsDebounced();
700 });732 });
701733
702 $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);734 UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
703 $('#reasoning_auto_parse').on('change', function () {735 UI.$autoParse.on('change', function () {
704 power_user.reasoning.auto_parse = !!$(this).prop('checked');736 power_user.reasoning.auto_parse = !!$(this).prop('checked');
705 saveSettingsDebounced();737 saveSettingsDebounced();
706 });738 });
707739
708 $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);740 UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
709 $('#reasoning_auto_expand').on('change', function () {741 UI.$autoExpand.on('change', function () {
710 power_user.reasoning.auto_expand = !!$(this).prop('checked');742 power_user.reasoning.auto_expand = !!$(this).prop('checked');
711 toggleReasoningAutoExpand();743 toggleReasoningAutoExpand();
712 saveSettingsDebounced();744 saveSettingsDebounced();
713 });745 });
714 toggleReasoningAutoExpand();746 toggleReasoningAutoExpand();
715747
716 $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);748 UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
717 $('#reasoning_show_hidden').on('change', function () {749 UI.$showHidden.on('change', function () {
718 power_user.reasoning.show_hidden = !!$(this).prop('checked');750 power_user.reasoning.show_hidden = !!$(this).prop('checked');
719 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);751 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
720 saveSettingsDebounced();752 saveSettingsDebounced();
721 });753 });
722 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);754 $('#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
776function 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
723}800}
724801
725function registerReasoningSlashCommands() {802function registerReasoningSlashCommands() {
@@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
853 : parsedReasoning.reasoning;930 : parsedReasoning.reasoning;
854 },931 },
855 }));932 }));
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 }));
856}969}
857970
858function registerReasoningMacros() {971function registerReasoningMacros() {
@@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
1212 }1325 }
1213}1326}
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 */
1334export 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 */
1215export function initReasoning() {1375export function initReasoning() {
1216 loadReasoningSettings();1376 loadReasoningSettings();
1217 setReasoningEventHandlers();1377 setReasoningEventHandlers();
public/scripts/samplerSelect.js+2 -19
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
9//import { getSortableDelay, onlyUnique } from './utils.js';9//import { getSortableDelay, onlyUnique } from './utils.js';
10//import { getCfgPrompt } from './cfg-scale.js';10//import { getCfgPrompt } from './cfg-scale.js';
11import { setting_names } from './textgen-settings.js';11import { setting_names } from './textgen-settings.js';
12import { renderTemplateAsync } from './templates.js';
1213
1314
14const TGsamplerNames = setting_names;15const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
25 const html = $(document.createElement('div'));26 const html = $(document.createElement('div'));
26 html.attr('id', 'sampler_view_list')27 html.attr('id', 'sampler_view_list')
27 .addClass('flex-container flexFlowColumn');28 .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
48 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');31 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
49 const APISamplers = await listSamplers(main_api);32 const APISamplers = await listSamplers(main_api);
public/scripts/secrets.js+2 -1
@@ -1,5 +1,6 @@
1import { DOMPurify } from '../lib.js';1import { DOMPurify } from '../lib.js';
2import { callPopup, getRequestHeaders } from '../script.js';2import { callPopup, getRequestHeaders } from '../script.js';
3import { t } from './i18n.js';
34
4export const SECRET_KEYS = {5export const SECRET_KEYS = {
5 HORDE: 'api_key_horde',6 HORDE: 'api_key_horde',
@@ -104,7 +105,7 @@ async function viewSecrets() {
104 });105 });
105106
106 if (response.status == 403) {107 if (response.status == 403) {
107 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');108 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');
108 return;109 return;
109 }110 }
110111
public/scripts/st-context.js+4 -1
@@ -49,6 +49,7 @@ import {
49 clearChat,49 clearChat,
50 unshallowCharacter,50 unshallowCharacter,
51 deleteLastMessage,51 deleteLastMessage,
52 getCharacterCardFields,
52} from '../script.js';53} from '../script.js';
53import {54import {
54 extension_settings,55 extension_settings,
@@ -78,7 +79,7 @@ import { ToolManager } from './tool-calling.js';
78import { accountStorage } from './util/AccountStorage.js';79import { accountStorage } from './util/AccountStorage.js';
79import { timestampToMoment, uuidv4 } from './utils.js';80import { timestampToMoment, uuidv4 } from './utils.js';
80import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';81import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
81import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';82import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
82import { ChatCompletionService, TextCompletionService } from './custom-request.js';83import { ChatCompletionService, TextCompletionService } from './custom-request.js';
83import { ConnectionManagerRequestService } from './extensions/shared.js';84import { ConnectionManagerRequestService } from './extensions/shared.js';
84import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';85import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
@@ -189,6 +190,7 @@ export function getContext() {
189 textCompletionSettings: textgenerationwebui_settings,190 textCompletionSettings: textgenerationwebui_settings,
190 powerUserSettings: power_user,191 powerUserSettings: power_user,
191 getCharacters,192 getCharacters,
193 getCharacterCardFields,
192 uuidv4,194 uuidv4,
193 humanizedDateTime,195 humanizedDateTime,
194 updateMessageBlock,196 updateMessageBlock,
@@ -207,6 +209,7 @@ export function getContext() {
207 saveWorldInfo,209 saveWorldInfo,
208 updateWorldInfoList,210 updateWorldInfoList,
209 convertCharacterBook,211 convertCharacterBook,
212 getWorldInfoPrompt,
210 CONNECT_API_MAP,213 CONNECT_API_MAP,
211 getTextGenServer,214 getTextGenServer,
212 extractMessageFromData,215 extractMessageFromData,
public/scripts/templates/assistantNote.html+2 -2
@@ -1,9 +1,9 @@
1<div data-type="assistant_note">1<div data-type="assistant_note">
2 <div>2 <div>
3 <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>3 <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>
4 <span>Click the button to save it as a file.</span>4 <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
5 </div>5 </div>
6 <div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL">6 <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
7 <i class="fa-solid fa-file-export"></i>7 <i class="fa-solid fa-file-export"></i>
8 </div>8 </div>
9</div>9</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>
\ No newline at end of file18 \ 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
754/**754/**
755 * Gets the world info based on chat messages.755 * Gets the world info based on chat messages.
756 * @param {string[]} chat The chat messages to scan, in reverse order.756 * @param {string[]} chat - The chat messages to scan, in reverse order.
757 * @param {number} maxContext The maximum context size of the generation.757 * @param {number} maxContext - The maximum context size of the generation.
758 * @param {boolean} isDryRun If true, the function will not emit any events.758 * @param {boolean} isDryRun - If true, the function will not emit any events.
759 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult759 * @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
760 * @returns {Promise<WIPromptResult>} The world info string and depth.767 * @returns {Promise<WIPromptResult>} The world info string and depth.
761 */768 */
762export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
778 worldInfoAfter,785 worldInfoAfter,
779 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],786 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
780 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],787 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
788 anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
789 anAfter: activatedWorldInfo.ANAfterEntries ?? [],
781 };790 };
782}791}
783792
@@ -3862,7 +3871,14 @@ function parseDecorators(content) {
3862 * @param {string[]} chat The chat messages to scan, in reverse order.3871 * @param {string[]} chat The chat messages to scan, in reverse order.
3863 * @param {number} maxContext The maximum context size of the generation.3872 * @param {number} maxContext The maximum context size of the generation.
3864 * @param {boolean} isDryRun Whether to perform a dry run.3873 * @param {boolean} isDryRun Whether to perform a dry run.
3865 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated3874 * @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.
3866 * @returns {Promise<WIActivated>} The world info activated.3882 * @returns {Promise<WIActivated>} The world info activated.
3867 */3883 */
3868export async function checkWorldInfo(chat, maxContext, isDryRun) {3884export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +3922,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3906 timedEffects.checkTimedEffects();3922 timedEffects.checkTimedEffects();
39073923
3908 if (sortedEntries.length === 0) {3924 if (sortedEntries.length === 0) {
3909 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };3925 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
3910 }3926 }
39113927
3912 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */3928 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4371,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4355 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));4371 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
4356 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);4372 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574373
4358 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };4374 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
4359}4375}
43604376
4361/**4377/**
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
43 vectors: 'vectors',43 vectors: 'vectors',
44 backups: 'backups',44 backups: 'backups',
45 sysprompt: 'sysprompt',45 sysprompt: 'sysprompt',
46 reasoning: 'reasoning',
46});47});
4748
48/**49/**
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
48 MOVING_UI: 'moving_ui',48 MOVING_UI: 'moving_ui',
49 QUICK_REPLIES: 'quick_replies',49 QUICK_REPLIES: 'quick_replies',
50 SYSPROMPT: 'sysprompt',50 SYSPROMPT: 'sysprompt',
51 REASONING: 'reasoning',
51};52};
5253
53/**54/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
61 const presets = [];62 const presets = [];
6263
63 for (const contentItem of contentIndex) {64 for (const contentItem of contentIndex) {
64 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {65 if (contentItem.type.endsWith('_preset') || ['instruct', 'context', 'sysprompt', 'reasoning'].includes(contentItem.type)) {
65 contentItem.name = path.parse(contentItem.filename).name;66 contentItem.name = path.parse(contentItem.filename).name;
66 contentItem.folder = getTargetByType(contentItem.type, directories);67 contentItem.folder = getTargetByType(contentItem.type, directories);
67 presets.push(contentItem);68 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299 return directories.quickreplies;300 return directories.quickreplies;
300 case CONTENT_TYPES.SYSPROMPT:301 case CONTENT_TYPES.SYSPROMPT:
301 return directories.sysprompt;302 return directories.sysprompt;
303 case CONTENT_TYPES.REASONING:
304 return directories.reasoning;
302 default:305 default:
303 return null;306 return null;
304 }307 }
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
30 return { folder: directories.context, extension: '.json' };30 return { folder: directories.context, extension: '.json' };
31 case 'sysprompt':31 case 'sysprompt':
32 return { folder: directories.sysprompt, extension: '.json' };32 return { folder: directories.sysprompt, extension: '.json' };
33 case 'reasoning':
34 return { folder: directories.reasoning, extension: '.json' };
33 default:35 default:
34 return { folder: null, extension: null };36 return { folder: null, extension: null };
35 }37 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255 const context = readAndParseFromDirectory(request.user.directories.context);255 const context = readAndParseFromDirectory(request.user.directories.context);
256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257 const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258 response.send({259 response.send({
259 settings,260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272 instruct,273 instruct,
273 context,274 context,
274 sysprompt,275 sysprompt,
276 reasoning,
275 enable_extensions: ENABLE_EXTENSIONS,277 enable_extensions: ENABLE_EXTENSIONS,
276 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277 enable_accounts: ENABLE_ACCOUNTS,279 enable_accounts: ENABLE_ACCOUNTS,
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
95 * @property {string} vectors - The directory where the vectors are stored95 * @property {string} vectors - The directory where the vectors are stored
96 * @property {string} backups - The directory where the backups are stored96 * @property {string} backups - The directory where the backups are stored
97 * @property {string} sysprompt - The directory where the system prompt data is stored97 * @property {string} sysprompt - The directory where the system prompt data is stored
98 * @property {string} reasoning - The directory where the reasoning templates are stored
98 */99 */
99100
100/**101/**