Merge branch 'staging' into deprecate-cc-or-instruct

8c39e20ed911bc010369090efb8acfba64855ab6

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

167 files changed, +3185 -1556Ignore whitespace
.github/readme-ja_jp.md+17 -3
@@ -1,9 +1,20 @@
1> [!IMPORTANT] 1> [!IMPORTANT]
2> ここに掲載されている情報は、古かったり不完全であったりする可能性があります。最新の情報は英語版をご利用ください。2> ここに掲載されている情報は、古かったり不完全であったりする可能性があります。最新の情報は英語版をご利用ください。
33
4![][cover]
5
6<div align="center">
7
4[English](readme.md) | [中文](readme-zh_cn.md) | 日本語 | [Русский](readme-ru_ru.md)8[English](readme.md) | [中文](readme-zh_cn.md) | 日本語 | [Русский](readme-ru_ru.md)
59
6![SillyTavern-Banner](https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4)10[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
11[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)
12[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
13[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
14
15</div>
16
17---
718
8モバイルデバイスにも適したレイアウト・マルチAPI(KoboldAI/CPP、Horde、NovelAI、Ooba、OpenAI、OpenRouter、Claude、Scale)、VN ライクな Waifu モード、Stable Diffusion、TTS、WorldInfo(伝承本)、カスタマイズ可能なUI、自動翻訳、大量のプロンプトオプション+サードパーティの拡張機能をインストールする機能19モバイルデバイスにも適したレイアウト・マルチAPI(KoboldAI/CPP、Horde、NovelAI、Ooba、OpenAI、OpenRouter、Claude、Scale)、VN ライクな Waifu モード、Stable Diffusion、TTS、WorldInfo(伝承本)、カスタマイズ可能なUI、自動翻訳、大量のプロンプトオプション+サードパーティの拡張機能をインストールする機能
920
@@ -284,8 +295,8 @@ ST サーバを再起動すると、ユーザ名とパスワードさえ知っ
284295
285## スクリーンショット296## スクリーンショット
286297
287<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">298<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
288<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">299<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
289300
290## ライセンスとクレジット301## ライセンスとクレジット
291302
@@ -314,3 +325,6 @@ ST サーバを再起動すると、ユーザ名とパスワードさえ知っ
314* @doloroushyeonse による韓国語翻訳325* @doloroushyeonse による韓国語翻訳
315* k_euler_a による Horde のサポート <https://github.com/Teashrock>326* k_euler_a による Horde のサポート <https://github.com/Teashrock>
316* [@XXpE3](https://github.com/XXpE3) による中国語翻訳、中国語 ISSUES の連絡先は @XXpE3327* [@XXpE3](https://github.com/XXpE3) による中国語翻訳、中国語 ISSUES の連絡先は @XXpE3
328
329<!-- LINK GROUP -->
330[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
.github/readme-ru_ru.md+15 -4
@@ -3,9 +3,20 @@
33
4<a name="readme-top"></a>4<a name="readme-top"></a>
55
6![][cover]
7
8<div align="center">
9
6[English](readme.md) | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | Русский10[English](readme.md) | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | Русский
711
8![][cover]12[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
13[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)
14[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
15[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
16
17</div>
18
19---
920
10Мобайл-френдли интерфейс, поддержка множества API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), ВН-образный режим Вайфу, Stable Diffusion, TTS, поддержка миров (лорбуков), кастомизируемый UI, автоперевод, тончайшая настройка промптов + возможность устанавливать расширения.21Мобайл-френдли интерфейс, поддержка множества API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), ВН-образный режим Вайфу, Stable Diffusion, TTS, поддержка миров (лорбуков), кастомизируемый UI, автоперевод, тончайшая настройка промптов + возможность устанавливать расширения.
1122
@@ -29,8 +40,8 @@ SillyTavern — это форк версии TavernAI 1.2.8, который ра
2940
30## Скриншоты41## Скриншоты
3142
32<img width="400" alt="image" src="https://github.com/SillyTavern/SillyTavern/assets/61471128/e902c7a2-45a6-4415-97aa-c59c597669c1"> 43<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
33<img width="400" alt="image" src="https://github.com/SillyTavern/SillyTavern/assets/61471128/f8a79c47-4fe9-4564-9e4a-bf247ed1c961">44<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
3445
35### Ветки46### Ветки
3647
@@ -355,7 +366,7 @@ SillyTavern сохраняет ключи от ваших API в файле `sec
355366
356<!-- LINK GROUP -->367<!-- LINK GROUP -->
357[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square368[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square
358[cover]: https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4369[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
359[discord-link]: https://discord.gg/sillytavern370[discord-link]: https://discord.gg/sillytavern
360[discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square371[discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
361[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge372[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge
.github/readme-zh_cn.md+17 -3
@@ -1,9 +1,20 @@
1> [!IMPORTANT] 1> [!IMPORTANT]
2> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。2> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。
33
4![][cover]
5
6<div align="center">
7
4[English](readme.md) | 中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)8[English](readme.md) | 中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
59
6![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4)10[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
11[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)
12[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
13[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
14
15</div>
16
17---
718
8移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。19移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。
920
@@ -278,8 +289,8 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
278289
279## 屏幕截图290## 屏幕截图
280291
281<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">292<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
282<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">293<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
283294
284## 许可证和贡献295## 许可证和贡献
285296
@@ -306,3 +317,6 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
306* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc317* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
307* Korean translation by @doloroushyeonse318* Korean translation by @doloroushyeonse
308* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3319* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3
320
321<!-- LINK GROUP -->
322[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
.github/readme.md+178 -258
@@ -1,76 +1,103 @@
1<a name="readme-top"></a>1<a name="readme-top"></a>
22
3English | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
4
5![][cover]3![][cover]
64
7Mobile-friendly layout, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), VN-like Waifu Mode, Stable Diffusion, TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need + ability to install third-party extensions.5<div align="center">
86
9Based on a fork of [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.87English | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
108
11## Important news!9[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
10[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)
11[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
12[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
1213
131. We have created a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.14</div>
1415
152. Missing extensions after the update? Since the 1.10.6 release version, most of the previously built-in extensions have been converted to downloadable add-ons. You can download them via the built-in "Download Extensions and Assets" menu in the extensions panel (stacked blocks icon in the top bar).16---
1617
173. Unsupported platform: android arm LEtime-web. 32-bit Android requires an external dependency that can't be installed with npm. Use the following command to install it: `pkg install esbuild`. Then run the usual installation steps.18SillyTavern provides a single unified interface for many LLM APIs (KoboldAI/CPP, Horde, NovelAI, Ooba, Tabby, OpenAI, OpenRouter, Claude, Mistral and more), a mobile-friendly layout, Visual Novel Mode, Automatic1111 & ComfyUI API image generation integration, TTS, WorldInfo (lorebooks), customizable UI, auto-translate, more prompt options than you'd ever want or need, and endless growth potential via third-party extensions.
1819
19### Brought to you by Cohee, RossAscends, and the SillyTavern community20We have a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.
2021
21### What is SillyTavern or TavernAI?22## What is SillyTavern?
2223
23SillyTavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.24SillyTavern (or ST for short) is a locally installed user interface that allows you to interact with text generation LLMs, image generation engines, and TTS voice models.
2425
25SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs.26Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 100 contributors and 2 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.
2627
27## Screenshots28## Our Vision
2829
29<img width="400" alt="image" src="https://github.com/SillyTavern/SillyTavern/assets/61471128/e902c7a2-45a6-4415-97aa-c59c597669c1"> 301. We aim to empower users with as much utility and control over their LLM prompts as possible. The steep learning curve is part of the fun!
30<img width="400" alt="image" src="https://github.com/SillyTavern/SillyTavern/assets/61471128/f8a79c47-4fe9-4564-9e4a-bf247ed1c961">312. We do not provide any online or hosted services, nor programmatically track any user data.
323. SillyTavern is a passion project brought to you by a dedicated community of LLM enthusiasts, and will always be free and open sourced.
3133
32### Branches34## Branches
3335
34SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users.36SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users.
3537
36* release -🌟 **Recommended for most users.** This is the most stable and recommended branch, updated only when major releases are pushed. It's suitable for the majority of users.38* `release` -🌟 **Recommended for most users.** This is the most stable and recommended branch, updated only when major releases are pushed. It's suitable for the majority of users. Typically updated once a month.
37* staging - ⚠️ **Not recommended for casual use.** This branch has the latest features, but be cautious as it may break at any time. Only for power users and enthusiasts.39* `staging` - ⚠️ **Not recommended for casual use.** This branch has the latest features, but be cautious as it may break at any time. Only for power users and enthusiasts. Updates several times daily.
3840
39If you're not familiar with using the git CLI or don't understand what a branch is, don't worry! The release branch is always the preferable option for you.41If you're not familiar with using the git CLI or don't understand what a branch is, don't worry! The release branch is always the preferable option for you.
4042
41### What do I need other than SillyTavern?43## What do I need other than SillyTavern?
4244
43On its own SillyTavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](https://docs.sillytavern.app/usage/faq/).45Since SillyTavern is only an interface, you will need access to an LLM backend to provide inference. You can use AI Horde for instant out-of-the-box chatting. Aside from that, we support many other local and cloud-based LLM backends: OpenAI-compatible API, KoboldAI, Tabby, and many more. You can read more about our supported APIs in [the FAQ](https://docs.sillytavern.app/usage/api-connections/).
4446
45### Do I need a powerful PC to run SillyTavern?47### Do I need a powerful PC to run SillyTavern?
4648
47Since SillyTavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful.49The hardware requirements are minimal: it will run on anything that can run NodeJS 18 or higher. If you intend to do LLM inference on your local machine, we recommend a 3000-series NVIDIA graphics card with at least 6GB of VRAM. Check your backend's documentation for more details.
50
51### Suggested Backends (not affiliated)
52
53* [AI Horde](https://aihorde.net/) - use models hosted by volunteers. Requires no further setup
54* [KoboldCpp](https://github.com/LostRuins/koboldcpp) - a community's favorite for running GGUF models locally
55* [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - a popular, lightweight, locally-hosted exl2 inference API
56* [OpenRouter](https://openrouter.ai) - a single API for many cloud providers (OpenAI, Claude, Meta Llama, etc.) as well as popular community models.
4857
49## Questions or suggestions?58## Questions or suggestions?
5059
51### We now have a community Discord server60### Discord server
5261
53| [![][discord-shield-badge]][discord-link] | [Join our Discord community!](https://discord.gg/sillytavern) Get support, share favorite characters and prompts. |62| [![][discord-shield-badge]][discord-link] | [Join our Discord community!](https://discord.gg/sillytavern) Get support, share favorite characters and prompts. |
54| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |63| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
5564
56Or get in touch with the developers directly:65Or get in touch with the developers directly:
5766
58* Discord: cohee or rossascends67* Discord: cohee, rossascends, wolfsblvt
59* Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/) or [/u/sillylossy](https://www.reddit.com/user/sillylossy/)68* Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/)
60* [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues)69* [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues)
6170
62## This version includes71### I like your project! How do I contribute?
72
731. Send pull requests. Learn how to contribute: [CONTRIBUTING.md](../CONTRIBUTING.md)
742. Send feature suggestions and issue reports using the provided templates.
753. Read this entire readme file and check the documentation website first, to avoid sending duplicate issues.
76
77## Screenshots
78
79<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
80<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
81
82## Character Cards
83
84SillyTavern is built around the concept of "character cards". A character card is a collection of prompts that set the behavior of the LLM and is required to have persistent conversations in SillyTavern. They function similarly to ChatGPT's GPTs or Poe's bots. The content of a character card can be anything: an abstract scenario, an assistant tailored for a specific task, a famous personality or a fictional character.
85
86The name field is the only required character card input. To start a neutral conversation with the language model, create a new card simply called "Assistant" and leave the rest of the boxes blank. For a more themed chat, you can provide the language model with various background details, behavior and writing patterns, and a scenario to jump start the chat.
6387
64* A heavily modified TavernAI 1.2.8 (more than 50% of code rewritten or optimized)88To have a quick conversation without selecting a character card or to just test the LLM connection, simply type your prompt input into the input bar on the Welcome Screen after opening SillyTavern. Please note that such chats are temporary and will not be saved.
65* Swipes89
66* Group chats: multi-bot rooms for characters to talk to you or each other90To get a general idea on how to define character cards, see the default character (Seraphina) or download selected community-made cards from the "Download Extensions & Assets" menu.
67* Chat checkpoints / branching91
68* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets92## Key Features
93
94* Advanced text generation settings with many community-made presets
69* World Info support: create rich lore or save tokens on your character card95* World Info support: create rich lore or save tokens on your character card
70* [OpenRouter](https://openrouter.ai) connection for various APIs (Claude, GPT-4/3.5 and more)96* Group chats: multi-bot rooms for characters to talk to you and/or each other
71* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection97* Rich UI customization options: theme colors, background images, custom CSS, and more
72* [AI Horde](https://aihorde.net/) connection98* User personas: let the AI know a bit about you for greater immersion
73* Prompt generation formatting tweaking99* Built-in RAG support: add documents to your chats for the AI to reference
100* Extensive chat commands subsystem and own [scripting engine](https://docs.sillytavern.app/usage/st-script/)
74101
75## Extensions102## Extensions
76103
@@ -78,83 +105,43 @@ SillyTavern has extensibility support.
78105
79* Character emotional expressions (sprites)106* Character emotional expressions (sprites)
80* Auto-Summary of the chat history107* Auto-Summary of the chat history
81* Sending images to chat, and the AI interpreting the content108* Automatic UI and chat translation
82* Stable Diffusion image generation (5 chat-related presets plus 'free mode')109* Stable Diffusion/FLUX/DALL-E image generation
83* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)110* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
84* Many more available to download from the "Download Extensions & Assets" menu.111* Web Search capabilities for adding additional real world context to your prompts
112* Many more are available to download from the "Download Extensions & Assets" menu.
85113
86Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/).114Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/).
87115
88## UI/CSS/Quality of Life tweaks by RossAscends
89
90* Mobile UI optimized for iOS, and supports saving a shortcut to the home screen and opening in fullscreen mode.
91* HotKeys
92 * Up = Edit last message in chat
93 * Ctrl+Up = Edit last USER message in chat
94 * Left = swipe left
95 * Right = swipe right (NOTE: swipe hotkeys are disabled when the chat bar has something typed into it)
96 * Ctrl+Left = view locally stored variables (in the browser console window)
97 * Enter (with chat bar selected) = send your message to AI
98 * Ctrl+Enter = Regenerate the last AI response
99
100* User Name Changes and Character Deletion no longer force the page to refresh.
101
102* Toggle option to automatically connect to API on page load.
103* Toggle option to automatically load the most recently viewed character on page load.
104* Better Token Counter - works on unsaved characters, and shows both permanent and temporary tokens.
105
106* Better Past Chats View
107 * New Chat filenames are saved in a readable format of "(character) - (when it was created)"
108 * Chat preview increased from 40 characters to 300.
109 * Multiple options for characters list sorting (by name, creation date, chat sizes).
110
111* By default the left and right settings panel will close when you click away from it.
112* Clicking the Lock on the nav panel will hold the panel open, and this setting be remembered across sessions.
113* Nav panel status of open or closed will also be saved across sessions.
114
115* Customizable chat UI:
116 * Play a sound when a new message arrives
117 * Switch between round or rectangle avatar styles
118 * Have a wider chat window on the desktop
119 * Optional semi-transparent glass-like panels
120 * Customizable page colors for 'main text', 'quoted text', and 'italics text'.
121 * Customizable UI background color and blur amount
122
123# ⌛ Installation116# ⌛ Installation
124117
125> \[!WARNING]118> \[!WARNING]
119>
126> * DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc).120> * DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc).
127> * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS121> * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
128> * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16122> * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16
129123
130## 🪟 Windows124## 🪟 Windows
131125
132## Installing via Git126### Installing via Git
133 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
134 2. Install [Git for Windows](https://gitforwindows.org/)
135 3. Open Windows Explorer (`Win+E`)
136 4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\)
137 5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
138 6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
139127
140- for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`1281. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
141- for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`1292. Install [Git for Windows](https://gitforwindows.org/)
1303. Open Windows Explorer (`Win+E`)
1314. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\)
1325. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
1336. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
142134
143 7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.135* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
144 8. The server will then start, and SillyTavern will pop up in your browser.136* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
145137
146## Installing via SillyTavern Launcher1387. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
1471. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:1398. The server will then start, and SillyTavern will pop up in your browser.
148```shell140
149cmd /c winget install -e --id Git.Git141### Installing via GitHub Desktop
150```
1512. On your keyboard: press **`WINDOWS + E`** to open File Explorer, then navigate to the folder where you want to install the launcher. Once in the desired folder, type `cmd` into the address bar and press enter. Then, run the following command:
152```shell
153git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
154```
155142
156## Installing via GitHub Desktop
157(This allows git usage **only** in GitHub Desktop, if you want to use `git` on the command line too, you also need to install [Git for Windows](https://gitforwindows.org/))143(This allows git usage **only** in GitHub Desktop, if you want to use `git` on the command line too, you also need to install [Git for Windows](https://gitforwindows.org/))
144
158 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)145 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
159 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)146 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
160 3. After installing GitHub Desktop, click on `Clone a repository from the internet....` (Note: You **do NOT need** to create a GitHub account for this step)147 3. After installing GitHub Desktop, click on `Clone a repository from the internet....` (Note: You **do NOT need** to create a GitHub account for this step)
@@ -172,50 +159,82 @@ For MacOS / Linux all of these will be done in a Terminal.
1721. Install git and nodeJS (the method for doing this will vary depending on your OS)1591. Install git and nodeJS (the method for doing this will vary depending on your OS)
1732. Clone the repo1602. Clone the repo
174161
175- for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`162* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
176- for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`163* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
177164
1783. `cd SillyTavern` to navigate into the install folder.1653. `cd SillyTavern` to navigate into the install folder.
1794. Run the `start.sh` script with one of these commands:1664. Run the `start.sh` script with one of these commands:
180167
181- `./start.sh`168* `./start.sh`
182- `bash start.sh`169* `bash start.sh`
170
171## ⚡ Installing via SillyTavern Launcher
172
173SillyTavern Launcher is an installation wizard that will help you get setup with many options, including installing a backend for local inference.
174
175### For Windows users
183176
184## Installing via SillyTavern Launcher1771. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:
178
179```shell
180cmd /c winget install -e --id Git.Git
181```
182
1832. On your keyboard: press **`WINDOWS + E`** to open File Explorer, then navigate to the folder where you want to install the launcher. Once in the desired folder, type `cmd` into the address bar and press enter. Then, run the following command:
184
185```shell
186git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
187```
185188
186### For Linux users189### For Linux users
190
1871. Open your favorite terminal and install git1911. Open your favorite terminal and install git
1882. Git clone the Sillytavern-Launcher with: 1922. Git clone the Sillytavern-Launcher with:
193
189```shell194```shell
190git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher195git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
191```196```
1923. Start the installer.sh with: 197
1983. Start the installer.sh with:
199
193```shell200```shell
194chmod +x install.sh && ./install.sh201chmod +x install.sh && ./install.sh
195```202```
1964. After installation start the launcher.sh with: 203
2044. After installation start the launcher.sh with:
205
197```shell206```shell
198chmod +x launcher.sh && ./launcher.sh207chmod +x launcher.sh && ./launcher.sh
199```208```
200209
201### For Mac users210### For Mac users
2021. Open a terminal and install brew with: 211
2121. Open a terminal and install brew with:
213
203```shell214```shell
204/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"215/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
205```216```
2062. Install git with: 217
2182. Install git with:
219
207```shell220```shell
208brew install git221brew install git
209```222```
2103. Git clone the Sillytavern-Launcher with: 223
2243. Git clone the Sillytavern-Launcher with:
225
211```shell226```shell
212git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher227git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
213```228```
2144. Start the installer.sh with: 229
2304. Start the installer.sh with:
231
215```shell232```shell
216chmod +x install.sh && ./install.sh233chmod +x install.sh && ./install.sh
217```234```
2185. After installation start the launcher.sh with: 235
2365. After installation start the launcher.sh with:
237
219```shell238```shell
220chmod +x launcher.sh && ./launcher.sh239chmod +x launcher.sh && ./launcher.sh
221```240```
@@ -236,18 +255,18 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
236255
237##### Volume Mappings256##### Volume Mappings
238257
239- [config] - The directory where SillyTavern configuration files will be stored on your host machine258* [config] - The directory where SillyTavern configuration files will be stored on your host machine
240- [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine259* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
241- [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine260* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
242261
243##### Port Mappings262##### Port Mappings
244263
245- [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.264* [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.
246265
247##### Additional Settings266##### Additional Settings
248267
249- [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/).268* [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/).
250- [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.269* [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.
251270
252#### Install command271#### Install command
253272
@@ -258,23 +277,28 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
258277
259> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.278> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
260279
261## 📱 Mobile - Installing via termux280## 📱 Installing via Termux on Android OS
262281
263> \[!NOTE]282> \[!NOTE]
264> **SillyTavern can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**283> **SillyTavern can be run natively on Android devices using Termux, but we do not provide official support for this use case.**
284>
285> **Please refer to this guide by ArroganceComplex#2659:**
286>
265> * <https://rentry.org/STAI-Termux>287> * <https://rentry.org/STAI-Termux>
266288
289**Unsupported platform: android arm LEtime-web.** 32-bit Android requires an external dependency that can't be installed with npm. Use the following command to install it: `pkg install esbuild`. Then run the usual installation steps.
267290
268## API keys management291## API keys management
269292
270SillyTavern saves your API keys to a `secrets.json` file in the user data directory (`/data/default-user/secrets.json` is the default path).293SillyTavern saves your API keys to a `secrets.json` file in the user data directory (`/data/default-user/secrets.json` is the default path).
271294
272By default, they will not be exposed to a frontend after you enter them and reload the page.295By default, API keys will not be visible from the interface after you have saved them and refreshed the page.
273296
274In order to enable viewing your keys by clicking a button in the API block:297In order to enable viewing your keys:
275298
2761. Set the value of `allowKeysExposure` to `true` in `config.yaml` file.2991. Set the value of `allowKeysExposure` to `true` in `config.yaml` file.
2772. Restart the SillyTavern server.3002. Restart the SillyTavern server.
3013. Click the 'View hidden API keys' link at the bottom right of the API Connection Panel.
278302
279## Command-line arguments303## Command-line arguments
280304
@@ -292,134 +316,43 @@ Start.bat --port 8000 --listen false
292316
293### Supported arguments317### Supported arguments
294318
295| Option | Description | Type | Default |319| Option | Description | Type |
296|-------------------------|------------------------------------------------------------------------------------------------------|----------|------------------------------|320|-------------------------|------------------------------------------------------------------------------------------------------|----------|
297| `--version` | Show version number | boolean | |321| `--version` | Show version number | boolean |
298| `--enableIPv6` | Enables IPv6. | boolean | false |322| `--enableIPv6` | Enables IPv6. | boolean |
299| `--enableIPv4` | Enables IPv4. | boolean | true |323| `--enableIPv4` | Enables IPv4. | boolean |
300| `--port` | Sets the port under which SillyTavern will run. If not provided falls back to yaml config 'port'. | number | 8000 |324| `--port` | Sets the port under which SillyTavern will run. If not provided falls back to yaml config 'port'. | number |
301| `--dnsPreferIPv6` | Prefers IPv6 for dns. If not provided falls back to yaml config 'preferIPv6'. | boolean | false |325| `--dnsPreferIPv6` | Prefers IPv6 for dns. If not provided falls back to yaml config 'preferIPv6'. | boolean |
302| `--autorun` | Automatically launch SillyTavern in the browser. If not provided falls back to yaml config 'autorun'.| boolean | false |326| `--autorun` | Automatically launch SillyTavern in the browser. If not provided falls back to yaml config 'autorun'.| boolean |
303| `--autorunHostname` | The autorun hostname, probably best left on 'auto'. | string | null |327| `--autorunHostname` | The autorun hostname, probably best left on 'auto'. | string |
304| `--autorunPortOverride` | Overrides the port for autorun. | string | null |328| `--autorunPortOverride` | Overrides the port for autorun. | string |
305| `--listen` | SillyTavern is listening on all network interfaces. If not provided falls back to yaml config 'listen'.| boolean | false |329| `--listen` | SillyTavern is listening on all network interfaces. If not provided falls back to yaml config 'listen'.| boolean |
306| `--corsProxy` | Enables CORS proxy. If not provided falls back to yaml config 'enableCorsProxy'. | boolean | false |330| `--corsProxy` | Enables CORS proxy. If not provided falls back to yaml config 'enableCorsProxy'. | boolean |
307| `--disableCsrf` | Disables CSRF protection | boolean | null |331| `--disableCsrf` | Disables CSRF protection | boolean |
308| `--ssl` | Enables SSL | boolean | false |332| `--ssl` | Enables SSL | boolean |
309| `--certPath` | Path to your certificate file. | string | "certs/cert.pem" |333| `--certPath` | Path to your certificate file. | string |
310| `--keyPath` | Path to your private key file. | string | "certs/privkey.pem" |334| `--keyPath` | Path to your private key file. | string |
311| `--whitelist` | Enables whitelist mode | boolean | null |335| `--whitelist` | Enables whitelist mode | boolean |
312| `--dataRoot` | Root directory for data storage | string | null |336| `--dataRoot` | Root directory for data storage | string |
313| `--avoidLocalhost` | Avoids using 'localhost' for autorun in auto mode. | boolean | null |337| `--avoidLocalhost` | Avoids using 'localhost' for autorun in auto mode. | boolean |
314| `--basicAuthMode` | Enables basic authentication | boolean | null |338| `--basicAuthMode` | Enables basic authentication | boolean |
315| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean | null |339| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean |
316| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string | null |340| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string |
317| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array | null |341| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array |
318342
319## Remote connections343## Remote connections
320344
321Most often this is for people who want to use SillyTavern on their mobile phones while their PC runs the ST server on the same wifi network.345Most often this is for people who want to use SillyTavern on their mobile phones while their PC runs the ST server on the same Wi-Fi network. However, it can be used to allow remote connections from anywhere as well.
322
323However, it can be used to allow remote connections from anywhere as well.
324
325**IMPORTANT: Refer to the official guide if you want to configure SillyTavern user accounts with (optional) password protection: [Users](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users).**
326
327### 1. Managing whitelisted IPs
328
329* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
330* Open the file in a text editor, and add a list of IPs you want to be allowed to connect.
331
332*Both individual IPs and wildcard IP ranges are accepted. Examples:*
333
334```txt
335192.168.0.1
336192.168.0.20
337```
338
339or
340
341```txt
342192.168.0.*
343```
344
345(the above wildcard IP range will allow any device on the local network to connect)
346
347CIDR masks are also accepted (eg. 10.0.0.0/24).
348
349* Save the `whitelist.txt` file.
350* Restart your ST server.
351
352Now devices which have the IP specified in the file will be able to connect.
353
354*Note: `config.yaml` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.*
355
356### 2. Getting the IP for the ST host machine
357
358After the whitelist has been setup, you'll need the IP of the ST-hosting device.
359
360If the ST-hosting device is on the same wifi network, you will use the ST-host's internal wifi IP:
361
362* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
363
364If you (or someone else) want to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
365
366* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for `IPv4`. This is what you would use to connect from the remote device.
367346
368### 3. Connect the remote device to the ST host machine347Read the detailed guide on how to set up remote connections in the [Docs](https://docs.sillytavern.app/usage/remoteconnections/).
369348
370Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.349You may also want to configure SillyTavern user profiles with (optional) password protection: [Users](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users).
371
372A typical address for an ST host on the same wifi network would look like this:
373
374`http://192.168.0.5:8000`
375
376Use http:// NOT https://
377
378### Opening your ST to all IPs
379
380We do not recommend doing this, but you can open `config.yaml` and change `whitelistMode` to `false`.
381
382You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder if it exists.
383
384This is usually an insecure practice, so we require you to set a username and password when you do this.
385
386The username and password are set in `config.yaml`.
387
388After restarting your ST server, any device will be able to connect to it, regardless of their IP as long as they know the username and password.
389
390### Still Unable To Connect?
391
392* Create an inbound/outbound firewall rule for the port found in `config.yaml`. Do NOT mistake this for port-forwarding on your router, otherwise, someone could find your chat logs and that's a big no-no.
393* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise, you would be unable to connect even with the aforementioned firewall rules.
394350
395## Performance issues?351## Performance issues?
396352
397Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.3531. Disable the Blur Effect and enable Reduced Motion on the User Settings panel (UI Theme toggles category).
3983542. If using response streaming, set the streaming FPS to a lower value (10-15 FPS is recommended).
399## I like your project! How do I contribute?3553. Make sure the browser is enabled to use GPU acceleration for rendering.
400
401### DO's
402
4031. Send pull requests
4042. Send feature suggestions and issue reports using established templates
4053. Read the readme file and built-in documentation before asking anything
406
407### DONT's
408
4091. Offer monetary donations
4102. Send bug reports without providing any context
4113. Ask the questions that were already answered numerous times
412
413## Where can I find the old backgrounds?
414
415We're moving to a 100% original content only policy, so old background images have been removed from this repository.
416
417You can find them archived here:
418
419<https://files.catbox.moe/1xevnc.zip>
420
421
422
423356
424## License and credits357## License and credits
425358
@@ -428,32 +361,19 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
428MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the361MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
429GNU Affero General Public License for more details.**362GNU Affero General Public License for more details.**
430363
431* TAI Base by Humi: MIT364* [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT License
432* Cohee's modifications and derived code: AGPL v3365* Portions of CncAnon's TavernAITurbo mod used with permission
433* RossAscends' additions: AGPL v3366* Visual Novel Mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
434* Portions of CncAnon's TavernAITurbo mod: Unknown license
435* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
436* city_unit's extensions and various QoL features (<https://github.com/city-unit>)
437* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
438* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
439* Thanks Pygmalion University for being awesome testers and suggesting cool features!
440* Thanks oobabooga for compiling presets for TextGen
441* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
442* Noto Sans font by Google (OFL license)367* Noto Sans font by Google (OFL license)
443* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)368* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
444* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>369* Default content by @OtisAlejandro (Seraphina character and lorebook) and @kallmeflocc (10K Discord Users Celebratory Background)
445* Linux startup script by AlpinDale
446* Thanks paniphons for providing a FAQ document
447* 10K Discord Users Celebratory Background by @kallmeflocc
448* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
449* Korean translation by @doloroushyeonse
450* k_euler_a support for Horde by <https://github.com/Teashrock>
451* Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3
452* Docker guide by [@mrguymiah](https://github.com/mrguymiah) and [@Bronya-Rand](https://github.com/Bronya-Rand)370* Docker guide by [@mrguymiah](https://github.com/mrguymiah) and [@Bronya-Rand](https://github.com/Bronya-Rand)
453371
372## Top Contributors
373
374[![Contributors](https://contrib.rocks/image?repo=SillyTavern/SillyTavern)](https://github.com/SillyTavern/SillyTavern/graphs/contributors)
375
454<!-- LINK GROUP -->376<!-- LINK GROUP -->
455[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square377[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
456[cover]: https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4
457[discord-link]: https://discord.gg/sillytavern378[discord-link]: https://discord.gg/sillytavern
458[discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
459[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge379[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge
Dockerfile+1 -1
@@ -19,7 +19,7 @@ ENV NODE_ENV=production
19COPY package*.json post-install.js ./19COPY package*.json post-install.js ./
20RUN \20RUN \
21 echo "*** Install npm packages ***" && \21 echo "*** Install npm packages ***" && \
22 npm i --no-audit --no-fund --quiet --omit=dev && npm cache clean --force22 npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force
2323
24# Bundle app source24# Bundle app source
25COPY . ./25COPY . ./
Start.bat+1 -1
@@ -1,7 +1,7 @@
1@echo off1@echo off
2pushd %~dp02pushd %~dp0
3set NODE_ENV=production3set NODE_ENV=production
4call npm install --no-audit --no-fund --quiet --omit=dev4call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
5node server.js %*5node server.js %*
6pause6pause
7popd7popd
UpdateAndStart.bat+1 -1
@@ -12,7 +12,7 @@ if %errorlevel% neq 0 (
12 )12 )
13)13)
14set NODE_ENV=production14set NODE_ENV=production
15call npm install --no-audit --no-fund --quiet --omit=dev15call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
16node server.js %*16node server.js %*
17pause17pause
18popd18popd
UpdateForkAndStart.bat+2 -2
@@ -42,7 +42,7 @@ if NOT "!AUTO_SWITCH!"=="" (
42 SET TARGET_BRANCH=release42 SET TARGET_BRANCH=release
43 goto update43 goto update
44 )44 )
45 45
46 echo Auto-switching defined to stay on current branch46 echo Auto-switching defined to stay on current branch
47 goto update47 goto update
48)48)
@@ -95,7 +95,7 @@ if %errorlevel% neq 0 (
9595
96echo Installing npm packages and starting server96echo Installing npm packages and starting server
97set NODE_ENV=production97set NODE_ENV=production
98call npm install --no-audit --no-fund --quiet --omit=dev98call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
99node server.js %*99node server.js %*
100100
101:end101:end
default/config.yaml+5 -0
@@ -83,6 +83,8 @@ skipContentCheck: false
83disableChatBackup: false83disableChatBackup: false
84# Number of backups to keep for each chat and settings file84# Number of backups to keep for each chat and settings file
85numberOfBackups: 5085numberOfBackups: 50
86# Interval in milliseconds to throttle chat backups per user
87chatBackupThrottleInterval: 10000
86# Allowed hosts for card downloads88# Allowed hosts for card downloads
87whitelistImportDomains:89whitelistImportDomains:
88 - localhost90 - localhost
@@ -108,6 +110,9 @@ enableExtensionsAutoUpdate: true
108# Additional model tokenizers can be downloaded on demand.110# Additional model tokenizers can be downloaded on demand.
109# Disabling will fallback to another locally available tokenizer.111# Disabling will fallback to another locally available tokenizer.
110enableDownloadableTokenizers: true112enableDownloadableTokenizers: true
113# Vector storage settings
114vectors:
115 enableModelScopes: false
111# Extension settings116# Extension settings
112extras:117extras:
113 # Disables automatic model download from HuggingFace118 # Disables automatic model download from HuggingFace
default/content/index.json+92 -16
@@ -320,6 +320,26 @@
320 "type": "novel_preset"320 "type": "novel_preset"
321 },321 },
322 {322 {
323 "filename": "presets/novel/Erato-Dragonfruit.json",
324 "type": "novel_preset"
325 },
326 {
327 "filename": "presets/novel/Erato-Golden Arrow.json",
328 "type": "novel_preset"
329 },
330 {
331 "filename": "presets/novel/Erato-Shosetsu.json",
332 "type": "novel_preset"
333 },
334 {
335 "filename": "presets/novel/Erato-Wilder.json",
336 "type": "novel_preset"
337 },
338 {
339 "filename": "presets/novel/Erato-Zany Scribe.json",
340 "type": "novel_preset"
341 },
342 {
323 "filename": "presets/textgen/Asterism.json",343 "filename": "presets/textgen/Asterism.json",
324 "type": "textgen_preset"344 "type": "textgen_preset"
325 },345 },
@@ -468,10 +488,6 @@
468 "type": "context"488 "type": "context"
469 },489 },
470 {490 {
471 "filename": "presets/context/Alpaca-Roleplay.json",
472 "type": "context"
473 },
474 {
475 "filename": "presets/context/Alpaca-Single-Turn.json",491 "filename": "presets/context/Alpaca-Single-Turn.json",
476 "type": "context"492 "type": "context"
477 },493 },
@@ -512,10 +528,6 @@
512 "type": "context"528 "type": "context"
513 },529 },
514 {530 {
515 "filename": "presets/context/Mistral.json",
516 "type": "context"
517 },
518 {
519 "filename": "presets/context/NovelAI.json",531 "filename": "presets/context/NovelAI.json",
520 "type": "context"532 "type": "context"
521 },533 },
@@ -556,10 +568,6 @@
556 "type": "instruct"568 "type": "instruct"
557 },569 },
558 {570 {
559 "filename": "presets/instruct/Alpaca-Roleplay.json",
560 "type": "instruct"
561 },
562 {
563 "filename": "presets/instruct/Alpaca-Single-Turn.json",571 "filename": "presets/instruct/Alpaca-Single-Turn.json",
564 "type": "instruct"572 "type": "instruct"
565 },573 },
@@ -600,10 +608,6 @@
600 "type": "instruct"608 "type": "instruct"
601 },609 },
602 {610 {
603 "filename": "presets/instruct/Mistral.json",
604 "type": "instruct"
605 },
606 {
607 "filename": "presets/instruct/OpenOrca-OpenChat.json",611 "filename": "presets/instruct/OpenOrca-OpenChat.json",
608 "type": "instruct"612 "type": "instruct"
609 },613 },
@@ -686,5 +690,77 @@
686 {690 {
687 "filename": "presets/instruct/Gemma 2.json",691 "filename": "presets/instruct/Gemma 2.json",
688 "type": "instruct"692 "type": "instruct"
693 },
694 {
695 "filename": "presets/sysprompt/Actor.json",
696 "type": "sysprompt"
697 },
698 {
699 "filename": "presets/sysprompt/Assistant - Expert.json",
700 "type": "sysprompt"
701 },
702 {
703 "filename": "presets/sysprompt/Assistant - Simple.json",
704 "type": "sysprompt"
705 },
706 {
707 "filename": "presets/sysprompt/Chain of Thought.json",
708 "type": "sysprompt"
709 },
710 {
711 "filename": "presets/sysprompt/Roleplay - Detailed.json",
712 "type": "sysprompt"
713 },
714 {
715 "filename": "presets/sysprompt/Roleplay - Immersive.json",
716 "type": "sysprompt"
717 },
718 {
719 "filename": "presets/sysprompt/Roleplay - Simple.json",
720 "type": "sysprompt"
721 },
722 {
723 "filename": "presets/sysprompt/Text Adventure.json",
724 "type": "sysprompt"
725 },
726 {
727 "filename": "presets/sysprompt/Writer - Creative.json",
728 "type": "sysprompt"
729 },
730 {
731 "filename": "presets/sysprompt/Writer - Realistic.json",
732 "type": "sysprompt"
733 },
734 {
735 "filename": "presets/sysprompt/Blank.json",
736 "type": "sysprompt"
737 },
738 {
739 "filename": "presets/sysprompt/Neutral - Chat.json",
740 "type": "sysprompt"
741 },
742 {
743 "filename": "presets/instruct/Mistral V1.json",
744 "type": "instruct"
745 },
746 {
747 "filename": "presets/context/Mistral V1.json",
748 "type": "context"
749 },
750 {
751 "filename": "presets/instruct/Mistral V2 & V3.json",
752 "type": "instruct"
753 },
754 {
755 "filename": "presets/context/Mistral V2 & V3.json",
756 "type": "context"
757 },
758 {
759 "filename": "presets/instruct/Mistral V3-Tekken.json",
760 "type": "instruct"
761 },
762 {
763 "filename": "presets/context/Mistral V3-Tekken.json",
764 "type": "context"
689 }765 }
690]766]
default/content/presets/context/Adventure.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": false,7 "always_force_name2": false,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": true,9 "single_line": true,
11 "name": "Adventure"10 "name": "Adventure"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Alpaca-Roleplay.json+0 -12
@@ -1,12 +0,0 @@
1{
2 "story_string": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\n{{#if system}}{{system}}\n\n{{/if}}### Input:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}\n\n",
3 "example_separator": "### New Roleplay:",
4 "chat_start": "### New Roleplay:",
5 "use_stop_strings": false,
6 "allow_jailbreak": false,
7 "always_force_name2": true,
8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,
11 "name": "Alpaca-Roleplay"
12}
default/content/presets/context/Alpaca-Single-Turn.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": false,7 "always_force_name2": false,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Alpaca-Single-Turn"10 "name": "Alpaca-Single-Turn"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Alpaca.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Alpaca"10 "name": "Alpaca"
12}11}
default/content/presets/context/ChatML-Names.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "ChatML-Names"10 "name": "ChatML-Names"
12}11}
default/content/presets/context/ChatML.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "ChatML"10 "name": "ChatML"
12}11}
default/content/presets/context/Command R.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Command R"10 "name": "Command R"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Default.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Default"10 "name": "Default"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/DreamGen Role-Play V1 ChatML.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": false,7 "always_force_name2": false,
8 "trim_sentences": true,8 "trim_sentences": true,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "DreamGen Role-Play V1 ChatML"10 "name": "DreamGen Role-Play V1 ChatML"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/DreamGen Role-Play V1 Llama3.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": false,7 "always_force_name2": false,
8 "trim_sentences": true,8 "trim_sentences": true,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "DreamGen Role-Play V1 Llama3"10 "name": "DreamGen Role-Play V1 Llama3"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Gemma 2.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Gemma 2"10 "name": "Gemma 2"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Libra-32B.json+2 -3
@@ -1,12 +1,11 @@
1{1{
2 "story_string": "### Instruction:\nWrite {{char}}'s next reply in this roleplay with {{user}}. Use the provided character sheet and example dialogue for formatting direction and character speech patterns.\n\n{{#if system}}{{system}}\n\n{{/if}}### Character Sheet:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",2 "story_string": "### Instruction:\n{{#if system}}{{system}}\n\n{{/if}}### Character Sheet:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
3 "example_separator": "### Example:",3 "example_separator": "### Example:",
4 "chat_start": "### START ROLEPLAY:",4 "chat_start": "### START ROLEPLAY:",
5 "use_stop_strings": false,5 "use_stop_strings": false,
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Libra-32B"10 "name": "Libra-32B"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Lightning 1.1.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Lightning 1.1"10 "name": "Lightning 1.1"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Llama 2 Chat.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Llama 2 Chat"10 "name": "Llama 2 Chat"
12}11}
default/content/presets/context/Llama 3 Instruct.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Llama 3 Instruct"10 "name": "Llama 3 Instruct"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Llama-3-Instruct-Names.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Llama-3-Instruct-Names"10 "name": "Llama-3-Instruct-Names"
12}11}
default/content/presets/context/Minimalist.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Minimalist"10 "name": "Minimalist"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Mistral V1.json+11 -0
@@ -0,0 +1,11 @@
1{
2 "story_string": " [INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}} [/INST] Understood.</s>",
3 "example_separator": "",
4 "chat_start": "",
5 "use_stop_strings": false,
6 "allow_jailbreak": false,
7 "always_force_name2": true,
8 "trim_sentences": false,
9 "single_line": false,
10 "name": "Mistral V1"
11}
default/content/presets/context/Mistral.json → default/content/presets/context/Mistral V2 & V3.json+3 -4
@@ -1,12 +1,11 @@
1{1{
2 "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}} [/INST]",2 "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}[/INST] Understood.</s>",
3 "example_separator": "Examples:",3 "example_separator": "",
4 "chat_start": "",4 "chat_start": "",
5 "use_stop_strings": false,5 "use_stop_strings": false,
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Mistral"10 "name": "Mistral V2 & V3"
12}11}
default/content/presets/context/Mistral V3-Tekken.json+11 -0
@@ -0,0 +1,11 @@
1{
2 "story_string": "[INST]{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}[/INST]Understood.</s>",
3 "example_separator": "",
4 "chat_start": "",
5 "use_stop_strings": false,
6 "allow_jailbreak": false,
7 "always_force_name2": true,
8 "trim_sentences": false,
9 "single_line": false,
10 "name": "Mistral V3-Tekken"
11}
default/content/presets/context/NovelAI.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "NovelAI"10 "name": "NovelAI"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/OldDefault.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "OldDefault"10 "name": "OldDefault"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Phi.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Phi"10 "name": "Phi"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Pygmalion.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Pygmalion"10 "name": "Pygmalion"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Story.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Story"10 "name": "Story"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/context/Synthia.json+0 -1
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "Synthia"10 "name": "Synthia"
12}11}
default/content/presets/context/simple-proxy-for-tavern.json+1 -2
@@ -6,7 +6,6 @@
6 "allow_jailbreak": false,6 "allow_jailbreak": false,
7 "always_force_name2": true,7 "always_force_name2": true,
8 "trim_sentences": false,8 "trim_sentences": false,
9 "include_newline": false,
10 "single_line": false,9 "single_line": false,
11 "name": "simple-proxy-for-tavern"10 "name": "simple-proxy-for-tavern"
12}
12 \ No newline at end of file \ No newline at end of file
11}
default/content/presets/instruct/Adventure.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]",
3 "input_sequence": "",2 "input_sequence": "",
4 "output_sequence": "",3 "output_sequence": "",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Alpaca-Roleplay.json+0 -24
@@ -1,24 +0,0 @@
1{
2 "system_prompt": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
3 "input_sequence": "### Instruction:",
4 "output_sequence": "### Response:",
5 "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
6 "system_sequence": "### Input:",
7 "stop_sequence": "",
8 "wrap": true,
9 "macro": true,
10 "names": true,
11 "names_force_groups": true,
12 "activation_regex": "",
13 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",
15 "first_output_sequence": "",
16 "skip_examples": false,
17 "output_suffix": "\n\n",
18 "input_suffix": "\n\n",
19 "system_suffix": "\n\n",
20 "user_alignment_message": "",
21 "system_same_as_user": false,
22 "last_system_sequence": "",
23 "name": "Alpaca-Roleplay"
24}
default/content/presets/instruct/Alpaca-Single-Turn.json+2 -4
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\nWrite 1 reply only, italicize actions, and avoid quotation marks. Use markdown. Be proactive, creative, and drive the plot and conversation forward. Include dialog as well as narration.",
3 "input_sequence": "",2 "input_sequence": "",
4 "output_sequence": "",3 "output_sequence": "",
5 "last_output_sequence": "\n### Response:",4 "last_output_sequence": "\n### Response:",
@@ -7,12 +6,11 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
15 "first_output_sequence": "<START OF ROLEPLAY>",13 "first_output_sequence": "<START OF CHAT>",
16 "skip_examples": false,14 "skip_examples": false,
17 "output_suffix": "",15 "output_suffix": "",
18 "input_suffix": "",16 "input_suffix": "",
default/content/presets/instruct/Alpaca.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
3 "input_sequence": "### Instruction:",2 "input_sequence": "### Instruction:",
4 "output_sequence": "### Response:",3 "output_sequence": "### Response:",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/ChatML-Names.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
3 "input_sequence": "<|im_start|>[{{name}}]",2 "input_sequence": "<|im_start|>[{{name}}]",
4 "output_sequence": "<|im_start|>[{{name}}]",3 "output_sequence": "<|im_start|>[{{name}}]",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<|im_end|>",6 "stop_sequence": "<|im_end|>",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/ChatML.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
3 "input_sequence": "<|im_start|>user",2 "input_sequence": "<|im_start|>user",
4 "output_sequence": "<|im_start|>assistant",3 "output_sequence": "<|im_start|>assistant",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<|im_end|>",6 "stop_sequence": "<|im_end|>",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": true,9 "names_behavior": "always",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Command R.json+2 -4
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
3 "input_sequence": "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>",2 "input_sequence": "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>",
4 "output_sequence": "<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",3 "output_sequence": "<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",
5 "first_output_sequence": "",4 "first_output_sequence": "",
@@ -9,8 +8,7 @@
9 "stop_sequence": "<|END_OF_TURN_TOKEN|>",8 "stop_sequence": "<|END_OF_TURN_TOKEN|>",
10 "wrap": false,9 "wrap": false,
11 "macro": true,10 "macro": true,
12 "names": true,11 "names_behavior": "always",
13 "names_force_groups": true,
14 "activation_regex": "",12 "activation_regex": "",
15 "skip_examples": false,13 "skip_examples": false,
16 "output_suffix": "<|END_OF_TURN_TOKEN|>",14 "output_suffix": "<|END_OF_TURN_TOKEN|>",
@@ -21,4 +19,4 @@
21 "last_system_sequence": "",19 "last_system_sequence": "",
22 "system_same_as_user": false,20 "system_same_as_user": false,
23 "name": "Command R"21 "name": "Command R"
24}
24 \ No newline at end of file \ No newline at end of file
22}
default/content/presets/instruct/DreamGen Role-Play V1 ChatML.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
3 "input_sequence": "\n<|im_start|>text names= {{name}}\n",2 "input_sequence": "\n<|im_start|>text names= {{name}}\n",
4 "output_sequence": "\n<|im_start|>text names= {{name}}\n",3 "output_sequence": "\n<|im_start|>text names= {{name}}\n",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "\n<|im_start|>",6 "stop_sequence": "\n<|im_start|>",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/DreamGen Role-Play V1 Llama3.json+2 -4
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
3 "input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",2 "input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
4 "output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",3 "output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
5 "first_output_sequence": "",4 "first_output_sequence": "",
@@ -10,9 +9,8 @@
10 "separator_sequence": "",9 "separator_sequence": "",
11 "wrap": false,10 "wrap": false,
12 "macro": true,11 "macro": true,
13 "names": false,12 "names_behavior": "none",
14 "names_force_groups": false,
15 "activation_regex": "",13 "activation_regex": "",
16 "skip_examples": false,14 "skip_examples": false,
17 "name": "DreamGen Role-Play V1 Llama3"15 "name": "DreamGen Role-Play V1 Llama3"
18}
18 \ No newline at end of file \ No newline at end of file
16}
default/content/presets/instruct/Gemma 2.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Continue writing this story and portray characters realistically.",
3 "input_sequence": "<start_of_turn>user",2 "input_sequence": "<start_of_turn>user",
4 "output_sequence": "<start_of_turn>model",3 "output_sequence": "<start_of_turn>model",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<end_of_turn>",6 "stop_sequence": "<end_of_turn>",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Koala.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
3 "input_sequence": "USER: ",2 "input_sequence": "USER: ",
4 "output_sequence": "GPT: ",3 "output_sequence": "GPT: ",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "BEGINNING OF CONVERSATION: ",11 "system_sequence_prefix": "BEGINNING OF CONVERSATION: ",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Libra-32B.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Avoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
3 "input_sequence": "",2 "input_sequence": "",
4 "output_sequence": "",3 "output_sequence": "",
5 "last_output_sequence": "\n### Response:",4 "last_output_sequence": "\n### Response:",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": true,9 "names_behavior": "always",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Lightning 1.1.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\nTake the role of {{char}} in a play that leaves a lasting impression on {{user}}. Write {{char}}'s next reply.\nNever skip or gloss over {{char}}’s actions. Progress the scene at a naturally slow pace.\n\n",
3 "input_sequence": "### Instruction:",2 "input_sequence": "### Instruction:",
4 "output_sequence": "### Response: (length = unlimited)",3 "output_sequence": "### Response: (length = unlimited)",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Llama 2 Chat.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
3 "input_sequence": "[INST] ",2 "input_sequence": "[INST] ",
4 "output_sequence": "",3 "output_sequence": "",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Llama 3 Instruct.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
3 "input_sequence": "<|start_header_id|>user<|end_header_id|>\n\n",2 "input_sequence": "<|start_header_id|>user<|end_header_id|>\n\n",
4 "output_sequence": "<|start_header_id|>assistant<|end_header_id|>\n\n",3 "output_sequence": "<|start_header_id|>assistant<|end_header_id|>\n\n",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<|eot_id|>",6 "stop_sequence": "<|eot_id|>",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": true,9 "names_behavior": "always",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Llama-3-Instruct-Names.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
3 "input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",2 "input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
4 "output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",3 "output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<|eot_id|>",6 "stop_sequence": "<|eot_id|>",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Metharme.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
3 "input_sequence": "<|user|>",2 "input_sequence": "<|user|>",
4 "output_sequence": "<|model|>",3 "output_sequence": "<|model|>",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "</s>",6 "stop_sequence": "</s>",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "<|system|>",11 "system_sequence_prefix": "<|system|>",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Mistral V1.json+22 -0
@@ -0,0 +1,22 @@
1{
2 "input_sequence": " [INST] ",
3 "output_sequence": " [/INST] ",
4 "last_output_sequence": " [/INST]",
5 "system_sequence": "",
6 "stop_sequence": "",
7 "wrap": false,
8 "macro": true,
9 "names_behavior": "always",
10 "activation_regex": "",
11 "system_sequence_prefix": "",
12 "system_sequence_suffix": "",
13 "first_output_sequence": "",
14 "skip_examples": false,
15 "output_suffix": "</s>",
16 "input_suffix": "",
17 "system_suffix": "",
18 "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
19 "system_same_as_user": true,
20 "last_system_sequence": "",
21 "name": "Mistral V1"
22}
default/content/presets/instruct/Mistral.json → default/content/presets/instruct/Mistral V2 & V3.json+6 -8
@@ -1,24 +1,22 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
3 "input_sequence": "[INST] ",2 "input_sequence": "[INST] ",
4 "output_sequence": "",3 "output_sequence": "[/INST] ",
5 "last_output_sequence": "",4 "last_output_sequence": "[/INST]",
6 "system_sequence": "",5 "system_sequence": "",
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": true,9 "names_behavior": "always",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
15 "first_output_sequence": "",13 "first_output_sequence": "",
16 "skip_examples": false,14 "skip_examples": false,
17 "output_suffix": "\n",15 "output_suffix": "</s>",
18 "input_suffix": " [/INST]\n",16 "input_suffix": "",
19 "system_suffix": "",17 "system_suffix": "",
20 "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",18 "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
21 "system_same_as_user": true,19 "system_same_as_user": true,
22 "last_system_sequence": "",20 "last_system_sequence": "",
23 "name": "Mistral"21 "name": "Mistral V2 & V3"
24}22}
default/content/presets/instruct/Mistral V3-Tekken.json+22 -0
@@ -0,0 +1,22 @@
1{
2 "input_sequence": "[INST]",
3 "output_sequence": "[/INST]",
4 "last_output_sequence": "",
5 "system_sequence": "",
6 "stop_sequence": "",
7 "wrap": false,
8 "macro": true,
9 "names_behavior": "always",
10 "activation_regex": "",
11 "system_sequence_prefix": "",
12 "system_sequence_suffix": "",
13 "first_output_sequence": "",
14 "skip_examples": false,
15 "output_suffix": "</s>",
16 "input_suffix": "",
17 "system_suffix": "",
18 "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
19 "system_same_as_user": true,
20 "last_system_sequence": "",
21 "name": "Mistral V3-Tekken"
22}
default/content/presets/instruct/OpenOrca-OpenChat.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
3 "input_sequence": "\nUser: ",2 "input_sequence": "\nUser: ",
4 "output_sequence": "\nAssistant: ",3 "output_sequence": "\nAssistant: ",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Phi.json+2 -4
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
3 "input_sequence": "<|user|>\n",2 "input_sequence": "<|user|>\n",
4 "output_sequence": "<|assistant|>\n",3 "output_sequence": "<|assistant|>\n",
5 "first_output_sequence": "",4 "first_output_sequence": "",
@@ -9,8 +8,7 @@
9 "stop_sequence": "<|end|>",8 "stop_sequence": "<|end|>",
10 "wrap": false,9 "wrap": false,
11 "macro": true,10 "macro": true,
12 "names": true,11 "names_behavior": "always",
13 "names_force_groups": true,
14 "activation_regex": "",12 "activation_regex": "",
15 "skip_examples": false,13 "skip_examples": false,
16 "output_suffix": "<|end|>\n",14 "output_suffix": "<|end|>\n",
@@ -21,4 +19,4 @@
21 "last_system_sequence": "",19 "last_system_sequence": "",
22 "system_same_as_user": false,20 "system_same_as_user": false,
23 "name": "Phi"21 "name": "Phi"
24}
24 \ No newline at end of file \ No newline at end of file
22}
default/content/presets/instruct/Pygmalion.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Enter RP mode. You shall reply to {{user}} while staying in character. Your responses must be detailed, creative, immersive, and drive the scenario forward. You will follow {{char}}'s persona.",
3 "input_sequence": "<|user|>",2 "input_sequence": "<|user|>",
4 "output_sequence": "<|model|>",3 "output_sequence": "<|model|>",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "<|user|>",6 "stop_sequence": "<|user|>",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": true,9 "names_behavior": "always",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "<|system|>",11 "system_sequence_prefix": "<|system|>",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Story.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "",
3 "input_sequence": "",2 "input_sequence": "",
4 "output_sequence": "",3 "output_sequence": "",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Synthia.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation.",
3 "input_sequence": "USER: ",2 "input_sequence": "USER: ",
4 "output_sequence": "ASSISTANT: ",3 "output_sequence": "ASSISTANT: ",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "SYSTEM: ",11 "system_sequence_prefix": "SYSTEM: ",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Vicuna 1.0.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
3 "input_sequence": "### Human:",2 "input_sequence": "### Human:",
4 "output_sequence": "### Assistant:",3 "output_sequence": "### Assistant:",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/Vicuna 1.1.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
3 "input_sequence": "\nUSER: ",2 "input_sequence": "\nUSER: ",
4 "output_sequence": "\nASSISTANT: ",3 "output_sequence": "\nASSISTANT: ",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": false,7 "wrap": false,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "BEGINNING OF CONVERSATION:",11 "system_sequence_prefix": "BEGINNING OF CONVERSATION:",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/WizardLM-13B.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.",
3 "input_sequence": "USER: ",2 "input_sequence": "USER: ",
4 "output_sequence": "ASSISTANT: ",3 "output_sequence": "ASSISTANT: ",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/WizardLM.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
3 "input_sequence": "",2 "input_sequence": "",
4 "output_sequence": "### Response:",3 "output_sequence": "### Response:",
5 "last_output_sequence": "",4 "last_output_sequence": "",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "force",
11 "names_force_groups": true,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/instruct/simple-proxy-for-tavern.json+1 -3
@@ -1,5 +1,4 @@
1{1{
2 "system_prompt": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]",
3 "input_sequence": "### Instruction:\n#### {{name}}:",2 "input_sequence": "### Instruction:\n#### {{name}}:",
4 "output_sequence": "### Response:\n#### {{name}}:",3 "output_sequence": "### Response:\n#### {{name}}:",
5 "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{name}}:",4 "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{name}}:",
@@ -7,8 +6,7 @@
7 "stop_sequence": "",6 "stop_sequence": "",
8 "wrap": true,7 "wrap": true,
9 "macro": true,8 "macro": true,
10 "names": false,9 "names_behavior": "none",
11 "names_force_groups": false,
12 "activation_regex": "",10 "activation_regex": "",
13 "system_sequence_prefix": "",11 "system_sequence_prefix": "",
14 "system_sequence_suffix": "",12 "system_sequence_suffix": "",
default/content/presets/novel/Asper-Kayra.json+5 -2
@@ -14,7 +14,10 @@
14 "use_cache": false,14 "use_cache": false,
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "cfg_scale": 1,
18 "phrase_rep_pen": "medium",17 "phrase_rep_pen": "medium",
19 "max_context": 780018 "max_context": 7800,
19 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Blended-Coffee-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [6, 0, 1, 2, 3],2 "order": [0, 1, 2, 3],
3 "temperature": 1,3 "temperature": 1,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -14,6 +14,9 @@
14 "return_full_text": false,14 "return_full_text": false,
15 "prefix": "vanilla",15 "prefix": "vanilla",
16 "phrase_rep_pen": "medium",16 "phrase_rep_pen": "medium",
17 "cfg_scale": 1.55,17 "max_context": 7800,
18 "max_context": 780018 "min_p": 0,
19 "math1_temp": 1,
20 "math1_quad": 0,
21 "math1_quad_entropy_scale": 0
19}22}
default/content/presets/novel/Blook-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [6, 2, 3, 1, 0],2 "order": [2, 3, 1, 0],
3 "temperature": 1,3 "temperature": 1,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -15,6 +15,9 @@
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "phrase_rep_pen": "very_aggressive",17 "phrase_rep_pen": "very_aggressive",
18 "cfg_scale": 1.3,18 "max_context": 7800,
19 "max_context": 780019 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Carefree-Kayra.json+5 -2
@@ -15,7 +15,10 @@
15 "use_cache": false,15 "use_cache": false,
16 "return_full_text": false,16 "return_full_text": false,
17 "prefix": "vanilla",17 "prefix": "vanilla",
18 "cfg_scale": 1,
19 "phrase_rep_pen": "aggressive",18 "phrase_rep_pen": "aggressive",
20 "max_context": 780019 "max_context": 7800,
20 "min_p": 0,
21 "math1_temp": 1,
22 "math1_quad": 0,
23 "math1_quad_entropy_scale": 0
21}24}
default/content/presets/novel/CosmicCube-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [8, 6, 5, 0, 3],2 "order": [8, 5, 0, 3],
3 "temperature": 0.9,3 "temperature": 0.9,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -15,6 +15,9 @@
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "phrase_rep_pen": "off",17 "phrase_rep_pen": "off",
18 "cfg_scale": 1.48,18 "max_context": 7800,
19 "max_context": 780019 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Edgewise-Clio.json+5 -2
@@ -15,7 +15,10 @@
15 "use_cache": false,15 "use_cache": false,
16 "return_full_text": false,16 "return_full_text": false,
17 "prefix": "vanilla",17 "prefix": "vanilla",
18 "cfg_scale": 1,
19 "phrase_rep_pen": "very_light",18 "phrase_rep_pen": "very_light",
20 "max_context": 780019 "max_context": 7800,
20 "min_p": 0,
21 "math1_temp": 1,
22 "math1_quad": 0,
23 "math1_quad_entropy_scale": 0
21}24}
default/content/presets/novel/Erato-Dragonfruit.json+31 -0
@@ -0,0 +1,31 @@
1{
2 "max_context": 8000,
3 "temperature": 1.37,
4 "max_length": 150,
5 "min_length": 1,
6 "top_k": 0,
7 "top_p": 1,
8 "top_a": 0.1,
9 "typical_p": 0.875,
10 "tail_free_sampling": 0.87,
11 "repetition_penalty": 3.25,
12 "repetition_penalty_range": 6000,
13 "repetition_penalty_slope": 3.25,
14 "repetition_penalty_frequency": 0,
15 "repetition_penalty_presence": 0,
16 "phrase_rep_pen": "off",
17 "mirostat_lr": 0.2,
18 "mirostat_tau": 4,
19 "math1_temp": 0.9,
20 "math1_quad": 0.07,
21 "math1_quad_entropy_scale": -0.05,
22 "min_p": 0.035,
23 "order": [
24 0,
25 5,
26 9,
27 10,
28 8,
29 4
30 ]
31}
default/content/presets/novel/Erato-Golden Arrow.json+27 -0
@@ -0,0 +1,27 @@
1{
2 "max_context": 8000,
3 "temperature": 1,
4 "max_length": 150,
5 "min_length": 1,
6 "top_k": 0,
7 "top_p": 0.995,
8 "top_a": 1,
9 "typical_p": 1,
10 "tail_free_sampling": 0.87,
11 "repetition_penalty": 1.5,
12 "repetition_penalty_range": 2240,
13 "repetition_penalty_slope": 1,
14 "repetition_penalty_frequency": 0,
15 "repetition_penalty_presence": 0,
16 "phrase_rep_pen": "light",
17 "mirostat_lr": 1,
18 "mirostat_tau": 0,
19 "math1_temp": 0.3,
20 "math1_quad": 0.19,
21 "math1_quad_entropy_scale": 0,
22 "min_p": 0,
23 "order": [
24 9,
25 2
26 ]
27}
default/content/presets/novel/Erato-Shosetsu.json+27 -0
@@ -0,0 +1,27 @@
1{
2 "max_context": 8000,
3 "temperature": 1,
4 "max_length": 150,
5 "min_length": 1,
6 "top_k": 50,
7 "top_p": 0.85,
8 "top_a": 1,
9 "typical_p": 1,
10 "tail_free_sampling": 0.895,
11 "repetition_penalty": 1.63,
12 "repetition_penalty_range": 1024,
13 "repetition_penalty_slope": 3.33,
14 "repetition_penalty_frequency": 0.0035,
15 "repetition_penalty_presence": 0,
16 "phrase_rep_pen": "medium",
17 "mirostat_lr": 1,
18 "mirostat_tau": 0,
19 "math1_temp": 0.3,
20 "math1_quad": 0.0645,
21 "math1_quad_entropy_scale": 0.05,
22 "min_p": 0.05,
23 "order": [
24 9,
25 10
26 ]
27}
default/content/presets/novel/Erato-Wilder.json+27 -0
@@ -0,0 +1,27 @@
1{
2 "max_context": 8000,
3 "temperature": 1,
4 "max_length": 150,
5 "min_length": 1,
6 "top_k": 300,
7 "top_p": 0.98,
8 "top_a": 0.004,
9 "typical_p": 0.96,
10 "tail_free_sampling": 0.96,
11 "repetition_penalty": 1.48,
12 "repetition_penalty_range": 2240,
13 "repetition_penalty_slope": 0.64,
14 "repetition_penalty_frequency": 0,
15 "repetition_penalty_presence": 0,
16 "phrase_rep_pen": "medium",
17 "mirostat_lr": 1,
18 "mirostat_tau": 0,
19 "math1_temp": -0.0485,
20 "math1_quad": 0.145,
21 "math1_quad_entropy_scale": 0,
22 "min_p": 0.02,
23 "order": [
24 9,
25 10
26 ]
27}
default/content/presets/novel/Erato-Zany Scribe.json+27 -0
@@ -0,0 +1,27 @@
1{
2 "max_context": 8000,
3 "temperature": 1,
4 "max_length": 150,
5 "min_length": 1,
6 "top_k": 0,
7 "top_p": 0.99,
8 "top_a": 1,
9 "typical_p": 1,
10 "tail_free_sampling": 0.99,
11 "repetition_penalty": 1,
12 "repetition_penalty_range": 64,
13 "repetition_penalty_slope": 1,
14 "repetition_penalty_frequency": 0.75,
15 "repetition_penalty_presence": 1.5,
16 "phrase_rep_pen": "medium",
17 "mirostat_lr": 1,
18 "mirostat_tau": 1,
19 "math1_temp": -0.4,
20 "math1_quad": 0.6,
21 "math1_quad_entropy_scale": -0.1,
22 "min_p": 0.08,
23 "order": [
24 9,
25 2
26 ]
27}
default/content/presets/novel/Fresh-Coffee-Clio.json+5 -2
@@ -16,7 +16,10 @@
16 "use_cache": false,16 "use_cache": false,
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "cfg_scale": 1,
20 "phrase_rep_pen": "very_light",19 "phrase_rep_pen": "very_light",
21 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Fresh-Coffee-Kayra.json+5 -2
@@ -14,7 +14,10 @@
14 "use_cache": false,14 "use_cache": false,
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "cfg_scale": 1,
18 "phrase_rep_pen": "off",17 "phrase_rep_pen": "off",
19 "max_context": 780018 "max_context": 7800,
19 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Green-Active-Writer-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [6, 0, 8, 5, 3],2 "order": [0, 8, 5, 3],
3 "temperature": 1.5,3 "temperature": 1.5,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -15,6 +15,9 @@
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "phrase_rep_pen": "very_aggressive",17 "phrase_rep_pen": "very_aggressive",
18 "cfg_scale": 1.4,18 "max_context": 7800,
19 "max_context": 780019 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Keelback-Clio.json+5 -2
@@ -16,7 +16,10 @@
16 "use_cache": false,16 "use_cache": false,
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "cfg_scale": 1,
20 "phrase_rep_pen": "very_light",19 "phrase_rep_pen": "very_light",
21 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Long-Press-Clio.json+5 -2
@@ -16,7 +16,10 @@
16 "use_cache": false,16 "use_cache": false,
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "cfg_scale": 1,
20 "phrase_rep_pen": "very_light",19 "phrase_rep_pen": "very_light",
21 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Pilotfish-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [6, 0, 4, 1, 2, 5, 3],2 "order": [0, 4, 1, 2, 5, 3],
3 "temperature": 1.31,3 "temperature": 1.31,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -17,6 +17,9 @@
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "phrase_rep_pen": "medium",19 "phrase_rep_pen": "medium",
20 "cfg_scale": 1.35,20 "max_context": 7800,
21 "max_context": 780021 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Pro_Writer-Kayra.json+5 -2
@@ -15,6 +15,9 @@
15 "return_full_text": false,15 "return_full_text": false,
16 "prefix": "vanilla",16 "prefix": "vanilla",
17 "phrase_rep_pen": "medium",17 "phrase_rep_pen": "medium",
18 "cfg_scale": 1.0,18 "max_context": 7800,
19 "max_context": 780019 "min_p": 0,
20 "math1_temp": 1,
21 "math1_quad": 0,
22 "math1_quad_entropy_scale": 0
20}23}
default/content/presets/novel/Stelenes-Kayra.json+5 -1
@@ -13,5 +13,9 @@
13 "return_full_text": false,13 "return_full_text": false,
14 "prefix": "vanilla",14 "prefix": "vanilla",
15 "phrase_rep_pen": "medium",15 "phrase_rep_pen": "medium",
16 "max_context": 780016 "max_context": 7800,
17 "min_p": 0,
18 "math1_temp": 1,
19 "math1_quad": 0,
20 "math1_quad_entropy_scale": 0
17}21}
default/content/presets/novel/Talker-Chat-Clio.json+5 -2
@@ -16,7 +16,10 @@
16 "use_cache": false,16 "use_cache": false,
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "cfg_scale": 1,
20 "phrase_rep_pen": "very_light",19 "phrase_rep_pen": "very_light",
21 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Tea_Time-Kayra.json+5 -2
@@ -14,6 +14,9 @@
14 "return_full_text": false,14 "return_full_text": false,
15 "prefix": "vanilla",15 "prefix": "vanilla",
16 "phrase_rep_pen": "aggressive",16 "phrase_rep_pen": "aggressive",
17 "cfg_scale": 1.0,17 "max_context": 7800,
18 "max_context": 780018 "min_p": 0,
19 "math1_temp": 1,
20 "math1_quad": 0,
21 "math1_quad_entropy_scale": 0
19}22}
default/content/presets/novel/Tesseract-Kayra.json+6 -3
@@ -1,5 +1,5 @@
1{1{
2 "order": [6, 0, 5],2 "order": [0, 5],
3 "temperature": 0.895,3 "temperature": 0.895,
4 "max_length": 150,4 "max_length": 150,
5 "min_length": 1,5 "min_length": 1,
@@ -13,6 +13,9 @@
13 "return_full_text": false,13 "return_full_text": false,
14 "prefix": "vanilla",14 "prefix": "vanilla",
15 "phrase_rep_pen": "aggressive",15 "phrase_rep_pen": "aggressive",
16 "cfg_scale": 1.3,16 "max_context": 7800,
17 "max_context": 780017 "min_p": 0,
18 "math1_temp": 1,
19 "math1_quad": 0,
20 "math1_quad_entropy_scale": 0
18}21}
default/content/presets/novel/Vingt-Un-Clio.json+5 -2
@@ -16,7 +16,10 @@
16 "use_cache": false,16 "use_cache": false,
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "cfg_scale": 1,
20 "phrase_rep_pen": "very_light",19 "phrase_rep_pen": "very_light",
21 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
22}25}
default/content/presets/novel/Writers-Daemon-Kayra.json+5 -1
@@ -17,5 +17,9 @@
17 "return_full_text": false,17 "return_full_text": false,
18 "prefix": "vanilla",18 "prefix": "vanilla",
19 "phrase_rep_pen": "very_aggressive",19 "phrase_rep_pen": "very_aggressive",
20 "max_context": 780020 "max_context": 7800,
21 "min_p": 0,
22 "math1_temp": 1,
23 "math1_quad": 0,
24 "math1_quad_entropy_scale": 0
21}25}
default/content/presets/sysprompt/Actor.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Actor",
3 "content": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}."
4}
default/content/presets/sysprompt/Assistant - Expert.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Assistant - Expert",
3 "content": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate."
4}
default/content/presets/sysprompt/Assistant - Simple.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Assistant - Simple",
3 "content": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions."
4}
default/content/presets/sysprompt/Blank.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Blank",
3 "content": ""
4}
default/content/presets/sysprompt/Chain of Thought.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Chain of Thought",
3 "content": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation."
4}
default/content/presets/sysprompt/Neutral - Chat.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Neutral - Chat",
3 "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}."
4}
default/content/presets/sysprompt/Roleplay - Detailed.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Roleplay - Detailed",
3 "content": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions. Keep the story immersive and engaging."
4}
default/content/presets/sysprompt/Roleplay - Immersive.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Roleplay - Immersive",
3 "content": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]"
4}
default/content/presets/sysprompt/Roleplay - Simple.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Roleplay - Simple",
3 "content": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}."
4}
default/content/presets/sysprompt/Text Adventure.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Text Adventure",
3 "content": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]"
4}
default/content/presets/sysprompt/Writer - Creative.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Writer - Creative",
3 "content": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below."
4}
default/content/presets/sysprompt/Writer - Realistic.json+4 -0
@@ -0,0 +1,4 @@
1{
2 "name": "Writer - Realistic",
3 "content": "Continue writing this story and portray characters realistically."
4}
default/content/settings.json+17 -18
@@ -90,7 +90,6 @@
90 "pin_examples": false,90 "pin_examples": false,
91 "strip_examples": false,91 "strip_examples": false,
92 "trim_sentences": false,92 "trim_sentences": false,
93 "include_newline": false,
94 "always_force_name2": true,93 "always_force_name2": true,
95 "user_prompt_bias": "",94 "user_prompt_bias": "",
96 "show_user_prompt_bias": true,95 "show_user_prompt_bias": true,
@@ -150,11 +149,9 @@
150 "continue_on_send": false,149 "continue_on_send": false,
151 "trim_spaces": true,150 "trim_spaces": true,
152 "relaxed_api_urls": false,151 "relaxed_api_urls": false,
153 "default_instruct": "",
154 "instruct": {152 "instruct": {
155 "enabled": false,153 "enabled": false,
156 "preset": "Alpaca",154 "preset": "Alpaca",
157 "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
158 "input_sequence": "### Instruction:",155 "input_sequence": "### Instruction:",
159 "output_sequence": "### Response:",156 "output_sequence": "### Response:",
160 "last_output_sequence": "",157 "last_output_sequence": "",
@@ -162,8 +159,7 @@
162 "stop_sequence": "",159 "stop_sequence": "",
163 "wrap": true,160 "wrap": true,
164 "macro": true,161 "macro": true,
165 "names": false,162 "names_behavior": "force",
166 "names_force_groups": true,
167 "activation_regex": "",163 "activation_regex": "",
168 "system_sequence_prefix": "",164 "system_sequence_prefix": "",
169 "system_sequence_suffix": "",165 "system_sequence_suffix": "",
@@ -175,7 +171,11 @@
175 "user_alignment_message": "",171 "user_alignment_message": "",
176 "system_same_as_user": false172 "system_same_as_user": false
177 },173 },
178 "default_context": "Default",174 "sysprompt": {
175 "enabled": true,
176 "name": "Neutral - Chat",
177 "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}."
178 },
179 "context": {179 "context": {
180 "preset": "Default",180 "preset": "Default",
181 "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",181 "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
@@ -235,7 +235,7 @@
235 "lengthPenaltyStep": 0.1,235 "lengthPenaltyStep": 0.1,
236 "memoryFrozen": false,236 "memoryFrozen": false,
237 "source": "extras",237 "source": "extras",
238 "prompt": "[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]",238 "prompt": "Ignore previous instructions. Summarize the most important facts and events in the story so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.",
239 "promptWords": 200,239 "promptWords": 200,
240 "promptMinWords": 25,240 "promptMinWords": 25,
241 "promptMaxWords": 1000,241 "promptMaxWords": 1000,
@@ -298,13 +298,13 @@
298 "horde_karras": true,298 "horde_karras": true,
299 "refine_mode": false,299 "refine_mode": false,
300 "prompts": {300 "prompts": {
301 "0": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",301 "0": "In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'",
302 "1": "[Pause your roleplay and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]",302 "1": "Ignore previous instructions and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'. Ignore the rest of the story when crafting this description. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.",
303 "2": "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",303 "2": "Ignore previous instructions and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.",
304 "3": "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",304 "3": "Ignore previous instructions and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.",
305 "4": "[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.\n\n Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').\n\n Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.\n\n Add keywords in this precise order:\n a keyword to describe the location of the scene,\n a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:\n {{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),\n\n keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',\n\n a single keyword or phrase to describe the primary act taking place in the last chat message,\n\n keywords to describe {{char}}'s physical appearance and facial expression,\n keywords to describe {{char}}'s actions,\n keywords to describe {{user}}'s physical appearance and actions.\n\n If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.\n\n A correctly formatted example response would be:\n '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']",305 "4": "Ignore previous instructions. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.\n\n Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').\n\n Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.\n\n Add keywords in this precise order:\n a keyword to describe the location of the scene,\n a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:\n {{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),\n\n keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',\n\n a single keyword or phrase to describe the primary act taking place in the last chat message,\n\n keywords to describe {{char}}'s physical appearance and facial expression,\n keywords to describe {{char}}'s actions,\n keywords to describe {{user}}'s physical appearance and actions.\n\n If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.\n\n A correctly formatted example response would be:\n '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)'",
306 "5": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait,']",306 "5": "In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait,'",
307 "7": "[Pause your roleplay and provide a detailed description of {{char}}'s surroundings in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: location, time of day, weather, lighting, and any other relevant details. Do not include descriptions of characters and non-visual qualities such as names, personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'background,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{user}} when writing this description, and do not attempt to continue the story.]"307 "7": "Ignore previous instructions and provide a detailed description of {{char}}'s surroundings in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: location, time of day, weather, lighting, and any other relevant details. Do not include descriptions of characters and non-visual qualities such as names, personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'background,'. Ignore the rest of the story when crafting this description. Do not reply as {{user}} when writing this description, and do not attempt to continue the story."
308 },308 },
309 "character_prompts": {}309 "character_prompts": {}
310 },310 },
@@ -318,9 +318,9 @@
318 "objective": {318 "objective": {
319 "customPrompts": {319 "customPrompts": {
320 "default": {320 "default": {
321 "createTask": "Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.\n\nThe objective that you must make a numbered task list for is: [{{objective}}].\nThe tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.\n\nGiven an example objective of 'Make me a four course dinner', here is an example output:\n1. Determine what the courses will be\n2. Find recipes for each course\n3. Go shopping for supplies with {{user}}\n4. Cook the food\n5. Get {{user}} to set the table\n6. Serve the food\n7. Enjoy eating the meal with {{user}}\n ",321 "createTask": "Ignore previous instructions and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.\n\nThe objective that you must make a numbered task list for is: [{{objective}}].\nThe tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.\n\nGiven an example objective of 'Make me a four course dinner', here is an example output:\n1. Determine what the courses will be\n2. Find recipes for each course\n3. Go shopping for supplies with {{user}}\n4. Cook the food\n5. Get {{user}} to set the table\n6. Serve the food\n7. Enjoy eating the meal with {{user}}\n ",
322 "checkTaskCompleted": "Pause your roleplay. Determine if this task is completed: [{{task}}].\nTo do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.\nExample output:\ntrue\n ",322 "checkTaskCompleted": "Ignore previous instructions. Determine if this task is completed: [{{task}}].\nTo do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.\nExample output:\ntrue\n ",
323 "currentTask": "Your current task is [{{task}}]. Balance existing roleplay with completing this task."323 "currentTask": "Your current task is [{{task}}]. Balance existing story with completing this task."
324 }324 }
325 }325 }
326 },326 },
@@ -417,7 +417,6 @@
417 "preset_settings_novel": "Talker-Chat-Clio",417 "preset_settings_novel": "Talker-Chat-Clio",
418 "streaming_novel": true,418 "streaming_novel": true,
419 "preamble": "[ Style: chat, complex, sensory, visceral ]",419 "preamble": "[ Style: chat, complex, sensory, visceral ]",
420 "cfg_uc": "",
421 "banned_tokens": "",420 "banned_tokens": "",
422 "order": [421 "order": [
423 1,422 1,
package-lock.json+47 -60
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.5",3 "version": "1.12.6",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.5",9 "version": "1.12.6",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
@@ -22,7 +22,7 @@
22 "cookie-session": "^2.1.0",22 "cookie-session": "^2.1.0",
23 "cors": "^2.8.5",23 "cors": "^2.8.5",
24 "csrf-csrf": "^2.2.3",24 "csrf-csrf": "^2.2.3",
25 "express": "^4.20.0",25 "express": "^4.21.0",
26 "form-data": "^4.0.0",26 "form-data": "^4.0.0",
27 "google-translate-api-browser": "^3.0.1",27 "google-translate-api-browser": "^3.0.1",
28 "he": "^1.2.0",28 "he": "^1.2.0",
@@ -1568,21 +1568,6 @@
1568 "node": ">=0.10.0"1568 "node": ">=0.10.0"
1569 }1569 }
1570 },1570 },
1571 "node_modules/body-parser/node_modules/qs": {
1572 "version": "6.13.0",
1573 "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
1574 "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
1575 "license": "BSD-3-Clause",
1576 "dependencies": {
1577 "side-channel": "^1.0.6"
1578 },
1579 "engines": {
1580 "node": ">=0.6"
1581 },
1582 "funding": {
1583 "url": "https://github.com/sponsors/ljharb"
1584 }
1585 },
1586 "node_modules/boolbase": {1571 "node_modules/boolbase": {
1587 "version": "1.0.0",1572 "version": "1.0.0",
1588 "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",1573 "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1728,6 +1713,7 @@
1728 "version": "2.7.0",1713 "version": "2.7.0",
1729 "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz",1714 "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz",
1730 "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==",1715 "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==",
1716 "license": "MIT",
1731 "dependencies": {1717 "dependencies": {
1732 "follow-redirects": "^1.15.6"1718 "follow-redirects": "^1.15.6"
1733 }1719 }
@@ -2540,9 +2526,9 @@
2540 "license": "MIT"2526 "license": "MIT"
2541 },2527 },
2542 "node_modules/encodeurl": {2528 "node_modules/encodeurl": {
2543 "version": "1.0.2",2529 "version": "2.0.0",
2544 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",2530 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
2545 "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",2531 "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
2546 "license": "MIT",2532 "license": "MIT",
2547 "engines": {2533 "engines": {
2548 "node": ">= 0.8"2534 "node": ">= 0.8"
@@ -2879,9 +2865,9 @@
2879 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="2865 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
2880 },2866 },
2881 "node_modules/express": {2867 "node_modules/express": {
2882 "version": "4.20.0",2868 "version": "4.21.0",
2883 "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz",2869 "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
2884 "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==",2870 "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
2885 "license": "MIT",2871 "license": "MIT",
2886 "dependencies": {2872 "dependencies": {
2887 "accepts": "~1.3.8",2873 "accepts": "~1.3.8",
@@ -2896,7 +2882,7 @@
2896 "encodeurl": "~2.0.0",2882 "encodeurl": "~2.0.0",
2897 "escape-html": "~1.0.3",2883 "escape-html": "~1.0.3",
2898 "etag": "~1.8.1",2884 "etag": "~1.8.1",
2899 "finalhandler": "1.2.0",2885 "finalhandler": "1.3.1",
2900 "fresh": "0.5.2",2886 "fresh": "0.5.2",
2901 "http-errors": "2.0.0",2887 "http-errors": "2.0.0",
2902 "merge-descriptors": "1.0.3",2888 "merge-descriptors": "1.0.3",
@@ -2905,11 +2891,11 @@
2905 "parseurl": "~1.3.3",2891 "parseurl": "~1.3.3",
2906 "path-to-regexp": "0.1.10",2892 "path-to-regexp": "0.1.10",
2907 "proxy-addr": "~2.0.7",2893 "proxy-addr": "~2.0.7",
2908 "qs": "6.11.0",2894 "qs": "6.13.0",
2909 "range-parser": "~1.2.1",2895 "range-parser": "~1.2.1",
2910 "safe-buffer": "5.2.1",2896 "safe-buffer": "5.2.1",
2911 "send": "0.19.0",2897 "send": "0.19.0",
2912 "serve-static": "1.16.0",2898 "serve-static": "1.16.2",
2913 "setprototypeof": "1.2.0",2899 "setprototypeof": "1.2.0",
2914 "statuses": "2.0.1",2900 "statuses": "2.0.1",
2915 "type-is": "~1.6.18",2901 "type-is": "~1.6.18",
@@ -2928,15 +2914,6 @@
2928 "node": ">= 0.6"2914 "node": ">= 0.6"
2929 }2915 }
2930 },2916 },
2931 "node_modules/express/node_modules/encodeurl": {
2932 "version": "2.0.0",
2933 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
2934 "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
2935 "license": "MIT",
2936 "engines": {
2937 "node": ">= 0.8"
2938 }
2939 },
2940 "node_modules/express/node_modules/safe-buffer": {2917 "node_modules/express/node_modules/safe-buffer": {
2941 "version": "5.2.1",2918 "version": "5.2.1",
2942 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",2919 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -3033,13 +3010,13 @@
3033 }3010 }
3034 },3011 },
3035 "node_modules/finalhandler": {3012 "node_modules/finalhandler": {
3036 "version": "1.2.0",3013 "version": "1.3.1",
3037 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",3014 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
3038 "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",3015 "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
3039 "license": "MIT",3016 "license": "MIT",
3040 "dependencies": {3017 "dependencies": {
3041 "debug": "2.6.9",3018 "debug": "2.6.9",
3042 "encodeurl": "~1.0.2",3019 "encodeurl": "~2.0.0",
3043 "escape-html": "~1.0.3",3020 "escape-html": "~1.0.3",
3044 "on-finished": "2.4.1",3021 "on-finished": "2.4.1",
3045 "parseurl": "~1.3.3",3022 "parseurl": "~1.3.3",
@@ -4080,9 +4057,9 @@
4080 }4057 }
4081 },4058 },
4082 "node_modules/load-bmfont": {4059 "node_modules/load-bmfont": {
4083 "version": "1.4.1",4060 "version": "1.4.2",
4084 "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz",4061 "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz",
4085 "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==",4062 "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==",
4086 "license": "MIT",4063 "license": "MIT",
4087 "dependencies": {4064 "dependencies": {
4088 "buffer-equal": "0.0.1",4065 "buffer-equal": "0.0.1",
@@ -4090,7 +4067,7 @@
4090 "parse-bmfont-ascii": "^1.0.3",4067 "parse-bmfont-ascii": "^1.0.3",
4091 "parse-bmfont-binary": "^1.0.5",4068 "parse-bmfont-binary": "^1.0.5",
4092 "parse-bmfont-xml": "^1.1.4",4069 "parse-bmfont-xml": "^1.1.4",
4093 "phin": "^2.9.1",4070 "phin": "^3.7.1",
4094 "xhr": "^2.0.1",4071 "xhr": "^2.0.1",
4095 "xtend": "^4.0.0"4072 "xtend": "^4.0.0"
4096 }4073 }
@@ -4692,13 +4669,13 @@
4692 "license": "MIT"4669 "license": "MIT"
4693 },4670 },
4694 "node_modules/parse-bmfont-xml": {4671 "node_modules/parse-bmfont-xml": {
4695 "version": "1.1.4",4672 "version": "1.1.6",
4696 "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz",4673 "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
4697 "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==",4674 "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
4698 "license": "MIT",4675 "license": "MIT",
4699 "dependencies": {4676 "dependencies": {
4700 "xml-parse-from-string": "^1.0.0",4677 "xml-parse-from-string": "^1.0.0",
4701 "xml2js": "^0.4.5"4678 "xml2js": "^0.5.0"
4702 }4679 }
4703 },4680 },
4704 "node_modules/parse-headers": {4681 "node_modules/parse-headers": {
@@ -4814,6 +4791,7 @@
4814 "version": "3.7.1",4791 "version": "3.7.1",
4815 "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz",4792 "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz",
4816 "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==",4793 "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==",
4794 "license": "MIT",
4817 "dependencies": {4795 "dependencies": {
4818 "centra": "^2.7.0"4796 "centra": "^2.7.0"
4819 },4797 },
@@ -5032,12 +5010,12 @@
5032 }5010 }
5033 },5011 },
5034 "node_modules/qs": {5012 "node_modules/qs": {
5035 "version": "6.11.0",5013 "version": "6.13.0",
5036 "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",5014 "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
5037 "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",5015 "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
5038 "license": "BSD-3-Clause",5016 "license": "BSD-3-Clause",
5039 "dependencies": {5017 "dependencies": {
5040 "side-channel": "^1.0.4"5018 "side-channel": "^1.0.6"
5041 },5019 },
5042 "engines": {5020 "engines": {
5043 "node": ">=0.6"5021 "node": ">=0.6"
@@ -5344,9 +5322,9 @@
5344 }5322 }
5345 },5323 },
5346 "node_modules/sax": {5324 "node_modules/sax": {
5347 "version": "1.2.4",5325 "version": "1.4.1",
5348 "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",5326 "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
5349 "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",5327 "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
5350 "license": "ISC"5328 "license": "ISC"
5351 },5329 },
5352 "node_modules/send": {5330 "node_modules/send": {
@@ -5373,6 +5351,15 @@
5373 "node": ">= 0.8.0"5351 "node": ">= 0.8.0"
5374 }5352 }
5375 },5353 },
5354 "node_modules/send/node_modules/encodeurl": {
5355 "version": "1.0.2",
5356 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
5357 "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
5358 "license": "MIT",
5359 "engines": {
5360 "node": ">= 0.8"
5361 }
5362 },
5376 "node_modules/send/node_modules/ms": {5363 "node_modules/send/node_modules/ms": {
5377 "version": "2.1.3",5364 "version": "2.1.3",
5378 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",5365 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5380,15 +5367,15 @@
5380 "license": "MIT"5367 "license": "MIT"
5381 },5368 },
5382 "node_modules/serve-static": {5369 "node_modules/serve-static": {
5383 "version": "1.16.0",5370 "version": "1.16.2",
5384 "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz",5371 "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
5385 "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==",5372 "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
5386 "license": "MIT",5373 "license": "MIT",
5387 "dependencies": {5374 "dependencies": {
5388 "encodeurl": "~1.0.2",5375 "encodeurl": "~2.0.0",
5389 "escape-html": "~1.0.3",5376 "escape-html": "~1.0.3",
5390 "parseurl": "~1.3.3",5377 "parseurl": "~1.3.3",
5391 "send": "0.18.0"5378 "send": "0.19.0"
5392 },5379 },
5393 "engines": {5380 "engines": {
5394 "node": ">= 0.8.0"5381 "node": ">= 0.8.0"
package.json+2 -11
@@ -12,7 +12,7 @@
12 "cookie-session": "^2.1.0",12 "cookie-session": "^2.1.0",
13 "cors": "^2.8.5",13 "cors": "^2.8.5",
14 "csrf-csrf": "^2.2.3",14 "csrf-csrf": "^2.2.3",
15 "express": "^4.20.0",15 "express": "^4.21.0",
16 "form-data": "^4.0.0",16 "form-data": "^4.0.0",
17 "google-translate-api-browser": "^3.0.1",17 "google-translate-api-browser": "^3.0.1",
18 "he": "^1.2.0",18 "he": "^1.2.0",
@@ -49,23 +49,14 @@
49 "node": ">= 18"49 "node": ">= 18"
50 },50 },
51 "overrides": {51 "overrides": {
52 "parse-bmfont-xml": {
53 "xml2js": "^0.5.0"
54 },
55 "vectra": {52 "vectra": {
56 "openai": "^4.17.0"53 "openai": "^4.17.0"
57 },54 },
58 "load-bmfont": {
59 "phin": "^3.7.1"
60 },
61 "axios": {55 "axios": {
62 "follow-redirects": "^1.15.4"56 "follow-redirects": "^1.15.4"
63 },57 },
64 "node-fetch": {58 "node-fetch": {
65 "whatwg-url": "^14.0.0"59 "whatwg-url": "^14.0.0"
66 },
67 "express": {
68 "send": "^0.19.0"
69 }60 }
70 },61 },
71 "name": "sillytavern",62 "name": "sillytavern",
@@ -75,7 +66,7 @@
75 "type": "git",66 "type": "git",
76 "url": "https://github.com/SillyTavern/SillyTavern.git"67 "url": "https://github.com/SillyTavern/SillyTavern.git"
77 },68 },
78 "version": "1.12.5",69 "version": "1.12.6",
79 "scripts": {70 "scripts": {
80 "start": "node server.js",71 "start": "node server.js",
81 "start:no-csrf": "node server.js --disableCsrf",72 "start:no-csrf": "node server.js --disableCsrf",
public/css/mobile-styles.css+9 -10
@@ -103,7 +103,6 @@
103 touch-action: none;103 touch-action: none;
104 overflow: hidden;104 overflow: hidden;
105 position: fixed;105 position: fixed;
106
107 }106 }
108107
109 .world_entry_form_control {108 .world_entry_form_control {
@@ -112,7 +111,7 @@
112111
113 .drawer-content {112 .drawer-content {
114 min-width: unset;113 min-width: unset;
115 width: 100%;114 width: 100dvw;
116 max-height: calc(100vh - 45px);115 max-height: calc(100vh - 45px);
117 max-height: calc(100dvh - 45px);116 max-height: calc(100dvh - 45px);
118 position: fixed;117 position: fixed;
@@ -159,9 +158,9 @@
159 #sheld,158 #sheld,
160 #character_popup,159 #character_popup,
161 .drawer-content {160 .drawer-content {
162 width: 100% !important;161 width: 100dvw !important;
163 margin: 0 auto;162 margin: 0 auto;
164 max-width: 100%;163 max-width: 100dvw;
165 left: 0 !important;164 left: 0 !important;
166 resize: none !important;165 resize: none !important;
167 top: var(--topBarBlockSize);166 top: var(--topBarBlockSize);
@@ -233,9 +232,9 @@
233 /* 100vh are fallback units for browsers that don't support dvh */232 /* 100vh are fallback units for browsers that don't support dvh */
234 height: calc(100vh - 45px);233 height: calc(100vh - 45px);
235 height: calc(100dvh - 45px);234 height: calc(100dvh - 45px);
236 min-width: 100% !important;235 min-width: 100dvw !important;
237 width: 100% !important;236 width: 100dvw !important;
238 max-width: 100% !important;237 max-width: 100dvw !important;
239 overflow-y: hidden;238 overflow-y: hidden;
240 border-left: 1px solid var(--SmartThemeBorderColor);239 border-left: 1px solid var(--SmartThemeBorderColor);
241 border-right: 1px solid var(--SmartThemeBorderColor);240 border-right: 1px solid var(--SmartThemeBorderColor);
@@ -481,10 +480,10 @@
481 margin: 0 auto;480 margin: 0 auto;
482 height: calc(100vh - 70px);481 height: calc(100vh - 70px);
483 height: calc(100dvh - 70px);482 height: calc(100dvh - 70px);
484 width: calc(100% - 5px);483 width: calc(100dvw - 5px);
485 max-height: calc(100vh - 70px);484 max-height: calc(100vh - 70px);
486 max-height: calc(100dvh - 70px);485 max-height: calc(100dvh - 70px);
487 max-width: calc(100% - 5px);486 max-width: calc(100dvw - 5px);
488487
489 }488 }
490489
@@ -502,4 +501,4 @@
502 #horde_model {501 #horde_model {
503 height: unset;502 height: unset;
504 }503 }
505}
505 \ No newline at end of file \ No newline at end of file
504}
public/css/popup.css+4 -0
@@ -47,6 +47,10 @@ dialog {
47 padding: 1px;47 padding: 1px;
48}48}
4949
50.popup:not(:has(.img_enlarged_container)) .popup-body {
51 max-height: 95dvh;
52}
53
50.popup .popup-content {54.popup .popup-content {
51 margin-top: 10px;55 margin-top: 10px;
52 padding: 0 8px;56 padding: 0 8px;
public/css/promptmanager.css+35 -8
@@ -91,15 +91,34 @@
91#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect {91#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect {
92 display: none;92 display: none;
93 padding: 0.5em;93 padding: 0.5em;
94 height: 100%;
95 display: flex;
96 flex-direction: column;
94}97}
9598
96#completion_prompt_manager_popup .completion_prompt_manager_popup_entry {99#completion_prompt_manager_popup .completion_prompt_manager_popup_entry {
97 padding: 1em;100 padding: 0.5em;
98 margin-top: 2em;101 flex: 1;
102}
103
104#completion_prompt_manager_popup .completion_prompt_manager_popup_entry_form {
105 height: 100%;
106 display: flex;
107 flex-direction: column;
108}
109
110#completion_prompt_manager_popup .completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt) {
111 flex: 1;
112 display: flex;
113 flex-direction: column;
114}
115
116#completion_prompt_manager_popup #completion_prompt_manager_popup_entry_form_prompt {
117 flex: 1;
99}118}
100119
101#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect .completion_prompt_manager_popup_entry {120#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect .completion_prompt_manager_popup_entry {
102 padding: 1em;121 padding: 0.5em;
103}122}
104123
105#completion_prompt_manager_popup #completion_prompt_manager_popup_entry_form_inspect_list {124#completion_prompt_manager_popup #completion_prompt_manager_popup_entry_form_inspect_list {
@@ -248,7 +267,7 @@
248}267}
249268
250#completion_prompt_manager_footer_append_prompt {269#completion_prompt_manager_footer_append_prompt {
251 font-size: 16px;270 font-size: 1em;
252}271}
253272
254#prompt-manager-export-format-popup {273#prompt-manager-export-format-popup {
@@ -303,10 +322,6 @@
303 justify-content: space-between;322 justify-content: space-between;
304}323}
305324
306#prompt-manager-export-format-popup span {
307 font-size: 16px;
308}
309
310@media screen and (max-width: 412px) {325@media screen and (max-width: 412px) {
311 #completion_prompt_manager_popup {326 #completion_prompt_manager_popup {
312 max-width: 100%;327 max-width: 100%;
@@ -316,3 +331,15 @@
316 margin-left: 0.5em;331 margin-left: 0.5em;
317 }332 }
318}333}
334
335.completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt:disabled)>div:first-child::after {
336 content: 'The content of this prompt is pulled from elsewhere and cannot be edited here.';
337 display: block;
338 width: 100%;
339 font-weight: 600;
340 text-align: center;
341}
342
343.completion_prompt_manager_popup_entry_form_control #completion_prompt_manager_popup_entry_form_prompt:disabled {
344 visibility: hidden;
345}
public/css/world-info.css+4 -0
@@ -274,3 +274,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
274 align-self: center;274 align-self: center;
275 width: 100%;275 width: 100%;
276}276}
277
278.world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) {
279 display: none;
280}
public/img/down-arrow.svg+3 -0
@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" width="10" height="7">
2 <polygon points="0,0 10,0 5,7" fill="#888888" />
3</svg>
public/img/user-default.png+0 -0

Binary file

public/index.html+455 -360
@@ -119,7 +119,7 @@
119 <option value="gui" data-i18n="Default">Default</option>119 <option value="gui" data-i18n="Default">Default</option>
120 </select>120 </select>
121 <div class="flex-container marginLeft5 ">121 <div class="flex-container marginLeft5 ">
122 <input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings">122 <input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings, .preset">
123 <i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>123 <i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
124 <i data-preset-manager-new="novel" class="menu_button fa-solid fa-file-circle-plus" title="Save preset as" data-i18n="[title]Save preset as"></i>124 <i data-preset-manager-new="novel" class="menu_button fa-solid fa-file-circle-plus" title="Save preset as" data-i18n="[title]Save preset as"></i>
125 <i data-preset-manager-import="novel" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>125 <i data-preset-manager-import="novel" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
@@ -324,6 +324,19 @@
324 </div>324 </div>
325 </div>325 </div>
326 <div class="range-block">326 <div class="range-block">
327 <div class="range-block-title" data-i18n="Min P">
328 Min P
329 </div>
330 <div class="range-block-range-and-counter">
331 <div class="range-block-range">
332 <input type="range" id="min_p_novel" name="volume" min="0" max="1" step="0.001">
333 </div>
334 <div class="range-block-counter">
335 <input type="number" min="0" max="1" step="0.001" data-for="min_p_novel" id="min_p_counter_novel">
336 </div>
337 </div>
338 </div>
339 <div class="range-block">
327 <div class="range-block-title" data-i18n="TFS">340 <div class="range-block-title" data-i18n="TFS">
328 TFS341 TFS
329 </div>342 </div>
@@ -337,6 +350,136 @@
337 </div>350 </div>
338 </div>351 </div>
339 <div class="range-block">352 <div class="range-block">
353 <div class="range-block-title" data-i18n="Top P">
354 Top P
355 </div>
356 <div class="range-block-range-and-counter">
357 <div class="range-block-range">
358 <input type="range" id="top_p_novel" name="volume" min="0" max="1" step="0.001">
359 </div>
360 <div class="range-block-counter">
361 <input type="number" min="0" max="1" step="0.001" data-for="top_p_novel" id="top_p_counter_novel">
362 </div>
363 </div>
364 </div>
365 <div class="range-block">
366 <div class="range-block-title" data-i18n="Top A">
367 Top A
368 </div>
369 <div class="range-block-range-and-counter">
370 <div class="range-block-range">
371 <input type="range" id="top_a_novel" name="volume" min="0" max="1" step="0.001">
372 </div>
373 <div class="range-block-counter">
374 <input type="number" min="0" max="1" step="0.001" data-for="top_a_novel" id="top_a_counter_novel">
375 </div>
376 </div>
377 </div>
378 <div class="range-block">
379 <div class="range-block-title" data-i18n="Top K">
380 Top K
381 </div>
382 <div class="range-block-range-and-counter">
383 <div class="range-block-range">
384 <input type="range" id="top_k_novel" name="volume" min="0" max="300" step="1">
385 </div>
386 <div class="range-block-counter">
387 <input type="number" min="0" max="300" step="1" data-for="top_k_novel" id="top_k_counter_novel">
388 </div>
389 </div>
390 </div>
391 <div class="range-block">
392 <div class="range-block-title" data-i18n="Mirostat Tau">
393 Mirostat Tau
394 </div>
395 <div class="range-block-range-and-counter">
396 <div class="range-block-range">
397 <input type="range" id="mirostat_tau_novel" name="volume" min="0" max="6" step="0.01">
398 </div>
399 <div class="range-block-counter">
400 <input type="number" min="0" max="6" step="0.01" data-for="mirostat_tau_novel" id="mirostat_tau_counter_novel">
401 </div>
402 </div>
403 </div>
404 <div class="range-block">
405 <div class="range-block-title" data-i18n="Mirostat LR">
406 Mirostat LR
407 </div>
408 <div class="range-block-range-and-counter">
409 <div class="range-block-range">
410 <input type="range" id="mirostat_lr_novel" name="volume" min="0" max="1" step="0.01">
411 </div>
412 <div class="range-block-counter">
413 <input type="number" min="0" max="1" step="0.01" data-for="mirostat_lr_novel" id="mirostat_lr_counter_novel">
414 </div>
415 </div>
416 </div>
417 <div class="range-block">
418 <div class="range-block-title" data-i18n="Typical P">
419 Typical P
420 </div>
421 <div class="range-block-range-and-counter">
422 <div class="range-block-range">
423 <input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.001">
424 </div>
425 <div class="range-block-counter">
426 <input type="number" min="0" max="1" step="0.001" data-for="typical_p_novel" id="typical_p_counter_novel">
427 </div>
428 </div>
429 </div>
430 <div class="range-block">
431 <div class="range-block-title" data-i18n="Linear">
432 Linear
433 </div>
434 <div class="range-block-range-and-counter">
435 <div class="range-block-range">
436 <input type="range" id="math1_temp_novel" name="volume" min="0" max="1.5" step="0.01">
437 </div>
438 <div class="range-block-counter">
439 <input type="number" min="0" max="1.5" step="0.01" data-for="math1_temp_novel" id="math1_temp_counter_novel">
440 </div>
441 </div>
442 </div>
443 <div class="range-block">
444 <div class="range-block-title" data-i18n="Quad">
445 Quad
446 </div>
447 <div class="range-block-range-and-counter">
448 <div class="range-block-range">
449 <input type="range" id="math1_quad_novel" name="volume" min="0" max="0.4" step="0.01">
450 </div>
451 <div class="range-block-counter">
452 <input type="number" min="0" max="0.4" step="0.01" data-for="math1_quad_novel" id="math1_quad_counter_novel">
453 </div>
454 </div>
455 </div>
456 <div class="range-block">
457 <div class="range-block-title" data-i18n="Conf">
458 Conf
459 </div>
460 <div class="range-block-range-and-counter">
461 <div class="range-block-range">
462 <input type="range" id="math1_quad_entropy_scale_novel" name="volume" min="-0.4" max="0.4" step="0.01">
463 </div>
464 <div class="range-block-counter">
465 <input type="number" min="-0.4" max="0.4" step="0.01" data-for="math1_quad_entropy_scale_novel" id="math1_quad_entropy_scale_counter_novel">
466 </div>
467 </div>
468 </div>
469 <div class="range-block">
470 <div class="range-block-title" data-i18n="Min Length">
471 Min Length
472 </div>
473 <div class="range-block-range-and-counter">
474 <div class="range-block-range">
475 <input type="range" id="min_length_novel" name="volume" min="1" max="150" step="1">
476 </div>
477 <div class="range-block-counter">
478 <input type="number" min="1" max="150" step="1" data-for="min_length_novel" id="min_length_counter_novel">
479 </div>
480 </div>
481 </div>
482 <div class="range-block">
340 <div class="range-block-title" data-i18n="Phrase Repetition Penalty">483 <div class="range-block-title" data-i18n="Phrase Repetition Penalty">
341 Phrase Repetition Penalty484 Phrase Repetition Penalty
342 </div>485 </div>
@@ -349,6 +492,46 @@
349 <option value="very_aggressive" data-i18n="Very aggressive">Very aggressive</option>492 <option value="very_aggressive" data-i18n="Very aggressive">Very aggressive</option>
350 </select>493 </select>
351 </div>494 </div>
495 <div class="range-block">
496 <div class="range-block-title openai_restorable">
497 <span data-i18n="Preamble">Preamble</span>
498 <div id="nai_preamble_restore" title="Restore default prompt" data-i18n="[title]Restore default prompt" class="right_menu_button">
499 <div class="fa-solid fa-clock-rotate-left "></div>
500 </div>
501 </div>
502 <div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output.">
503 Use style tags to modify the writing style of the output.
504 </div>
505 <div class="wide100p">
506 <textarea id="nai_preamble_textarea" class="text_pole textarea_compact" name="nai_preamble" rows="2" placeholder=""></textarea>
507 </div>
508 </div>
509 <div class="range-block">
510 <div class="range-block-title justifyLeft">
511 <span data-i18n="Banned Tokens">Banned Tokens</span>
512 </div>
513 <div class="toggle-description justifyLeft" data-i18n="Sequences you don't want to appear in the output. One per line.">
514 Sequences you don't want to appear in the output. One per line. Text or [token ids].
515 </div>
516 <div class="wide100p">
517 <textarea id="nai_banned_tokens" class="text_pole textarea_compact" name="nai_banned_tokens" rows="3" placeholder="Example:&#10;some text&#10;[42, 69, 1337]"></textarea>
518 </div>
519 </div>
520 <div class="range-block">
521 <div id="logit_bias_novel" class="range-block-title title_restorable">
522 <span data-i18n="Logit Bias">Logit Bias</span>
523 <div id="novelai_logit_bias_new_entry" class="menu_button menu_button_icon">
524 <i class="fa-xs fa-solid fa-plus"></i>
525 <small data-i18n="Add">Add</small>
526 </div>
527 </div>
528 <div class="toggle-description justifyLeft" data-i18n="Helps to ban or reenforce the usage of certain words">
529 Helps to ban or reinforce the usage of certain tokens.
530 </div>
531 <div class="flex-container flexFlowColumn wide100p">
532 <div class="logit_bias_list"></div>
533 </div>
534 </div>
352 </div>535 </div>
353 <div id="range_block_openai">536 <div id="range_block_openai">
354 <div class="range-block">537 <div class="range-block">
@@ -907,163 +1090,6 @@
907 </div>1090 </div>
908 </div><!-- end of kobold settings-->1091 </div><!-- end of kobold settings-->
909 <div id="novel_api-settings">1092 <div id="novel_api-settings">
910 <div class="range-block">
911 <div class="range-block-title openai_restorable">
912 <span data-i18n="Preamble">Preamble</span>
913 <div id="nai_preamble_restore" title="Restore default prompt" data-i18n="[title]Restore default prompt" class="right_menu_button">
914 <div class="fa-solid fa-clock-rotate-left "></div>
915 </div>
916 </div>
917 <div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output.">
918 Use style tags to modify the writing style of the output.
919 </div>
920 <div class="wide100p">
921 <textarea id="nai_preamble_textarea" class="text_pole textarea_compact" name="nai_preamble" rows="2" placeholder=""></textarea>
922 </div>
923 </div>
924 <div class="range-block">
925 <div class="range-block-title justifyLeft">
926 <span data-i18n="Banned Tokens">Banned Tokens</span>
927 </div>
928 <div class="toggle-description justifyLeft" data-i18n="Sequences you don't want to appear in the output. One per line.">
929 Sequences you don't want to appear in the output. One per line. Text or [token ids].
930 </div>
931 <div class="wide100p">
932 <textarea id="nai_banned_tokens" class="text_pole textarea_compact" name="nai_banned_tokens" rows="3" placeholder="Example:&#10;some text&#10;[42, 69, 1337]"></textarea>
933 </div>
934 </div>
935 <div class="range-block">
936 <div id="logit_bias_novel" class="range-block-title title_restorable">
937 <span data-i18n="Logit Bias">Logit Bias</span>
938 <div id="novelai_logit_bias_new_entry" class="menu_button menu_button_icon">
939 <i class="fa-xs fa-solid fa-plus"></i>
940 <small data-i18n="Add">Add</small>
941 </div>
942 </div>
943 <div class="toggle-description justifyLeft" data-i18n="Helps to ban or reenforce the usage of certain words">
944 Helps to ban or reinforce the usage of certain tokens.
945 </div>
946 <div class="flex-container flexFlowColumn wide100p">
947 <div class="logit_bias_list"></div>
948 </div>
949 </div>
950 <div class="range-block">
951 <hr>
952 <div class="range-block-title justifyLeft" data-i18n="CFG Scale">
953 CFG Scale
954 </div>
955 <div class="range-block-range-and-counter">
956 <div class="range-block-range">
957 <input type="range" id="cfg_scale_novel" name="volume" min="1" max="3" step="0.05">
958 </div>
959 <div class="range-block-counter">
960 <input type="number" min="1" max="3" step="0.05" data-for="cfg_scale_novel" id="cfg_scale_counter_novel">
961 </div>
962 </div>
963 </div>
964 <div class="range-block">
965 <div class="range-block-title justifyLeft">
966 <span data-i18n="Negative Prompt">Negative Prompt</span>
967 </div>
968 <div class="wide100p">
969 <textarea id="nai_cfg_uc" class="text_pole textarea_compact" name="cfg_uc" rows="2" data-i18n="[placeholder]Add text here that would make the AI generate things you don't want in your outputs." placeholder="Add text here that would make the AI generate things you don't want in your outputs."></textarea>
970 </div>
971 <small class="margin-bot-10px" data-i18n="Used if CFG Scale is unset globally, per chat or character">
972 Used if CFG Scale is unset globally, per chat or character
973 </small>
974 <hr>
975 </div>
976 <div class="range-block">
977 <div class="range-block-title" data-i18n="Top P">
978 Top P
979 </div>
980 <div class="range-block-range-and-counter">
981 <div class="range-block-range">
982 <input type="range" id="top_p_novel" name="volume" min="0" max="1" step="0.001">
983 </div>
984 <div class="range-block-counter">
985 <input type="number" min="0" max="1" step="0.001" data-for="top_p_novel" id="top_p_counter_novel">
986 </div>
987 </div>
988 </div>
989 <div class="range-block">
990 <div class="range-block-title" data-i18n="Top A">
991 Top A
992 </div>
993 <div class="range-block-range-and-counter">
994 <div class="range-block-range">
995 <input type="range" id="top_a_novel" name="volume" min="0" max="1" step="0.001">
996 </div>
997 <div class="range-block-counter">
998 <input type="number" min="0" max="1" step="0.001" data-for="top_a_novel" id="top_a_counter_novel">
999 </div>
1000 </div>
1001 </div>
1002 <div class="range-block">
1003 <div class="range-block-title" data-i18n="Top K">
1004 Top K
1005 </div>
1006 <div class="range-block-range-and-counter">
1007 <div class="range-block-range">
1008 <input type="range" id="top_k_novel" name="volume" min="0" max="300" step="1">
1009 </div>
1010 <div class="range-block-counter">
1011 <input type="number" min="0" max="300" step="1" data-for="top_k_novel" id="top_k_counter_novel">
1012 </div>
1013 </div>
1014 </div>
1015 <div class="range-block">
1016 <div class="range-block-title" data-i18n="Mirostat Tau">
1017 Mirostat Tau
1018 </div>
1019 <div class="range-block-range-and-counter">
1020 <div class="range-block-range">
1021 <input type="range" id="mirostat_tau_novel" name="volume" min="0" max="6" step="0.01">
1022 </div>
1023 <div class="range-block-counter">
1024 <input type="number" min="0" max="6" step="0.01" data-for="mirostat_tau_novel" id="mirostat_tau_counter_novel">
1025 </div>
1026 </div>
1027 </div>
1028 <div class="range-block">
1029 <div class="range-block-title" data-i18n="Mirostat LR">
1030 Mirostat LR
1031 </div>
1032 <div class="range-block-range-and-counter">
1033 <div class="range-block-range">
1034 <input type="range" id="mirostat_lr_novel" name="volume" min="0" max="1" step="0.01">
1035 </div>
1036 <div class="range-block-counter">
1037 <input type="number" min="0" max="1" step="0.01" data-for="mirostat_lr_novel" id="mirostat_lr_counter_novel">
1038 </div>
1039 </div>
1040 </div>
1041 <div class="range-block">
1042 <div class="range-block-title" data-i18n="Typical P">
1043 Typical P
1044 </div>
1045 <div class="range-block-range-and-counter">
1046 <div class="range-block-range">
1047 <input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.001">
1048 </div>
1049 <div class="range-block-counter">
1050 <input type="number" min="0" max="1" step="0.001" data-for="typical_p_novel" id="typical_p_counter_novel">
1051 </div>
1052 </div>
1053 </div>
1054 <div class="range-block">
1055 <div class="range-block-title" data-i18n="Min Length">
1056 Min Length
1057 </div>
1058 <div class="range-block-range-and-counter">
1059 <div class="range-block-range">
1060 <input type="range" id="min_length_novel" name="volume" min="1" max="150" step="1">
1061 </div>
1062 <div class="range-block-counter">
1063 <input type="number" min="1" max="150" step="1" data-for="min_length_novel" id="min_length_counter_novel">
1064 </div>
1065 </div>
1066 </div>
1067 <div class="range-block flexFlowColumn">1093 <div class="range-block flexFlowColumn">
1068 <div class="range-block-title">1094 <div class="range-block-title">
1069 <span data-i18n="Samplers Order">Samplers Order</span>1095 <span data-i18n="Samplers Order">Samplers Order</span>
@@ -1102,17 +1128,22 @@
1102 <small>5</small>1128 <small>5</small>
1103 <div class="toggle_button right_menu_button"></div>1129 <div class="toggle_button right_menu_button"></div>
1104 </div>1130 </div>
1105 <div data-id="6">1131 <!-- 6 and 7 were removed samplers. -->
1106 <span data-i18n="CFG">CFG</span>
1107 <small>6</small>
1108 <div class="toggle_button right_menu_button"></div>
1109 </div>
1110 <!-- Yes, there is no 7. It was a removed Top G sampling. RIP. -->
1111 <div data-id="8">1132 <div data-id="8">
1112 <span data-i18n="Mirostat">Mirostat</span>1133 <span data-i18n="Mirostat">Mirostat</span>
1113 <small>8</small>1134 <small>8</small>
1114 <div class="toggle_button right_menu_button"></div>1135 <div class="toggle_button right_menu_button"></div>
1115 </div>1136 </div>
1137 <div data-id="9">
1138 <span data-i18n="Unified Sampling">Unified Sampling</span>
1139 <small>9</small>
1140 <div class="toggle_button right_menu_button"></div>
1141 </div>
1142 <div data-id="10">
1143 <span data-i18n="Min P">Min P</span>
1144 <small>10</small>
1145 <div class="toggle_button right_menu_button"></div>
1146 </div>
1116 </div>1147 </div>
1117 </div>1148 </div>
1118 </div><!-- end of novel settings-->1149 </div><!-- end of novel settings-->
@@ -1279,7 +1310,7 @@
1279 </div>1310 </div>
1280 </div>1311 </div>
12811312
1282 <div data-tg-type="koboldcpp" id="xtc_block" class="wide100p">1313 <div data-tg-type="koboldcpp, aphrodite" id="xtc_block" class="wide100p">
1283 <h4 class="wide100p textAlignCenter">1314 <h4 class="wide100p textAlignCenter">
1284 <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>1315 <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
1285 <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">1316 <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
@@ -1339,7 +1370,7 @@
1339 </div>1370 </div>
1340 </div>1371 </div>
1341 </div>1372 </div>
1342 <div data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp" id="dynatemp_block_ooba" class="wide100p">1373 <div data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" id="dynatemp_block_ooba" class="wide100p">
1343 <h4 class="wide100p textAlignCenter">1374 <h4 class="wide100p textAlignCenter">
1344 <div class="flex-container alignitemscenter justifyCenter">1375 <div class="flex-container alignitemscenter justifyCenter">
1345 <div class="checkbox_label" for="dynatemp_textgenerationwebui">1376 <div class="checkbox_label" for="dynatemp_textgenerationwebui">
@@ -2038,6 +2069,7 @@
2038 <select id="model_novel_select">2069 <select id="model_novel_select">
2039 <option value="clio-v1">Clio</option>2070 <option value="clio-v1">Clio</option>
2040 <option value="kayra-v1">Kayra</option>2071 <option value="kayra-v1">Kayra</option>
2072 <option value="llama-3-erato-v1">Erato</option>
2041 </select>2073 </select>
2042 <div class="flex-container">2074 <div class="flex-container">
2043 <div id="api_button_novel" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>2075 <div id="api_button_novel" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
@@ -2850,10 +2882,13 @@
2850 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>2882 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
2851 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>2883 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
2852 <option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option>2884 <option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option>
2885 <option value="gemini-1.5-pro-002">Gemini 1.5 Pro [002]</option>
2853 <option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experiment 2024-08-27</option>2886 <option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experiment 2024-08-27</option>
2854 <option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experiment 2024-08-27</option>2887 <option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experiment 2024-08-27</option>
2888 <option value="gemini-1.5-flash-8b-exp-0924">Gemini 1.5 Flash 8B Experiment 2024-09-24</option>
2855 <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option>2889 <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option>
2856 <option value="gemini-1.5-flash-001">Gemini 1.5 Flash [001]</option>2890 <option value="gemini-1.5-flash-001">Gemini 1.5 Flash [001]</option>
2891 <option value="gemini-1.5-flash-002">Gemini 1.5 Flash [002]</option>
2857 <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest]</option>2892 <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest]</option>
2858 <option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001]</option>2893 <option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001]</option>
2859 <option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision [latest]</option>2894 <option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision [latest]</option>
@@ -2884,6 +2919,7 @@
2884 <option value="mistral-large-latest">mistral-large-latest</option>2919 <option value="mistral-large-latest">mistral-large-latest</option>
2885 <option value="codestral-latest">codestral-latest</option>2920 <option value="codestral-latest">codestral-latest</option>
2886 <option value="codestral-mamba-latest">codestral-mamba-latest</option>2921 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
2922 <option value="pixtral-latest">pixtral-latest</option>
2887 </optgroup>2923 </optgroup>
2888 <optgroup label="Sub-versions">2924 <optgroup label="Sub-versions">
2889 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>2925 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>
@@ -2891,11 +2927,13 @@
2891 <option value="mistral-tiny-2312">mistral-tiny-2312</option>2927 <option value="mistral-tiny-2312">mistral-tiny-2312</option>
2892 <option value="mistral-small-2312">mistral-small-2312</option>2928 <option value="mistral-small-2312">mistral-small-2312</option>
2893 <option value="mistral-small-2402">mistral-small-2402</option>2929 <option value="mistral-small-2402">mistral-small-2402</option>
2930 <option value="mistral-small-2409">mistral-small-2409</option>
2894 <option value="mistral-medium-2312">mistral-medium-2312</option>2931 <option value="mistral-medium-2312">mistral-medium-2312</option>
2895 <option value="mistral-large-2402">mistral-large-2402</option>2932 <option value="mistral-large-2402">mistral-large-2402</option>
2896 <option value="mistral-large-2407">mistral-large-2407</option>2933 <option value="mistral-large-2407">mistral-large-2407</option>
2897 <option value="codestral-2405">codestral-2405</option>2934 <option value="codestral-2405">codestral-2405</option>
2898 <option value="codestral-mamba-2407">codestral-mamba-2407</option>2935 <option value="codestral-mamba-2407">codestral-mamba-2407</option>
2936 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
2899 </optgroup>2937 </optgroup>
2900 </select>2938 </select>
2901 </div>2939 </div>
@@ -3085,9 +3123,22 @@
3085 <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>3123 <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
3086 </div>3124 </div>
3087 <div id="AdvancedFormatting" class="drawer-content">3125 <div id="AdvancedFormatting" class="drawer-content">
3088 <h3 class="margin0" data-i18n="Advanced Formatting">3126 <div class="flex-container alignItemsBaseline">
3089 Advanced Formatting3127 <h3 class="margin0 flex1" data-i18n="Advanced Formatting">
3090 </h3>3128 Advanced Formatting
3129 </h3>
3130 <div class="flex-container">
3131 <input id="af_master_import_file" type="file" hidden accept=".json" class="displayNone">
3132 <div id="af_master_import" class="menu_button menu_button_icon" title="Import Advanced Formatting settings&#10;&#10;You can also provide legacy files for Instruct and Context templates." data-i18n="[title]Import Advanced Formatting settings">
3133 <i class="fa-solid fa-file-import"></i>
3134 <span data-i18n="Master Import">Master Import</span>
3135 </div>
3136 <div id="af_master_export" class="menu_button menu_button_icon" title="Export Advanced Formatting settings" data-i18n="[title]Export Advanced Formatting settings">
3137 <i class="fa-solid fa-file-export"></i>
3138 <span data-i18n="Master Export">Master Export</span>
3139 </div>
3140 </div>
3141 </div>
3091 <div class="flex-container spaceEvenly">3142 <div class="flex-container spaceEvenly">
3092 <div id="ContextSettings" class="flex-container flexNoGap flexFlowColumn flex1">3143 <div id="ContextSettings" class="flex-container flexNoGap flexFlowColumn flex1">
3093 <div>3144 <div>
@@ -3099,18 +3150,17 @@
3099 </a>3150 </a>
3100 </div>3151 </div>
3101 </h4>3152 </h4>
3102 <div class="flex-container justifyCenter">
3103 <input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
3104 <i id="context_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this template for Instruct Mode." data-i18n="[title]Auto-select this template for Instruct Mode"></i>
3105 <i data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3106 <i data-preset-manager-new="context" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3107 <i data-preset-manager-import="context" class="menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3108 <i data-preset-manager-export="context" class="menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3109 <i data-preset-manager-restore="context" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3110 <i id="context_delete_preset" data-preset-manager-delete="context" class="menu_button fa-solid fa-trash-can" title="Delete the template" data-i18n="[title]Delete the template"></i>
3111 </div>
3112 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">3153 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">
3113 <select id="context_presets" data-preset-manager-for="context" class="flex1 text_pole"></select>3154 <select id="context_presets" data-preset-manager-for="context" class="flex1 text_pole"></select>
3155 <div class="flex-container justifyCenter">
3156 <input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
3157 <i data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3158 <i data-preset-manager-new="context" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3159 <i data-preset-manager-import="context" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3160 <i data-preset-manager-export="context" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3161 <i data-preset-manager-restore="context" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3162 <i id="context_delete_preset" data-preset-manager-delete="context" class="menu_button fa-solid fa-trash-can" title="Delete the template" data-i18n="[title]Delete the template"></i>
3163 </div>
3114 </div>3164 </div>
3115 <div>3165 <div>
3116 <label for="context_story_string" class="flex-container">3166 <label for="context_story_string" class="flex-container">
@@ -3141,9 +3191,6 @@
3141 <span data-i18n="Context Formatting">3191 <span data-i18n="Context Formatting">
3142 Context Formatting3192 Context Formatting
3143 </span>3193 </span>
3144 <small data-i18n="(Saves to Template)">
3145 (Saves to Template)
3146 </small>
3147 </h4>3194 </h4>
31483195
3149 <label class="checkbox_label" for="always-force-name2-checkbox">3196 <label class="checkbox_label" for="always-force-name2-checkbox">
@@ -3174,11 +3221,6 @@
3174 Trim Incomplete Sentences3221 Trim Incomplete Sentences
3175 </small>3222 </small>
3176 </label>3223 </label>
3177 <!-- Add margin since this is a child of above -->
3178 <label class="checkbox_label indent20p" for="include_newline_checkbox">
3179 <input id="include_newline_checkbox" type="checkbox" />
3180 <small data-i18n="Include Newline">Include Newline</small>
3181 </label>
3182 <label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings.">3224 <label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings.">
3183 <input id="context_use_stop_strings" type="checkbox" />3225 <input id="context_use_stop_strings" type="checkbox" />
3184 <small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small>3226 <small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small>
@@ -3194,31 +3236,6 @@
3194 <small data-i18n="Allow Post-History Instructions">Allow Post-History Instructions</small>3236 <small data-i18n="Allow Post-History Instructions">Allow Post-History Instructions</small>
3195 </label>3237 </label>
3196 </div>3238 </div>
3197 <div>
3198 <h4 class="range-block-title justifyLeft standoutHeader">
3199 <span data-i18n="Custom Stopping Strings">
3200 Custom Stopping Strings
3201 </span>
3202 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#custom-stopping-strings" class="notes-link" target="_blank">
3203 <span class="fa-solid fa-circle-question note-link-span"></span>
3204 </a>
3205 </h4>
3206 <div>
3207 <small>
3208 <span data-i18n="JSON serialized array of strings">JSON serialized array of strings</span>
3209 <i class="fa-solid fa-question-circle opacity50p" title="e.g: [&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></i>
3210 </small>
3211 </div>
3212 <div>
3213 <textarea id="custom_stopping_strings" class="text_pole textarea_compact monospace autoSetHeight"></textarea>
3214 </div>
3215 <label class="checkbox_label" for="custom_stopping_strings_macro">
3216 <input id="custom_stopping_strings_macro" type="checkbox" checked>
3217 <small data-i18n="Replace Macro in Custom Stopping Strings">
3218 Replace Macro in Custom Stopping Strings
3219 </small>
3220 </label>
3221 </div>
3222 </div>3239 </div>
3223 </div>3240 </div>
3224 <div id="InstructSettingsColumn" class="flex-container flexNoGap flexFlowColumn flex1">3241 <div id="InstructSettingsColumn" class="flex-container flexNoGap flexFlowColumn flex1">
@@ -3231,29 +3248,28 @@
3231 </a>3248 </a>
3232 </div>3249 </div>
3233 <div class="flex-container">3250 <div class="flex-container">
3234 <label for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
3235 <input id="instruct_enabled" type="checkbox" style="display:none;" />
3236 <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
3237 </label>
3238 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">3251 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">
3239 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />3252 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
3240 <small><i class="fa-solid fa-link menu_button margin0"></i></small>3253 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
3241 </label>3254 </label>
3255 <label for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
3256 <input id="instruct_enabled" type="checkbox" style="display:none;" />
3257 <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
3258 </label>
3242 </div>3259 </div>
3243 </h4>3260 </h4>
3244 <div class="flex-container margin0 justifyCenter">
3245 <input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
3246 <i id="instruct_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this template on API connection." data-i18n="[title]Auto-select this template on API connection"></i>
3247 <i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3248 <i data-preset-manager-new="instruct" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3249 <i data-preset-manager-import="instruct" class=" menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3250 <i data-preset-manager-export="instruct" class=" menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3251 <i data-preset-manager-restore="instruct" class=" menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3252 <i data-preset-manager-delete="instruct" class=" menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3253 </div>
32543261
3255 <div class="flex-container" title="Select your current Instruct Template" data-i18n="[title]Select your current Instruct Template">3262 <div class="flex-container" title="Select your current Instruct Template" data-i18n="[title]Select your current Instruct Template">
3256 <select id="instruct_presets" data-preset-manager-for="instruct" class="flex1 text_pole"></select>3263 <select id="instruct_presets" data-preset-manager-for="instruct" class="flex1 text_pole"></select>
3264 <div class="flex-container margin0 justifyCenter">
3265 <input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
3266 <i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3267 <i data-preset-manager-new="instruct" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3268 <i data-preset-manager-import="instruct" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3269 <i data-preset-manager-export="instruct" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3270 <i data-preset-manager-restore="instruct" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3271 <i data-preset-manager-delete="instruct" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3272 </div>
3257 </div>3273 </div>
3258 <label>3274 <label>
3259 <small>3275 <small>
@@ -3265,14 +3281,6 @@
3265 <input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i">3281 <input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i">
3266 </div>3282 </div>
3267 <div>3283 <div>
3268 <label for="instruct_system_prompt" class="flex-container">
3269 <small data-i18n="System Prompt">System Prompt</small>
3270 <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="instruct_system_prompt" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3271 </label>
3272 <textarea id="instruct_system_prompt" class="text_pole textarea_compact autoSetHeight"></textarea>
3273 </div>
3274
3275 <div>
3276 <label for="instruct_wrap" class="checkbox_label">3284 <label for="instruct_wrap" class="checkbox_label">
3277 <input id="instruct_wrap" type="checkbox" />3285 <input id="instruct_wrap" type="checkbox" />
3278 <small data-i18n="Wrap Sequences with Newline">Wrap Sequences with Newline</small>3286 <small data-i18n="Wrap Sequences with Newline">Wrap Sequences with Newline</small>
@@ -3285,16 +3293,203 @@
3285 <input id="instruct_skip_examples" type="checkbox" />3293 <input id="instruct_skip_examples" type="checkbox" />
3286 <small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>3294 <small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>
3287 </label>3295 </label>
3288 <label for="instruct_names" class="checkbox_label">3296 <div>
3289 <input id="instruct_names" type="checkbox" />3297 <small data-i18n="Include Names">
3290 <small data-i18n="Include Names">Include Names</small>3298 Include Names
3291 </label>3299 </small>
3292 <label for="instruct_names_force_groups" class="checkbox_label indent20p">3300 <select id="instruct_names_behavior">
3293 <input id="instruct_names_force_groups" type="checkbox" />3301 <option value="none" data-i18n="Never">Never</option>
3294 <small data-i18n="Force for Groups and Personas">Force for Groups and Personas</small>3302 <option value="force" data-i18n="Groups and Past Personas">Groups and Past Personas</option>
3303 <option value="always" data-i18n="Always">Always</option>
3304 </select>
3305 </div>
3306 </div>
3307 </div>
3308 <div id="InstructSequencesColumn" class="wide100p flexFlowColumn">
3309 <h4 class="standoutHeader title_restorable">
3310 <b>
3311 <span data-i18n="Instruct Sequences">
3312 Instruct Sequences
3313 </span>
3314 </b>
3315 </h4>
3316 <!-- We keep one auto-open so the user would know what is going on in the picked template -->
3317 <details open>
3318 <summary>User Message Sequences</summary>
3319 <div class="flex-container">
3320 <div class="flexAuto" title="Inserted before a User message and as a last prompt line when impersonating." data-i18n="[title]Inserted before a User message and as a last prompt line when impersonating.">
3321 <small data-i18n="User Prefix">User Message Prefix</small>
3322 <textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3323 </div>
3324 <div class="flexAuto" title="Inserted after a User message." data-i18n="[title]Inserted after a User message.">
3325 <small data-i18n="User Suffix">User Message Suffix</small>
3326 <textarea id="instruct_input_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3327 </div>
3328 </div>
3329 </details>
3330 <details open>
3331 <summary>Assistant Message Sequences</summary>
3332 <div class="flex-container">
3333 <div class="flexAuto" title="Inserted before an Assistant message and as a last prompt line when generating an AI reply." data-i18n="[title]Inserted before an Assistant message and as a last prompt line when generating an AI reply.">
3334 <small data-i18n="Assistant Prefix">Assistant Message Prefix</small>
3335 <textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3336 </div>
3337 <div class="flexAuto" title="Inserted after an Assistant message." data-i18n="[title]Inserted after an Assistant message.">
3338 <small data-i18n="Assistant Suffix">Assistant Message Suffix</small>
3339 <textarea id="instruct_output_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3340 </div>
3341 </div>
3342 </details>
3343 <details>
3344 <summary>System Message Sequences</summary>
3345 <div class="flex-container">
3346 <div class="flexAuto" id="instruct_system_sequence_block" title="Inserted before a System (added by slash commands or extensions) message." data-i18n="[title]Inserted before a System (added by slash commands or extensions) message.">
3347 <small data-i18n="System Prefix">System Message Prefix</small>
3348 <textarea id="instruct_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3349 </div>
3350 <div class="flexAuto" id="instruct_system_suffix_block" title="Inserted after a System message." data-i18n="[title]Inserted after a System message.">
3351 <small data-i18n="System Suffix">System Message Suffix</small>
3352 <textarea id="instruct_system_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3353 </div>
3354 </div>
3355 <div class="flexBasis100p" title="If enabled, System Sequences will be the same as User Sequences." data-i18n="[title]If enabled, System Sequences will be the same as User Sequences.">
3356 <label class="checkbox_label" for="instruct_system_same_as_user">
3357 <input id="instruct_system_same_as_user" type="checkbox" />
3358 <small data-i18n="System same as User">System same as User</small>
3359 </label>
3360 </div>
3361 </details>
3362 <details>
3363 <summary>System Prompt Sequences</summary>
3364 <div class="flex-container">
3365 <div class="flexAuto" title="Inserted before a System prompt." data-i18n="[title]Inserted before a System prompt.">
3366 <label for="instruct_system_sequence_prefix">
3367 <small data-i18n="System Prompt Prefix">System Prompt Prefix</small>
3368 </label>
3369 <div>
3370 <textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight"></textarea>
3371 </div>
3372 </div>
3373 <div class="flexAuto" title="Inserted after a System prompt." data-i18n="[title]Inserted after a System prompt.">
3374 <label for="instruct_system_sequence_suffix">
3375 <small data-i18n="System Prompt Suffix">System Prompt Suffix</small>
3376 </label>
3377 <div>
3378 <textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3379 </div>
3380 </div>
3381 </div>
3382 </details>
3383 <details>
3384 <summary data-i18n="Misc. Sequences">Misc. Sequences</summary>
3385 <div class="flex-container">
3386 <div class="flexAuto" title="Inserted before the first Assistant's message." data-i18n="[title]Inserted before the first Assistant's message.">
3387 <small data-i18n="First Assistant Prefix">First Assistant Prefix</small>
3388 <textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3389 </div>
3390 <div class="flexAuto" title="Inserted before the last Assistant's message or as a last prompt line when generating an AI reply (except a neutral/system role)." data-i18n="[title]instruct_last_output_sequence">
3391 <small data-i18n="Last Assistant Prefix">Last Assistant Prefix</small>
3392 <textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3393 </div>
3394 </div>
3395 <div class="flex-container">
3396 <div class="flexAuto" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
3397 <small data-i18n="First User Prefix">First User Prefix</small>
3398 <textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3399 </div>
3400 <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">
3401 <small data-i18n="Last User Prefix">Last User Prefix</small>
3402 <textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3403 </div>
3404 </div>
3405 <div class="flex-container">
3406 <div class="flexAuto" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
3407 <small data-i18n="System Instruction Prefix">System Instruction Prefix</small>
3408 <textarea id="instruct_last_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3409 </div>
3410 <div class="flexAuto" title="If a stop sequence is generated, everything past it will be removed from the output (inclusive)." data-i18n="[title]If a stop sequence is generated, everything past it will be removed from the output (inclusive).">
3411 <small data-i18n="Stop Sequence">Stop Sequence</small>
3412 <textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3413 </div>
3414 </div>
3415 <div class="flex-container">
3416 <div class="flexAuto" title="Will be inserted at the start of the chat history if it doesn't start with a User message." data-i18n="[title]Will be inserted at the start of the chat history if it doesn't start with a User message.">
3417 <small data-i18n="User Filler Message">User Filler Message</small>
3418 <textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight"></textarea>
3419 </div>
3420 </div>
3421 </details>
3422 </div>
3423 </div>
3424 <div id="SystemPromptColumn" class="flex-container flexNoGap flexFlowColumn flex1">
3425 <div id="SystemPromptBlock">
3426 <h4 class="standoutHeader title_restorable justifySpaceBetween">
3427 <div class="flex-container">
3428 <span data-i18n="System Prompt">System Prompt</span>
3429 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#system-prompt" class="notes-link" target="_blank">
3430 <span class="fa-solid fa-circle-question note-link-span"></span>
3431 </a>
3432 </div>
3433 <div class="flex-container">
3434 <label for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled">
3435 <input id="sysprompt_enabled" type="checkbox" style="display:none;" />
3436 <small><i class="fa-solid fa-power-off menu_button margin0"></i></small>
3437 </label>
3438 </div>
3439 </h4>
3440
3441 <div class="flex-container" title="Select your current System Prompt" data-i18n="[title]Select your current System Prompt">
3442 <select id="sysprompt_select" data-preset-manager-for="sysprompt" class="flex1 text_pole"></select>
3443 <div class="flex-container margin0 justifyCenter">
3444 <input type="file" hidden data-preset-manager-file="sysprompt" accept=".json, .settings">
3445 <i data-preset-manager-update="sysprompt" class="menu_button fa-solid fa-save" title="Update current prompt" data-i18n="[title]Update current prompt"></i>
3446 <i data-preset-manager-new="sysprompt" class="menu_button fa-solid fa-file-circle-plus" title="Save prompt as" data-i18n="[title]Save prompt as"></i>
3447 <i data-preset-manager-import="sysprompt" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3448 <i data-preset-manager-export="sysprompt" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3449 <i data-preset-manager-restore="sysprompt" class="menu_button fa-solid fa-recycle" title="Restore current prompt" data-i18n="[title]Restore current prompt"></i>
3450 <i data-preset-manager-delete="sysprompt" class="menu_button fa-solid fa-trash-can" title="Delete prompt" data-i18n="[title]Delete prompt"></i>
3451 </div>
3452 </div>
3453
3454 <div>
3455 <label for="sysprompt_content" class="flex-container">
3456 <small data-i18n="Prompt Content">Prompt Content</small>
3457 <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="sysprompt_content" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3295 </label>3458 </label>
3459 <textarea id="sysprompt_content" class="text_pole textarea_compact autoSetHeight"></textarea>
3460 </div>
3461 </div>
3462
3463 <div>
3464 &nbsp;
3465 </div>
3466
3467 <div>
3468 <h4 class="range-block-title justifyLeft standoutHeader">
3469 <span data-i18n="Custom Stopping Strings">
3470 Custom Stopping Strings
3471 </span>
3472 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#custom-stopping-strings" class="notes-link" target="_blank">
3473 <span class="fa-solid fa-circle-question note-link-span"></span>
3474 </a>
3475 </h4>
3476 <div>
3477 <small>
3478 <span data-i18n="JSON serialized array of strings">JSON serialized array of strings</span>
3479 <i class="fa-solid fa-question-circle opacity50p" title="e.g: [&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></i>
3480 </small>
3481 </div>
3482 <div>
3483 <textarea id="custom_stopping_strings" class="text_pole textarea_compact monospace autoSetHeight"></textarea>
3296 </div>3484 </div>
3485 <label class="checkbox_label" for="custom_stopping_strings_macro">
3486 <input id="custom_stopping_strings_macro" type="checkbox" checked>
3487 <small data-i18n="Replace Macro in Custom Stopping Strings">
3488 Replace Macro in Custom Stopping Strings
3489 </small>
3490 </label>
3297 </div>3491 </div>
3492
3298 <div name="tokenizerSettingsBlock">3493 <div name="tokenizerSettingsBlock">
3299 <div name="tokenizerSelectorBlock">3494 <div name="tokenizerSelectorBlock">
3300 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>3495 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
@@ -3315,7 +3510,8 @@
3315 <option value="16">Command-R</option>3510 <option value="16">Command-R</option>
3316 <option value="4">NerdStash (NovelAI Clio)</option>3511 <option value="4">NerdStash (NovelAI Clio)</option>
3317 <option value="5">NerdStash v2 (NovelAI Kayra)</option>3512 <option value="5">NerdStash v2 (NovelAI Kayra)</option>
3318 <option value="7">Mistral</option>3513 <option value="7">Mistral V1</option>
3514 <option value="17">Mistral Nemo</option>
3319 <option value="8">Yi</option>3515 <option value="8">Yi</option>
3320 <option value="11">Claude 1/2</option>3516 <option value="11">Claude 1/2</option>
3321 <option value="6">API (WebUI / koboldcpp)</option>3517 <option value="6">API (WebUI / koboldcpp)</option>
@@ -3365,114 +3561,6 @@
3365 </div>3561 </div>
3366 </div>3562 </div>
3367 </div>3563 </div>
3368 <div id="InstructSequencesColumn" class="flex-container flexNoGap flexFlowColumn flex1">
3369 <div class="wide100p flexFlowColumn">
3370 <h4 class="standoutHeader title_restorable">
3371 <b>
3372 <span data-i18n="Instruct Sequences">
3373 Instruct Sequences
3374 </span>
3375 <small data-i18n="(Saves to Template)">
3376 (Saves to Template)
3377 </small>
3378 </b>
3379 </h4>
3380 <div class="flex-container">
3381 <div class="flexAuto" title="Inserted before a System prompt." data-i18n="[title]Inserted before a System prompt.">
3382 <label for="instruct_system_sequence_prefix">
3383 <small data-i18n="System Prompt Prefix">System Prompt Prefix</small>
3384 </label>
3385 <div>
3386 <textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight"></textarea>
3387 </div>
3388 </div>
3389 <div class="flexAuto" title="Inserted after a System prompt." data-i18n="[title]Inserted after a System prompt.">
3390 <label for="instruct_system_sequence_suffix">
3391 <small data-i18n="System Prompt Suffix">System Prompt Suffix</small>
3392 </label>
3393 <div>
3394 <textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3395 </div>
3396 </div>
3397 </div>
3398 <div class="flex-container">
3399 <div class="flexAuto" title="Inserted before a User message and as a last prompt line when impersonating." data-i18n="[title]Inserted before a User message and as a last prompt line when impersonating.">
3400 <small data-i18n="User Prefix">User Message Prefix</small>
3401 <textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3402 </div>
3403 <div class="flexAuto" title="Inserted after a User message." data-i18n="[title]Inserted after a User message.">
3404 <small data-i18n="User Suffix">User Message Suffix</small>
3405 <textarea id="instruct_input_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3406 </div>
3407 </div>
3408 <div class="flex-container">
3409 <div class="flexAuto" title="Inserted before an Assistant message and as a last prompt line when generating an AI reply." data-i18n="[title]Inserted before an Assistant message and as a last prompt line when generating an AI reply.">
3410 <small data-i18n="Assistant Prefix">Assistant Message Prefix</small>
3411 <textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3412 </div>
3413 <div class="flexAuto" title="Inserted after an Assistant message." data-i18n="[title]Inserted after an Assistant message.">
3414 <small data-i18n="Assistant Suffix">Assistant Message Suffix</small>
3415 <textarea id="instruct_output_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3416 </div>
3417 </div>
3418 <div class="flex-container">
3419 <div class="flexAuto" id="instruct_system_sequence_block" title="Inserted before a System (added by slash commands or extensions) message." data-i18n="[title]Inserted before a System (added by slash commands or extensions) message.">
3420 <small data-i18n="System Prefix">System Message Prefix</small>
3421 <textarea id="instruct_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3422 </div>
3423 <div class="flexAuto" id="instruct_system_suffix_block" title="Inserted after a System message." data-i18n="[title]Inserted after a System message.">
3424 <small data-i18n="System Suffix">System Message Suffix</small>
3425 <textarea id="instruct_system_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3426 </div>
3427 </div>
3428 <div class="flexBasis100p" title="If enabled, System Sequences will be the same as User Sequences." data-i18n="[title]If enabled, System Sequences will be the same as User Sequences.">
3429 <label class="checkbox_label" for="instruct_system_same_as_user">
3430 <input id="instruct_system_same_as_user" type="checkbox" />
3431 <small data-i18n="System same as User">System same as User</small>
3432 </label>
3433 </div>
3434
3435 <h5 class="textAlignCenter" data-i18n="Misc. Sequences">
3436 Misc. Sequences
3437 </h5>
3438 <div class="flex-container">
3439 <div class="flexAuto" title="Inserted before the first Assistant's message." data-i18n="[title]Inserted before the first Assistant's message.">
3440 <small data-i18n="First Assistant Prefix">First Assistant Prefix</small>
3441 <textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3442 </div>
3443 <div class="flexAuto" title="Inserted before the last Assistant's message or as a last prompt line when generating an AI reply (except a neutral/system role)." data-i18n="[title]instruct_last_output_sequence">
3444 <small data-i18n="Last Assistant Prefix">Last Assistant Prefix</small>
3445 <textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3446 </div>
3447 </div>
3448 <div class="flex-container">
3449 <div class="flexAuto" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
3450 <small data-i18n="First User Prefix">First User Prefix</small>
3451 <textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3452 </div>
3453 <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">
3454 <small data-i18n="Last User Prefix">Last User Prefix</small>
3455 <textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
3456 </div>
3457 </div>
3458 <div class="flex-container">
3459 <div class="flexAuto" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
3460 <small data-i18n="System Instruction Prefix">System Instruction Prefix</small>
3461 <textarea id="instruct_last_system_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3462 </div>
3463 <div class="flexAuto" title="If a stop sequence is generated, everything past it will be removed from the output (inclusive)." data-i18n="[title]If a stop sequence is generated, everything past it will be removed from the output (inclusive).">
3464 <small data-i18n="Stop Sequence">Stop Sequence</small>
3465 <textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight"></textarea>
3466 </div>
3467 </div>
3468 <div class="flex-container">
3469 <div class="flexAuto" title="Will be inserted at the start of the chat history if it doesn't start with a User message." data-i18n="[title]Will be inserted at the start of the chat history if it doesn't start with a User message.">
3470 <small data-i18n="User Filler Message">User Filler Message</small>
3471 <textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight"></textarea>
3472 </div>
3473 </div>
3474 </div>
3475 </div>
3476 </div>3564 </div>
3477 </div>3565 </div>
3478 </div>3566 </div>
@@ -5452,23 +5540,23 @@
5452 <div class="world_entry_form_control flex1">5540 <div class="world_entry_form_control flex1">
5453 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>5541 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
5454 <select name="caseSensitive" class="text_pole widthNatural margin0">5542 <select name="caseSensitive" class="text_pole widthNatural margin0">
5455 <option value="null" data-i18n="Use global setting">Use global setting</option>5543 <option value="null" data-i18n="Use global">Use global</option>
5456 <option value="true" data-i18n="Yes">Yes</option>5544 <option value="true" data-i18n="Yes">Yes</option>
5457 <option value="false" data-i18n="No">No</option>5545 <option value="false" data-i18n="No">No</option>
5458 </select>5546 </select>
5459 </div>5547 </div>
5460 <div class="world_entry_form_control flex1">5548 <div class="world_entry_form_control flex1">
5461 <small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small>5549 <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
5462 <select name="matchWholeWords" class="text_pole widthNatural margin0">5550 <select name="matchWholeWords" class="text_pole widthNatural margin0">
5463 <option value="null" data-i18n="Use global setting">Use global setting</option>5551 <option value="null" data-i18n="Use global">Use global</option>
5464 <option value="true" data-i18n="Yes">Yes</option>5552 <option value="true" data-i18n="Yes">Yes</option>
5465 <option value="false" data-i18n="No">No</option>5553 <option value="false" data-i18n="No">No</option>
5466 </select>5554 </select>
5467 </div>5555 </div>
5468 <div class="world_entry_form_control flex1">5556 <div class="world_entry_form_control flex1">
5469 <small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small>5557 <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
5470 <select name="useGroupScoring" class="text_pole widthNatural margin0">5558 <select name="useGroupScoring" class="text_pole widthNatural margin0">
5471 <option value="null" data-i18n="Use global setting">Use global setting</option>5559 <option value="null" data-i18n="Use global">Use global</option>
5472 <option value="true" data-i18n="Yes">Yes</option>5560 <option value="true" data-i18n="Yes">Yes</option>
5473 <option value="false" data-i18n="No">No</option>5561 <option value="false" data-i18n="No">No</option>
5474 </select>5562 </select>
@@ -5477,6 +5565,13 @@
5477 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>5565 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
5478 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">5566 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
5479 </div>5567 </div>
5568 <div class="world_entry_form_control flex1" title="Defines delay levels for recursive scans.&#13;&#13;Initially, only the first level (smallest number) will match.&#13;Once no matches are found, the next level becomes eligible for matching.&#13;This repeats until all levels are checked.&#13;&#13;Tied to the &quot;Delay until recursion&quot; setting." data-i18n="[title]delay_until_recursion_level">
5569 <small class="textAlignCenter">
5570 <span data-i18n="Recursion Level">Recursion Level</span>
5571 <div class="fa-solid fa-circle-info opacity50p"></div>
5572 </small>
5573 <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
5574 </div>
5480 </div>5575 </div>
5481 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">5576 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
5482 <div class="world_entry_form_control flex1">5577 <div class="world_entry_form_control flex1">
@@ -5493,20 +5588,20 @@
5493 <div>5588 <div>
5494 <label class="checkbox flex-container alignitemscenter flexNoGap">5589 <label class="checkbox flex-container alignitemscenter flexNoGap">
5495 <input type="checkbox" name="exclude_recursion" />5590 <input type="checkbox" name="exclude_recursion" />
5496 <span data-i18n="Exclude from recursion">5591 <span data-i18n="Non-recursable (will not be activated by another)">
5497 Non-recursable (this entry will not be activated by another)5592 Non-recursable (will not be activated by another)
5498 </span>5593 </span>
5499 </label>5594 </label>
5500 <label class="checkbox flex-container alignitemscenter flexNoGap">5595 <label class="checkbox flex-container alignitemscenter flexNoGap">
5501 <input type="checkbox" name="prevent_recursion" />5596 <input type="checkbox" name="prevent_recursion" />
5502 <span data-i18n="Prevent further recursion (this entry will not activate others)">5597 <span data-i18n="Prevent further recursion (this entry will not activate others)">
5503 Prevent further recursion (this entry will not activate others)5598 Prevent further recursion (will not activate others)
5504 </span>5599 </span>
5505 </label>5600 </label>
5506 <label class="checkbox flex-container alignitemscenter flexNoGap">5601 <label class="checkbox flex-container alignitemscenter flexNoGap">
5507 <input type="checkbox" name="delay_until_recursion" />5602 <input type="checkbox" name="delay_until_recursion" />
5508 <span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)">5603 <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
5509 Delay until recursion (this entry can only be activated on recursive checking)5604 Delay until recursion (can only be activated on recursive checking)
5510 </span>5605 </span>
5511 </label>5606 </label>
5512 </div>5607 </div>
public/script.js+65 -33
@@ -99,7 +99,7 @@ import {
99 proxies,99 proxies,
100 loadProxyPresets,100 loadProxyPresets,
101 selected_proxy,101 selected_proxy,
102 initOpenai,102 initOpenAI,
103} from './scripts/openai.js';103} from './scripts/openai.js';
104104
105import {105import {
@@ -210,7 +210,7 @@ import {
210 selectContextPreset,210 selectContextPreset,
211} from './scripts/instruct-mode.js';211} from './scripts/instruct-mode.js';
212import { initLocales, t, translate } from './scripts/i18n.js';212import { initLocales, t, translate } from './scripts/i18n.js';
213import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache } from './scripts/tokenizers.js';213import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
214import {214import {
215 user_avatar,215 user_avatar,
216 getUserAvatars,216 getUserAvatars,
@@ -224,7 +224,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set
224import { hideLoader, showLoader } from './scripts/loader.js';224import { hideLoader, showLoader } from './scripts/loader.js';
225import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';225import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
226import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';226import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
227import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';227import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
228import { initPresetManager } from './scripts/preset-manager.js';228import { initPresetManager } from './scripts/preset-manager.js';
229import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';229import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';
230import { currentUser, setUserControls } from './scripts/user.js';230import { currentUser, setUserControls } from './scripts/user.js';
@@ -243,6 +243,7 @@ import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/Slash
243import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';243import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
244import { initInputMarkdown } from './scripts/input-md-formatting.js';244import { initInputMarkdown } from './scripts/input-md-formatting.js';
245import { AbortReason } from './scripts/util/AbortReason.js';245import { AbortReason } from './scripts/util/AbortReason.js';
246import { initSystemPrompts } from './scripts/sysprompt.js';
246247
247//exporting functions and vars for mods248//exporting functions and vars for mods
248export {249export {
@@ -411,6 +412,7 @@ export const event_types = {
411 MESSAGE_FILE_EMBEDDED: 'message_file_embedded',412 MESSAGE_FILE_EMBEDDED: 'message_file_embedded',
412 IMPERSONATE_READY: 'impersonate_ready',413 IMPERSONATE_READY: 'impersonate_ready',
413 CHAT_CHANGED: 'chat_id_changed',414 CHAT_CHANGED: 'chat_id_changed',
415 GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS',
414 GENERATION_STARTED: 'generation_started',416 GENERATION_STARTED: 'generation_started',
415 GENERATION_STOPPED: 'generation_stopped',417 GENERATION_STOPPED: 'generation_stopped',
416 GENERATION_ENDED: 'generation_ended',418 GENERATION_ENDED: 'generation_ended',
@@ -509,6 +511,7 @@ let saveCharactersPage = 0;
509export const default_avatar = 'img/ai4.png';511export const default_avatar = 'img/ai4.png';
510export const system_avatar = 'img/five.png';512export const system_avatar = 'img/five.png';
511export const comment_avatar = 'img/quill.png';513export const comment_avatar = 'img/quill.png';
514export const default_user_avatar = 'img/user-default.png';
512export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header515export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header
513let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {516let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
514 placement: 'top-start',517 placement: 'top-start',
@@ -927,9 +930,12 @@ async function firstLoadInit() {
927 addSafariPatch();930 addSafariPatch();
928 await getClientVersion();931 await getClientVersion();
929 await readSecretState();932 await readSecretState();
930 initLocales();933 await initLocales();
931 initDefaultSlashCommands();934 initDefaultSlashCommands();
932 initTextGenModels();935 initTextGenModels();
936 initOpenAI();
937 initSystemPrompts();
938 await initPresetManager();
933 await getSystemMessages();939 await getSystemMessages();
934 sendSystemMessage(system_message_types.WELCOME);940 sendSystemMessage(system_message_types.WELCOME);
935 sendSystemMessage(system_message_types.WELCOME_PROMPT);941 sendSystemMessage(system_message_types.WELCOME_PROMPT);
@@ -937,13 +943,11 @@ async function firstLoadInit() {
937 initKeyboard();943 initKeyboard();
938 initDynamicStyles();944 initDynamicStyles();
939 initTags();945 initTags();
940 initOpenai();
941 initBookmarks();946 initBookmarks();
942 await getUserAvatars(true, user_avatar);947 await getUserAvatars(true, user_avatar);
943 await getCharacters();948 await getCharacters();
944 await getBackgrounds();949 await getBackgrounds();
945 await initTokenizers();950 await initTokenizers();
946 await initPresetManager();
947 initBackgrounds();951 initBackgrounds();
948 initAuthorsNote();952 initAuthorsNote();
949 initPersonas();953 initPersonas();
@@ -1213,6 +1217,9 @@ async function getStatusTextgen() {
1213 // Determine instruct mode preset1217 // Determine instruct mode preset
1214 autoSelectInstructPreset(online_status);1218 autoSelectInstructPreset(online_status);
12151219
1220 const supportsTokenization = response.headers.get('x-supports-tokenization') === 'true';
1221 supportsTokenization ? sessionStorage.setItem(TOKENIZER_SUPPORTED_KEY, 'true') : sessionStorage.removeItem(TOKENIZER_SUPPORTED_KEY);
1222
1216 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.1223 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
1217 if (online_status === 'no_connection' && data.response) {1224 if (online_status === 'no_connection' && data.response) {
1218 toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });1225 toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });
@@ -1845,6 +1852,7 @@ export async function deleteLastMessage() {
1845}1852}
18461853
1847export async function reloadCurrentChat() {1854export async function reloadCurrentChat() {
1855 preserveNeutralChat();
1848 await clearChat();1856 await clearChat();
1849 chat.length = 0;1857 chat.length = 0;
18501858
@@ -1856,6 +1864,7 @@ export async function reloadCurrentChat() {
1856 }1864 }
1857 else {1865 else {
1858 resetChatState();1866 resetChatState();
1867 restoreNeutralChat();
1859 await getCharacters();1868 await getCharacters();
1860 await printMessages();1869 await printMessages();
1861 await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());1870 await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
@@ -3332,10 +3341,12 @@ function removeLastMessage() {
3332 */3341 */
3333export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName } = {}, dryRun = false) {3342export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName } = {}, dryRun = false) {
3334 console.log('Generate entered');3343 console.log('Generate entered');
3335 await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
3336 setGenerationProgress(0);3344 setGenerationProgress(0);
3337 generation_started = new Date();3345 generation_started = new Date();
33383346
3347 // Occurs every time, even if the generation is aborted due to slash commands execution
3348 await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
3349
3339 // Don't recreate abort controller if signal is passed3350 // Don't recreate abort controller if signal is passed
3340 if (!(abortController && signal)) {3351 if (!(abortController && signal)) {
3341 abortController = new AbortController();3352 abortController = new AbortController();
@@ -3355,6 +3366,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3355 }3366 }
3356 }3367 }
33573368
3369 // Occurs only if the generation is not aborted due to slash commands execution
3370 await eventSource.emit(event_types.GENERATION_AFTER_COMMANDS, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
3371
3358 if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {3372 if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
3359 toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true });3373 toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true });
3360 unblockGeneration(type);3374 unblockGeneration(type);
@@ -3512,9 +3526,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3512 jailbreak,3526 jailbreak,
3513 } = getCharacterCardFields();3527 } = getCharacterCardFields();
35143528
3515 if (isInstruct) {3529 if (main_api !== 'openai') {
3516 system = power_user.prefer_character_prompt && system ? system : baseChatReplace(power_user.instruct.system_prompt, name1, name2);3530 if (power_user.sysprompt.enabled) {
3517 system = formatInstructModeSystemPrompt(substituteParams(system, name1, name2, power_user.instruct.system_prompt));3531 system = power_user.prefer_character_prompt && system ? system : baseChatReplace(power_user.sysprompt.content, name1, name2);
3532 system = isInstruct ? formatInstructModeSystemPrompt(substituteParams(system, name1, name2, power_user.sysprompt.content)) : system;
3533 } else {
3534 // Nullify if it's not enabled
3535 system = '';
3536 }
3518 }3537 }
35193538
3520 // Depth prompt (character-specific A/N)3539 // Depth prompt (character-specific A/N)
@@ -3767,7 +3786,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3767 personality: personality,3786 personality: personality,
3768 persona: power_user.persona_description_position == persona_description_positions.IN_PROMPT ? persona : '',3787 persona: power_user.persona_description_position == persona_description_positions.IN_PROMPT ? persona : '',
3769 scenario: scenario,3788 scenario: scenario,
3770 system: isInstruct ? system : '',3789 system: system,
3771 char: name2,3790 char: name2,
3772 user: name1,3791 user: name1,
3773 wiBefore: worldInfoBefore,3792 wiBefore: worldInfoBefore,
@@ -4340,7 +4359,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4340 this_max_context: this_max_context,4359 this_max_context: this_max_context,
4341 padding: power_user.token_padding,4360 padding: power_user.token_padding,
4342 main_api: main_api,4361 main_api: main_api,
4343 instruction: isInstruct ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.instruct.system_prompt) : '',4362 instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',
4344 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),4363 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
4345 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',4364 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
4346 };4365 };
@@ -4839,6 +4858,13 @@ export function getMaxContextSize(overrideResponseLength = null) {
4839 console.log(`NovelAI subscription limit reached. Max context size is now ${this_max_context}`);4858 console.log(`NovelAI subscription limit reached. Max context size is now ${this_max_context}`);
4840 }4859 }
4841 }4860 }
4861 if (nai_settings.model_novel.includes('erato')) {
4862 // subscriber limits coming soon
4863 this_max_context = Math.min(max_context, 8192);
4864
4865 // Added special tokens and whatnot
4866 this_max_context -= 1;
4867 }
48424868
4843 this_max_context = this_max_context - (overrideResponseLength || amount_gen);4869 this_max_context = this_max_context - (overrideResponseLength || amount_gen);
4844 }4870 }
@@ -5466,7 +5492,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5466 }5492 }
54675493
5468 if (!displayIncompleteSentences && power_user.trim_sentences) {5494 if (!displayIncompleteSentences && power_user.trim_sentences) {
5469 getMessage = trimToEndSentence(getMessage, power_user.include_newline);5495 getMessage = trimToEndSentence(getMessage);
5470 }5496 }
54715497
5472 if (power_user.trim_spaces) {5498 if (power_user.trim_spaces) {
@@ -5698,10 +5724,10 @@ export function deactivateSendButtons() {
5698}5724}
56995725
5700export function resetChatState() {5726export function resetChatState() {
5727 // replaces deleted charcter name with system user since it will be displayed next.
5728 name2 = (this_chid === undefined && neutralCharacterName) ? neutralCharacterName : systemUserName;
5701 //unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)5729 //unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
5702 this_chid = undefined;5730 this_chid = undefined;
5703 // replaces deleted charcter name with system user since it will be displayed next.
5704 name2 = systemUserName;
5705 // sets up system user to tell user about having deleted a character5731 // sets up system user to tell user about having deleted a character
5706 chat.splice(0, chat.length, ...SAFETY_CHAT);5732 chat.splice(0, chat.length, ...SAFETY_CHAT);
5707 // resets chat metadata5733 // resets chat metadata
@@ -6917,15 +6943,13 @@ export async function displayPastChats() {
6917 }6943 }
6918 // Check whether `text` {string} includes all of the `fragments` {string[]}.6944 // Check whether `text` {string} includes all of the `fragments` {string[]}.
6919 function matchFragments(fragments, text) {6945 function matchFragments(fragments, text) {
6920 if (!text) {6946 if (!text || !text.toLowerCase) return false;
6921 return false;6947 return fragments.every(item => text.toLowerCase().includes(item));
6922 }
6923 return fragments.every(item => text.includes(item));
6924 }6948 }
6925 const fragments = makeQueryFragments(searchQuery);6949 const fragments = makeQueryFragments(searchQuery);
6926 // At least one chat message must match *all* the fragments.6950 // At least one chat message must match *all* the fragments.
6927 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.6951 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.
6928 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase()));6952 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes));
6929 });6953 });
69306954
6931 console.debug(filteredData);6955 console.debug(filteredData);
@@ -8565,20 +8589,27 @@ async function connectAPISlash(args, text) {
8565 return '';8589 return '';
8566 }8590 }
85678591
8568 $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);8592 let connectionRequired = false;
8569 $('#main_api').trigger('change');
85708593
8571 if (apiConfig.source) {8594 if (main_api !== apiConfig.selected) {
8595 $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
8596 $('#main_api').trigger('change');
8597 connectionRequired = true;
8598 }
8599
8600 if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) {
8572 $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true);8601 $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true);
8573 $('#chat_completion_source').trigger('change');8602 $('#chat_completion_source').trigger('change');
8603 connectionRequired = true;
8574 }8604 }
85758605
8576 if (apiConfig.type) {8606 if (apiConfig.type && textgen_settings.type !== apiConfig.type) {
8577 $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true);8607 $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true);
8578 $('#textgen_type').trigger('change');8608 $('#textgen_type').trigger('change');
8609 connectionRequired = true;
8579 }8610 }
85808611
8581 if (apiConfig.button) {8612 if (connectionRequired && apiConfig.button) {
8582 $(apiConfig.button).trigger('click');8613 $(apiConfig.button).trigger('click');
8583 }8614 }
85848615
@@ -8803,7 +8834,7 @@ export async function renameChat(oldFileName, newName) {
8803 is_group: !!selected_group,8834 is_group: !!selected_group,
8804 avatar_url: characters[this_chid]?.avatar,8835 avatar_url: characters[this_chid]?.avatar,
8805 original_file: `${oldFileName}.jsonl`,8836 original_file: `${oldFileName}.jsonl`,
8806 renamed_file: `${newName}.jsonl`,8837 renamed_file: `${newName.trim()}.jsonl`,
8807 };8838 };
88088839
8809 try {8840 try {
@@ -8824,6 +8855,10 @@ export async function renameChat(oldFileName, newName) {
8824 throw new Error('Server returned an error.');8855 throw new Error('Server returned an error.');
8825 }8856 }
88268857
8858 if (data.sanitizedFileName) {
8859 newName = data.sanitizedFileName;
8860 }
8861
8827 if (selected_group) {8862 if (selected_group) {
8828 await renameGroupChat(selected_group, oldFileName, newName);8863 await renameGroupChat(selected_group, oldFileName, newName);
8829 }8864 }
@@ -8945,15 +8980,12 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
8945 * It also ensures to save the settings after all the operations.8980 * It also ensures to save the settings after all the operations.
8946 */8981 */
8947async function removeCharacterFromUI() {8982async function removeCharacterFromUI() {
8983 preserveNeutralChat();
8948 await clearChat();8984 await clearChat();
8949 $('#character_cross').click();8985 $('#character_cross').trigger('click');
8950 this_chid = undefined;8986 resetChatState();
8951 characters.length = 0;
8952 name2 = systemUserName;
8953 chat.splice(0, chat.length, ...SAFETY_CHAT);
8954 chat_metadata = {};
8955 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');8987 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');
8956 this_chid = undefined;8988 restoreNeutralChat();
8957 await getCharacters();8989 await getCharacters();
8958 await printMessages();8990 await printMessages();
8959 saveSettingsDebounced();8991 saveSettingsDebounced();
public/scripts/PromptManager.js+51 -25
@@ -1,12 +1,13 @@
1'use strict';1'use strict';
22
3import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';3import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
4import { is_group_generating } from './group-chats.js';4import { is_group_generating } from './group-chats.js';
5import { Message, TokenHandler } from './openai.js';5import { Message, TokenHandler } from './openai.js';
6import { power_user } from './power-user.js';6import { power_user } from './power-user.js';
7import { debounce, waitUntilCondition, escapeHtml } from './utils.js';7import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
8import { debounce_timeout } from './constants.js';8import { debounce_timeout } from './constants.js';
9import { renderTemplateAsync } from './templates.js';9import { renderTemplateAsync } from './templates.js';
10import { Popup } from './popup.js';
1011
11function debouncePromise(func, delay) {12function debouncePromise(func, delay) {
12 let timeoutId;13 let timeoutId;
@@ -427,12 +428,13 @@ class PromptManager {
427428
428 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name;429 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name;
429 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system';430 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system';
430 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content;431 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? '';
431 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;432 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;
432 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;433 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;
433 document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';434 document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
434 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;435 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;
435 document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';436 document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
437 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false;
436438
437 if (!this.systemPrompts.includes(promptId)) {439 if (!this.systemPrompts.includes(promptId)) {
438 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');440 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');
@@ -452,21 +454,24 @@ class PromptManager {
452 };454 };
453455
454 // Delete selected prompt from list form and close edit form456 // Delete selected prompt from list form and close edit form
455 this.handleDeletePrompt = (event) => {457 this.handleDeletePrompt = async (event) => {
456 const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;458 Popup.show.confirm('Are you sure you want to delete this prompt?', null).then((userChoice) => {
457 const prompt = this.getPromptById(promptID);459 if (!userChoice) return;
460 const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
461 const prompt = this.getPromptById(promptID);
458462
459 if (prompt && true === this.isPromptDeletionAllowed(prompt)) {463 if (prompt && true === this.isPromptDeletionAllowed(prompt)) {
460 const promptIndex = this.getPromptIndexById(promptID);464 const promptIndex = this.getPromptIndexById(promptID);
461 this.serviceSettings.prompts.splice(Number(promptIndex), 1);465 this.serviceSettings.prompts.splice(Number(promptIndex), 1);
462466
463 this.log('Deleted prompt: ' + prompt.identifier);467 this.log('Deleted prompt: ' + prompt.identifier);
464468
465 this.hidePopup();469 this.hidePopup();
466 this.clearEditForm();470 this.clearEditForm();
467 this.render();471 this.render();
468 this.saveServiceSettings();472 this.saveServiceSettings();
469 }473 }
474 });
470 };475 };
471476
472 // Create new prompt, then save it to settings and close form.477 // Create new prompt, then save it to settings and close form.
@@ -526,9 +531,9 @@ class PromptManager {
526531
527 // Import prompts for the selected character532 // Import prompts for the selected character
528 this.handleImport = () => {533 this.handleImport = () => {
529 callPopup('Existing prompts with the same ID will be overridden. Do you want to proceed?', 'confirm')534 Popup.show.confirm('Existing prompts with the same ID will be overridden. Do you want to proceed?', null)
530 .then(userChoice => {535 .then(userChoice => {
531 if (false === userChoice) return;536 if (!userChoice) return;
532537
533 const fileOpener = document.createElement('input');538 const fileOpener = document.createElement('input');
534 fileOpener.type = 'file';539 fileOpener.type = 'file';
@@ -562,9 +567,9 @@ class PromptManager {
562567
563 // Restore default state of a characters prompt order568 // Restore default state of a characters prompt order
564 this.handleCharacterReset = () => {569 this.handleCharacterReset = () => {
565 callPopup('This will reset the prompt order for this character. You will not lose any prompts.', 'confirm')570 Popup.show.confirm('This will reset the prompt order for this character. You will not lose any prompts.', null)
566 .then(userChoice => {571 .then(userChoice => {
567 if (false === userChoice) return;572 if (!userChoice) return;
568573
569 this.removePromptOrderForCharacter(this.activeCharacter);574 this.removePromptOrderForCharacter(this.activeCharacter);
570 this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);575 this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
@@ -920,7 +925,15 @@ class PromptManager {
920 * @returns {boolean} True if the prompt can be edited, false otherwise.925 * @returns {boolean} True if the prompt can be edited, false otherwise.
921 */926 */
922 isPromptEditAllowed(prompt) {927 isPromptEditAllowed(prompt) {
923 return !prompt.marker;928 const forceEditPrompts = [
929 'charDescription',
930 'charPersonality',
931 'scenario',
932 'personaDescription',
933 'worldInfoBefore',
934 'worldInfoAfter',
935 ];
936 return forceEditPrompts.includes(prompt.identifier) || !prompt.marker;
924 }937 }
925938
926 /**939 /**
@@ -929,7 +942,17 @@ class PromptManager {
929 * @returns {boolean} True if the prompt can be deleted, false otherwise.942 * @returns {boolean} True if the prompt can be deleted, false otherwise.
930 */943 */
931 isPromptToggleAllowed(prompt) {944 isPromptToggleAllowed(prompt) {
932 const forceTogglePrompts = ['charDescription', 'charPersonality', 'scenario', 'personaDescription', 'worldInfoBefore', 'worldInfoAfter', 'main', 'chatHistory', 'dialogueExamples'];945 const forceTogglePrompts = [
946 'charDescription',
947 'charPersonality',
948 'scenario',
949 'personaDescription',
950 'worldInfoBefore',
951 'worldInfoAfter',
952 'main',
953 'chatHistory',
954 'dialogueExamples',
955 ];
933 return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);956 return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);
934 }957 }
935958
@@ -1182,8 +1205,9 @@ class PromptManager {
1182 const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');1205 const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
11831206
1184 nameField.value = prompt.name ?? '';1207 nameField.value = prompt.name ?? '';
1185 roleField.value = prompt.role ?? '';1208 roleField.value = prompt.role ?? 'system';
1186 promptField.value = prompt.content ?? '';1209 promptField.value = prompt.content ?? '';
1210 promptField.disabled = prompt.marker ?? false;
1187 injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;1211 injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;
1188 injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;1212 injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;
1189 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';1213 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
@@ -1279,6 +1303,7 @@ class PromptManager {
1279 nameField.value = '';1303 nameField.value = '';
1280 roleField.selectedIndex = 0;1304 roleField.selectedIndex = 0;
1281 promptField.value = '';1305 promptField.value = '';
1306 promptField.disabled = false;
1282 injectionPositionField.selectedIndex = 0;1307 injectionPositionField.selectedIndex = 0;
1283 injectionPositionField.removeAttribute('disabled');1308 injectionPositionField.removeAttribute('disabled');
1284 injectionDepthField.value = DEFAULT_DEPTH;1309 injectionDepthField.value = DEFAULT_DEPTH;
@@ -1515,16 +1540,17 @@ class PromptManager {
1515 }1540 }
15161541
1517 const encodedName = escapeHtml(prompt.name);1542 const encodedName = escapeHtml(prompt.name);
1543 const isMarkerPrompt = prompt.marker && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
1518 const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && !prompt.forbid_overrides;1544 const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && !prompt.forbid_overrides;
1519 const isImportantPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && prompt.forbid_overrides;1545 const isImportantPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && prompt.forbid_overrides;
1520 const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;1546 const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
1521 const isInjectionPrompt = !prompt.marker && prompt.injection_position === INJECTION_POSITION.ABSOLUTE;1547 const isInjectionPrompt = prompt.injection_position === INJECTION_POSITION.ABSOLUTE;
1522 const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier);1548 const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier);
1523 const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : '';1549 const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : '';
1524 listItemHtml += `1550 listItemHtml += `
1525 <li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${prompt.identifier}">1551 <li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${prompt.identifier}">
1526 <span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">1552 <span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
1527 ${prompt.marker ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}1553 ${isMarkerPrompt ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
1528 ${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}1554 ${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
1529 ${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}1555 ${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}
1530 ${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}1556 ${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}
@@ -1724,7 +1750,7 @@ class PromptManager {
1724 */1750 */
1725 showPopup(area = 'edit') {1751 showPopup(area = 'edit') {
1726 const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);1752 const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);
1727 areaElement.style.display = 'block';1753 areaElement.style.display = 'flex';
17281754
1729 $('#' + this.configuration.prefix + 'prompt_manager_popup').first()1755 $('#' + this.configuration.prefix + 'prompt_manager_popup').first()
1730 .slideDown(200, 'swing')1756 .slideDown(200, 'swing')
public/scripts/RossAscends-mods.js+10 -0
@@ -737,6 +737,16 @@ export function initRossMods() {
737 RA_autoconnect();737 RA_autoconnect();
738 }738 }
739739
740 if (isMobile()) {
741 const fixFunkyPositioning = () => {
742 console.debug('[Mobile] Device viewport change detected.');
743 document.documentElement.style.position = 'fixed';
744 requestAnimationFrame(() => document.documentElement.style.position = '');
745 };
746 window.addEventListener('resize', fixFunkyPositioning);
747 window.addEventListener('orientationchange', fixFunkyPositioning);
748 }
749
740 $('#main_api').change(function () {750 $('#main_api').change(function () {
741 var PrevAPI = main_api;751 var PrevAPI = main_api;
742 setTimeout(() => RA_autoconnect(PrevAPI), 100);752 setTimeout(() => RA_autoconnect(PrevAPI), 100);
public/scripts/backgrounds.js+1 -1
@@ -313,7 +313,7 @@ async function onDeleteBackgroundClick(e) {
313 }313 }
314}314}
315315
316const autoBgPrompt = 'Pause your roleplay and choose a location ONLY from the provided list that is the most suitable for the current scene. Do not output any other text:\n{0}';316const autoBgPrompt = 'Ignore previous instructions and choose a location ONLY from the provided list that is the most suitable for the current scene. Do not output any other text:\n{0}';
317317
318async function autoBackgroundCommand() {318async function autoBackgroundCommand() {
319 /** @type {HTMLElement[]} */319 /** @type {HTMLElement[]} */
public/scripts/chats.js+31 -2
@@ -19,6 +19,8 @@ import {
19 this_chid,19 this_chid,
20 saveChatConditional,20 saveChatConditional,
21 chat_metadata,21 chat_metadata,
22 neutralCharacterName,
23 updateChatMetadata,
22} from '../script.js';24} from '../script.js';
23import { selected_group } from './group-chats.js';25import { selected_group } from './group-chats.js';
24import { power_user } from './power-user.js';26import { power_user } from './power-user.js';
@@ -133,10 +135,11 @@ export async function hideChatMessageRange(start, end, unhide) {
133 const message = chat[messageId];135 const message = chat[messageId];
134 if (!message) continue;136 if (!message) continue;
135137
138 message.is_system = hide;
139
140 // Also toggle "hidden" state for all visible messages
136 const messageBlock = $(`.mes[mesid="${messageId}"]`);141 const messageBlock = $(`.mes[mesid="${messageId}"]`);
137 if (!messageBlock.length) continue;142 if (!messageBlock.length) continue;
138
139 message.is_system = hide;
140 messageBlock.attr('is_system', String(hide));143 messageBlock.attr('is_system', String(hide));
141 }144 }
142145
@@ -1352,6 +1355,32 @@ async function verifyAttachmentsForSource(source) {
1352 }1355 }
1353}1356}
13541357
1358const NEUTRAL_CHAT_KEY = 'neutralChat';
1359
1360export function preserveNeutralChat() {
1361 if (this_chid !== undefined || selected_group || name2 !== neutralCharacterName) {
1362 return;
1363 }
1364
1365 sessionStorage.setItem(NEUTRAL_CHAT_KEY, JSON.stringify({ chat, chat_metadata }));
1366}
1367
1368export function restoreNeutralChat() {
1369 if (this_chid !== undefined || selected_group || name2 !== neutralCharacterName) {
1370 return;
1371 }
1372
1373 const neutralChat = sessionStorage.getItem(NEUTRAL_CHAT_KEY);
1374 if (!neutralChat) {
1375 return;
1376 }
1377
1378 const { chat: neutralChatData, chat_metadata: neutralChatMetadata } = JSON.parse(neutralChat);
1379 chat.splice(0, chat.length, ...neutralChatData);
1380 updateChatMetadata(neutralChatMetadata, true);
1381 sessionStorage.removeItem(NEUTRAL_CHAT_KEY);
1382}
1383
1355/**1384/**
1356 * Registers a file converter function.1385 * Registers a file converter function.
1357 * @param {string} mimeType MIME type1386 * @param {string} mimeType MIME type
public/scripts/extensions.js+0 -1
@@ -29,7 +29,6 @@ export function saveMetadataDebounced() {
29 const characterId = context.characterId;29 const characterId = context.characterId;
3030
31 if (saveMetadataTimeout) {31 if (saveMetadataTimeout) {
32 console.debug('Clearing save metadata timeout');
33 clearTimeout(saveMetadataTimeout);32 clearTimeout(saveMetadataTimeout);
34 }33 }
3534
public/scripts/extensions/caption/index.js+56 -23
@@ -1,6 +1,6 @@
1import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';1import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
3import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';3import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, main_api, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
4import { getMessageTimeStamp } from '../../RossAscends-mods.js';4import { getMessageTimeStamp } from '../../RossAscends-mods.js';
5import { SECRET_KEYS, secret_state } from '../../secrets.js';5import { SECRET_KEYS, secret_state } from '../../secrets.js';
6import { getMultimodalCaption } from '../shared.js';6import { getMultimodalCaption } from '../shared.js';
@@ -403,6 +403,7 @@ jQuery(async function () {
403 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||403 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
404 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||404 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
405 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'zerooneai' && secret_state[SECRET_KEYS.ZEROONEAI]) ||405 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'zerooneai' && secret_state[SECRET_KEYS.ZEROONEAI]) ||
406 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'mistral' && (secret_state[SECRET_KEYS.MISTRALAI] || extension_settings.caption.allow_reverse_proxy)) ||
406 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||407 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
407 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||408 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
408 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||409 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||
@@ -431,14 +432,9 @@ jQuery(async function () {
431 $('#form_sheld').append(imgForm);432 $('#form_sheld').append(imgForm);
432 $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false));433 $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false));
433 }434 }
434 function switchMultimodalBlocks() {435 async function switchMultimodalBlocks() {
436 await addOpenRouterModels();
435 const isMultimodal = extension_settings.caption.source === 'multimodal';437 const isMultimodal = extension_settings.caption.source === 'multimodal';
436 $('#caption_ollama_pull').on('click', (e) => {
437 const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : '';
438 e.preventDefault();
439 $('#ollama_download_model').trigger('click');
440 $('#dialogue_popup_input').val(presetModel);
441 });
442 $('#caption_multimodal_block').toggle(isMultimodal);438 $('#caption_multimodal_block').toggle(isMultimodal);
443 $('#caption_prompt_block').toggle(isMultimodal);439 $('#caption_prompt_block').toggle(isMultimodal);
444 $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);440 $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);
@@ -448,30 +444,48 @@ jQuery(async function () {
448 const types = type.split(',');444 const types = type.split(',');
449 $(this).toggle(types.includes(extension_settings.caption.multimodal_api));445 $(this).toggle(types.includes(extension_settings.caption.multimodal_api));
450 });446 });
451 $('#caption_multimodal_api').on('change', () => {
452 const api = String($('#caption_multimodal_api').val());
453 const model = String($(`#caption_multimodal_model option[data-type="${api}"]`).first().val());
454 extension_settings.caption.multimodal_api = api;
455 extension_settings.caption.multimodal_model = model;
456 saveSettingsDebounced();
457 switchMultimodalBlocks();
458 });
459 $('#caption_multimodal_model').on('change', () => {
460 extension_settings.caption.multimodal_model = String($('#caption_multimodal_model').val());
461 saveSettingsDebounced();
462 });
463 }447 }
464 async function addSettings() {448 async function addSettings() {
465 const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });449 const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });
466 $('#caption_container').append(html);450 $('#caption_container').append(html);
467 }451 }
452 async function addOpenRouterModels() {
453 const dropdown = document.getElementById('caption_multimodal_model');
454 if (!(dropdown instanceof HTMLSelectElement)) {
455 return;
456 }
457 if (extension_settings.caption.source !== 'multimodal' || extension_settings.caption.multimodal_api !== 'openrouter') {
458 return;
459 }
460 const options = Array.from(dropdown.options);
461 const response = await fetch('/api/openrouter/models/multimodal', {
462 method: 'POST',
463 headers: getRequestHeaders(),
464 });
465 if (!response.ok) {
466 return;
467 }
468 const modelIds = await response.json();
469 if (Array.isArray(modelIds) && modelIds.length > 0) {
470 modelIds.forEach((modelId) => {
471 if (!modelId || typeof modelId !== 'string' || options.some(o => o.value === modelId)) {
472 return;
473 }
474 const option = document.createElement('option');
475 option.value = modelId;
476 option.textContent = modelId;
477 option.dataset.type = 'openrouter';
478 dropdown.add(option);
479 });
480 }
481 }
468482
469 await addSettings();483 await addSettings();
470 addPictureSendForm();484 addPictureSendForm();
471 addSendPictureButton();485 addSendPictureButton();
472 setImageIcon();486 setImageIcon();
473 migrateSettings();487 migrateSettings();
474 switchMultimodalBlocks();488 await switchMultimodalBlocks();
475489
476 $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));490 $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
477 $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));491 $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));
@@ -506,6 +520,24 @@ jQuery(async function () {
506 extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');520 extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');
507 saveSettingsDebounced();521 saveSettingsDebounced();
508 });522 });
523 $('#caption_ollama_pull').on('click', (e) => {
524 const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : '';
525 e.preventDefault();
526 $('#ollama_download_model').trigger('click');
527 $('#dialogue_popup_input').val(presetModel);
528 });
529 $('#caption_multimodal_api').on('change', () => {
530 const api = String($('#caption_multimodal_api').val());
531 const model = String($(`#caption_multimodal_model option[data-type="${api}"]`).first().val());
532 extension_settings.caption.multimodal_api = api;
533 extension_settings.caption.multimodal_model = model;
534 saveSettingsDebounced();
535 switchMultimodalBlocks();
536 });
537 $('#caption_multimodal_model').on('change', () => {
538 extension_settings.caption.multimodal_model = String($('#caption_multimodal_model').val());
539 saveSettingsDebounced();
540 });
509541
510 const onMessageEvent = async (index) => {542 const onMessageEvent = async (index) => {
511 if (!extension_settings.caption.auto_mode) {543 if (!extension_settings.caption.auto_mode) {
@@ -531,14 +563,15 @@ jQuery(async function () {
531 await captionExistingMessage(data);563 await captionExistingMessage(data);
532 appendMediaToMessage(data, messageBlock, false);564 appendMediaToMessage(data, messageBlock, false);
533 await saveChatConditional();565 await saveChatConditional();
534 } catch(e) {566 } catch (e) {
535 console.error('Message image recaption failed', e);567 console.error('Message image recaption failed', e);
536 } finally {568 } finally {
537 messageImg.removeClass(animationClass);569 messageImg.removeClass(animationClass);
538 }570 }
539 });571 });
540572
541 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',573 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
574 name: 'caption',
542 callback: captionCommandCallback,575 callback: captionCommandCallback,
543 returns: 'caption',576 returns: 'caption',
544 namedArgumentList: [577 namedArgumentList: [
public/scripts/extensions/caption/settings.html+4 -1
@@ -23,6 +23,7 @@
23 <option value="google">Google AI Studio</option>23 <option value="google">Google AI Studio</option>
24 <option value="koboldcpp">KoboldCpp</option>24 <option value="koboldcpp">KoboldCpp</option>
25 <option value="llamacpp">llama.cpp</option>25 <option value="llamacpp">llama.cpp</option>
26 <option value="mistral">MistralAI</option>
26 <option value="ollama">Ollama</option>27 <option value="ollama">Ollama</option>
27 <option value="openai">OpenAI</option>28 <option value="openai">OpenAI</option>
28 <option value="openrouter">OpenRouter</option>29 <option value="openrouter">OpenRouter</option>
@@ -33,6 +34,8 @@
33 <div class="flex1 flex-container flexFlowColumn flexNoGap">34 <div class="flex1 flex-container flexFlowColumn flexNoGap">
34 <label for="caption_multimodal_model" data-i18n="Model">Model</label>35 <label for="caption_multimodal_model" data-i18n="Model">Model</label>
35 <select id="caption_multimodal_model" class="flex1 text_pole">36 <select id="caption_multimodal_model" class="flex1 text_pole">
37 <option data-type="mistral" value="pixtral-latest">pixtral-latest</option>
38 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
36 <option data-type="zerooneai" value="yi-vision">yi-vision</option>39 <option data-type="zerooneai" value="yi-vision">yi-vision</option>
37 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>40 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
38 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>41 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
@@ -96,7 +99,7 @@
96 <div data-type="ollama">99 <div data-type="ollama">
97 The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.100 The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
98 </div>101 </div>
99 <label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">102 <label data-type="openai,anthropic,google,mistral" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
100 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">103 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
101 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>104 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
102 </label>105 </label>
public/scripts/extensions/connection-manager/index.js+42 -7
@@ -19,20 +19,23 @@ const DEFAULT_SETTINGS = {
19 selectedProfile: null,19 selectedProfile: null,
20};20};
2121
22const COMMON_COMMANDS = [22const CC_COMMANDS = [
23 'api',23 'api',
24 'preset',24 'preset',
25 // Do not fix; CC needs to set the API twice because it could be overridden by the preset
26 'api',
25 'api-url',27 'api-url',
26 'model',28 'model',
27];
28
29const CC_COMMANDS = [
30 ...COMMON_COMMANDS,
31 'proxy',29 'proxy',
32];30];
3331
34const TC_COMMANDS = [32const TC_COMMANDS = [
35 ...COMMON_COMMANDS,33 'api',
34 'preset',
35 'api-url',
36 'model',
37 'sysprompt',
38 'sysprompt-state',
36 'instruct',39 'instruct',
37 'context',40 'context',
38 'instruct-state',41 'instruct-state',
@@ -45,6 +48,8 @@ const FANCY_NAMES = {
45 'preset': 'Settings Preset',48 'preset': 'Settings Preset',
46 'model': 'Model',49 'model': 'Model',
47 'proxy': 'Proxy Preset',50 'proxy': 'Proxy Preset',
51 'sysprompt-state': 'Use System Prompt',
52 'sysprompt': 'System Prompt Name',
48 'instruct-state': 'Instruct Mode',53 'instruct-state': 'Instruct Mode',
49 'instruct': 'Instruct Template',54 'instruct': 'Instruct Template',
50 'context': 'Context Template',55 'context': 'Context Template',
@@ -180,6 +185,11 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
180 }185 }
181186
182 if (cleanUp) {187 if (cleanUp) {
188 for (const command of commands) {
189 if (command.endsWith('-state') && profile[command] === 'false') {
190 delete profile[command.replace('-state', '')];
191 }
192 }
183 for (const command of opposingCommands) {193 for (const command of opposingCommands) {
184 if (commands.includes(command)) {194 if (commands.includes(command)) {
185 continue;195 continue;
@@ -325,7 +335,7 @@ function renderConnectionProfiles(profiles) {
325 noneOption.selected = !extension_settings.connectionManager.selectedProfile;335 noneOption.selected = !extension_settings.connectionManager.selectedProfile;
326 profiles.appendChild(noneOption);336 profiles.appendChild(noneOption);
327337
328 for (const profile of extension_settings.connectionManager.profiles) {338 for (const profile of extension_settings.connectionManager.profiles.sort((a, b) => a.name.localeCompare(b.name))) {
329 const option = document.createElement('option');339 const option = document.createElement('option');
330 option.value = profile.id;340 option.value = profile.id;
331 option.textContent = profile.name;341 option.textContent = profile.name;
@@ -462,6 +472,31 @@ async function renderDetailsContent(detailsContent) {
462 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);472 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
463 });473 });
464474
475 const renameButton = document.getElementById('rename_connection_profile');
476 renameButton.addEventListener('click', async () => {
477 const selectedProfile = extension_settings.connectionManager.selectedProfile;
478 const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
479 if (!profile) {
480 console.log('No profile selected');
481 return;
482 }
483
484 const newName = await Popup.show.input('Enter a new name', null, profile.name, { rows: 2 });
485 if (!newName) {
486 return;
487 }
488
489 if (extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
490 toastr.error('A profile with the same name already exists.');
491 return;
492 }
493
494 profile.name = newName;
495 saveSettingsDebounced();
496 renderConnectionProfiles(profiles);
497 toastr.success('Connection profile renamed', '', { timeOut: 1500 });
498 });
499
465 /** @type {HTMLElement} */500 /** @type {HTMLElement} */
466 const viewDetails = document.getElementById('view_connection_profile');501 const viewDetails = document.getElementById('view_connection_profile');
467 const detailsContent = document.getElementById('connection_profile_details_content');502 const detailsContent = document.getElementById('connection_profile_details_content');
public/scripts/extensions/connection-manager/settings.html+1 -0
@@ -13,6 +13,7 @@
13 <i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>13 <i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>
14 <i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>14 <i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>
15 <i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>15 <i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>
16 <i id="rename_connection_profile" class="menu_button fa-solid fa-pencil" title="Rename a connection profile" data-i18n="[title]Rename a connection profile"></i>
16 <i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>17 <i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>
17 <i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>18 <i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>
18 </div>19 </div>
public/scripts/extensions/expressions/index.js+6 -1
@@ -20,7 +20,7 @@ const STREAMING_UPDATE_INTERVAL = 10000;
20const TALKINGCHECK_UPDATE_INTERVAL = 500;20const TALKINGCHECK_UPDATE_INTERVAL = 500;
21const DEFAULT_FALLBACK_EXPRESSION = 'joy';21const DEFAULT_FALLBACK_EXPRESSION = 'joy';
22const FUNCTION_NAME = 'set_emotion';22const FUNCTION_NAME = 'set_emotion';
23const DEFAULT_LLM_PROMPT = 'Pause your roleplay. Classify the emotion of the last message. Output just one word, e.g. "joy" or "anger". Choose only one of the following labels: {{labels}}';23const DEFAULT_LLM_PROMPT = 'Ignore previous instructions. Classify the emotion of the last message. Output just one word, e.g. "joy" or "anger". Choose only one of the following labels: {{labels}}';
24const DEFAULT_EXPRESSIONS = [24const DEFAULT_EXPRESSIONS = [
25 'talkinghead',25 'talkinghead',
26 'admiration',26 'admiration',
@@ -2064,6 +2064,11 @@ function migrateSettings() {
2064 });2064 });
2065 eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);2065 eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
2066 eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);2066 eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
2067 eventSource.on(event_types.EXTRAS_CONNECTED, () => {
2068 if (extension_settings.expressions.talkinghead) {
2069 setTalkingHeadState(extension_settings.expressions.talkinghead);
2070 }
2071 });
20672072
2068 const localEnumProviders = {2073 const localEnumProviders = {
2069 expressions: () => getCachedExpressions().map(expression => {2074 expressions: () => getCachedExpressions().map(expression => {
public/scripts/extensions/memory/index.js+1 -1
@@ -102,7 +102,7 @@ const prompt_builders = {
102 RAW_NON_BLOCKING: 2,102 RAW_NON_BLOCKING: 2,
103};103};
104104
105const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events in the story so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';105const defaultPrompt = 'Ignore previous instructions. Summarize the most important facts and events in the story so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.';
106const defaultTemplate = '[Summary: {{summary}}]';106const defaultTemplate = '[Summary: {{summary}}]';
107107
108const defaultSettings = {108const defaultSettings = {
public/scripts/extensions/quick-reply/src/QuickReply.js+1 -0
@@ -1904,6 +1904,7 @@ export class QuickReply {
1904 executeOnAi: this.executeOnAi,1904 executeOnAi: this.executeOnAi,
1905 executeOnChatChange: this.executeOnChatChange,1905 executeOnChatChange: this.executeOnChatChange,
1906 executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,1906 executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,
1907 executeOnNewChat: this.executeOnNewChat,
1907 automationId: this.automationId,1908 automationId: this.automationId,
1908 };1909 };
1909 }1910 }
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+4 -0
@@ -35,6 +35,7 @@ export class SlashCommandHandler {
35 if (qr.executeOnUser) icons += enumIcons.user;35 if (qr.executeOnUser) icons += enumIcons.user;
36 if (qr.executeOnAi) icons += enumIcons.assistant;36 if (qr.executeOnAi) icons += enumIcons.assistant;
37 if (qr.executeOnChatChange) icons += '💬';37 if (qr.executeOnChatChange) icons += '💬';
38 if (qr.executeOnNewChat) icons += '🆕';
38 if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;39 if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
39 return icons;40 return icons;
40 }41 }
@@ -265,6 +266,7 @@ export class SlashCommandHandler {
265 new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),266 new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
266 new SlashCommandNamedArgument('bot', 'auto execute on AI message, e.g., bot=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),267 new SlashCommandNamedArgument('bot', 'auto execute on AI message, e.g., bot=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
267 new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),268 new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
269 new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
268 new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),270 new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
269 new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false),271 new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false),
270 ];272 ];
@@ -857,6 +859,7 @@ export class SlashCommandHandler {
857 executeOnUser: isTrueBoolean(args.user),859 executeOnUser: isTrueBoolean(args.user),
858 executeOnAi: isTrueBoolean(args.bot),860 executeOnAi: isTrueBoolean(args.bot),
859 executeOnChatChange: isTrueBoolean(args.load),861 executeOnChatChange: isTrueBoolean(args.load),
862 executeOnNewChat: isTrueBoolean(args.new),
860 executeOnGroupMemberDraft: isTrueBoolean(args.group),863 executeOnGroupMemberDraft: isTrueBoolean(args.group),
861 automationId: args.automationId ?? '',864 automationId: args.automationId ?? '',
862 },865 },
@@ -889,6 +892,7 @@ export class SlashCommandHandler {
889 executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),892 executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),
890 executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),893 executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
891 executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),894 executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
895 executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new),
892 automationId: args.automationId ?? '',896 automationId: args.automationId ?? '',
893 },897 },
894 );898 );
public/scripts/extensions/shared.js+6 -2
@@ -13,7 +13,7 @@ import { createThumbnail, isValidUrl } from '../utils.js';
13 */13 */
14export async function getMultimodalCaption(base64Img, prompt) {14export async function getMultimodalCaption(base64Img, prompt) {
15 const useReverseProxy =15 const useReverseProxy =
16 (['openai', 'anthropic', 'google'].includes(extension_settings.caption.multimodal_api))16 (['openai', 'anthropic', 'google', 'mistral'].includes(extension_settings.caption.multimodal_api))
17 && extension_settings.caption.allow_reverse_proxy17 && extension_settings.caption.allow_reverse_proxy
18 && oai_settings.reverse_proxy18 && oai_settings.reverse_proxy
19 && isValidUrl(oai_settings.reverse_proxy);19 && isValidUrl(oai_settings.reverse_proxy);
@@ -36,7 +36,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
36 const isVllm = extension_settings.caption.multimodal_api === 'vllm';36 const isVllm = extension_settings.caption.multimodal_api === 'vllm';
37 const base64Bytes = base64Img.length * 0.75;37 const base64Bytes = base64Img.length * 0.75;
38 const compressionLimit = 2 * 1024 * 1024;38 const compressionLimit = 2 * 1024 * 1024;
39 if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {39 if ((['google', 'openrouter', 'mistral'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
40 const maxSide = 1024;40 const maxSide = 1024;
41 base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');41 base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
42 }42 }
@@ -139,6 +139,10 @@ function throwIfInvalidModel(useReverseProxy) {
139 throw new Error('Google AI Studio API key is not set.');139 throw new Error('Google AI Studio API key is not set.');
140 }140 }
141141
142 if (extension_settings.caption.multi_modal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {
143 throw new Error('Mistral AI API key is not set.');
144 }
145
142 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {146 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
143 throw new Error('Ollama server URL is not set.');147 throw new Error('Ollama server URL is not set.');
144 }148 }
public/scripts/extensions/stable-diffusion/index.js+13 -11
@@ -125,15 +125,14 @@ const messageTrigger = {
125const promptTemplates = {125const promptTemplates = {
126 // Not really a prompt template, rather an outcome message template126 // Not really a prompt template, rather an outcome message template
127 [generationMode.MESSAGE]: '[{{char}} sends a picture that contains: {{prompt}}].',127 [generationMode.MESSAGE]: '[{{char}} sends a picture that contains: {{prompt}}].',
128 /*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */128 [generationMode.CHARACTER]: 'In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\'',
129 [generationMode.CHARACTER]: '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\']',
130 //face-specific prompt129 //face-specific prompt
131 [generationMode.FACE]: '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'close up facial portrait,\']',130 [generationMode.FACE]: 'In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'close up facial portrait,\'',
132 //prompt for only the last message131 //prompt for only the last message
133 [generationMode.USER]: '[Pause your roleplay and provide a detailed description of {{user}}\'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]',132 [generationMode.USER]: 'Ignore previous instructions and provide a detailed description of {{user}}\'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\'. Ignore the rest of the story when crafting this description. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.',
134 [generationMode.SCENARIO]: '[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}\'s appearance, and {{char}}\'s surroundings. Do not roleplay while writing this description.]',133 [generationMode.SCENARIO]: 'Ignore previous instructions and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}\'s appearance, and {{char}}\'s surroundings. Do not reply as {{char}} while writing this description.',
135134
136 [generationMode.NOW]: `[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.135 [generationMode.NOW]: `Ignore previous instructions. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.
137136
138 Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').137 Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').
139138
@@ -155,14 +154,14 @@ const promptTemplates = {
155 If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.154 If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.
156155
157 A correctly formatted example response would be:156 A correctly formatted example response would be:
158 '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']`,157 '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)'`,
159158
160 [generationMode.RAW_LAST]: '[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]',159 [generationMode.RAW_LAST]: 'Ignore previous instructions and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.',
161 [generationMode.BACKGROUND]: '[Pause your roleplay and provide a detailed description of {{char}}\'s surroundings in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: location, time of day, weather, lighting, and any other relevant details. Do not include descriptions of characters and non-visual qualities such as names, personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'background,\'. Ignore the rest of the story when crafting this description. Do not roleplay as {{user}} when writing this description, and do not attempt to continue the story.]',160 [generationMode.BACKGROUND]: 'Ignore previous instructions and provide a detailed description of {{char}}\'s surroundings in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: location, time of day, weather, lighting, and any other relevant details. Do not include descriptions of characters and non-visual qualities such as names, personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'background,\'. Ignore the rest of the story when crafting this description. Do not reply as {{char}} when writing this description, and do not attempt to continue the story.',
162 [generationMode.FACE_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "close-up portrait".',161 [generationMode.FACE_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "close-up portrait".',
163 [generationMode.CHARACTER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',162 [generationMode.CHARACTER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
164 [generationMode.USER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',163 [generationMode.USER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
165 [generationMode.FREE_EXTENDED]: 'Pause your roleplay and provide an exhaustive comma-separated list of tags describing the appearance of "{0}" in great detail. Start with {{charPrefix}} (sic) if the subject is associated with {{char}}.',164 [generationMode.FREE_EXTENDED]: 'Ignore previous instructions and provide an exhaustive comma-separated list of tags describing the appearance of "{0}" in great detail. Start with {{charPrefix}} (sic) if the subject is associated with {{char}}.',
166};165};
167166
168const defaultPrefix = 'best quality, absurdres, aesthetic,';167const defaultPrefix = 'best quality, absurdres, aesthetic,';
@@ -3813,7 +3812,10 @@ jQuery(async () => {
3813 ],3812 ],
3814 helpString: `3813 helpString: `
3815 <div>3814 <div>
3816 Requests to generate an image and posts it to chat (unless <code>quiet=true</code> argument is specified).</code>.3815 Requests to generate an image and posts it to chat (unless <code>quiet=true</code> argument is specified).
3816 </div>
3817 <div>
3818 Supported arguments: <code>${Object.values(triggerWords).flat().join(', ')}</code>.
3817 </div>3819 </div>
3818 <div>3820 <div>
3819 Anything else would trigger a "free mode" to make generate whatever you prompted. Example: <code>/imagine apple tree</code> would generate a picture of an apple tree. Returns a link to the generated image.3821 Anything else would trigger a "free mode" to make generate whatever you prompted. Example: <code>/imagine apple tree</code> would generate a picture of an apple tree. Returns a link to the generated image.
public/scripts/extensions/stable-diffusion/settings.html+1 -1
@@ -349,7 +349,7 @@
349 </div>349 </div>
350 </div>350 </div>
351351
352 <div class="flex-container marginTopBot5" data-sd-source="auto,vlad,extras,horde,drawthings,comfy">352 <div class="flex-container marginTopBot5" data-sd-source="auto,vlad,extras,horde,drawthings">
353 <label class="flex1 checkbox_label">353 <label class="flex1 checkbox_label">
354 <input id="sd_restore_faces" type="checkbox" />354 <input id="sd_restore_faces" type="checkbox" />
355 <small data-i18n="Restore Faces">Restore Faces</small>355 <small data-i18n="Restore Faces">Restore Faces</small>
public/scripts/extensions/vectors/index.js+56 -93
@@ -56,7 +56,7 @@ const settings = {
56 summarize: false,56 summarize: false,
57 summarize_sent: false,57 summarize_sent: false,
58 summary_source: 'main',58 summary_source: 'main',
59 summary_prompt: 'Pause your roleplay. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.',59 summary_prompt: 'Ignore previous instructions. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.',
60 force_chunk_delimiter: '',60 force_chunk_delimiter: '',
6161
62 // For chats62 // For chats
@@ -718,7 +718,7 @@ async function getQueryText(chat, initiator) {
718async function getSavedHashes(collectionId) {718async function getSavedHashes(collectionId) {
719 const response = await fetch('/api/vector/list', {719 const response = await fetch('/api/vector/list', {
720 method: 'POST',720 method: 'POST',
721 headers: getRequestHeaders(),721 headers: getVectorHeaders(),
722 body: JSON.stringify({722 body: JSON.stringify({
723 collectionId: collectionId,723 collectionId: collectionId,
724 source: settings.source,724 source: settings.source,
@@ -737,25 +737,43 @@ function getVectorHeaders() {
737 const headers = getRequestHeaders();737 const headers = getRequestHeaders();
738 switch (settings.source) {738 switch (settings.source) {
739 case 'extras':739 case 'extras':
740 addExtrasHeaders(headers);740 Object.assign(headers, {
741 'X-Extras-Url': extension_settings.apiUrl,
742 'X-Extras-Key': extension_settings.apiKey,
743 });
741 break;744 break;
742 case 'togetherai':745 case 'togetherai':
743 addTogetherAiHeaders(headers);746 Object.assign(headers, {
747 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,
748 });
744 break;749 break;
745 case 'openai':750 case 'openai':
746 addOpenAiHeaders(headers);751 Object.assign(headers, {
752 'X-OpenAI-Model': extension_settings.vectors.openai_model,
753 });
747 break;754 break;
748 case 'cohere':755 case 'cohere':
749 addCohereHeaders(headers);756 Object.assign(headers, {
757 'X-Cohere-Model': extension_settings.vectors.cohere_model,
758 });
750 break;759 break;
751 case 'ollama':760 case 'ollama':
752 addOllamaHeaders(headers);761 Object.assign(headers, {
762 'X-Ollama-Model': extension_settings.vectors.ollama_model,
763 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],
764 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,
765 });
753 break;766 break;
754 case 'llamacpp':767 case 'llamacpp':
755 addLlamaCppHeaders(headers);768 Object.assign(headers, {
769 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],
770 });
756 break;771 break;
757 case 'vllm':772 case 'vllm':
758 addVllmHeaders(headers);773 Object.assign(headers, {
774 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],
775 'X-Vllm-Model': extension_settings.vectors.vllm_model,
776 });
759 break;777 break;
760 default:778 default:
761 break;779 break;
@@ -764,81 +782,6 @@ function getVectorHeaders() {
764}782}
765783
766/**784/**
767 * Add headers for the Extras API source.
768 * @param {object} headers Headers object
769 */
770function addExtrasHeaders(headers) {
771 console.log(`Vector source is extras, populating API URL: ${extension_settings.apiUrl}`);
772 Object.assign(headers, {
773 'X-Extras-Url': extension_settings.apiUrl,
774 'X-Extras-Key': extension_settings.apiKey,
775 });
776}
777
778/**
779 * Add headers for the TogetherAI API source.
780 * @param {object} headers Headers object
781 */
782function addTogetherAiHeaders(headers) {
783 Object.assign(headers, {
784 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,
785 });
786}
787
788/**
789 * Add headers for the OpenAI API source.
790 * @param {object} headers Header object
791 */
792function addOpenAiHeaders(headers) {
793 Object.assign(headers, {
794 'X-OpenAI-Model': extension_settings.vectors.openai_model,
795 });
796}
797
798/**
799 * Add headers for the Cohere API source.
800 * @param {object} headers Header object
801 */
802function addCohereHeaders(headers) {
803 Object.assign(headers, {
804 'X-Cohere-Model': extension_settings.vectors.cohere_model,
805 });
806}
807
808/**
809 * Add headers for the Ollama API source.
810 * @param {object} headers Header object
811 */
812function addOllamaHeaders(headers) {
813 Object.assign(headers, {
814 'X-Ollama-Model': extension_settings.vectors.ollama_model,
815 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],
816 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,
817 });
818}
819
820/**
821 * Add headers for the LlamaCpp API source.
822 * @param {object} headers Header object
823 */
824function addLlamaCppHeaders(headers) {
825 Object.assign(headers, {
826 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],
827 });
828}
829
830/**
831 * Add headers for the VLLM API source.
832 * @param {object} headers Header object
833 */
834function addVllmHeaders(headers) {
835 Object.assign(headers, {
836 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],
837 'X-Vllm-Model': extension_settings.vectors.vllm_model,
838 });
839}
840
841/**
842 * Inserts vector items into a collection785 * Inserts vector items into a collection
843 * @param {string} collectionId - The collection to insert into786 * @param {string} collectionId - The collection to insert into
844 * @param {{ hash: number, text: string }[]} items - The items to insert787 * @param {{ hash: number, text: string }[]} items - The items to insert
@@ -901,7 +844,7 @@ function throwIfSourceInvalid() {
901async function deleteVectorItems(collectionId, hashes) {844async function deleteVectorItems(collectionId, hashes) {
902 const response = await fetch('/api/vector/delete', {845 const response = await fetch('/api/vector/delete', {
903 method: 'POST',846 method: 'POST',
904 headers: getRequestHeaders(),847 headers: getVectorHeaders(),
905 body: JSON.stringify({848 body: JSON.stringify({
906 collectionId: collectionId,849 collectionId: collectionId,
907 hashes: hashes,850 hashes: hashes,
@@ -987,7 +930,7 @@ async function purgeFileVectorIndex(fileUrl) {
987930
988 const response = await fetch('/api/vector/purge', {931 const response = await fetch('/api/vector/purge', {
989 method: 'POST',932 method: 'POST',
990 headers: getRequestHeaders(),933 headers: getVectorHeaders(),
991 body: JSON.stringify({934 body: JSON.stringify({
992 collectionId: collectionId,935 collectionId: collectionId,
993 }),936 }),
@@ -1016,7 +959,7 @@ async function purgeVectorIndex(collectionId) {
1016959
1017 const response = await fetch('/api/vector/purge', {960 const response = await fetch('/api/vector/purge', {
1018 method: 'POST',961 method: 'POST',
1019 headers: getRequestHeaders(),962 headers: getVectorHeaders(),
1020 body: JSON.stringify({963 body: JSON.stringify({
1021 collectionId: collectionId,964 collectionId: collectionId,
1022 }),965 }),
@@ -1041,7 +984,7 @@ async function purgeAllVectorIndexes() {
1041 try {984 try {
1042 const response = await fetch('/api/vector/purge-all', {985 const response = await fetch('/api/vector/purge-all', {
1043 method: 'POST',986 method: 'POST',
1044 headers: getRequestHeaders(),987 headers: getVectorHeaders(),
1045 });988 });
1046989
1047 if (!response.ok) {990 if (!response.ok) {
@@ -1056,6 +999,25 @@ async function purgeAllVectorIndexes() {
1056 }999 }
1057}1000}
10581001
1002async function isModelScopesEnabled() {
1003 try {
1004 const response = await fetch('/api/vector/scopes-enabled', {
1005 method: 'GET',
1006 headers: getVectorHeaders(),
1007 });
1008
1009 if (!response.ok) {
1010 return false;
1011 }
1012
1013 const data = await response.json();
1014 return data?.enabled ?? false;
1015 } catch (error) {
1016 console.error('Vectors: Failed to check model scopes', error);
1017 return false;
1018 }
1019}
1020
1059function toggleSettings() {1021function toggleSettings() {
1060 $('#vectors_files_settings').toggle(!!settings.enabled_files);1022 $('#vectors_files_settings').toggle(!!settings.enabled_files);
1061 $('#vectors_chats_settings').toggle(!!settings.enabled_chats);1023 $('#vectors_chats_settings').toggle(!!settings.enabled_chats);
@@ -1320,6 +1282,7 @@ jQuery(async () => {
1320 }1282 }
13211283
1322 Object.assign(settings, extension_settings.vectors);1284 Object.assign(settings, extension_settings.vectors);
1285 const scopesEnabled = await isModelScopesEnabled();
13231286
1324 // Migrate from TensorFlow to Transformers1287 // Migrate from TensorFlow to Transformers
1325 settings.source = settings.source !== 'local' ? settings.source : 'transformers';1288 settings.source = settings.source !== 'local' ? settings.source : 'transformers';
@@ -1371,31 +1334,31 @@ jQuery(async () => {
1371 saveSettingsDebounced();1334 saveSettingsDebounced();
1372 });1335 });
1373 $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {1336 $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {
1374 $('#vectors_modelWarning').show();1337 !scopesEnabled && $('#vectors_modelWarning').show();
1375 settings.togetherai_model = String($('#vectors_togetherai_model').val());1338 settings.togetherai_model = String($('#vectors_togetherai_model').val());
1376 Object.assign(extension_settings.vectors, settings);1339 Object.assign(extension_settings.vectors, settings);
1377 saveSettingsDebounced();1340 saveSettingsDebounced();
1378 });1341 });
1379 $('#vectors_openai_model').val(settings.openai_model).on('change', () => {1342 $('#vectors_openai_model').val(settings.openai_model).on('change', () => {
1380 $('#vectors_modelWarning').show();1343 !scopesEnabled && $('#vectors_modelWarning').show();
1381 settings.openai_model = String($('#vectors_openai_model').val());1344 settings.openai_model = String($('#vectors_openai_model').val());
1382 Object.assign(extension_settings.vectors, settings);1345 Object.assign(extension_settings.vectors, settings);
1383 saveSettingsDebounced();1346 saveSettingsDebounced();
1384 });1347 });
1385 $('#vectors_cohere_model').val(settings.cohere_model).on('change', () => {1348 $('#vectors_cohere_model').val(settings.cohere_model).on('change', () => {
1386 $('#vectors_modelWarning').show();1349 !scopesEnabled && $('#vectors_modelWarning').show();
1387 settings.cohere_model = String($('#vectors_cohere_model').val());1350 settings.cohere_model = String($('#vectors_cohere_model').val());
1388 Object.assign(extension_settings.vectors, settings);1351 Object.assign(extension_settings.vectors, settings);
1389 saveSettingsDebounced();1352 saveSettingsDebounced();
1390 });1353 });
1391 $('#vectors_ollama_model').val(settings.ollama_model).on('input', () => {1354 $('#vectors_ollama_model').val(settings.ollama_model).on('input', () => {
1392 $('#vectors_modelWarning').show();1355 !scopesEnabled && $('#vectors_modelWarning').show();
1393 settings.ollama_model = String($('#vectors_ollama_model').val());1356 settings.ollama_model = String($('#vectors_ollama_model').val());
1394 Object.assign(extension_settings.vectors, settings);1357 Object.assign(extension_settings.vectors, settings);
1395 saveSettingsDebounced();1358 saveSettingsDebounced();
1396 });1359 });
1397 $('#vectors_vllm_model').val(settings.vllm_model).on('input', () => {1360 $('#vectors_vllm_model').val(settings.vllm_model).on('input', () => {
1398 $('#vectors_modelWarning').show();1361 !scopesEnabled && $('#vectors_modelWarning').show();
1399 settings.vllm_model = String($('#vectors_vllm_model').val());1362 settings.vllm_model = String($('#vectors_vllm_model').val());
1400 Object.assign(extension_settings.vectors, settings);1363 Object.assign(extension_settings.vectors, settings);
1401 saveSettingsDebounced();1364 saveSettingsDebounced();
public/scripts/extensions/vectors/settings.html+3 -2
@@ -98,8 +98,9 @@
9898
99 <small id="vectors_modelWarning">99 <small id="vectors_modelWarning">
100 <i class="fa-solid fa-exclamation-triangle"></i>100 <i class="fa-solid fa-exclamation-triangle"></i>
101 <span data-i18n="Vectors Model Warning">101 <span>
102 It is recommended to purge vectors when changing the model mid-chat. Otherwise, it will lead to sub-par results.102 Set <code>vectors.enableModelScopes</code> to true in config.yaml to switch between vectorization models without needing to purge existing vectors.
103 This option will soon be enabled by default.
103 </span>104 </span>
104 </small>105 </small>
105106
public/scripts/horde.js+79 -19
@@ -1,10 +1,10 @@
1import {1import {
2 saveSettingsDebounced,2 amount_gen,
3 callPopup,3 callPopup,
4 setGenerationProgress,
5 getRequestHeaders,4 getRequestHeaders,
6 max_context,5 max_context,
7 amount_gen,6 saveSettingsDebounced,
7 setGenerationProgress,
8} from '../script.js';8} from '../script.js';
9import { SECRET_KEYS, writeSecret } from './secrets.js';9import { SECRET_KEYS, writeSecret } from './secrets.js';
10import { delay } from './utils.js';10import { delay } from './utils.js';
@@ -45,8 +45,7 @@ async function getWorkers(force) {
45 headers: getRequestHeaders(),45 headers: getRequestHeaders(),
46 body: JSON.stringify({ force }),46 body: JSON.stringify({ force }),
47 });47 });
48 const data = await response.json();48 return await response.json();
49 return data;
50}49}
5150
52/**51/**
@@ -61,16 +60,18 @@ async function getModels(force) {
61 body: JSON.stringify({ force }),60 body: JSON.stringify({ force }),
62 });61 });
63 const data = await response.json();62 const data = await response.json();
63 console.log('getModels', data);
64 return data;64 return data;
65}65}
6666
67
67/**68/**
68 * Gets the status of a Horde task.69 * Gets the status of a Horde task.
69 * @param {string} taskId Task ID70 * @param {string} taskId Task ID
70 * @returns {Promise<Object>} Task status71 * @returns {Promise<Object>} Task status
71 */72 */
72async function getTaskStatus(taskId) {73async function getTaskStatus(taskId) {
73 const response = await fetch('/api/horde/task-status', {74 const response = await fetch('/api/horde/task-status', {
74 method: 'POST',75 method: 'POST',
75 headers: getRequestHeaders(),76 headers: getRequestHeaders(),
76 body: JSON.stringify({ taskId }),77 body: JSON.stringify({ taskId }),
@@ -80,8 +81,7 @@ async function getTaskStatus(taskId) {
80 throw new Error(`Failed to get task status: ${response.statusText}`);81 throw new Error(`Failed to get task status: ${response.statusText}`);
81 }82 }
8283
83 const data = await response.json();84 return await response.json();
84 return data;
85}85}
8686
87/**87/**
@@ -148,7 +148,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
148148
149 for (const model of selectedModels) {149 for (const model of selectedModels) {
150 for (const worker of workers) {150 for (const worker of workers) {
151 if (model.cluster == worker.cluster && worker.models.includes(model.name)) {151 if (model.cluster === worker.cluster && worker.models.includes(model.name)) {
152 // Skip workers that are not trusted if the option is enabled152 // Skip workers that are not trusted if the option is enabled
153 if (horde_settings.trusted_workers_only && !worker.trusted) {153 if (horde_settings.trusted_workers_only && !worker.trusted) {
154 continue;154 continue;
@@ -250,12 +250,10 @@ async function generateHorde(prompt, params, signal, reportProgress) {
250 console.log(generatedText);250 console.log(generatedText);
251 console.log(`Generated by Horde Worker: ${WorkerName} [${WorkerModel}]`);251 console.log(`Generated by Horde Worker: ${WorkerName} [${WorkerModel}]`);
252 return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName} [${WorkerModel}]` };252 return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName} [${WorkerModel}]` };
253 }253 } else if (!queue_position_first) {
254 else if (!queue_position_first) {
255 queue_position_first = statusCheckJson.queue_position;254 queue_position_first = statusCheckJson.queue_position;
256 reportProgress && setGenerationProgress(0);255 reportProgress && setGenerationProgress(0);
257 }256 } else if (statusCheckJson.queue_position >= 0) {
258 else if (statusCheckJson.queue_position >= 0) {
259 let queue_position = statusCheckJson.queue_position;257 let queue_position = statusCheckJson.queue_position;
260 const progress = Math.round(100 - (queue_position / queue_position_first * 100));258 const progress = Math.round(100 - (queue_position / queue_position_first * 100));
261 reportProgress && setGenerationProgress(progress);259 reportProgress && setGenerationProgress(progress);
@@ -268,17 +266,24 @@ async function generateHorde(prompt, params, signal, reportProgress) {
268 throw new Error('Horde timeout');266 throw new Error('Horde timeout');
269}267}
270268
269
271/**270/**
272 * Displays the available models in the Horde model selection dropdown.271 * Displays the available models in the Horde model selection dropdown.
273 * @param {boolean} force Force refresh of the models272 * @param {boolean} force Force refresh of the models
274 */273 */
275async function getHordeModels(force) {274async function getHordeModels(force) {
275 const sortByPerformance = (a, b) => b.performance - a.performance;
276 const sortByWhitelisted = (a, b) => b.is_whitelisted - a.is_whitelisted;
277 const sortByPopular = (a, b) => b.tags?.includes('popular') - a.tags?.includes('popular');
278
276 $('#horde_model').empty();279 $('#horde_model').empty();
277 models = (await getModels(force)).sort((a, b) => b.performance - a.performance);280 models = (await getModels(force)).sort((a, b) => {
281 return sortByWhitelisted(a, b) || sortByPopular(a, b) || sortByPerformance(a, b);
282 });
278 for (const model of models) {283 for (const model of models) {
279 const option = document.createElement('option');284 const option = document.createElement('option');
280 option.value = model.name;285 option.value = model.name;
281 option.innerText = `${model.name} (ETA: ${model.eta}s, Speed: ${model.performance}, Queue: ${model.queued}, Workers: ${model.count})`;286 option.innerText = hordeModelTextString(model);
282 option.selected = horde_settings.models.includes(model.name);287 option.selected = horde_settings.models.includes(model.name);
283 $('#horde_model').append(option);288 $('#horde_model').append(option);
284 }289 }
@@ -323,8 +328,66 @@ async function showKudos() {
323 toastr.info(`Kudos: ${data.kudos}`, data.username);328 toastr.info(`Kudos: ${data.kudos}`, data.username);
324}329}
325330
331function hordeModelTextString(model) {
332 const q = hordeModelQueueStateString(model);
333 return `${model.name} (${q})`;
334}
335
336function hordeModelQueueStateString(model) {
337 return `ETA: ${model.eta}s, Speed: ${model.performance}, Queue: ${model.queued}, Workers: ${model.count}`;
338}
339
340function getHordeModelTemplate(option) {
341 const model = models.find(x => x.name === option?.element?.value);
342
343 if (!option.id || !model) {
344 console.debug('No model found for option', option, option?.element?.value);
345 console.debug('Models', models);
346 return option.text;
347 }
348
349 const strip = html => {
350 const tmp = document.createElement('DIV');
351 tmp.innerHTML = html || '';
352 return tmp.textContent || tmp.innerText || '';
353 };
354
355 // how much do we trust the metadata from the models repo? about this much
356 const displayName = strip(model.display_name || model.name).replace(/.*\//g, '');
357 const description = strip(model.description);
358 const tags = model.tags ? model.tags.map(strip) : [];
359 const url = strip(model.url);
360 const style = strip(model.style);
361
362 const workerInfo = hordeModelQueueStateString(model);
363 const isPopular = model.tags?.includes('popular');
364 const descriptionDiv = description ? `<div class="horde-model-description">${description}</div>` : '';
365 const tagSpans = tags.length > 0 &&
366 `${tags.map(tag => `<span class="tag tag_name">${tag}</span>`).join('')}</span>` || '';
367
368 const modelDetailsLink = url && `<a href="${url}" target="_blank" rel="noopener noreferrer" class="model-details-link fa-solid fa-circle-question"> </a>`;
369 const capitalize = s => s ? s[0].toUpperCase() + s.slice(1) : '';
370 const innerContent = [
371 `<strong>${displayName}</strong> ${modelDetailsLink}`,
372 style ? `${capitalize(style)}` : '',
373 tagSpans ? `<span class="tags tags_inline inline-flex margin-r2">${tagSpans}</span>` : '',
374 ].filter(Boolean).join(' | ');
375
376 return $((`
377 <div class="flex-container flexFlowColumn">
378 <div>
379 ${isPopular ? '<span class="fa-fw fa-solid fa-star" title="Popular"></span>' : ''}
380 ${innerContent}
381 </div>
382 ${descriptionDiv}
383 <div><small>${workerInfo}</small></div>
384 </div>
385 `));
386}
387
326jQuery(function () {388jQuery(function () {
327 $('#horde_model').on('mousedown change', async function (e) {389 $('#horde_model').on('mousedown change', async function (e) {
390 console.log('Horde model change', e);
328 horde_settings.models = $('#horde_model').val();391 horde_settings.models = $('#horde_model').val();
329 console.log('Updated Horde models', horde_settings.models);392 console.log('Updated Horde models', horde_settings.models);
330393
@@ -374,10 +437,7 @@ jQuery(function () {
374 // Customize the pillbox text by shortening the full text437 // Customize the pillbox text by shortening the full text
375 return data.id;438 return data.id;
376 },439 },
377 templateResult: function (data) {440 templateResult: getHordeModelTemplate,
378 // Return the full text for the dropdown
379 return data.text;
380 },
381 });441 });
382 }442 }
383});443});
public/scripts/instruct-mode.js+63 -55
@@ -14,10 +14,15 @@ import { regexFromString, resetScrollHeight } from './utils.js';
14 */14 */
15export let instruct_presets = [];15export let instruct_presets = [];
1616
17export const names_behavior_types = {
18 NONE: 'none',
19 FORCE: 'force',
20 ALWAYS: 'always',
21};
22
17const controls = [23const controls = [
18 { id: 'instruct_enabled', property: 'enabled', isCheckbox: true },24 { id: 'instruct_enabled', property: 'enabled', isCheckbox: true },
19 { id: 'instruct_wrap', property: 'wrap', isCheckbox: true },25 { id: 'instruct_wrap', property: 'wrap', isCheckbox: true },
20 { id: 'instruct_system_prompt', property: 'system_prompt', isCheckbox: false },
21 { id: 'instruct_system_sequence_prefix', property: 'system_sequence_prefix', isCheckbox: false },26 { id: 'instruct_system_sequence_prefix', property: 'system_sequence_prefix', isCheckbox: false },
22 { id: 'instruct_system_sequence_suffix', property: 'system_sequence_suffix', isCheckbox: false },27 { id: 'instruct_system_sequence_suffix', property: 'system_sequence_suffix', isCheckbox: false },
23 { id: 'instruct_input_sequence', property: 'input_sequence', isCheckbox: false },28 { id: 'instruct_input_sequence', property: 'input_sequence', isCheckbox: false },
@@ -29,9 +34,6 @@ const controls = [
29 { id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },34 { id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },
30 { id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },35 { id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
31 { id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },36 { id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },
32 { id: 'instruct_names', property: 'names', isCheckbox: true },
33 { id: 'instruct_macro', property: 'macro', isCheckbox: true },
34 { id: 'instruct_names_force_groups', property: 'names_force_groups', isCheckbox: true },
35 { id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },37 { id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
36 { id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },38 { id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },
37 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },39 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
@@ -39,6 +41,7 @@ const controls = [
39 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },41 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
40 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },42 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
41 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },43 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
44 { id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },
42 { id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },45 { id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
43];46];
4447
@@ -54,6 +57,15 @@ function migrateInstructModeSettings(settings) {
54 delete settings.separator_sequence;57 delete settings.separator_sequence;
55 }58 }
5659
60 // names, names_force_groups => names_behavior
61 if (settings.names !== undefined) {
62 settings.names_behavior = settings.names
63 ? names_behavior_types.ALWAYS
64 : (settings.names_force_groups ? names_behavior_types.FORCE : names_behavior_types.NONE);
65 delete settings.names;
66 delete settings.names_force_groups;
67 }
68
57 const defaults = {69 const defaults = {
58 input_suffix: '',70 input_suffix: '',
59 system_sequence: '',71 system_sequence: '',
@@ -65,6 +77,7 @@ function migrateInstructModeSettings(settings) {
65 names_force_groups: true,77 names_force_groups: true,
66 skip_examples: false,78 skip_examples: false,
67 system_same_as_user: false,79 system_same_as_user: false,
80 names_behavior: names_behavior_types.FORCE,
68 };81 };
6982
70 for (let key in defaults) {83 for (let key in defaults) {
@@ -94,6 +107,10 @@ export async function loadInstructMode(data) {
94107
95 if (control.isCheckbox) {108 if (control.isCheckbox) {
96 $element.prop('checked', power_user.instruct[control.property]);109 $element.prop('checked', power_user.instruct[control.property]);
110 } else if ($element.is('select')) {
111 const value = power_user.instruct[control.property];
112 $element.val(value);
113 $element.filter(`[value="${value}"]`).prop('checked', true);
97 } else {114 } else {
98 $element.val(power_user.instruct[control.property]);115 $element.val(power_user.instruct[control.property]);
99 }116 }
@@ -119,12 +136,6 @@ export async function loadInstructMode(data) {
119 option.selected = name === power_user.instruct.preset;136 option.selected = name === power_user.instruct.preset;
120 $('#instruct_presets').append(option);137 $('#instruct_presets').append(option);
121 });138 });
122
123 highlightDefaultPreset();
124}
125
126function highlightDefaultPreset() {
127 $('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
128}139}
129140
130/**141/**
@@ -141,13 +152,6 @@ export function selectContextPreset(preset, { quiet = false, isAuto = false } =
141 !quiet && toastr.info(`Context Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);152 !quiet && toastr.info(`Context Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);
142 }153 }
143154
144 // If instruct mode is disabled, enable it, except for default context template
145 if (!power_user.instruct.enabled && preset !== power_user.default_context) {
146 power_user.instruct.enabled = true;
147 $('#instruct_enabled').prop('checked', true).trigger('change');
148 !quiet && toastr.info('Instruct Mode enabled');
149 }
150
151 saveSettingsDebounced();155 saveSettingsDebounced();
152}156}
153157
@@ -217,13 +221,6 @@ export function autoSelectInstructPreset(modelId) {
217 }221 }
218 }222 }
219 }223 }
220
221 if (power_user.instruct.bind_to_context && power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) {
222 if (instruct_presets.some(p => p.name === power_user.default_instruct)) {
223 console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`);
224 $('#instruct_presets').val(power_user.default_instruct).trigger('change');
225 }
226 }
227 }224 }
228225
229 return false;226 return false;
@@ -311,9 +308,9 @@ export const force_output_sequence = {
311 * @returns {string} Formatted instruct mode chat message.308 * @returns {string} Formatted instruct mode chat message.
312 */309 */
313export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {310export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {
314 let includeNames = isNarrator ? false : power_user.instruct.names;311 let includeNames = isNarrator ? false : power_user.instruct.names_behavior === names_behavior_types.ALWAYS;
315312
316 if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {313 if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
317 includeNames = true;314 includeNames = true;
318 }315 }
319316
@@ -416,7 +413,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
416 return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));413 return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
417 }414 }
418415
419 const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);416 const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE);
420417
421 let inputPrefix = power_user.instruct.input_sequence || '';418 let inputPrefix = power_user.instruct.input_sequence || '';
422 let outputPrefix = power_user.instruct.output_sequence || '';419 let outputPrefix = power_user.instruct.output_sequence || '';
@@ -460,7 +457,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
460457
461 for (const example of blockExamples) {458 for (const example of blockExamples) {
462 // If force group/persona names is set, we should override the include names for the user placeholder459 // If force group/persona names is set, we should override the include names for the user placeholder
463 const includeThisName = includeNames || (power_user.instruct.names_force_groups && example.name == 'example_user');460 const includeThisName = includeNames || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
464461
465 const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;462 const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
466 const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;463 const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
@@ -490,7 +487,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
490 * @returns {string} Formatted instruct mode last prompt line.487 * @returns {string} Formatted instruct mode last prompt line.
491 */488 */
492export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {489export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {
493 const includeNames = name && (power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups)) && !(isQuiet && !isQuietToLoud);490 const includeNames = name && (power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud);
494491
495 function getSequence() {492 function getSequence() {
496 // User impersonation prompt493 // User impersonation prompt
@@ -514,6 +511,19 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
514 }511 }
515512
516 let sequence = getSequence() || '';513 let sequence = getSequence() || '';
514 let nameFiller = '';
515
516 // A hack for Mistral's formatting that has a normal output sequence ending with a space
517 if (
518 includeNames &&
519 power_user.instruct.last_output_sequence &&
520 power_user.instruct.output_sequence &&
521 sequence === power_user.instruct.last_output_sequence &&
522 /\s$/.test(power_user.instruct.output_sequence) &&
523 !/\s$/.test(power_user.instruct.last_output_sequence)
524 ) {
525 nameFiller = power_user.instruct.output_sequence.slice(-1);
526 }
517527
518 if (power_user.instruct.macro) {528 if (power_user.instruct.macro) {
519 sequence = substituteParams(sequence, name1, name2);529 sequence = substituteParams(sequence, name1, name2);
@@ -521,7 +531,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
521 }531 }
522532
523 const separator = power_user.instruct.wrap ? '\n' : '';533 const separator = power_user.instruct.wrap ? '\n' : '';
524 let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);534 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
525535
526 // Quiet prompt already has a newline at the end536 // Quiet prompt already has a newline at the end
527 if (isQuiet && separator) {537 if (isQuiet && separator) {
@@ -549,10 +559,6 @@ function selectMatchingContextTemplate(name) {
549 break;559 break;
550 }560 }
551 }561 }
552 if (!foundMatch) {
553 // If no match was found, select default context preset
554 selectContextPreset(power_user.default_context, { isAuto: true });
555 }
556}562}
557563
558/**564/**
@@ -566,9 +572,13 @@ export function replaceInstructMacros(input, env) {
566 if (!input) {572 if (!input) {
567 return '';573 return '';
568 }574 }
575
576 const syspromptMacros = {
577 'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.sysprompt.content),
578 'defaultSystemPrompt|instructSystem|instructSystemPrompt': power_user.sysprompt.content,
579 };
580
569 const instructMacros = {581 const instructMacros = {
570 'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.instruct.system_prompt),
571 'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt,
572 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,582 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,
573 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,583 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,
574 'instructInput|instructUserPrefix': power_user.instruct.input_sequence,584 'instructInput|instructUserPrefix': power_user.instruct.input_sequence,
@@ -591,6 +601,11 @@ export function replaceInstructMacros(input, env) {
591 input = input.replace(regex, power_user.instruct.enabled ? value : '');601 input = input.replace(regex, power_user.instruct.enabled ? value : '');
592 }602 }
593603
604 for (const [placeholder, value] of Object.entries(syspromptMacros)) {
605 const regex = new RegExp(`{{(${placeholder})}}`, 'gi');
606 input = input.replace(regex, power_user.sysprompt.enabled ? value : '');
607 }
608
594 input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);609 input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
595 input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);610 input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);
596611
@@ -598,20 +613,6 @@ export function replaceInstructMacros(input, env) {
598}613}
599614
600jQuery(() => {615jQuery(() => {
601 $('#instruct_set_default').on('click', function () {
602 if (power_user.instruct.preset === power_user.default_instruct) {
603 power_user.default_instruct = null;
604 $(this).removeClass('default');
605 toastr.info('Default instruct template cleared');
606 } else {
607 power_user.default_instruct = power_user.instruct.preset;
608 $(this).addClass('default');
609 toastr.info(`Default instruct template set to ${power_user.default_instruct}`);
610 }
611
612 saveSettingsDebounced();
613 });
614
615 $('#instruct_system_same_as_user').on('input', function () {616 $('#instruct_system_same_as_user').on('input', function () {
616 const state = !!$(this).prop('checked');617 const state = !!$(this).prop('checked');
617 if (state) {618 if (state) {
@@ -640,9 +641,6 @@ jQuery(() => {
640 // When instruct mode gets enabled, select context template matching selected instruct preset641 // When instruct mode gets enabled, select context template matching selected instruct preset
641 if (power_user.instruct.enabled) {642 if (power_user.instruct.enabled) {
642 selectMatchingContextTemplate(power_user.instruct.preset);643 selectMatchingContextTemplate(power_user.instruct.preset);
643 } else {
644 // When instruct mode gets disabled, select default context preset
645 selectContextPreset(power_user.default_context);
646 }644 }
647 });645 });
648646
@@ -668,6 +666,10 @@ jQuery(() => {
668666
669 if (control.isCheckbox) {667 if (control.isCheckbox) {
670 $element.prop('checked', power_user.instruct[control.property]).trigger('input');668 $element.prop('checked', power_user.instruct[control.property]).trigger('input');
669 } else if ($element.is('select')) {
670 const value = power_user.instruct[control.property];
671 $element.val(value);
672 $element.filter(`[value="${value}"]`).prop('checked', true).trigger('input');
671 } else {673 } else {
672 $element.val(power_user.instruct[control.property]);674 $element.val(power_user.instruct[control.property]);
673 $element.trigger('input');675 $element.trigger('input');
@@ -679,7 +681,13 @@ jQuery(() => {
679 // Select matching context template681 // Select matching context template
680 selectMatchingContextTemplate(name);682 selectMatchingContextTemplate(name);
681 }683 }
682
683 highlightDefaultPreset();
684 });684 });
685
686 if (!CSS.supports('field-sizing', 'content')) {
687 $('#InstructSequencesColumn details').on('toggle', function () {
688 if ($(this).prop('open')) {
689 resetScrollHeight($(this).find('textarea'));
690 }
691 });
692 }
685});693});
public/scripts/nai-settings.js+107 -30
@@ -5,9 +5,7 @@ import {
5 novelai_setting_names,5 novelai_setting_names,
6 saveSettingsDebounced,6 saveSettingsDebounced,
7 setGenerationParamsFromPreset,7 setGenerationParamsFromPreset,
8 substituteParams,
9} from '../script.js';8} from '../script.js';
10import { getCfgPrompt } from './cfg-scale.js';
11import { MAX_CONTEXT_DEFAULT, MAX_RESPONSE_DEFAULT, power_user } from './power-user.js';9import { MAX_CONTEXT_DEFAULT, MAX_RESPONSE_DEFAULT, power_user } from './power-user.js';
12import { getTextTokens, tokenizers } from './tokenizers.js';10import { getTextTokens, tokenizers } from './tokenizers.js';
13import { getEventSourceStream } from './sse-stream.js';11import { getEventSourceStream } from './sse-stream.js';
@@ -24,6 +22,7 @@ const maximum_output_length = 150;
24const default_presets = {22const default_presets = {
25 'clio-v1': 'Talker-Chat-Clio',23 'clio-v1': 'Talker-Chat-Clio',
26 'kayra-v1': 'Carefree-Kayra',24 'kayra-v1': 'Carefree-Kayra',
25 'llama-3-erato-v1': 'Erato-Dragonfruit',
27};26};
2827
29export const nai_settings = {28export const nai_settings = {
@@ -38,13 +37,16 @@ export const nai_settings = {
38 top_p: 0.75,37 top_p: 0.75,
39 top_a: 0.08,38 top_a: 0.08,
40 typical_p: 0.975,39 typical_p: 0.975,
40 min_p: 0,
41 math1_temp: 1,
42 math1_quad: 0,
43 math1_quad_entropy_scale: 0,
41 min_length: 1,44 min_length: 1,
42 model_novel: 'clio-v1',45 model_novel: 'clio-v1',
43 preset_settings_novel: 'Talker-Chat-Clio',46 preset_settings_novel: 'Talker-Chat-Clio',
44 streaming_novel: false,47 streaming_novel: false,
45 preamble: default_preamble,48 preamble: default_preamble,
46 prefix: '',49 prefix: '',
47 cfg_uc: '',
48 banned_tokens: '',50 banned_tokens: '',
49 order: default_order,51 order: default_order,
50 logit_bias: [],52 logit_bias: [],
@@ -57,6 +59,19 @@ const nai_tiers = {
57 3: 'Opus',59 3: 'Opus',
58};60};
5961
62const samplers = {
63 temperature: 0,
64 top_k: 1,
65 top_p: 2,
66 tfs: 3,
67 top_a: 4,
68 typical_p: 5,
69 // removed samplers were here
70 mirostat: 8,
71 math1: 9,
72 min_p: 10,
73};
74
60let novel_data = null;75let novel_data = null;
61let badWordsCache = {};76let badWordsCache = {};
62const BIAS_KEY = '#novel_api-settings';77const BIAS_KEY = '#novel_api-settings';
@@ -91,6 +106,37 @@ export function getKayraMaxResponseTokens() {
91 return maximum_output_length;106 return maximum_output_length;
92}107}
93108
109export function convertNovelPreset(data) {
110 if (!data || typeof data !== 'object' || data.presetVersion !== 3 || !data.parameters || typeof data.parameters !== 'object') {
111 return data;
112 }
113
114 return {
115 max_context: 8000,
116 temperature: data.parameters.temperature,
117 max_length: data.parameters.max_length,
118 min_length: data.parameters.min_length,
119 top_k: data.parameters.top_k,
120 top_p: data.parameters.top_p,
121 top_a: data.parameters.top_a,
122 typical_p: data.parameters.typical_p,
123 tail_free_sampling: data.parameters.tail_free_sampling,
124 repetition_penalty: data.parameters.repetition_penalty,
125 repetition_penalty_range: data.parameters.repetition_penalty_range,
126 repetition_penalty_slope: data.parameters.repetition_penalty_slope,
127 repetition_penalty_frequency: data.parameters.repetition_penalty_frequency,
128 repetition_penalty_presence: data.parameters.repetition_penalty_presence,
129 phrase_rep_pen: data.parameters.phrase_rep_pen,
130 mirostat_lr: data.parameters.mirostat_lr,
131 mirostat_tau: data.parameters.mirostat_tau,
132 math1_temp: data.parameters.math1_temp,
133 math1_quad: data.parameters.math1_quad,
134 math1_quad_entropy_scale: data.parameters.math1_quad_entropy_scale,
135 min_p: data.parameters.min_p,
136 order: Array.isArray(data.parameters.order) ? data.parameters.order.filter(s => s.enabled && Object.keys(samplers).includes(s.id)).map(s => samplers[s.id]) : default_order,
137 };
138}
139
94export function getNovelTier() {140export function getNovelTier() {
95 return nai_tiers[novel_data?.tier] ?? 'no_connection';141 return nai_tiers[novel_data?.tier] ?? 'no_connection';
96}142}
@@ -141,16 +187,18 @@ export function loadNovelPreset(preset) {
141 nai_settings.top_a = preset.top_a;187 nai_settings.top_a = preset.top_a;
142 nai_settings.typical_p = preset.typical_p;188 nai_settings.typical_p = preset.typical_p;
143 nai_settings.min_length = preset.min_length;189 nai_settings.min_length = preset.min_length;
144 nai_settings.cfg_scale = preset.cfg_scale;
145 nai_settings.phrase_rep_pen = preset.phrase_rep_pen;190 nai_settings.phrase_rep_pen = preset.phrase_rep_pen;
146 nai_settings.mirostat_lr = preset.mirostat_lr;191 nai_settings.mirostat_lr = preset.mirostat_lr;
147 nai_settings.mirostat_tau = preset.mirostat_tau;192 nai_settings.mirostat_tau = preset.mirostat_tau;
148 nai_settings.prefix = preset.prefix;193 nai_settings.prefix = preset.prefix;
149 nai_settings.cfg_uc = preset.cfg_uc || '';
150 nai_settings.banned_tokens = preset.banned_tokens || '';194 nai_settings.banned_tokens = preset.banned_tokens || '';
151 nai_settings.order = preset.order || default_order;195 nai_settings.order = preset.order || default_order;
152 nai_settings.logit_bias = preset.logit_bias || [];196 nai_settings.logit_bias = preset.logit_bias || [];
153 nai_settings.preamble = preset.preamble || default_preamble;197 nai_settings.preamble = preset.preamble || default_preamble;
198 nai_settings.min_p = preset.min_p || 0;
199 nai_settings.math1_temp = preset.math1_temp || 1;
200 nai_settings.math1_quad = preset.math1_quad || 0;
201 nai_settings.math1_quad_entropy_scale = preset.math1_quad_entropy_scale || 0;
154 loadNovelSettingsUi(nai_settings);202 loadNovelSettingsUi(nai_settings);
155}203}
156204
@@ -178,16 +226,18 @@ export function loadNovelSettings(settings) {
178 nai_settings.typical_p = settings.typical_p;226 nai_settings.typical_p = settings.typical_p;
179 nai_settings.min_length = settings.min_length;227 nai_settings.min_length = settings.min_length;
180 nai_settings.phrase_rep_pen = settings.phrase_rep_pen;228 nai_settings.phrase_rep_pen = settings.phrase_rep_pen;
181 nai_settings.cfg_scale = settings.cfg_scale;
182 nai_settings.mirostat_lr = settings.mirostat_lr;229 nai_settings.mirostat_lr = settings.mirostat_lr;
183 nai_settings.mirostat_tau = settings.mirostat_tau;230 nai_settings.mirostat_tau = settings.mirostat_tau;
184 nai_settings.streaming_novel = !!settings.streaming_novel;231 nai_settings.streaming_novel = !!settings.streaming_novel;
185 nai_settings.preamble = settings.preamble || default_preamble;232 nai_settings.preamble = settings.preamble || default_preamble;
186 nai_settings.prefix = settings.prefix;233 nai_settings.prefix = settings.prefix;
187 nai_settings.cfg_uc = settings.cfg_uc || '';
188 nai_settings.banned_tokens = settings.banned_tokens || '';234 nai_settings.banned_tokens = settings.banned_tokens || '';
189 nai_settings.order = settings.order || default_order;235 nai_settings.order = settings.order || default_order;
190 nai_settings.logit_bias = settings.logit_bias || [];236 nai_settings.logit_bias = settings.logit_bias || [];
237 nai_settings.min_p = settings.min_p || 0;
238 nai_settings.math1_temp = settings.math1_temp || 1;
239 nai_settings.math1_quad = settings.math1_quad || 0;
240 nai_settings.math1_quad_entropy_scale = settings.math1_quad_entropy_scale || 0;
191 loadNovelSettingsUi(nai_settings);241 loadNovelSettingsUi(nai_settings);
192}242}
193243
@@ -214,8 +264,6 @@ function loadNovelSettingsUi(ui_settings) {
214 $('#top_a_counter_novel').val(Number(ui_settings.top_a).toFixed(3));264 $('#top_a_counter_novel').val(Number(ui_settings.top_a).toFixed(3));
215 $('#typical_p_novel').val(ui_settings.typical_p);265 $('#typical_p_novel').val(ui_settings.typical_p);
216 $('#typical_p_counter_novel').val(Number(ui_settings.typical_p).toFixed(3));266 $('#typical_p_counter_novel').val(Number(ui_settings.typical_p).toFixed(3));
217 $('#cfg_scale_novel').val(ui_settings.cfg_scale);
218 $('#cfg_scale_counter_novel').val(Number(ui_settings.cfg_scale).toFixed(2));
219 $('#phrase_rep_pen_novel').val(ui_settings.phrase_rep_pen || 'off');267 $('#phrase_rep_pen_novel').val(ui_settings.phrase_rep_pen || 'off');
220 $('#mirostat_lr_novel').val(ui_settings.mirostat_lr);268 $('#mirostat_lr_novel').val(ui_settings.mirostat_lr);
221 $('#mirostat_lr_counter_novel').val(Number(ui_settings.mirostat_lr).toFixed(2));269 $('#mirostat_lr_counter_novel').val(Number(ui_settings.mirostat_lr).toFixed(2));
@@ -225,8 +273,15 @@ function loadNovelSettingsUi(ui_settings) {
225 $('#min_length_counter_novel').val(Number(ui_settings.min_length).toFixed(0));273 $('#min_length_counter_novel').val(Number(ui_settings.min_length).toFixed(0));
226 $('#nai_preamble_textarea').val(ui_settings.preamble);274 $('#nai_preamble_textarea').val(ui_settings.preamble);
227 $('#nai_prefix').val(ui_settings.prefix || 'vanilla');275 $('#nai_prefix').val(ui_settings.prefix || 'vanilla');
228 $('#nai_cfg_uc').val(ui_settings.cfg_uc || '');
229 $('#nai_banned_tokens').val(ui_settings.banned_tokens || '');276 $('#nai_banned_tokens').val(ui_settings.banned_tokens || '');
277 $('#min_p_novel').val(ui_settings.min_p);
278 $('#min_p_counter_novel').val(Number(ui_settings.min_p).toFixed(3));
279 $('#math1_temp_novel').val(ui_settings.math1_temp);
280 $('#math1_temp_counter_novel').val(Number(ui_settings.math1_temp).toFixed(2));
281 $('#math1_quad_novel').val(ui_settings.math1_quad);
282 $('#math1_quad_counter_novel').val(Number(ui_settings.math1_quad).toFixed(2));
283 $('#math1_quad_entropy_scale_novel').val(ui_settings.math1_quad_entropy_scale);
284 $('#math1_quad_entropy_scale_counter_novel').val(Number(ui_settings.math1_quad_entropy_scale).toFixed(2));
230285
231 $('#streaming_novel').prop('checked', ui_settings.streaming_novel);286 $('#streaming_novel').prop('checked', ui_settings.streaming_novel);
232 sortItemsByOrder(ui_settings.order);287 sortItemsByOrder(ui_settings.order);
@@ -313,29 +368,41 @@ const sliders = [
313 setValue: (val) => { nai_settings.mirostat_lr = Number(val).toFixed(2); },368 setValue: (val) => { nai_settings.mirostat_lr = Number(val).toFixed(2); },
314 },369 },
315 {370 {
316 sliderId: '#cfg_scale_novel',
317 counterId: '#cfg_scale_counter_novel',
318 format: (val) => `${val}`,
319 setValue: (val) => { nai_settings.cfg_scale = Number(val).toFixed(2); },
320 },
321 {
322 sliderId: '#min_length_novel',371 sliderId: '#min_length_novel',
323 counterId: '#min_length_counter_novel',372 counterId: '#min_length_counter_novel',
324 format: (val) => `${val}`,373 format: (val) => `${val}`,
325 setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },374 setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },
326 },375 },
327 {376 {
328 sliderId: '#nai_cfg_uc',
329 counterId: '#nai_cfg_uc_counter',
330 format: (val) => val,
331 setValue: (val) => { nai_settings.cfg_uc = val; },
332 },
333 {
334 sliderId: '#nai_banned_tokens',377 sliderId: '#nai_banned_tokens',
335 counterId: '#nai_banned_tokens_counter',378 counterId: '#nai_banned_tokens_counter',
336 format: (val) => val,379 format: (val) => val,
337 setValue: (val) => { nai_settings.banned_tokens = val; },380 setValue: (val) => { nai_settings.banned_tokens = val; },
338 },381 },
382 {
383 sliderId: '#min_p_novel',
384 counterId: '#min_p_counter_novel',
385 format: (val) => Number(val).toFixed(3),
386 setValue: (val) => { nai_settings.min_p = Number(val); },
387 },
388 {
389 sliderId: '#math1_temp_novel',
390 counterId: '#math1_temp_counter_novel',
391 format: (val) => Number(val).toFixed(2),
392 setValue: (val) => { nai_settings.math1_temp = Number(val); },
393 },
394 {
395 sliderId: '#math1_quad_novel',
396 counterId: '#math1_quad_counter_novel',
397 format: (val) => Number(val).toFixed(2),
398 setValue: (val) => { nai_settings.math1_quad = Number(val); },
399 },
400 {
401 sliderId: '#math1_quad_entropy_scale_novel',
402 counterId: '#math1_quad_entropy_scale_counter_novel',
403 format: (val) => Number(val).toFixed(2),
404 setValue: (val) => { nai_settings.math1_quad_entropy_scale = Number(val); },
405 },
339];406];
340407
341function getBadWordIds(banned_tokens, tokenizerType) {408function getBadWordIds(banned_tokens, tokenizerType) {
@@ -423,11 +490,8 @@ function getBadWordPermutations(text) {
423 return result.filter(onlyUnique);490 return result.filter(onlyUnique);
424}491}
425492
426export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, cfgValues, type) {493export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) {
427 console.debug('NovelAI generation data for', type);494 console.debug('NovelAI generation data for', type);
428 if (cfgValues && cfgValues.guidanceScale && cfgValues.guidanceScale?.value !== 1) {
429 cfgValues.negativePrompt = (getCfgPrompt(cfgValues.guidanceScale, true))?.value;
430 }
431495
432 const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);496 const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);
433 const stopSequences = (tokenizerType !== tokenizers.NONE)497 const stopSequences = (tokenizerType !== tokenizers.NONE)
@@ -451,7 +515,14 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
451 console.log(finalPrompt);515 console.log(finalPrompt);
452 }516 }
453517
454 const adjustedMaxLength = nai_settings.model_novel.includes('kayra') ? getKayraMaxResponseTokens() : maximum_output_length;518 const isKayra = nai_settings.model_novel.includes('kayra');
519 const isErato = nai_settings.model_novel.includes('erato');
520
521 if (isErato) {
522 finalPrompt = '<|startoftext|>' + finalPrompt;
523 }
524
525 const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length;
455526
456 return {527 return {
457 'input': finalPrompt,528 'input': finalPrompt,
@@ -469,11 +540,13 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
469 'top_a': Number(nai_settings.top_a),540 'top_a': Number(nai_settings.top_a),
470 'top_p': Number(nai_settings.top_p),541 'top_p': Number(nai_settings.top_p),
471 'top_k': Number(nai_settings.top_k),542 'top_k': Number(nai_settings.top_k),
543 'min_p': Number(nai_settings.min_p),
544 'math1_temp': Number(nai_settings.math1_temp),
545 'math1_quad': Number(nai_settings.math1_quad),
546 'math1_quad_entropy_scale': Number(nai_settings.math1_quad_entropy_scale),
472 'typical_p': Number(nai_settings.typical_p),547 'typical_p': Number(nai_settings.typical_p),
473 'mirostat_lr': Number(nai_settings.mirostat_lr),548 'mirostat_lr': Number(nai_settings.mirostat_lr),
474 'mirostat_tau': Number(nai_settings.mirostat_tau),549 'mirostat_tau': Number(nai_settings.mirostat_tau),
475 'cfg_scale': cfgValues?.guidanceScale?.value ?? Number(nai_settings.cfg_scale),
476 'cfg_uc': cfgValues?.negativePrompt ?? substituteParams(nai_settings.cfg_uc) ?? '',
477 'phrase_rep_pen': nai_settings.phrase_rep_pen,550 'phrase_rep_pen': nai_settings.phrase_rep_pen,
478 'stop_sequences': stopSequences,551 'stop_sequences': stopSequences,
479 'bad_words_ids': badWordIds,552 'bad_words_ids': badWordIds,
@@ -492,7 +565,8 @@ function selectPrefix(selected_prefix, finalPrompt) {
492 let useInstruct = false;565 let useInstruct = false;
493 const clio = nai_settings.model_novel.includes('clio');566 const clio = nai_settings.model_novel.includes('clio');
494 const kayra = nai_settings.model_novel.includes('kayra');567 const kayra = nai_settings.model_novel.includes('kayra');
495 const isNewModel = clio || kayra;568 const erato = nai_settings.model_novel.includes('erato');
569 const isNewModel = clio || kayra || erato;
496570
497 if (isNewModel) {571 if (isNewModel) {
498 // NovelAI claims they scan backwards 1000 characters (not tokens!) to look for instruct brackets. That's really short.572 // NovelAI claims they scan backwards 1000 characters (not tokens!) to look for instruct brackets. That's really short.
@@ -511,6 +585,9 @@ function getTokenizerTypeForModel(model) {
511 if (model.includes('kayra')) {585 if (model.includes('kayra')) {
512 return tokenizers.NERD2;586 return tokenizers.NERD2;
513 }587 }
588 if (model.includes('erato')) {
589 return tokenizers.LLAMA3;
590 }
514 return tokenizers.NONE;591 return tokenizers.NONE;
515}592}
516593
public/scripts/openai.js+40 -13
@@ -431,7 +431,7 @@ function setOpenAIMessages(chat) {
431 case character_names_behavior.NONE:431 case character_names_behavior.NONE:
432 break;432 break;
433 case character_names_behavior.DEFAULT:433 case character_names_behavior.DEFAULT:
434 if (selected_group || (chat[j].force_avatar && chat[j].name !== name1 && chat[j].extra?.type !== system_message_types.NARRATOR)) {434 if ((selected_group && chat[j].name !== name1) || (chat[j].force_avatar && chat[j].name !== name1 && chat[j].extra?.type !== system_message_types.NARRATOR)) {
435 content = `${chat[j].name}: ${content}`;435 content = `${chat[j].name}: ${content}`;
436 }436 }
437 break;437 break;
@@ -855,6 +855,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
855 }855 }
856856
857 const prompt = prompts.get(source);857 const prompt = prompts.get(source);
858
859 if (prompt.injection_position === INJECTION_POSITION.ABSOLUTE) {
860 promptManager.log(`Skipping prompt ${source} because it is an absolute prompt`);
861 return;
862 }
863
858 const index = target ? prompts.index(target) : prompts.index(source);864 const index = target ? prompts.index(target) : prompts.index(source);
859 const collection = new MessageCollection(source);865 const collection = new MessageCollection(source);
860 collection.add(Message.fromPrompt(prompt));866 collection.add(Message.fromPrompt(prompt));
@@ -899,8 +905,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
899 acc.push(prompt.identifier);905 acc.push(prompt.identifier);
900 return acc;906 return acc;
901 }, []);907 }, []);
902 const userAbsolutePrompts = prompts.collection908 const absolutePrompts = prompts.collection
903 .filter((prompt) => false === prompt.system_prompt && prompt.injection_position === INJECTION_POSITION.ABSOLUTE)909 .filter((prompt) => prompt.injection_position === INJECTION_POSITION.ABSOLUTE)
904 .reduce((acc, prompt) => {910 .reduce((acc, prompt) => {
905 acc.push(prompt);911 acc.push(prompt);
906 return acc;912 return acc;
@@ -965,7 +971,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
965 }971 }
966972
967 // Add in-chat injections973 // Add in-chat injections
968 messages = populationInjectionPrompts(userAbsolutePrompts, messages);974 messages = populationInjectionPrompts(absolutePrompts, messages);
969975
970 // Decide whether dialogue examples should always be added976 // Decide whether dialogue examples should always be added
971 if (power_user.pin_examples) {977 if (power_user.pin_examples) {
@@ -1102,6 +1108,18 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
11021108
1103 // Merge system prompts with prompt manager prompts1109 // Merge system prompts with prompt manager prompts
1104 systemPrompts.forEach(prompt => {1110 systemPrompts.forEach(prompt => {
1111 const collectionPrompt = prompts.get(prompt.identifier);
1112
1113 // Apply system prompt role/depth overrides if they set in the prompt manager
1114 if (collectionPrompt) {
1115 // In-Chat / Relative
1116 prompt.injection_position = collectionPrompt.injection_position ?? prompt.injection_position;
1117 // Depth for In-Chat
1118 prompt.injection_depth = collectionPrompt.injection_depth ?? prompt.injection_depth;
1119 // Role (system, user, assistant)
1120 prompt.role = collectionPrompt.role ?? prompt.role;
1121 }
1122
1105 const newPrompt = promptManager.preparePrompt(prompt);1123 const newPrompt = promptManager.preparePrompt(prompt);
1106 const markerIndex = prompts.index(prompt.identifier);1124 const markerIndex = prompts.index(prompt.identifier);
11071125
@@ -2370,7 +2388,7 @@ class Message {
2370 * @returns {Promise<string>} Compressed image as a Data URL.2388 * @returns {Promise<string>} Compressed image as a Data URL.
2371 */2389 */
2372 async compressImage(image) {2390 async compressImage(image) {
2373 if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) {2391 if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {
2374 const sizeThreshold = 2 * 1024 * 1024;2392 const sizeThreshold = 2 * 1024 * 1024;
2375 const dataSize = image.length * 0.75;2393 const dataSize = image.length * 0.75;
2376 const maxSide = 1024;2394 const maxSide = 1024;
@@ -2747,7 +2765,7 @@ export class ChatCompletion {
2747 const message = { role: item.role, content: item.content, ...(item.name ? { name: item.name } : {}) };2765 const message = { role: item.role, content: item.content, ...(item.name ? { name: item.name } : {}) };
2748 chat.push(message);2766 chat.push(message);
2749 } else {2767 } else {
2750 console.warn('Invalid message in collection', item);2768 this.log(`Skipping invalid or empty message in collection: ${JSON.stringify(item)}`);
2751 }2769 }
2752 }2770 }
2753 return chat;2771 return chat;
@@ -3216,12 +3234,12 @@ async function getStatusOpen() {
32163234
3217 const responseData = await response.json();3235 const responseData = await response.json();
32183236
3219 if (!('error' in responseData)) {
3220 setOnlineStatus('Valid');
3221 }
3222 if ('data' in responseData && Array.isArray(responseData.data)) {3237 if ('data' in responseData && Array.isArray(responseData.data)) {
3223 saveModelList(responseData.data);3238 saveModelList(responseData.data);
3224 }3239 }
3240 if (!('error' in responseData)) {
3241 setOnlineStatus('Valid');
3242 }
3225 } catch (error) {3243 } catch (error) {
3226 console.error(error);3244 console.error(error);
32273245
@@ -3986,6 +4004,8 @@ async function onModelChange() {
3986 $('#openai_max_context').attr('max', max_2mil);4004 $('#openai_max_context').attr('max', max_2mil);
3987 } else if (value.includes('gemini-1.5-pro')) {4005 } else if (value.includes('gemini-1.5-pro')) {
3988 $('#openai_max_context').attr('max', max_2mil);4006 $('#openai_max_context').attr('max', max_2mil);
4007 } else if (value.match('gemini-1.5-flash-002')) {
4008 $('#openai_max_context').attr('max', max_2mil);
3989 } else if (value.includes('gemini-1.5-flash')) {4009 } else if (value.includes('gemini-1.5-flash')) {
3990 $('#openai_max_context').attr('max', max_1mil);4010 $('#openai_max_context').attr('max', max_1mil);
3991 } else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {4011 } else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {
@@ -4097,6 +4117,8 @@ async function onModelChange() {
4097 $('#openai_max_context').attr('max', max_128k);4117 $('#openai_max_context').attr('max', max_128k);
4098 } else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {4118 } else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
4099 $('#openai_max_context').attr('max', max_64k);4119 $('#openai_max_context').attr('max', max_64k);
4120 } else if (oai_settings.mistralai_model.includes('pixtral')) {
4121 $('#openai_max_context').attr('max', max_128k);
4100 } else {4122 } else {
4101 $('#openai_max_context').attr('max', max_32k);4123 $('#openai_max_context').attr('max', max_32k);
4102 }4124 }
@@ -4630,12 +4652,15 @@ export function isImageInliningSupported() {
4630 'gemini-1.5-flash',4652 'gemini-1.5-flash',
4631 'gemini-1.5-flash-latest',4653 'gemini-1.5-flash-latest',
4632 'gemini-1.5-flash-001',4654 'gemini-1.5-flash-001',
4655 'gemini-1.5-flash-002',
4633 'gemini-1.5-flash-exp-0827',4656 'gemini-1.5-flash-exp-0827',
4634 'gemini-1.5-flash-8b-exp-0827',4657 'gemini-1.5-flash-8b-exp-0827',
4658 'gemini-1.5-flash-8b-exp-0924',
4635 'gemini-1.0-pro-vision-latest',4659 'gemini-1.0-pro-vision-latest',
4636 'gemini-1.5-pro',4660 'gemini-1.5-pro',
4637 'gemini-1.5-pro-latest',4661 'gemini-1.5-pro-latest',
4638 'gemini-1.5-pro-001',4662 'gemini-1.5-pro-001',
4663 'gemini-1.5-pro-002',
4639 'gemini-1.5-pro-exp-0801',4664 'gemini-1.5-pro-exp-0801',
4640 'gemini-1.5-pro-exp-0827',4665 'gemini-1.5-pro-exp-0827',
4641 'gemini-pro-vision',4666 'gemini-pro-vision',
@@ -4646,6 +4671,8 @@ export function isImageInliningSupported() {
4646 'gpt-4o-mini',4671 'gpt-4o-mini',
4647 'chatgpt-4o-latest',4672 'chatgpt-4o-latest',
4648 'yi-vision',4673 'yi-vision',
4674 'pixtral-latest',
4675 'pixtral-12b-2409',
4649 ];4676 ];
46504677
4651 switch (oai_settings.chat_completion_source) {4678 switch (oai_settings.chat_completion_source) {
@@ -4661,6 +4688,8 @@ export function isImageInliningSupported() {
4661 return true;4688 return true;
4662 case chat_completion_sources.ZEROONEAI:4689 case chat_completion_sources.ZEROONEAI:
4663 return visionSupportedModels.some(model => oai_settings.zerooneai_model.includes(model));4690 return visionSupportedModels.some(model => oai_settings.zerooneai_model.includes(model));
4691 case chat_completion_sources.MISTRALAI:
4692 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
4664 default:4693 default:
4665 return false;4694 return false;
4666 }4695 }
@@ -4789,7 +4818,7 @@ function runProxyCallback(_, value) {
4789 return foundName;4818 return foundName;
4790}4819}
47914820
4792export function initOpenai() {4821export function initOpenAI() {
4793 SlashCommandParser.addCommandObject(SlashCommand.fromProps({4822 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4794 name: 'proxy',4823 name: 'proxy',
4795 callback: runProxyCallback,4824 callback: runProxyCallback,
@@ -4805,9 +4834,7 @@ export function initOpenai() {
4805 ],4834 ],
4806 helpString: 'Sets a proxy preset by name.',4835 helpString: 'Sets a proxy preset by name.',
4807 }));4836 }));
4808}
48094837
4810$(document).ready(async function () {
4811 $('#test_api_button').on('click', testApiConnection);4838 $('#test_api_button').on('click', testApiConnection);
48124839
4813 $('#scale-alt').on('change', function () {4840 $('#scale-alt').on('change', function () {
@@ -5266,4 +5293,4 @@ $(document).ready(async function () {
5266 $('#openai_proxy_password_show').on('click', onProxyPasswordShowClick);5293 $('#openai_proxy_password_show').on('click', onProxyPasswordShowClick);
5267 $('#customize_additional_parameters').on('click', onCustomizeParametersClick);5294 $('#customize_additional_parameters').on('click', onCustomizeParametersClick);
5268 $('#openai_proxy_preset').on('change', onProxyPresetChange);5295 $('#openai_proxy_preset').on('change', onProxyPresetChange);
5269});5296}
public/scripts/personas.js+3 -3
@@ -2,7 +2,7 @@ import {
2 characters,2 characters,
3 chat,3 chat,
4 chat_metadata,4 chat_metadata,
5 default_avatar,5 default_user_avatar,
6 eventSource,6 eventSource,
7 event_types,7 event_types,
8 getRequestHeaders,8 getRequestHeaders,
@@ -357,7 +357,7 @@ async function createDummyPersona() {
357 // Date + name (only ASCII) to make it unique357 // Date + name (only ASCII) to make it unique
358 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;358 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
359 initPersona(avatarId, personaName, '');359 initPersona(avatarId, personaName, '');
360 await uploadUserAvatar(default_avatar, avatarId);360 await uploadUserAvatar(default_user_avatar, avatarId);
361}361}
362362
363/**363/**
@@ -944,7 +944,7 @@ async function onPersonasRestoreInput(e) {
944 // If the avatar is missing, upload it944 // If the avatar is missing, upload it
945 if (!avatarsList.includes(key)) {945 if (!avatarsList.includes(key)) {
946 warnings.push(`Persona image "${key}" (${value}) is missing, uploading default avatar`);946 warnings.push(`Persona image "${key}" (${value}) is missing, uploading default avatar`);
947 await uploadUserAvatar(default_avatar, key);947 await uploadUserAvatar(default_user_avatar, key);
948 }948 }
949 }949 }
950950
public/scripts/power-user.js+10 -42
@@ -32,6 +32,7 @@ import {
32import {32import {
33 instruct_presets,33 instruct_presets,
34 loadInstructMode,34 loadInstructMode,
35 names_behavior_types,
35 selectInstructPreset,36 selectInstructPreset,
36} from './instruct-mode.js';37} from './instruct-mode.js';
3738
@@ -49,6 +50,7 @@ import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/Auto
49import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';50import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
50import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';51import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
51import { POPUP_TYPE, callGenericPopup } from './popup.js';52import { POPUP_TYPE, callGenericPopup } from './popup.js';
53import { loadSystemPrompts } from './sysprompt.js';
5254
53export {55export {
54 loadPowerUserSettings,56 loadPowerUserSettings,
@@ -113,7 +115,6 @@ let power_user = {
113 pin_examples: false,115 pin_examples: false,
114 strip_examples: false,116 strip_examples: false,
115 trim_sentences: false,117 trim_sentences: false,
116 include_newline: false,
117 always_force_name2: false,118 always_force_name2: false,
118 user_prompt_bias: '',119 user_prompt_bias: '',
119 show_user_prompt_bias: true,120 show_user_prompt_bias: true,
@@ -205,11 +206,9 @@ let power_user = {
205 disable_group_trimming: false,206 disable_group_trimming: false,
206 single_line: false,207 single_line: false,
207208
208 default_instruct: '',
209 instruct: {209 instruct: {
210 enabled: false,210 enabled: false,
211 preset: 'Alpaca',211 preset: 'Alpaca',
212 system_prompt: 'Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}\'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n',
213 input_sequence: '### Instruction:',212 input_sequence: '### Instruction:',
214 input_suffix: '',213 input_suffix: '',
215 output_sequence: '### Response:',214 output_sequence: '### Response:',
@@ -224,8 +223,7 @@ let power_user = {
224 stop_sequence: '',223 stop_sequence: '',
225 wrap: true,224 wrap: true,
226 macro: true,225 macro: true,
227 names: false,226 names_behavior: names_behavior_types.FORCE,
228 names_force_groups: true,
229 activation_regex: '',227 activation_regex: '',
230 bind_to_context: false,228 bind_to_context: false,
231 user_alignment_message: '',229 user_alignment_message: '',
@@ -234,7 +232,6 @@ let power_user = {
234 separator_sequence: '',232 separator_sequence: '',
235 },233 },
236234
237 default_context: 'Default',
238 context: {235 context: {
239 preset: 'Default',236 preset: 'Default',
240 story_string: defaultStoryString,237 story_string: defaultStoryString,
@@ -245,6 +242,12 @@ let power_user = {
245 names_as_stop_strings: true,242 names_as_stop_strings: true,
246 },243 },
247244
245 sysprompt: {
246 enabled: true,
247 name: 'Neutral - Chat',
248 content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',
249 },
250
248 personas: {},251 personas: {},
249 default_persona: null,252 default_persona: null,
250 persona_descriptions: {},253 persona_descriptions: {},
@@ -317,7 +320,6 @@ const contextControls = [
317 // Existing power user settings320 // Existing power user settings
318 { id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },321 { id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },
319 { id: 'trim_sentences_checkbox', property: 'trim_sentences', isCheckbox: true, isGlobalSetting: true, defaultValue: false },322 { id: 'trim_sentences_checkbox', property: 'trim_sentences', isCheckbox: true, isGlobalSetting: true, defaultValue: false },
320 { id: 'include_newline_checkbox', property: 'include_newline', isCheckbox: true, isGlobalSetting: true, defaultValue: false },
321 { id: 'single_line', property: 'single_line', isCheckbox: true, isGlobalSetting: true, defaultValue: false },323 { id: 'single_line', property: 'single_line', isCheckbox: true, isGlobalSetting: true, defaultValue: false },
322];324];
323325
@@ -1487,7 +1489,6 @@ async function loadPowerUserSettings(settings, data) {
1487 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);1489 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
1488 $('#always-force-name2-checkbox').prop('checked', power_user.always_force_name2);1490 $('#always-force-name2-checkbox').prop('checked', power_user.always_force_name2);
1489 $('#trim_sentences_checkbox').prop('checked', power_user.trim_sentences);1491 $('#trim_sentences_checkbox').prop('checked', power_user.trim_sentences);
1490 $('#include_newline_checkbox').prop('checked', power_user.include_newline);
1491 $('#render_formulas').prop('checked', power_user.render_formulas);1492 $('#render_formulas').prop('checked', power_user.render_formulas);
1492 $('#disable_group_trimming').prop('checked', power_user.disable_group_trimming);1493 $('#disable_group_trimming').prop('checked', power_user.disable_group_trimming);
1493 $('#markdown_escape_strings').val(power_user.markdown_escape_strings);1494 $('#markdown_escape_strings').val(power_user.markdown_escape_strings);
@@ -1598,6 +1599,7 @@ async function loadPowerUserSettings(settings, data) {
1598 reloadMarkdownProcessor(power_user.render_formulas);1599 reloadMarkdownProcessor(power_user.render_formulas);
1599 await loadInstructMode(data);1600 await loadInstructMode(data);
1600 await loadContextSettings();1601 await loadContextSettings();
1602 await loadSystemPrompts(data);
1601 loadMaxContextUnlocked();1603 loadMaxContextUnlocked();
1602 switchWaifuMode();1604 switchWaifuMode();
1603 switchSpoilerMode();1605 switchSpoilerMode();
@@ -1817,29 +1819,8 @@ async function loadContextSettings() {
1817 }1819 }
1818 }1820 }
18191821
1820 highlightDefaultContext();
1821 saveSettingsDebounced();1822 saveSettingsDebounced();
1822 });1823 });
1823
1824 $('#context_set_default').on('click', function () {
1825 if (power_user.context.preset !== power_user.default_context) {
1826 power_user.default_context = power_user.context.preset;
1827 $(this).addClass('default');
1828 toastr.info(`Default context template set to ${power_user.default_context}`);
1829
1830 highlightDefaultContext();
1831
1832 saveSettingsDebounced();
1833 }
1834 });
1835
1836 highlightDefaultContext();
1837}
1838
1839function highlightDefaultContext() {
1840 $('#context_set_default').toggleClass('default', power_user.default_context === power_user.context.preset);
1841 $('#context_set_default').toggleClass('disabled', power_user.default_context === power_user.context.preset);
1842 $('#context_delete_preset').toggleClass('disabled', power_user.default_context === power_user.context.preset);
1843}1824}
18441825
1845/**1826/**
@@ -3069,19 +3050,6 @@ $(document).ready(() => {
3069 // if trim sentences is unchecked, include newline must be unchecked3050 // if trim sentences is unchecked, include newline must be unchecked
3070 $('#trim_sentences_checkbox').change(function () {3051 $('#trim_sentences_checkbox').change(function () {
3071 power_user.trim_sentences = !!$(this).prop('checked');3052 power_user.trim_sentences = !!$(this).prop('checked');
3072 if (!$(this).prop('checked')) {
3073 $('#include_newline_checkbox').prop('checked', false);
3074 power_user.include_newline = false;
3075 }
3076 saveSettingsDebounced();
3077 });
3078
3079 $('#include_newline_checkbox').change(function () {
3080 power_user.include_newline = !!$(this).prop('checked');
3081 if ($(this).prop('checked')) {
3082 $('#trim_sentences_checkbox').prop('checked', true);
3083 power_user.trim_sentences = true;
3084 }
3085 saveSettingsDebounced();3053 saveSettingsDebounced();
3086 });3054 });
30873055
public/scripts/preset-manager.js+264 -9
@@ -18,13 +18,16 @@ import {
18import { groups, selected_group } from './group-chats.js';18import { groups, selected_group } from './group-chats.js';
19import { instruct_presets } from './instruct-mode.js';19import { instruct_presets } from './instruct-mode.js';
20import { kai_settings } from './kai-settings.js';20import { kai_settings } from './kai-settings.js';
21import { Popup } from './popup.js';21import { convertNovelPreset } from './nai-settings.js';
22import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
22import { context_presets, getContextSettings, power_user } from './power-user.js';23import { context_presets, getContextSettings, power_user } from './power-user.js';
23import { SlashCommand } from './slash-commands/SlashCommand.js';24import { SlashCommand } from './slash-commands/SlashCommand.js';
24import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';25import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
25import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';26import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
26import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';27import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
27import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';28import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
29import { checkForSystemPromptInInstructTemplate, system_prompts } from './sysprompt.js';
30import { renderTemplateAsync } from './templates.js';
28import {31import {
29 textgenerationwebui_preset_names,32 textgenerationwebui_preset_names,
30 textgenerationwebui_presets,33 textgenerationwebui_presets,
@@ -71,7 +74,7 @@ function autoSelectPreset() {
71 * @param {string} apiId API id74 * @param {string} apiId API id
72 * @returns {PresetManager} Preset manager75 * @returns {PresetManager} Preset manager
73 */76 */
74function getPresetManager(apiId = '') {77export function getPresetManager(apiId = '') {
75 if (!apiId) {78 if (!apiId) {
76 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;79 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
77 }80 }
@@ -102,6 +105,215 @@ class PresetManager {
102 this.apiId = apiId;105 this.apiId = apiId;
103 }106 }
104107
108 static masterSections = {
109 'instruct': {
110 name: 'Instruct Template',
111 getData: () => {
112 const manager = getPresetManager('instruct');
113 const name = manager.getSelectedPresetName();
114 return manager.getPresetSettings(name);
115 },
116 setData: (data) => {
117 const manager = getPresetManager('instruct');
118 const name = data.name;
119 return manager.savePreset(name, data);
120 },
121 isValid: (data) => PresetManager.isPossiblyInstructData(data),
122 },
123 'context': {
124 name: 'Context Template',
125 getData: () => {
126 const manager = getPresetManager('context');
127 const name = manager.getSelectedPresetName();
128 return manager.getPresetSettings(name);
129 },
130 setData: (data) => {
131 const manager = getPresetManager('context');
132 const name = data.name;
133 return manager.savePreset(name, data);
134 },
135 isValid: (data) => PresetManager.isPossiblyContextData(data),
136 },
137 'sysprompt': {
138 name: 'System Prompt',
139 getData: () => {
140 const manager = getPresetManager('sysprompt');
141 const name = manager.getSelectedPresetName();
142 return manager.getPresetSettings(name);
143 },
144 setData: (data) => {
145 const manager = getPresetManager('sysprompt');
146 const name = data.name;
147 return manager.savePreset(name, data);
148 },
149 isValid: (data) => PresetManager.isPossiblySystemPromptData(data),
150 },
151 'preset': {
152 name: 'Text Completion Preset',
153 getData: () => {
154 const manager = getPresetManager('textgenerationwebui');
155 const name = manager.getSelectedPresetName();
156 const data = manager.getPresetSettings(name);
157 data['name'] = name;
158 return data;
159 },
160 setData: (data) => {
161 const manager = getPresetManager('textgenerationwebui');
162 const name = data.name;
163 return manager.savePreset(name, data);
164 },
165 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
166 },
167 };
168
169 static isPossiblyInstructData(data) {
170 const instructProps = ['name', 'input_sequence', 'output_sequence'];
171 return data && instructProps.every(prop => Object.keys(data).includes(prop));
172 }
173
174 static isPossiblyContextData(data) {
175 const contextProps = ['name', 'story_string'];
176 return data && contextProps.every(prop => Object.keys(data).includes(prop));
177 }
178
179 static isPossiblySystemPromptData(data) {
180 const sysPromptProps = ['name', 'content'];
181 return data && sysPromptProps.every(prop => Object.keys(data).includes(prop));
182 }
183
184 static isPossiblyTextCompletionData(data) {
185 const textCompletionProps = ['temp', 'top_k', 'top_p', 'rep_pen'];
186 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
187 }
188
189 /**
190 * Imports master settings from JSON data.
191 * @param {object} data Data to import
192 * @param {string} fileName File name
193 * @returns {Promise<void>}
194 */
195 static async performMasterImport(data, fileName) {
196 if (!data || typeof data !== 'object') {
197 toastr.error('Invalid data provided for master import');
198 return;
199 }
200
201 // Check for legacy file imports
202 // 1. Instruct Template
203 if (this.isPossiblyInstructData(data)) {
204 toastr.info('Importing instruct template...', 'Instruct template detected');
205 return await getPresetManager('instruct').savePreset(data.name, data);
206 }
207
208 // 2. Context Template
209 if (this.isPossiblyContextData(data)) {
210 toastr.info('Importing as context template...', 'Context template detected');
211 return await getPresetManager('context').savePreset(data.name, data);
212 }
213
214 // 3. System Prompt
215 if (this.isPossiblySystemPromptData(data)) {
216 toastr.info('Importing as system prompt...', 'System prompt detected');
217 return await getPresetManager('sysprompt').savePreset(data.name, data);
218 }
219
220 // 4. Text Completion settings
221 if (this.isPossiblyTextCompletionData(data)) {
222 toastr.info('Importing as settings preset...', 'Text Completion settings detected');
223 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
224 }
225
226 const validSections = [];
227 for (const [key, section] of Object.entries(this.masterSections)) {
228 if (key in data && section.isValid(data[key])) {
229 validSections.push(key);
230 }
231 }
232
233 if (validSections.length === 0) {
234 toastr.error('No valid sections found in imported data');
235 return;
236 }
237
238 const sectionNames = validSections.reduce((acc, key) => {
239 acc[key] = { key: key, name: this.masterSections[key].name, preset: data[key]?.name || '' };
240 return acc;
241 }, {});
242
243 const html = $(await renderTemplateAsync('masterImport', { sections: sectionNames }));
244 const popup = new Popup(html, POPUP_TYPE.CONFIRM, '', {
245 okButton: 'Import',
246 cancelButton: 'Cancel',
247 });
248
249 const result = await popup.show();
250
251 // Import cancelled
252 if (result !== POPUP_RESULT.AFFIRMATIVE) {
253 return;
254 }
255
256 const importedSections = [];
257 const confirmedSections = html.find('input:checked').map((_, el) => el instanceof HTMLInputElement && el.value).get();
258
259 if (confirmedSections.length === 0) {
260 toastr.info('No sections selected for import');
261 return;
262 }
263
264 for (const section of confirmedSections) {
265 const sectionData = data[section];
266 const masterSection = this.masterSections[section];
267 if (sectionData && masterSection) {
268 await masterSection.setData(sectionData);
269 importedSections.push(masterSection.name);
270 }
271 }
272
273 toastr.success(`Imported ${importedSections.length} settings: ${importedSections.join(', ')}`);
274 }
275
276 /**
277 * Exports master settings to JSON data.
278 * @returns {Promise<string>} JSON data
279 */
280 static async performMasterExport() {
281 const sectionNames = Object.entries(this.masterSections).reduce((acc, [key, section]) => {
282 acc[key] = { key: key, name: section.name, checked: key !== 'preset' };
283 return acc;
284 }, {});
285 const html = $(await renderTemplateAsync('masterExport', { sections: sectionNames }));
286
287 const popup = new Popup(html, POPUP_TYPE.CONFIRM, '', {
288 okButton: 'Export',
289 cancelButton: 'Cancel',
290 });
291
292 const result = await popup.show();
293
294 // Export cancelled
295 if (result !== POPUP_RESULT.AFFIRMATIVE) {
296 return;
297 }
298
299 const confirmedSections = html.find('input:checked').map((_, el) => el instanceof HTMLInputElement && el.value).get();
300 const data = {};
301
302 if (confirmedSections.length === 0) {
303 toastr.info('No sections selected for export');
304 return;
305 }
306
307 for (const section of confirmedSections) {
308 const masterSection = this.masterSections[section];
309 if (masterSection) {
310 data[section] = masterSection.getData();
311 }
312 }
313
314 return JSON.stringify(data, null, 4);
315 }
316
105 /**317 /**
106 * Gets all preset names.318 * Gets all preset names.
107 * @returns {string[]} List of preset names319 * @returns {string[]} List of preset names
@@ -182,6 +394,14 @@ class PresetManager {
182 }394 }
183395
184 async savePreset(name, settings) {396 async savePreset(name, settings) {
397 if (this.apiId === 'instruct' && settings) {
398 await checkForSystemPromptInInstructTemplate(name, settings);
399 }
400
401 if (this.apiId === 'novel' && settings) {
402 settings = convertNovelPreset(settings);
403 }
404
185 const preset = settings ?? this.getPresetSettings(name);405 const preset = settings ?? this.getPresetSettings(name);
186406
187 const response = await fetch('/api/presets/save', {407 const response = await fetch('/api/presets/save', {
@@ -228,6 +448,10 @@ class PresetManager {
228 presets = instruct_presets;448 presets = instruct_presets;
229 preset_names = instruct_presets.map(x => x.name);449 preset_names = instruct_presets.map(x => x.name);
230 break;450 break;
451 case 'sysprompt':
452 presets = system_prompts;
453 preset_names = system_prompts.map(x => x.name);
454 break;
231 default:455 default:
232 console.warn(`Unknown API ID ${this.apiId}`);456 console.warn(`Unknown API ID ${this.apiId}`);
233 }457 }
@@ -236,11 +460,11 @@ class PresetManager {
236 }460 }
237461
238 isKeyedApi() {462 isKeyedApi() {
239 return this.apiId == 'textgenerationwebui' || this.apiId == 'context' || this.apiId == 'instruct';463 return this.apiId == 'textgenerationwebui' || this.isAdvancedFormatting();
240 }464 }
241465
242 isAdvancedFormatting() {466 isAdvancedFormatting() {
243 return this.apiId == 'context' || this.apiId == 'instruct';467 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
244 }468 }
245469
246 updateList(name, preset) {470 updateList(name, preset) {
@@ -298,6 +522,11 @@ class PresetManager {
298 instruct_preset['name'] = name || power_user.instruct.preset;522 instruct_preset['name'] = name || power_user.instruct.preset;
299 return instruct_preset;523 return instruct_preset;
300 }524 }
525 case 'sysprompt': {
526 const sysprompt_preset = structuredClone(power_user.sysprompt);
527 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
528 return sysprompt_preset;
529 }
301 default:530 default:
302 console.warn(`Unknown API ID ${apiId}`);531 console.warn(`Unknown API ID ${apiId}`);
303 return {};532 return {};
@@ -601,11 +830,6 @@ export async function initPresetManager() {
601 return;830 return;
602 }831 }
603832
604 // default context preset cannot be deleted
605 if (apiId == 'context' && power_user.default_context === power_user.context.preset) {
606 return;
607 }
608
609 const headerText = !presetManager.isAdvancedFormatting() ? 'Delete this preset?' : 'Delete this template?';833 const headerText = !presetManager.isAdvancedFormatting() ? 'Delete this preset?' : 'Delete this template?';
610 const confirm = await Popup.show.confirm(headerText, 'This action is irreversible and your current settings will be overwritten.');834 const confirm = await Popup.show.confirm(headerText, 'This action is irreversible and your current settings will be overwritten.');
611 if (!confirm) {835 if (!confirm) {
@@ -682,4 +906,35 @@ export async function initPresetManager() {
682 toastr.success(successToast);906 toastr.success(successToast);
683 }907 }
684 });908 });
909
910 $('#af_master_import').on('click', () => {
911 $('#af_master_import_file').trigger('click');
912 });
913
914 $('#af_master_import_file').on('change', async function (e) {
915 if (!(e.target instanceof HTMLInputElement)) {
916 return;
917 }
918 const file = e.target.files[0];
919
920 if (!file) {
921 return;
922 }
923
924 const data = await parseJsonFile(file);
925 const fileName = file.name.replace('.json', '');
926 await PresetManager.performMasterImport(data, fileName);
927 e.target.value = null;
928 });
929
930 $('#af_master_export').on('click', async () => {
931 const data = await PresetManager.performMasterExport();
932
933 if (!data) {
934 return;
935 }
936
937 const shortDate = new Date().toISOString().split('T')[0];
938 download(data, `ST-formatting-${shortDate}.json`, 'application/json');
939 });
685}940}
public/scripts/sysprompt.js+250 -0
@@ -0,0 +1,250 @@
1import { saveSettingsDebounced } from '../script.js';
2import { callGenericPopup, POPUP_TYPE } from './popup.js';
3import { power_user } from './power-user.js';
4import { getPresetManager } from './preset-manager.js';
5import { SlashCommand } from './slash-commands/SlashCommand.js';
6import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
7import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10import { renderTemplateAsync } from './templates.js';
11import { isTrueBoolean, resetScrollHeight } from './utils.js';
12
13export let system_prompts = [];
14
15const $enabled = $('#sysprompt_enabled');
16const $select = $('#sysprompt_select');
17const $content = $('#sysprompt_content');
18const $contentBlock = $('#SystemPromptBlock');
19
20async function migrateSystemPromptFromInstructMode() {
21 if ('system_prompt' in power_user.instruct) {
22 const prompt = String(power_user.instruct.system_prompt);
23 delete power_user.instruct.system_prompt;
24 power_user.sysprompt.enabled = power_user.instruct.enabled;
25 power_user.sysprompt.content = prompt;
26
27 const existingPromptName = system_prompts.find(x => x.content === prompt)?.name;
28
29 if (existingPromptName) {
30 power_user.sysprompt.name = existingPromptName;
31 } else {
32 const data = { name: `[Migrated] ${power_user.instruct.preset}`, content: prompt };
33 await getPresetManager('sysprompt')?.savePreset(data.name, data);
34 power_user.sysprompt.name = data.name;
35 }
36
37 saveSettingsDebounced();
38 toastr.info('System prompt settings have been moved from the Instruct Mode.', 'Migration notice', { timeOut: 5000 });
39 }
40}
41
42/**
43 * Loads sysprompt settings from the given data object.
44 * @param {object} data Settings data object.
45 */
46export async function loadSystemPrompts(data) {
47 if (data.sysprompt !== undefined) {
48 system_prompts = data.sysprompt;
49 }
50
51 await migrateSystemPromptFromInstructMode();
52 toggleSystemPromptDisabledControls();
53
54 for (const prompt of system_prompts) {
55 $('<option>').val(prompt.name).text(prompt.name).appendTo($select);
56 }
57
58 $enabled.prop('checked', power_user.sysprompt.enabled);
59 $select.val(power_user.sysprompt.name);
60 $content.val(power_user.sysprompt.content);
61 if (!CSS.supports('field-sizing', 'content')) {
62 await resetScrollHeight($content);
63 }
64}
65
66/**
67 * Checks if the instruct template has a system prompt and prompts the user to save it as a system prompt.
68 * @param {string} name Name of the instruct template
69 * @param {object} template Instruct template object
70 */
71export async function checkForSystemPromptInInstructTemplate(name, template) {
72 if (!template || !name || typeof name !== 'string' || typeof template !== 'object') {
73 return;
74 }
75 if ('system_prompt' in template && template.system_prompt) {
76 const existingName = system_prompts.find(x => x.content === template.system_prompt)?.name;
77 const html = await renderTemplateAsync('migrateInstructPrompt', { prompt: template.system_prompt, existing: existingName });
78 const confirm = await callGenericPopup(html, POPUP_TYPE.CONFIRM);
79 if (confirm) {
80 const migratedName = `[Migrated] ${name}`;
81 const prompt = { name: migratedName, content: template.system_prompt };
82 const presetManager = getPresetManager('sysprompt');
83 await presetManager.savePreset(migratedName, prompt);
84 toastr.success(`System prompt "${migratedName}" has been saved.`);
85 } else {
86 toastr.info('System prompt has been discarded.');
87 }
88
89 delete template.system_prompt;
90 }
91}
92
93function toggleSystemPromptDisabledControls() {
94 $enabled.parent().find('i').toggleClass('toggleEnabled', !!power_user.sysprompt.enabled);
95 $contentBlock.toggleClass('disabled', !power_user.sysprompt.enabled);
96}
97
98/**
99 * Sets the system prompt state.
100 * @param {boolean} state System prompt state
101 * @returns {string} Empty string
102 */
103function setSystemPromptStateCallback(state) {
104 power_user.sysprompt.enabled = state;
105 $enabled.prop('checked', state);
106 toggleSystemPromptDisabledControls();
107 saveSettingsDebounced();
108 return '';
109}
110
111function toggleSystemPromptCallback(_args, state) {
112 if (!state || typeof state !== 'string') {
113 return String(power_user.sysprompt.enabled);
114 }
115
116 const newState = isTrueBoolean(state);
117 setSystemPromptStateCallback(newState);
118 return String(power_user.sysprompt.enabled);
119}
120
121function selectSystemPromptCallback(args, name) {
122 if (!power_user.sysprompt.enabled && !isTrueBoolean(args.forceGet)) {
123 return '';
124 }
125
126 if (!name) {
127 return power_user.sysprompt.name ?? '';
128 }
129
130 const quiet = isTrueBoolean(args?.quiet);
131 const systemPromptNames = system_prompts.map(preset => preset.name);
132 let foundName = systemPromptNames.find(x => x.toLowerCase() === name.toLowerCase());
133
134 if (!foundName) {
135 const fuse = new Fuse(systemPromptNames);
136 const result = fuse.search(name);
137
138 if (result.length === 0) {
139 !quiet && toastr.warning(`System prompt "${name}" not found`);
140 return '';
141 }
142
143 foundName = result[0].item;
144 }
145
146 $select.val(foundName).trigger('input');
147 !quiet && toastr.success(`System prompt "${foundName}" selected`);
148 return foundName;
149}
150
151export function initSystemPrompts() {
152 $enabled.on('input', function () {
153 power_user.sysprompt.enabled = !!$(this).prop('checked');
154 toggleSystemPromptDisabledControls();
155 saveSettingsDebounced();
156 });
157
158 $select.on('change', async function () {
159 if (!power_user.sysprompt.enabled) {
160 $enabled.prop('checked', true).trigger('input');
161 }
162
163 const name = String($(this).val());
164 const prompt = system_prompts.find(p => p.name === name);
165 if (prompt) {
166 $content.val(prompt.content);
167 if (!CSS.supports('field-sizing', 'content')) {
168 await resetScrollHeight($content);
169 }
170
171 power_user.sysprompt.name = name;
172 power_user.sysprompt.content = prompt.content;
173 }
174 saveSettingsDebounced();
175 });
176
177 $content.on('input', function () {
178 power_user.sysprompt.content = String($(this).val());
179 saveSettingsDebounced();
180 });
181
182 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
183 name: 'sysprompt',
184 aliases: ['system-prompt'],
185 callback: selectSystemPromptCallback,
186 returns: 'current prompt name',
187 namedArgumentList: [
188 SlashCommandNamedArgument.fromProps({
189 name: 'quiet',
190 description: 'Suppress the toast message on prompt change',
191 typeList: [ARGUMENT_TYPE.BOOLEAN],
192 defaultValue: 'false',
193 enumList: commonEnumProviders.boolean('trueFalse')(),
194 }),
195 SlashCommandNamedArgument.fromProps({
196 name: 'forceGet',
197 description: 'Force getting a name even if system prompt is disabled',
198 typeList: [ARGUMENT_TYPE.BOOLEAN],
199 defaultValue: 'false',
200 enumList: commonEnumProviders.boolean('trueFalse')(),
201 }),
202 ],
203 unnamedArgumentList: [
204 SlashCommandArgument.fromProps({
205 description: 'system prompt name',
206 typeList: [ARGUMENT_TYPE.STRING],
207 enumProvider: () => system_prompts.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
208 }),
209 ],
210 helpString: `
211 <div>
212 Selects a system prompt by name, using fuzzy search to find the closest match.
213 Gets the current system prompt if no name is provided and sysprompt is enabled or <code>forceGet=true</code> is passed.
214 </div>
215 <div>
216 <strong>Example:</strong>
217 <ul>
218 <li>
219 <pre><code class="language-stscript">/sysprompt </code></pre>
220 </li>
221 </ul>
222 </div>
223 `,
224 }));
225 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
226 name: 'sysprompt-on',
227 aliases: ['sysprompt-enable'],
228 callback: () => setSystemPromptStateCallback(true),
229 helpString: 'Enables system prompt.',
230 }));
231 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
232 name: 'sysprompt-off',
233 aliases: ['sysprompt-disable'],
234 callback: () => setSystemPromptStateCallback(false),
235 helpString: 'Disables system prompt',
236 }));
237 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
238 name: 'sysprompt-state',
239 aliases: ['sysprompt-toggle'],
240 helpString: 'Gets the current system prompt state. If an argument is provided, it will set the system prompt state.',
241 unnamedArgumentList: [
242 SlashCommandArgument.fromProps({
243 description: 'system prompt state',
244 typeList: [ARGUMENT_TYPE.BOOLEAN],
245 enumList: commonEnumProviders.boolean('trueFalse')(),
246 }),
247 ],
248 callback: toggleSystemPromptCallback,
249 }));
250}
public/scripts/templates/macros.html+2 -2
@@ -57,8 +57,8 @@
57 <li><tt>&lcub;&lcub;maxPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_41">max allowed prompt length in tokens = (context size - response length)</span></li>57 <li><tt>&lcub;&lcub;maxPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_41">max allowed prompt length in tokens = (context size - response length)</span></li>
58 <li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> – <span data-i18n="help_macros_42">context template example dialogues separator</span></li>58 <li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> – <span data-i18n="help_macros_42">context template example dialogues separator</span></li>
59 <li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> – <span data-i18n="help_macros_43">context template chat start line</span></li>59 <li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> – <span data-i18n="help_macros_43">context template chat start line</span></li>
60 <li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_44">main system prompt (either character prompt override if chosen, or instructSystemPrompt)</span></li>60 <li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_44">system prompt content if enabled (either character prompt override if allowed, or defaultSystemPrompt)</span></li>
61 <li><tt>&lcub;&lcub;instructSystemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_45">instruct system prompt</span></li>61 <li><tt>&lcub;&lcub;defaultSystemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_45">system prompt content</span></li>
62 <li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_46">instruct system prompt prefix sequence</span></li>62 <li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_46">instruct system prompt prefix sequence</span></li>
63 <li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> – <span data-i18n="help_macros_47">instruct system prompt suffix sequence</span></li>63 <li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> – <span data-i18n="help_macros_47">instruct system prompt suffix sequence</span></li>
64 <li><tt>&lcub;&lcub;instructUserPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_48">instruct user prefix sequence</span></li>64 <li><tt>&lcub;&lcub;instructUserPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_48">instruct user prefix sequence</span></li>
public/scripts/templates/masterExport.html+17 -0
@@ -0,0 +1,17 @@
1<h3>
2 Choose what to export
3</h3>
4<div class="flex-container flexFlowColumn justifyLeft">
5 {{#each sections}}
6 {{#with this}}
7 <label class="checkbox_label">
8 {{#if checked}}
9 <input type="checkbox" value="{{key}}" checked />
10 {{else}}
11 <input type="checkbox" value="{{key}}" />
12 {{/if}}
13 <span data-i18n="{{name}}">{{name}}</span>
14 </label>
15 {{/with}}
16 {{/each}}
17</div>
public/scripts/templates/masterImport.html+17 -0
@@ -0,0 +1,17 @@
1<h3>
2 Choose what to import
3</h3>
4<div class="flex-container flexFlowColumn justifyLeft">
5 {{#each sections}}
6 {{#with this}}
7 <label class="checkbox_label">
8 <input type="checkbox" value="{{key}}" checked>
9 <span data-i18n="{{name}}">{{name}}</span>
10 {{#if preset}}
11 <span>&ndash;</span>
12 <small>{{preset}}</small>
13 {{/if}}
14 </label>
15 {{/with}}
16 {{/each}}
17</div>
public/scripts/templates/migrateInstructPrompt.html+27 -0
@@ -0,0 +1,27 @@
1<h3>
2 This instruct template also contains a system prompt.
3</h3>
4<div>
5 Would you like to migrate the system prompt from the template?
6</div>
7{{#if existing}}
8<div class="marginTopBot5">
9 <b>Note:</b>
10 <span>you already have this prompt saved as:</span>
11 <span>{{existing}}</span>
12</div>
13{{/if}}
14<div class="justifyLeft marginTop5">
15 <div>
16 <small>
17 <b>"Yes"</b> &ndash; The prompt will be imported and selected as a current system prompt.
18 </small>
19
20 </div>
21 <div>
22 <small>
23 <b>"No"</b> &ndash; The prompt will be ignored, no changes to the current system prompt.
24 </small>
25 </div>
26</div>
27<textarea class="wide100p textarea_compact" rows="10">{{prompt}}</textarea>
public/scripts/templates/welcome.html+0 -5
@@ -41,11 +41,6 @@
41 SillyTavern Documentation Site41 SillyTavern Documentation Site
42 </a>42 </a>
43 </li>43 </li>
44 <li>
45 <a target="_blank" href="https://docs.sillytavern.app/extras/installation/" data-i18n="Extras Installation Guide">
46 Extras Installation Guide
47 </a>
48 </li>
49</ul>44</ul>
5045
51<hr>46<hr>
public/scripts/textgen-settings.js+10 -4
@@ -15,7 +15,7 @@ import { BIAS_CACHE, createNewLogitBiasEntry, displayLogitBias, getLogitBiasList
15import { power_user, registerDebugFunction } from './power-user.js';15import { power_user, registerDebugFunction } from './power-user.js';
16import { getEventSourceStream } from './sse-stream.js';16import { getEventSourceStream } from './sse-stream.js';
17import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer } from './textgen-models.js';17import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer } from './textgen-models.js';
18import { SENTENCEPIECE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';18import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
19import { getSortableDelay, onlyUnique } from './utils.js';19import { getSortableDelay, onlyUnique } from './utils.js';
2020
21export {21export {
@@ -353,7 +353,7 @@ function getTokenizerForTokenIds() {
353 return tokenizers.API_CURRENT;353 return tokenizers.API_CURRENT;
354 }354 }
355355
356 if (SENTENCEPIECE_TOKENIZERS.includes(power_user.tokenizer)) {356 if (ENCODE_TOKENIZERS.includes(power_user.tokenizer)) {
357 return power_user.tokenizer;357 return power_user.tokenizer;
358 }358 }
359359
@@ -1201,9 +1201,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1201 'frequency_penalty': settings.freq_pen,1201 'frequency_penalty': settings.freq_pen,
1202 'presence_penalty': settings.presence_pen,1202 'presence_penalty': settings.presence_pen,
1203 'repetition_penalty': settings.rep_pen,1203 'repetition_penalty': settings.rep_pen,
1204 'seed': settings.seed,1204 'seed': settings.seed >= 0 ? settings.seed : undefined,
1205 'stop': getStoppingStrings(isImpersonate, isContinue),1205 'stop': getStoppingStrings(isImpersonate, isContinue),
1206 'temperature': settings.temp,1206 'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
1207 'temperature_last': settings.temperature_last,1207 'temperature_last': settings.temperature_last,
1208 'top_p': settings.top_p,1208 'top_p': settings.top_p,
1209 'top_k': settings.top_k,1209 'top_k': settings.top_k,
@@ -1223,6 +1223,12 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1223 'guided_json': settings.json_schema,1223 'guided_json': settings.json_schema,
1224 'early_stopping': false, // hacks1224 'early_stopping': false, // hacks
1225 'include_stop_str_in_output': false,1225 'include_stop_str_in_output': false,
1226 'dynatemp_min': dynatemp ? settings.min_temp : undefined,
1227 'dynatemp_max': dynatemp ? settings.max_temp : undefined,
1228 'dynatemp_exponent': dynatemp ? settings.dynatemp_exponent : undefined,
1229 'xtc_threshold': settings.xtc_threshold,
1230 'xtc_probability': settings.xtc_probability,
1231 'custom_token_bans': toIntArray(banned_tokens),
1226 };1232 };
12271233
1228 if (settings.type === OPENROUTER) {1234 if (settings.type === OPENROUTER) {
public/scripts/tokenizers.js+34 -9
@@ -10,7 +10,8 @@ import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, o
10const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;10const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
1111
12export const CHARACTERS_PER_TOKEN_RATIO = 3.35;12export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
13const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';13export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
14export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
1415
15export const tokenizers = {16export const tokenizers = {
16 NONE: 0,17 NONE: 0,
@@ -30,21 +31,27 @@ export const tokenizers = {
30 JAMBA: 14,31 JAMBA: 14,
31 QWEN2: 15,32 QWEN2: 15,
32 COMMAND_R: 16,33 COMMAND_R: 16,
34 NEMO: 17,
33 BEST_MATCH: 99,35 BEST_MATCH: 99,
34};36};
3537
36export const SENTENCEPIECE_TOKENIZERS = [38// A list of local tokenizers that support encoding and decoding token ids.
39export const ENCODE_TOKENIZERS = [
37 tokenizers.LLAMA,40 tokenizers.LLAMA,
38 tokenizers.MISTRAL,41 tokenizers.MISTRAL,
39 tokenizers.YI,42 tokenizers.YI,
40 tokenizers.LLAMA3,43 tokenizers.LLAMA3,
41 tokenizers.GEMMA,44 tokenizers.GEMMA,
42 tokenizers.JAMBA,45 tokenizers.JAMBA,
46 tokenizers.QWEN2,
47 tokenizers.COMMAND_R,
48 tokenizers.NEMO,
43 // uncomment when NovelAI releases Kayra and Clio weights, lol49 // uncomment when NovelAI releases Kayra and Clio weights, lol
44 //tokenizers.NERD,50 //tokenizers.NERD,
45 //tokenizers.NERD2,51 //tokenizers.NERD2,
46];52];
4753
54// A list of Text Completion sources that support remote tokenization.
48export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE];55export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE];
4956
50const TOKENIZER_URLS = {57const TOKENIZER_URLS = {
@@ -117,6 +124,11 @@ const TOKENIZER_URLS = {
117 decode: '/api/tokenizers/command-r/decode',124 decode: '/api/tokenizers/command-r/decode',
118 count: '/api/tokenizers/command-r/encode',125 count: '/api/tokenizers/command-r/encode',
119 },126 },
127 [tokenizers.NEMO]: {
128 encode: '/api/tokenizers/nemo/encode',
129 decode: '/api/tokenizers/nemo/decode',
130 count: '/api/tokenizers/nemo/encode',
131 },
120 [tokenizers.API_TEXTGENERATIONWEBUI]: {132 [tokenizers.API_TEXTGENERATIONWEBUI]: {
121 encode: '/api/tokenizers/remote/textgenerationwebui/encode',133 encode: '/api/tokenizers/remote/textgenerationwebui/encode',
122 count: '/api/tokenizers/remote/textgenerationwebui/encode',134 count: '/api/tokenizers/remote/textgenerationwebui/encode',
@@ -262,6 +274,9 @@ export function getTokenizerBestMatch(forApi) {
262 if (nai_settings.model_novel.includes('kayra')) {274 if (nai_settings.model_novel.includes('kayra')) {
263 return tokenizers.NERD2;275 return tokenizers.NERD2;
264 }276 }
277 if (nai_settings.model_novel.includes('erato')) {
278 return tokenizers.LLAMA3;
279 }
265 }280 }
266 if (forApi === 'kobold' || forApi === 'textgenerationwebui' || forApi === 'koboldhorde') {281 if (forApi === 'kobold' || forApi === 'textgenerationwebui' || forApi === 'koboldhorde') {
267 // Try to use the API tokenizer if possible:282 // Try to use the API tokenizer if possible:
@@ -269,8 +284,9 @@ export function getTokenizerBestMatch(forApi) {
269 // - Kobold must pass a version check284 // - Kobold must pass a version check
270 // - Tokenizer haven't reported an error previously285 // - Tokenizer haven't reported an error previously
271 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);286 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
287 const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
272 const isConnected = online_status !== 'no_connection';288 const isConnected = online_status !== 'no_connection';
273 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type);289 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== OOBA || hasValidEndpoint);
274290
275 if (!hasTokenizerError && isConnected) {291 if (!hasTokenizerError && isConnected) {
276 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {292 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@@ -531,6 +547,7 @@ export function getTokenizerModel() {
531 const jambaTokenizer = 'jamba';547 const jambaTokenizer = 'jamba';
532 const qwen2Tokenizer = 'qwen2';548 const qwen2Tokenizer = 'qwen2';
533 const commandRTokenizer = 'command-r';549 const commandRTokenizer = 'command-r';
550 const nemoTokenizer = 'nemo';
534551
535 // Assuming no one would use it for different models.. right?552 // Assuming no one would use it for different models.. right?
536 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {553 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
@@ -624,6 +641,9 @@ export function getTokenizerModel() {
624 }641 }
625642
626 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {643 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {
644 if (oai_settings.mistralai_model.includes('nemo') || oai_settings.mistralai_model.includes('pixtral')) {
645 return nemoTokenizer;
646 }
627 return mistralTokenizer;647 return mistralTokenizer;
628 }648 }
629649
@@ -908,15 +928,20 @@ function countTokensFromTextgenAPI(str, resolve) {
908928
909function apiFailureTokenCount(str) {929function apiFailureTokenCount(str) {
910 console.error('Error counting tokens');930 console.error('Error counting tokens');
931 let shouldTryAgain = false;
911932
912 if (!sessionStorage.getItem(TOKENIZER_WARNING_KEY)) {933 if (!sessionStorage.getItem(TOKENIZER_WARNING_KEY)) {
913 toastr.warning(934 const bestMatchBefore = getTokenizerBestMatch(main_api);
914 'Your selected API doesn\'t support the tokenization endpoint. Using estimated counts.',
915 'Error counting tokens',
916 { timeOut: 10000, preventDuplicates: true },
917 );
918
919 sessionStorage.setItem(TOKENIZER_WARNING_KEY, String(true));935 sessionStorage.setItem(TOKENIZER_WARNING_KEY, String(true));
936 const bestMatchAfter = getTokenizerBestMatch(main_api);
937 if ([tokenizers.API_TEXTGENERATIONWEBUI, tokenizers.API_KOBOLD].includes(bestMatchBefore) && bestMatchBefore !== bestMatchAfter) {
938 shouldTryAgain = true;
939 }
940 }
941
942 // Only try again if we guarantee not to be looped by the same error
943 if (shouldTryAgain && power_user.tokenizer === tokenizers.BEST_MATCH) {
944 return getTokenCount(str);
920 }945 }
921946
922 return guesstimate(str);947 return guesstimate(str);
public/scripts/utils.js+6 -12
@@ -607,12 +607,11 @@ export function sortByCssOrder(a, b) {
607/**607/**
608 * Trims a string to the end of a nearest sentence.608 * Trims a string to the end of a nearest sentence.
609 * @param {string} input The string to trim.609 * @param {string} input The string to trim.
610 * @param {boolean} include_newline Whether to include a newline character in the trimmed string.
611 * @returns {string} The trimmed string.610 * @returns {string} The trimmed string.
612 * @example611 * @example
613 * trimToEndSentence('Hello, world! I am from'); // 'Hello, world!'612 * trimToEndSentence('Hello, world! I am from'); // 'Hello, world!'
614 */613 */
615export function trimToEndSentence(input, include_newline = false) {614export function trimToEndSentence(input) {
616 if (!input) {615 if (!input) {
617 return '';616 return '';
618 }617 }
@@ -633,11 +632,6 @@ export function trimToEndSentence(input, include_newline = false) {
633 }632 }
634 break;633 break;
635 }634 }
636
637 if (include_newline && char === '\n') {
638 last = i;
639 break;
640 }
641 }635 }
642636
643 if (last === -1) {637 if (last === -1) {
@@ -1908,13 +1902,13 @@ export function select2ChoiceClickSubscribe(control, action, { buttonStyle = fal
1908 * @returns {string} The html representation of the highlighted regex1902 * @returns {string} The html representation of the highlighted regex
1909 */1903 */
1910export function highlightRegex(regexStr) {1904export function highlightRegex(regexStr) {
1911 // Function to escape HTML special characters for safety1905 // Function to escape special characters for safety or readability
1912 const escapeHtml = (str) => str.replace(/[&<>"']/g, match => ({1906 const escape = (str) => str.replace(/[&<>"'\x01]/g, match => ({
1913 '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;',1907 '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;', '\x01': '\\x01',
1914 })[match]);1908 })[match]);
19151909
1916 // Replace special characters with their HTML-escaped forms1910 // Replace special characters with their escaped forms
1917 regexStr = escapeHtml(regexStr);1911 regexStr = escape(regexStr);
19181912
1919 // Patterns that we want to highlight only if they are not escaped1913 // Patterns that we want to highlight only if they are not escaped
1920 function getPatterns() {1914 function getPatterns() {
public/scripts/world-info.js+72 -10
@@ -217,8 +217,9 @@ class WorldInfoBuffer {
217 depth = MAX_SCAN_DEPTH;217 depth = MAX_SCAN_DEPTH;
218 }218 }
219219
220 const JOINER = '\n\x01';220 const MATCHER = '\x01';
221 let result = this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);221 const JOINER = '\n' + MATCHER;
222 let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);
222223
223 if (this.#injectBuffer.length > 0) {224 if (this.#injectBuffer.length > 0) {
224 result += JOINER + this.#injectBuffer.join(JOINER);225 result += JOINER + this.#injectBuffer.join(JOINER);
@@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) {
2949 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');2950 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
29502951
2951 // delay until recursion2952 // delay until recursion
2953 // delay until recursion level
2952 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');2954 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
2953 delayUntilRecursionInput.data('uid', entry.uid);2955 delayUntilRecursionInput.data('uid', entry.uid);
2956 const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
2957 delayUntilRecursionLevelInput.data('uid', entry.uid);
2954 delayUntilRecursionInput.on('input', async function () {2958 delayUntilRecursionInput.on('input', async function () {
2955 const uid = $(this).data('uid');2959 const uid = $(this).data('uid');
2956 const value = $(this).prop('checked');2960 const toggled = $(this).prop('checked');
2961
2962 // If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch
2963 const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
2964
2965 if (!toggled) delayUntilRecursionLevelInput.val('');
2966
2957 data.entries[uid].delayUntilRecursion = value;2967 data.entries[uid].delayUntilRecursion = value;
2958 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);2968 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2959 await saveWorldInfo(name, data);2969 await saveWorldInfo(name, data);
2960 });2970 });
2961 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');2971 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
2972 delayUntilRecursionLevelInput.on('input', async function () {
2973 const uid = $(this).data('uid');
2974 const content = $(this).val();
2975 const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true)
2976 : content === 1 ? true
2977 : !isNaN(Number(content)) ? Number(content)
2978 : false;
2979
2980 data.entries[uid].delayUntilRecursion = value;
2981 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2982 await saveWorldInfo(name, data);
2983 });
2984 // No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already
2985 delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
29622986
2963 // duplicate button2987 // duplicate button
2964 const duplicateButton = template.find('.duplicate_entry_button');2988 const duplicateButton = template.find('.duplicate_entry_button');
@@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = {
3257 disable: { default: false, type: 'boolean' },3281 disable: { default: false, type: 'boolean' },
3258 excludeRecursion: { default: false, type: 'boolean' },3282 excludeRecursion: { default: false, type: 'boolean' },
3259 preventRecursion: { default: false, type: 'boolean' },3283 preventRecursion: { default: false, type: 'boolean' },
3260 delayUntilRecursion: { default: false, type: 'boolean' },3284 delayUntilRecursion: { default: 0, type: 'number' },
3261 probability: { default: 100, type: 'number' },3285 probability: { default: 100, type: 'number' },
3262 useProbability: { default: true, type: 'boolean' },3286 useProbability: { default: true, type: 'boolean' },
3263 depth: { default: DEFAULT_DEPTH, type: 'number' },3287 depth: { default: DEFAULT_DEPTH, type: 'number' },
@@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3696 }3720 }
3697 }3721 }
36983722
3723 /** @type {scan_state} */
3699 let scanState = scan_state.INITIAL;3724 let scanState = scan_state.INITIAL;
3700 let token_budget_overflowed = false;3725 let token_budget_overflowed = false;
3701 let count = 0;3726 let count = 0;
@@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3720 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };3745 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
3721 }3746 }
37223747
3748 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
3749 const availableRecursionDelayLevels = [...new Set(sortedEntries
3750 .filter(entry => entry.delayUntilRecursion)
3751 .map(entry => entry.delayUntilRecursion === true ? 1 : entry.delayUntilRecursion),
3752 )].sort((a, b) => a - b);
3753 // Already preset with the first level
3754 let currentRecursionDelayLevel = availableRecursionDelayLevels.shift() ?? 0;
3755 if (currentRecursionDelayLevel > 0 && availableRecursionDelayLevels.length) {
3756 console.debug('[WI] Preparing first delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels);
3757 }
3758
3723 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);3759 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);
37243760
3725 while (scanState) {3761 while (scanState) {
@@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3732 // Track how many times the loop has run. May be useful for debugging.3768 // Track how many times the loop has run. May be useful for debugging.
3733 count++;3769 count++;
37343770
3735 console.debug(`[WI] Loop #${count}. Search state`, Object.entries(scan_state).find(x => x[1] === scanState));3771 console.debug(`[WI] --- LOOP #${count} START ---`);
3772 console.debug('[WI] Scan state', Object.entries(scan_state).find(x => x[1] === scanState));
37363773
3737 // Until decided otherwise, we set the loop to stop scanning after this3774 // Until decided otherwise, we set the loop to stop scanning after this
3738 let nextScanState = scan_state.NONE;3775 let nextScanState = scan_state.NONE;
@@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3811 continue;3848 continue;
3812 }3849 }
38133850
3851 if (scanState === scan_state.RECURSION && entry.delayUntilRecursion && entry.delayUntilRecursion > currentRecursionDelayLevel && !isSticky) {
3852 log('suppressed by delay until recursion level', entry.delayUntilRecursion, '. Currently', currentRecursionDelayLevel);
3853 continue;
3854 }
3855
3814 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {3856 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {
3815 log('suppressed by exclude recursion');3857 log('suppressed by exclude recursion');
3816 continue;3858 continue;
@@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3949 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);3991 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
39503992
3951 console.debug('[WI] --- PROBABILITY CHECKS ---');3993 console.debug('[WI] --- PROBABILITY CHECKS ---');
3994 !newEntries.length && console.debug('[WI] No probability checks to do');
3995
3952 for (const entry of newEntries) {3996 for (const entry of newEntries) {
3953 function verifyProbability() {3997 function verifyProbability() {
3954 // If we don't need to roll, it's always true3998 // If we don't need to roll, it's always true
@@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3984 newContent += `${entry.content}\n`;4028 newContent += `${entry.content}\n`;
39854029
3986 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {4030 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
4031 console.debug('[WI] --- BUDGET OVERFLOW CHECK ---');
3987 if (world_info_overflow_alert) {4032 if (world_info_overflow_alert) {
3988 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);4033 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);
3989 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');4034 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
@@ -4001,23 +4046,31 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4001 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));4046 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));
4002 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);4047 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);
40034048
4049 console.debug(`[WI] --- LOOP #${count} RESULT ---`);
4004 if (!newEntries.length) {4050 if (!newEntries.length) {
4005 console.debug('[WI] No new entries activated, stopping');4051 console.debug('[WI] No new entries activated.');
4006 } else if (!successfulNewEntries.length) {4052 } else if (!successfulNewEntries.length) {
4007 console.debug('[WI] Probability checks failed for all activated entries, stopping');4053 console.debug('[WI] Probability checks failed for all activated entries. No new entries activated.');
4008 } else {4054 } else {
4009 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);4055 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);
4010 }4056 }
40114057
4058 function logNextState(...args) {
4059 args.length && console.debug(args.shift(), ...args);
4060 console.debug('[WI] Setting scan state', Object.entries(scan_state).find(x => x[1] === scanState));
4061 }
4062
4012 // After processing and rolling entries is done, see if we should continue with normal recursion4063 // After processing and rolling entries is done, see if we should continue with normal recursion
4013 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {4064 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {
4014 nextScanState = scan_state.RECURSION;4065 nextScanState = scan_state.RECURSION;
4066 logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion');
4015 }4067 }
40164068
4017 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again4069 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again
4018 // There might be recurse-trigger-able entries that match the buffer, so we need to check that4070 // There might be recurse-trigger-able entries that match the buffer, so we need to check that
4019 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {4071 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {
4020 nextScanState = scan_state.RECURSION;4072 nextScanState = scan_state.RECURSION;
4073 logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan');
4021 }4074 }
40224075
4023 // If scanning is planned to stop, but min activations is set and not satisfied, check if we should continue4076 // If scanning is planned to stop, but min activations is set and not satisfied, check if we should continue
@@ -4031,14 +4084,21 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4031 ) || (buffer.getDepth() > chat.length);4084 ) || (buffer.getDepth() > chat.length);
40324085
4033 if (!over_max) {4086 if (!over_max) {
4034 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1} and checking again`);
4035 nextScanState = scan_state.MIN_ACTIVATIONS; // loop4087 nextScanState = scan_state.MIN_ACTIVATIONS; // loop
4088 logNextState(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1}, starting another scan`);
4036 buffer.advanceScan();4089 buffer.advanceScan();
4037 } else {4090 } else {
4038 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);4091 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);
4039 }4092 }
4040 }4093 }
40414094
4095 // If the scan is done, but we still have open "delay until recursion" levels, we should continue with the next one
4096 if (nextScanState === scan_state.NONE && availableRecursionDelayLevels.length) {
4097 nextScanState = scan_state.RECURSION;
4098 currentRecursionDelayLevel = availableRecursionDelayLevels.shift();
4099 logNextState('[WI] Open delayed recursion levels left. Preparing next delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels);
4100 }
4101
4042 // Final check if we should really continue scan, and extend the current WI recurse buffer4102 // Final check if we should really continue scan, and extend the current WI recurse buffer
4043 scanState = nextScanState;4103 scanState = nextScanState;
4044 if (scanState) {4104 if (scanState) {
@@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4046 .map(x => x.content).join('\n');4106 .map(x => x.content).join('\n');
4047 buffer.addRecurse(text);4107 buffer.addRecurse(text);
4048 allActivatedText = (text + '\n' + allActivatedText);4108 allActivatedText = (text + '\n' + allActivatedText);
4109 } else {
4110 logNextState('[WI] Scan done. No new entries to prompt. Stopping.');
4049 }4111 }
4050 }4112 }
40514113
@@ -4072,10 +4134,10 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40724134
4073 switch (entry.position) {4135 switch (entry.position) {
4074 case world_info_position.before:4136 case world_info_position.before:
4075 WIBeforeEntries.unshift(substituteParams(content));4137 WIBeforeEntries.unshift(content);
4076 break;4138 break;
4077 case world_info_position.after:4139 case world_info_position.after:
4078 WIAfterEntries.unshift(substituteParams(content));4140 WIAfterEntries.unshift(content);
4079 break;4141 break;
4080 case world_info_position.EMTop:4142 case world_info_position.EMTop:
4081 EMEntries.unshift(4143 EMEntries.unshift(
public/style.css+25 -11
@@ -2570,20 +2570,25 @@ input[type="file"] {
2570/*#################################################################*/2570/*#################################################################*/
25712571
2572select {2572select {
25732573 -moz-appearance: none;
2574 padding: 3px 2px;2574 -webkit-appearance: none;
2575 appearance: none;
2576 padding: 3px 5px;
2575 background-color: var(--black30a);2577 background-color: var(--black30a);
2576 border: 1px solid var(--SmartThemeBorderColor);2578 border: 1px solid var(--SmartThemeBorderColor);
2577 border-radius: 5px;2579 border-radius: 5px;
2578 margin-bottom: 5px;2580 margin-bottom: 5px;
2579 height: min-content;2581 height: min-content;
2582 background-image: url('/img/down-arrow.svg');
2583 background-repeat: no-repeat;
2584 background-position: right 6px center;
2585 background-size: 8px 5px;
2580}2586}
25812587
2582select option {2588select option {
2583 /* works to highlight selected/active option */2589 /* works to highlight selected/active option */
2584 background-color: var(--white50a);2590 background-color: var(--white50a);
2585 color: var(--black70a);2591 color: var(--black70a);
2586
2587}2592}
25882593
2589select option:not(:checked) {2594select option:not(:checked) {
@@ -2625,14 +2630,6 @@ select option:not(:checked) {
2625 color: var(--active) !important;2630 color: var(--active) !important;
2626}2631}
26272632
2628#context_set_default.default {
2629 color: var(--preferred) !important;
2630}
2631
2632#instruct_set_default.default {
2633 color: var(--preferred) !important;
2634}
2635
2636.displayBlock {2633.displayBlock {
2637 display: block !important;2634 display: block !important;
2638}2635}
@@ -3518,6 +3515,14 @@ grammarly-extension {
3518 column-gap: 20px;3515 column-gap: 20px;
3519}3516}
35203517
3518.horde-model-description {
3519 -webkit-line-clamp: 3;
3520 line-clamp: 3;
3521 font-size: 0.9em;
3522 overflow: hidden;
3523 text-overflow: ellipsis;
3524}
3525
3521.drag-handle {3526.drag-handle {
3522 cursor: grab;3527 cursor: grab;
3523 /* Make the drag handle not selectable in most browsers */3528 /* Make the drag handle not selectable in most browsers */
@@ -5464,3 +5469,12 @@ body:not(.movingUI) .drawer-content.maximized {
5464#AdvancedFormatting .autoSetHeight {5469#AdvancedFormatting .autoSetHeight {
5465 overflow-wrap: anywhere;5470 overflow-wrap: anywhere;
5466}5471}
5472
5473#InstructSequencesColumn summary {
5474 font-size: 0.95em;
5475 cursor: pointer;
5476}
5477
5478#InstructSequencesColumn details:not(:last-of-type) {
5479 margin-bottom: 5px;
5480}
server.js+4 -0
@@ -610,6 +610,9 @@ app.use('/api/search', require('./src/endpoints/search').router);
610// Ooba/OpenAI text completions610// Ooba/OpenAI text completions
611app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router);611app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router);
612612
613// OpenRouter
614app.use('/api/openrouter', require('./src/endpoints/openrouter').router);
615
613// KoboldAI616// KoboldAI
614app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router);617app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router);
615618
@@ -929,6 +932,7 @@ async function verifySecuritySettings() {
929userModule.initUserStorage(dataRoot)932userModule.initUserStorage(dataRoot)
930 .then(userModule.ensurePublicDirectoriesExist)933 .then(userModule.ensurePublicDirectoriesExist)
931 .then(userModule.migrateUserData)934 .then(userModule.migrateUserData)
935 .then(userModule.migrateSystemPrompts)
932 .then(verifySecuritySettings)936 .then(verifySecuritySettings)
933 .then(preSetupTasks)937 .then(preSetupTasks)
934 .finally(startServer);938 .finally(startServer);
src/constants.js+1 -2
@@ -5,7 +5,6 @@ const PUBLIC_DIRECTORIES = {
5 extensions: 'public/scripts/extensions',5 extensions: 'public/scripts/extensions',
6};6};
77
8const DEFAULT_AVATAR = '/img/ai4.png';
9const SETTINGS_FILE = 'settings.json';8const SETTINGS_FILE = 'settings.json';
109
11/**10/**
@@ -42,6 +41,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({
42 files: 'user/files',41 files: 'user/files',
43 vectors: 'vectors',42 vectors: 'vectors',
44 backups: 'backups',43 backups: 'backups',
44 sysprompt: 'sysprompt',
45});45});
4646
47/**47/**
@@ -422,7 +422,6 @@ const VLLM_KEYS = [
422422
423module.exports = {423module.exports = {
424 DEFAULT_USER,424 DEFAULT_USER,
425 DEFAULT_AVATAR,
426 SETTINGS_FILE,425 SETTINGS_FILE,
427 PUBLIC_DIRECTORIES,426 PUBLIC_DIRECTORIES,
428 USER_DIRECTORY_TEMPLATE,427 USER_DIRECTORY_TEMPLATE,
src/endpoints/backends/text-completions.js+1 -0
@@ -186,6 +186,7 @@ router.post('/status', jsonParser, async function (request, response) {
186186
187 const modelName = modelInfo?.model_name;187 const modelName = modelInfo?.model_name;
188 result = modelName || result;188 result = modelName || result;
189 response.setHeader('x-supports-tokenization', 'true');
189 }190 }
190 } catch (error) {191 } catch (error) {
191 console.error(`Failed to get Ooba model info: ${error}`);192 console.error(`Failed to get Ooba model info: ${error}`);
src/endpoints/characters.js+7 -2
@@ -726,13 +726,12 @@ router.post('/create', urlencodedParser, async function (request, response) {
726 const char = JSON.stringify(charaFormatData(request.body, request.user.directories));726 const char = JSON.stringify(charaFormatData(request.body, request.user.directories));
727 const internalName = getPngName(request.body.ch_name, request.user.directories);727 const internalName = getPngName(request.body.ch_name, request.user.directories);
728 const avatarName = `${internalName}.png`;728 const avatarName = `${internalName}.png`;
729 const defaultAvatar = './public/img/ai4.png';
730 const chatsPath = path.join(request.user.directories.chats, internalName);729 const chatsPath = path.join(request.user.directories.chats, internalName);
731730
732 if (!fs.existsSync(chatsPath)) fs.mkdirSync(chatsPath);731 if (!fs.existsSync(chatsPath)) fs.mkdirSync(chatsPath);
733732
734 if (!request.file) {733 if (!request.file) {
735 await writeCharacterData(defaultAvatar, char, internalName, request);734 await writeCharacterData(defaultAvatarPath, char, internalName, request);
736 return response.send(avatarName);735 return response.send(avatarName);
737 } else {736 } else {
738 const crop = tryParse(request.query.crop);737 const crop = tryParse(request.query.crop);
@@ -1028,6 +1027,12 @@ router.post('/chats', jsonParser, async function (request, response) {
1028 const stats = fs.statSync(pathToFile);1027 const stats = fs.statSync(pathToFile);
1029 const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`;1028 const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`;
10301029
1030 if (stats.size === 0) {
1031 console.log(`Found an empty chat file: ${pathToFile}`);
1032 res({});
1033 return;
1034 }
1035
1031 const rl = readline.createInterface({1036 const rl = readline.createInterface({
1032 input: fileStream,1037 input: fileStream,
1033 crlfDelay: Infinity,1038 crlfDelay: Infinity,
src/endpoints/chats.js+101 -44
@@ -4,6 +4,7 @@ const readline = require('readline');
4const express = require('express');4const express = require('express');
5const sanitize = require('sanitize-filename');5const sanitize = require('sanitize-filename');
6const writeFileAtomicSync = require('write-file-atomic').sync;6const writeFileAtomicSync = require('write-file-atomic').sync;
7const _ = require('lodash');
78
8const { jsonParser, urlencodedParser } = require('../express-common');9const { jsonParser, urlencodedParser } = require('../express-common');
9const { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } = require('../util');10const { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } = require('../util');
@@ -34,6 +35,27 @@ function backupChat(directory, name, chat) {
34 }35 }
35}36}
3637
38const backupFunctions = new Map();
39
40/**
41 * Gets a backup function for a user.
42 * @param {string} handle User handle
43 * @returns {function(string, string, string): void} Backup function
44 */
45function getBackupFunction(handle) {
46 const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000);
47 if (!backupFunctions.has(handle)) {
48 backupFunctions.set(handle, _.throttle(backupChat, throttleInterval, { leading: true, trailing: true }));
49 }
50 return backupFunctions.get(handle);
51}
52
53process.on('exit', () => {
54 for (const func of backupFunctions.values()) {
55 func.flush();
56 }
57});
58
37/**59/**
38 * Imports a chat from Ooba's format.60 * Imports a chat from Ooba's format.
39 * @param {string} userName User name61 * @param {string} userName User name
@@ -70,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) {
70 }92 }
71 }93 }
7294
73 const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n');95 return chat.map(obj => JSON.stringify(obj)).join('\n');
74 return chatContent;
75}96}
7697
77/**98/**
@@ -99,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) {
99 });120 });
100 }121 }
101122
102 const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n');123 return chat.map(obj => JSON.stringify(obj)).join('\n');
103 return chatContent;
104}124}
105125
106/**126/**
@@ -137,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) {
137 return newChats;157 return newChats;
138}158}
139159
160/**
161 * Flattens `msg` and `swipes` data from Chub Chat format.
162 * Only changes enough to make it compatible with the standard chat serialization format.
163 * @param {string} userName User name
164 * @param {string} characterName Character name
165 * @param {string[]} lines serialised JSONL data
166 * @returns {string} Converted data
167 */
168function flattenChubChat(userName, characterName, lines) {
169 function flattenSwipe(swipe) {
170 return swipe.message ? swipe.message : swipe;
171 }
172
173 function convert(line) {
174 const lineData = tryParse(line);
175 if (!lineData) return line;
176
177 if (lineData.mes && lineData.mes.message) {
178 lineData.mes = lineData?.mes.message;
179 }
180
181 if (lineData?.swipes && Array.isArray(lineData.swipes)) {
182 lineData.swipes = lineData.swipes.map(swipe => flattenSwipe(swipe));
183 }
184
185 return JSON.stringify(lineData);
186 }
187
188 return (lines ?? []).map(convert).join('\n');
189}
190
140const router = express.Router();191const router = express.Router();
141192
142router.post('/save', jsonParser, function (request, response) {193router.post('/save', jsonParser, function (request, response) {
@@ -144,10 +195,10 @@ router.post('/save', jsonParser, function (request, response) {
144 const directoryName = String(request.body.avatar_url).replace('.png', '');195 const directoryName = String(request.body.avatar_url).replace('.png', '');
145 const chatData = request.body.chat;196 const chatData = request.body.chat;
146 const jsonlData = chatData.map(JSON.stringify).join('\n');197 const jsonlData = chatData.map(JSON.stringify).join('\n');
147 const fileName = `${sanitize(String(request.body.file_name))}.jsonl`;198 const fileName = `${String(request.body.file_name)}.jsonl`;
148 const filePath = path.join(request.user.directories.chats, directoryName, fileName);199 const filePath = path.join(request.user.directories.chats, directoryName, sanitize(fileName));
149 writeFileAtomicSync(filePath, jsonlData, 'utf8');200 writeFileAtomicSync(filePath, jsonlData, 'utf8');
150 backupChat(request.user.directories.backups, directoryName, jsonlData);201 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
151 return response.send({ result: 'ok' });202 return response.send({ result: 'ok' });
152 } catch (error) {203 } catch (error) {
153 response.send(error);204 response.send(error);
@@ -171,14 +222,15 @@ router.post('/get', jsonParser, function (request, response) {
171 return response.send({});222 return response.send({});
172 }223 }
173224
174 const fileName = path.join(directoryPath, `${sanitize(String(request.body.file_name))}.jsonl`);225 const fileName = `${String(request.body.file_name)}.jsonl`;
175 const chatFileExists = fs.existsSync(fileName);226 const filePath = path.join(directoryPath, sanitize(fileName));
227 const chatFileExists = fs.existsSync(filePath);
176228
177 if (!chatFileExists) {229 if (!chatFileExists) {
178 return response.send({});230 return response.send({});
179 }231 }
180232
181 const data = fs.readFileSync(fileName, 'utf8');233 const data = fs.readFileSync(filePath, 'utf8');
182 const lines = data.split('\n');234 const lines = data.split('\n');
183235
184 // Iterate through the array of strings and parse each line as JSON236 // Iterate through the array of strings and parse each line as JSON
@@ -199,8 +251,9 @@ router.post('/rename', jsonParser, async function (request, response) {
199 const pathToFolder = request.body.is_group251 const pathToFolder = request.body.is_group
200 ? request.user.directories.groupChats252 ? request.user.directories.groupChats
201 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));253 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));
202 const pathToOriginalFile = path.join(pathToFolder, request.body.original_file);254 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));
203 const pathToRenamedFile = path.join(pathToFolder, request.body.renamed_file);255 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));
256 const sanitizedFileName = path.parse(pathToRenamedFile).name;
204 console.log('Old chat name', pathToOriginalFile);257 console.log('Old chat name', pathToOriginalFile);
205 console.log('New chat name', pathToRenamedFile);258 console.log('New chat name', pathToRenamedFile);
206259
@@ -212,32 +265,22 @@ router.post('/rename', jsonParser, async function (request, response) {
212 fs.copyFileSync(pathToOriginalFile, pathToRenamedFile);265 fs.copyFileSync(pathToOriginalFile, pathToRenamedFile);
213 fs.rmSync(pathToOriginalFile);266 fs.rmSync(pathToOriginalFile);
214 console.log('Successfully renamed.');267 console.log('Successfully renamed.');
215 return response.send({ ok: true });268 return response.send({ ok: true, sanitizedFileName });
216});269});
217270
218router.post('/delete', jsonParser, function (request, response) {271router.post('/delete', jsonParser, function (request, response) {
219 if (!request.body) {
220 console.log('no request body seen');
221 return response.sendStatus(400);
222 }
223
224 if (request.body.chatfile !== sanitize(request.body.chatfile)) {
225 console.error('Malicious chat name prevented');
226 return response.sendStatus(403);
227 }
228
229 const dirName = String(request.body.avatar_url).replace('.png', '');272 const dirName = String(request.body.avatar_url).replace('.png', '');
230 const fileName = path.join(request.user.directories.chats, dirName, sanitize(String(request.body.chatfile)));273 const fileName = String(request.body.chatfile);
231 const chatFileExists = fs.existsSync(fileName);274 const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName));
275 const chatFileExists = fs.existsSync(filePath);
232276
233 if (!chatFileExists) {277 if (!chatFileExists) {
234 console.log(`Chat file not found '${fileName}'`);278 console.log(`Chat file not found '${filePath}'`);
235 return response.sendStatus(400);279 return response.sendStatus(400);
236 } else {
237 fs.rmSync(fileName);
238 console.log('Deleted chat file: ' + fileName);
239 }280 }
240281
282 fs.rmSync(filePath);
283 console.log('Deleted chat file: ' + filePath);
241 return response.send('ok');284 return response.send('ok');
242});285});
243286
@@ -259,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) {
259 }302 }
260 try {303 try {
261 // Short path for JSONL files304 // Short path for JSONL files
262 if (request.body.format == 'jsonl') {305 if (request.body.format === 'jsonl') {
263 try {306 try {
264 const rawFile = fs.readFileSync(filename, 'utf8');307 const rawFile = fs.readFileSync(filename, 'utf8');
265 const successMessage = {308 const successMessage = {
@@ -269,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) {
269312
270 console.log(`Chat exported as ${exportfilename}`);313 console.log(`Chat exported as ${exportfilename}`);
271 return response.status(200).json(successMessage);314 return response.status(200).json(successMessage);
272 }315 } catch (err) {
273 catch (err) {
274 console.error(err);316 console.error(err);
275 const errorMessage = {317 const errorMessage = {
276 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,318 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
@@ -305,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) {
305 console.log(`Chat exported as ${exportfilename}`);347 console.log(`Chat exported as ${exportfilename}`);
306 return response.status(200).json(successMessage);348 return response.status(200).json(successMessage);
307 });349 });
308 }350 } catch (err) {
309 catch (err) {
310 console.log('chat export failed.');351 console.log('chat export failed.');
311 console.log(err);352 console.log(err);
312 return response.sendStatus(400);353 return response.sendStatus(400);
@@ -382,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) {
382 }423 }
383424
384 if (format === 'jsonl') {425 if (format === 'jsonl') {
385 const line = data.split('\n')[0];426 let lines = data.split('\n');
427 const header = lines[0];
386428
387 const jsonData = JSON.parse(line);429 const jsonData = JSON.parse(header);
388430
389 if (jsonData.user_name !== undefined || jsonData.name !== undefined) {431 if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
390 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
391 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
392 fs.copyFileSync(pathToUpload, filePath);
393 fs.unlinkSync(pathToUpload);
394 response.send({ res: true });
395 } else {
396 console.log('Incorrect chat format .jsonl');432 console.log('Incorrect chat format .jsonl');
397 return response.send({ error: true });433 return response.send({ error: true });
398 }434 }
435
436 // Do a tiny bit of work to import Chub Chat data
437 // Processing the entire file is so fast that it's not worth checking if it's a Chub chat first
438 let flattenedChat = data;
439 try {
440 // flattening is unlikely to break, but it's not worth failing to
441 // import normal chats in an attempt to import a Chub chat
442 flattenedChat = flattenChubChat(userName, characterName, lines);
443 } catch (error) {
444 console.warn('Failed to flatten Chub Chat data: ', error);
445 }
446
447 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
448 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
449 if (flattenedChat !== data) {
450 writeFileAtomicSync(filePath, flattenedChat, 'utf8');
451 } else {
452 fs.copyFileSync(pathToUpload, filePath);
453 }
454 fs.unlinkSync(pathToUpload);
455 response.send({ res: true });
399 }456 }
400 } catch (error) {457 } catch (error) {
401 console.error(error);458 console.error(error);
@@ -454,7 +511,7 @@ router.post('/group/save', jsonParser, (request, response) => {
454 let chat_data = request.body.chat;511 let chat_data = request.body.chat;
455 let jsonlData = chat_data.map(JSON.stringify).join('\n');512 let jsonlData = chat_data.map(JSON.stringify).join('\n');
456 writeFileAtomicSync(pathToFile, jsonlData, 'utf8');513 writeFileAtomicSync(pathToFile, jsonlData, 'utf8');
457 backupChat(request.user.directories.backups, String(id), jsonlData);514 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData);
458 return response.send({ ok: true });515 return response.send({ ok: true });
459});516});
460517
src/endpoints/content-manager.js+40 -1
@@ -43,6 +43,7 @@ const CONTENT_TYPES = {
43 CONTEXT: 'context',43 CONTEXT: 'context',
44 MOVING_UI: 'moving_ui',44 MOVING_UI: 'moving_ui',
45 QUICK_REPLIES: 'quick_replies',45 QUICK_REPLIES: 'quick_replies',
46 SYSPROMPT: 'sysprompt',
46};47};
4748
48/**49/**
@@ -56,7 +57,7 @@ function getDefaultPresets(directories) {
56 const presets = [];57 const presets = [];
5758
58 for (const contentItem of contentIndex) {59 for (const contentItem of contentIndex) {
59 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context') {60 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {
60 contentItem.name = path.parse(contentItem.filename).name;61 contentItem.name = path.parse(contentItem.filename).name;
61 contentItem.folder = getTargetByType(contentItem.type, directories);62 contentItem.folder = getTargetByType(contentItem.type, directories);
62 presets.push(contentItem);63 presets.push(contentItem);
@@ -218,6 +219,41 @@ function getContentIndex() {
218}219}
219220
220/**221/**
222 * Gets content by type and format.
223 * @param {string} type Type of content
224 * @param {'json'|'string'|'raw'} format Format of content
225 * @returns {string[]|Buffer[]} Array of content
226 */
227function getContentOfType(type, format) {
228 const contentIndex = getContentIndex();
229 const indexItems = contentIndex.filter((item) => item.type === type && item.folder);
230 const files = [];
231 for (const item of indexItems) {
232 if (!item.folder) {
233 continue;
234 }
235 try {
236 const filePath = path.join(item.folder, item.filename);
237 const fileContent = fs.readFileSync(filePath);
238 switch (format) {
239 case 'json':
240 files.push(JSON.parse(fileContent.toString()));
241 break;
242 case 'string':
243 files.push(fileContent.toString());
244 break;
245 case 'raw':
246 files.push(fileContent);
247 break;
248 }
249 } catch {
250 // Ignore errors
251 }
252 }
253 return files;
254}
255
256/**
221 * Gets the target directory for the specified asset type.257 * Gets the target directory for the specified asset type.
222 * @param {ContentType} type Asset type258 * @param {ContentType} type Asset type
223 * @param {import('../users').UserDirectoryList} directories User directories259 * @param {import('../users').UserDirectoryList} directories User directories
@@ -257,6 +293,8 @@ function getTargetByType(type, directories) {
257 return directories.movingUI;293 return directories.movingUI;
258 case CONTENT_TYPES.QUICK_REPLIES:294 case CONTENT_TYPES.QUICK_REPLIES:
259 return directories.quickreplies;295 return directories.quickreplies;
296 case CONTENT_TYPES.SYSPROMPT:
297 return directories.sysprompt;
260 default:298 default:
261 return null;299 return null;
262 }300 }
@@ -721,5 +759,6 @@ module.exports = {
721 checkForNewContent,759 checkForNewContent,
722 getDefaultPresets,760 getDefaultPresets,
723 getDefaultPresetFile,761 getDefaultPresetFile,
762 getContentOfType,
724 router,763 router,
725};764};
src/endpoints/horde.js+29 -4
@@ -6,6 +6,7 @@ const { readSecret, SECRET_KEYS } = require('./secrets');
6const { jsonParser } = require('../express-common');6const { jsonParser } = require('../express-common');
77
8const ANONYMOUS_KEY = '0000000000';8const ANONYMOUS_KEY = '0000000000';
9const HORDE_TEXT_MODEL_METADATA_URL = 'https://raw.githubusercontent.com/db0/AI-Horde-text-model-reference/main/db.json';
9const cache = new Cache(60 * 1000);10const cache = new Cache(60 * 1000);
10const router = express.Router();11const router = express.Router();
1112
@@ -23,10 +24,9 @@ async function getClientAgent() {
23 * @returns {Promise<AIHorde>} AIHorde client24 * @returns {Promise<AIHorde>} AIHorde client
24 */25 */
25async function getHordeClient() {26async function getHordeClient() {
26 const ai_horde = new AIHorde({27 return new AIHorde({
27 client_agent: await getClientAgent(),28 client_agent: await getClientAgent(),
28 });29 });
29 return ai_horde;
30}30}
3131
32/**32/**
@@ -79,10 +79,24 @@ router.post('/text-workers', jsonParser, async (request, response) => {
79 }79 }
80});80});
8181
82async function getHordeTextModelMetadata() {
83 const response = await fetch(HORDE_TEXT_MODEL_METADATA_URL);
84 return await response.json();
85}
86
87async function mergeModelsAndMetadata(models, metadata) {
88 return models.map(model => {
89 const metadataModel = metadata[model.name];
90 if (!metadataModel) {
91 return { ...model, is_whitelisted: false };
92 }
93 return { ...model, ...metadataModel, is_whitelisted: true };
94 });
95}
96
82router.post('/text-models', jsonParser, async (request, response) => {97router.post('/text-models', jsonParser, async (request, response) => {
83 try {98 try {
84 const cachedModels = cache.get('models');99 const cachedModels = cache.get('models');
85
86 if (cachedModels && !request.body.force) {100 if (cachedModels && !request.body.force) {
87 return response.send(cachedModels);101 return response.send(cachedModels);
88 }102 }
@@ -94,7 +108,17 @@ router.post('/text-models', jsonParser, async (request, response) => {
94 },108 },
95 });109 });
96110
97 const data = await fetchResult.json();111 let data = await fetchResult.json();
112
113 // attempt to fetch and merge models metadata
114 try {
115 const metadata = await getHordeTextModelMetadata();
116 data = await mergeModelsAndMetadata(data, metadata);
117 }
118 catch (error) {
119 console.error('Failed to fetch metadata:', error);
120 }
121
98 cache.set('models', data);122 cache.set('models', data);
99 return response.send(data);123 return response.send(data);
100 } catch (error) {124 } catch (error) {
@@ -310,6 +334,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
310 console.log('Stable Horde request:', request.body);334 console.log('Stable Horde request:', request.body);
311335
312 const ai_horde = await getHordeClient();336 const ai_horde = await getHordeClient();
337 // noinspection JSCheckFunctionSignatures -- see @ts-ignore - use_gfpgan
313 const generation = await ai_horde.postAsyncImageGenerate(338 const generation = await ai_horde.postAsyncImageGenerate(
314 {339 {
315 prompt: `${request.body.prompt} ### ${request.body.negative_prompt}`,340 prompt: `${request.body.prompt} ### ${request.body.negative_prompt}`,
src/endpoints/novelai.js+68 -13
@@ -15,6 +15,11 @@ const badWordsList = [
15 [19438], [43145], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115], [24],15 [19438], [43145], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115], [24],
16];16];
1717
18const eratoBadWordsList = [
19 [16067], [933, 11144], [25106, 11144], [58, 106901, 16073, 33710, 25, 109933],
20 [933, 58, 11144], [128030], [58, 30591, 33503, 17663, 100204, 25, 11144],
21];
22
18const hypeBotBadWordsList = [23const hypeBotBadWordsList = [
19 [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895],24 [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895],
20 [5512], [5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208],25 [5512], [5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208],
@@ -42,12 +47,24 @@ const repPenaltyAllowList = [
42 803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12],47 803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12],
43];48];
4449
50const eratoRepPenWhitelist = [
51 6, 1, 11, 13, 25, 198, 12, 9, 8, 279, 264, 459, 323, 477, 539, 912, 374, 574, 1051, 1550, 1587, 4536, 5828, 15058,
52 3287, 3250, 1461, 1077, 813, 11074, 872, 1202, 1436, 7846, 1288, 13434, 1053, 8434, 617, 9167, 1047, 19117, 706,
53 12775, 649, 4250, 527, 7784, 690, 2834, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 1210, 1359, 608, 220, 596, 956,
54 3077, 44886, 4265, 3358, 2351, 2846, 311, 389, 315, 304, 520, 505, 430
55];
56
45// Ban the dinkus and asterism57// Ban the dinkus and asterism
46const logitBiasExp = [58const logitBiasExp = [
47 { 'sequence': [23], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },59 { 'sequence': [23], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
48 { 'sequence': [21], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },60 { 'sequence': [21], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
49];61];
5062
63const eratoLogitBiasExp = [
64 { 'sequence': [12488], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
65 { 'sequence': [128041], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
66];
67
51function getBadWordsList(model) {68function getBadWordsList(model) {
52 let list = [];69 let list = [];
5370
@@ -59,10 +76,40 @@ function getBadWordsList(model) {
59 list = badWordsList;76 list = badWordsList;
60 }77 }
6178
79 if (model.includes('erato')) {
80 list = eratoBadWordsList;
81 }
82
62 // Clone the list so we don't modify the original83 // Clone the list so we don't modify the original
63 return list.slice();84 return list.slice();
64}85}
6586
87function getLogitBiasList(model) {
88 let list = [];
89
90 if (model.includes('erato')) {
91 list = eratoLogitBiasExp;
92 }
93
94 if (model.includes('clio') || model.includes('kayra')) {
95 list = logitBiasExp;
96 }
97
98 return list.slice();
99}
100
101function getRepPenaltyWhitelist(model) {
102 if (model.includes('clio') || model.includes('kayra')) {
103 return repPenaltyAllowList.flat();
104 }
105
106 if (model.includes('erato')) {
107 return eratoRepPenWhitelist.flat();
108 }
109
110 return null;
111}
112
66const router = express.Router();113const router = express.Router();
67114
68router.post('/status', jsonParser, async function (req, res) {115router.post('/status', jsonParser, async function (req, res) {
@@ -116,11 +163,10 @@ router.post('/generate', jsonParser, async function (req, res) {
116 controller.abort();163 controller.abort();
117 });164 });
118165
119 const isNewModel = (req.body.model.includes('clio') || req.body.model.includes('kayra'));166 // Add customized bad words for Clio, Kayra, and Erato
120 const badWordsList = getBadWordsList(req.body.model);167 const badWordsList = getBadWordsList(req.body.model);
121168
122 // Add customized bad words for Clio and Kayra169 if (Array.isArray(badWordsList) && Array.isArray(req.body.bad_words_ids)) {
123 if (isNewModel && Array.isArray(req.body.bad_words_ids)) {
124 for (const badWord of req.body.bad_words_ids) {170 for (const badWord of req.body.bad_words_ids) {
125 if (Array.isArray(badWord) && badWord.every(x => Number.isInteger(x))) {171 if (Array.isArray(badWord) && badWord.every(x => Number.isInteger(x))) {
126 badWordsList.push(badWord);172 badWordsList.push(badWord);
@@ -136,12 +182,14 @@ router.post('/generate', jsonParser, async function (req, res) {
136 }182 }
137183
138 // Add default biases for dinkus and asterism184 // Add default biases for dinkus and asterism
139 const logit_bias_exp = isNewModel ? logitBiasExp.slice() : [];185 const logitBiasList = getLogitBiasList(req.body.model);
140186
141 if (Array.isArray(logit_bias_exp) && Array.isArray(req.body.logit_bias_exp)) {187 if (Array.isArray(logitBiasList) && Array.isArray(req.body.logit_bias_exp)) {
142 logit_bias_exp.push(...req.body.logit_bias_exp);188 logitBiasList.push(...req.body.logit_bias_exp);
143 }189 }
144190
191 const repPenWhitelist = getRepPenaltyWhitelist(req.body.model);
192
145 const data = {193 const data = {
146 'input': req.body.input,194 'input': req.body.input,
147 'model': req.body.model,195 'model': req.body.model,
@@ -156,31 +204,38 @@ router.post('/generate', jsonParser, async function (req, res) {
156 'repetition_penalty_slope': req.body.repetition_penalty_slope,204 'repetition_penalty_slope': req.body.repetition_penalty_slope,
157 'repetition_penalty_frequency': req.body.repetition_penalty_frequency,205 'repetition_penalty_frequency': req.body.repetition_penalty_frequency,
158 'repetition_penalty_presence': req.body.repetition_penalty_presence,206 'repetition_penalty_presence': req.body.repetition_penalty_presence,
159 'repetition_penalty_whitelist': isNewModel ? repPenaltyAllowList.flat() : null,207 'repetition_penalty_whitelist': repPenWhitelist,
160 'top_a': req.body.top_a,208 'top_a': req.body.top_a,
161 'top_p': req.body.top_p,209 'top_p': req.body.top_p,
162 'top_k': req.body.top_k,210 'top_k': req.body.top_k,
163 'typical_p': req.body.typical_p,211 'typical_p': req.body.typical_p,
164 'mirostat_lr': req.body.mirostat_lr,212 'mirostat_lr': req.body.mirostat_lr,
165 'mirostat_tau': req.body.mirostat_tau,213 'mirostat_tau': req.body.mirostat_tau,
166 'cfg_scale': req.body.cfg_scale,
167 'cfg_uc': req.body.cfg_uc,
168 'phrase_rep_pen': req.body.phrase_rep_pen,214 'phrase_rep_pen': req.body.phrase_rep_pen,
169 'stop_sequences': req.body.stop_sequences,215 'stop_sequences': req.body.stop_sequences,
170 'bad_words_ids': badWordsList.length ? badWordsList : null,216 'bad_words_ids': badWordsList.length ? badWordsList : null,
171 'logit_bias_exp': logit_bias_exp,217 'logit_bias_exp': logitBiasList,
172 'generate_until_sentence': req.body.generate_until_sentence,218 'generate_until_sentence': req.body.generate_until_sentence,
173 'use_cache': req.body.use_cache,219 'use_cache': req.body.use_cache,
174 'return_full_text': req.body.return_full_text,220 'return_full_text': req.body.return_full_text,
175 'prefix': req.body.prefix,221 'prefix': req.body.prefix,
176 'order': req.body.order,222 'order': req.body.order,
177 'num_logprobs': req.body.num_logprobs,223 'num_logprobs': req.body.num_logprobs,
224 'min_p': req.body.min_p,
225 'math1_temp': req.body.math1_temp,
226 'math1_quad': req.body.math1_quad,
227 'math1_quad_entropy_scale': req.body.math1_quad_entropy_scale,
178 },228 },
179 };229 };
180230
181 // Tells the model to stop generation at '>'231 // Tells the model to stop generation at '>'
182 if ('theme_textadventure' === req.body.prefix && isNewModel) {232 if ('theme_textadventure' === req.body.prefix) {
183 data.parameters.eos_token_id = 49405;233 if (req.body.model.includes('clio') || req.body.model.includes('kayra')) {
234 data.parameters.eos_token_id = 49405;
235 }
236 if (req.body.model.includes('erato')) {
237 data.parameters.eos_token_id = 29;
238 }
184 }239 }
185240
186 console.log(util.inspect(data, { depth: 4 }));241 console.log(util.inspect(data, { depth: 4 }));
@@ -192,7 +247,7 @@ router.post('/generate', jsonParser, async function (req, res) {
192 };247 };
193248
194 try {249 try {
195 const baseURL = req.body.model.includes('kayra') ? TEXT_NOVELAI : API_NOVELAI;250 const baseURL = (req.body.model.includes('kayra') || req.body.model.includes('erato')) ? TEXT_NOVELAI : API_NOVELAI;
196 const url = req.body.streaming ? `${baseURL}/ai/generate-stream` : `${baseURL}/ai/generate`;251 const url = req.body.streaming ? `${baseURL}/ai/generate-stream` : `${baseURL}/ai/generate`;
197 const response = await fetch(url, { method: 'POST', timeout: 0, ...args });252 const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
198253
src/endpoints/openai.js+8 -0
@@ -51,6 +51,10 @@ router.post('/caption-image', jsonParser, async (request, response) => {
51 key = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);51 key = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
52 }52 }
5353
54 if (request.body.api === 'mistral') {
55 key = readSecret(request.user.directories, SECRET_KEYS.MISTRALAI);
56 }
57
54 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {58 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
55 console.log('No key found for API', request.body.api);59 console.log('No key found for API', request.body.api);
56 return response.sendStatus(400);60 return response.sendStatus(400);
@@ -107,6 +111,10 @@ router.post('/caption-image', jsonParser, async (request, response) => {
107 apiUrl = 'https://api.01.ai/v1/chat/completions';111 apiUrl = 'https://api.01.ai/v1/chat/completions';
108 }112 }
109113
114 if (request.body.api === 'mistral') {
115 apiUrl = 'https://api.mistral.ai/v1/chat/completions';
116 }
117
110 if (request.body.api === 'ooba') {118 if (request.body.api === 'ooba') {
111 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;119 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
112 const imgMessage = body.messages.pop();120 const imgMessage = body.messages.pop();
src/endpoints/openrouter.js+32 -0
@@ -0,0 +1,32 @@
1const express = require('express');
2const { jsonParser } = require('../express-common');
3
4const router = express.Router();
5const API_OPENROUTER = 'https://openrouter.ai/api/v1';
6
7router.post('/models/multimodal', jsonParser, async (_req, res) => {
8 try {
9 // The endpoint is available without authentication
10 const response = await fetch(`${API_OPENROUTER}/models`, {
11 method: 'GET',
12 headers: {
13 'Accept': 'application/json',
14 },
15 });
16
17 if (!response.ok) {
18 return res.json([]);
19 }
20
21 const data = await response.json();
22 const models = data?.data || [];
23 const multimodalModels = models.filter(m => m?.architecture?.modality === 'text+image->text').map(m => m.id);
24
25 return res.json(multimodalModels);
26 } catch (error) {
27 console.error(error);
28 return res.sendStatus(500);
29 }
30});
31
32module.exports = { router };
src/endpoints/presets.js+2 -0
@@ -27,6 +27,8 @@ function getPresetSettingsByAPI(apiId, directories) {
27 return { folder: directories.instruct, extension: '.json' };27 return { folder: directories.instruct, extension: '.json' };
28 case 'context':28 case 'context':
29 return { folder: directories.context, extension: '.json' };29 return { folder: directories.context, extension: '.json' };
30 case 'sysprompt':
31 return { folder: directories.sysprompt, extension: '.json' };
30 default:32 default:
31 return { folder: null, extension: null };33 return { folder: null, extension: null };
32 }34 }
src/endpoints/settings.js+2 -0
@@ -251,6 +251,7 @@ router.post('/get', jsonParser, (request, response) => {
251251
252 const instruct = readAndParseFromDirectory(request.user.directories.instruct);252 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
253 const context = readAndParseFromDirectory(request.user.directories.context);253 const context = readAndParseFromDirectory(request.user.directories.context);
254 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
254255
255 response.send({256 response.send({
256 settings,257 settings,
@@ -268,6 +269,7 @@ router.post('/get', jsonParser, (request, response) => {
268 quickReplyPresets,269 quickReplyPresets,
269 instruct,270 instruct,
270 context,271 context,
272 sysprompt,
271 enable_extensions: ENABLE_EXTENSIONS,273 enable_extensions: ENABLE_EXTENSIONS,
272 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,274 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
273 enable_accounts: ENABLE_ACCOUNTS,275 enable_accounts: ENABLE_ACCOUNTS,
src/endpoints/tokenizers.js+24 -0
@@ -221,6 +221,7 @@ const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json');
221const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');221const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');
222const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');222const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');
223const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');223const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');
224const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json');
224225
225const sentencepieceTokenizers = [226const sentencepieceTokenizers = [
226 'llama',227 'llama',
@@ -418,6 +419,10 @@ function getTokenizerModel(requestModel) {
418 return 'command-r';419 return 'command-r';
419 }420 }
420421
422 if (requestModel.includes('nemo')) {
423 return 'nemo';
424 }
425
421 // default426 // default
422 return 'gpt-3.5-turbo';427 return 'gpt-3.5-turbo';
423}428}
@@ -645,6 +650,7 @@ router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(clau
645router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer));650router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer));
646router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));651router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));
647router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));652router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));
653router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer));
648router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));654router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));
649router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));655router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));
650router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));656router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));
@@ -657,6 +663,7 @@ router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(clau
657router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer));663router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer));
658router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));664router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));
659router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));665router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));
666router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer));
660667
661router.post('/openai/encode', jsonParser, async function (req, res) {668router.post('/openai/encode', jsonParser, async function (req, res) {
662 try {669 try {
@@ -707,6 +714,11 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
707 return handler(req, res);714 return handler(req, res);
708 }715 }
709716
717 if (queryModel.includes('nemo')) {
718 const handler = createWebTokenizerEncodingHandler(nemoTokenizer);
719 return handler(req, res);
720 }
721
710 const model = getTokenizerModel(queryModel);722 const model = getTokenizerModel(queryModel);
711 const handler = createTiktokenEncodingHandler(model);723 const handler = createTiktokenEncodingHandler(model);
712 return handler(req, res);724 return handler(req, res);
@@ -765,6 +777,11 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
765 return handler(req, res);777 return handler(req, res);
766 }778 }
767779
780 if (queryModel.includes('nemo')) {
781 const handler = createWebTokenizerDecodingHandler(nemoTokenizer);
782 return handler(req, res);
783 }
784
768 const model = getTokenizerModel(queryModel);785 const model = getTokenizerModel(queryModel);
769 const handler = createTiktokenDecodingHandler(model);786 const handler = createTiktokenDecodingHandler(model);
770 return handler(req, res);787 return handler(req, res);
@@ -835,6 +852,13 @@ router.post('/openai/count', jsonParser, async function (req, res) {
835 return res.send({ 'token_count': num_tokens });852 return res.send({ 'token_count': num_tokens });
836 }853 }
837854
855 if (model === 'nemo') {
856 const instance = await nemoTokenizer.get();
857 if (!instance) throw new Error('Failed to load the Nemo tokenizer');
858 num_tokens = countWebTokenizerTokens(instance, req.body);
859 return res.send({ 'token_count': num_tokens });
860 }
861
838 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;862 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;
839 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;863 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;
840 const tokensPadding = 3;864 const tokensPadding = 3;
src/endpoints/vectors.js+105 -88
@@ -4,6 +4,7 @@ const fs = require('fs');
4const express = require('express');4const express = require('express');
5const sanitize = require('sanitize-filename');5const sanitize = require('sanitize-filename');
6const { jsonParser } = require('../express-common');6const { jsonParser } = require('../express-common');
7const { getConfigValue, color } = require('../util');
78
8// Don't forget to add new sources to the SOURCES array9// Don't forget to add new sources to the SOURCES array
9const SOURCES = [10const SOURCES = [
@@ -110,18 +111,94 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
110}111}
111112
112/**113/**
114 * Extracts settings for the vectorization sources from the HTTP request headers.
115 * @param {string} source - Which source to extract settings for.
116 * @param {object} request - The HTTP request object.
117 * @returns {object} - An object that can be used as `sourceSettings` in functions that take that parameter.
118 */
119function getSourceSettings(source, request) {
120 switch (source) {
121 case 'togetherai':
122 return {
123 model: String(request.headers['x-togetherai-model']),
124 };
125 case 'openai':
126 return {
127 model: String(request.headers['x-openai-model']),
128 };
129 case 'cohere':
130 return {
131 model: String(request.headers['x-cohere-model']),
132 };
133 case 'llamacpp':
134 return {
135 apiUrl: String(request.headers['x-llamacpp-url']),
136 };
137 case 'vllm':
138 return {
139 apiUrl: String(request.headers['x-vllm-url']),
140 model: String(request.headers['x-vllm-model']),
141 };
142 case 'ollama':
143 return {
144 apiUrl: String(request.headers['x-ollama-url']),
145 model: String(request.headers['x-ollama-model']),
146 keep: Boolean(request.headers['x-ollama-keep']),
147 };
148 case 'extras':
149 return {
150 extrasUrl: String(request.headers['x-extras-url']),
151 extrasKey: String(request.headers['x-extras-key']),
152 };
153 case 'local':
154 return {
155 model: getConfigValue('extras.embeddingModel', ''),
156 };
157 case 'palm':
158 return {
159 // TODO: Add support for multiple models
160 model: 'text-embedding-004',
161 };
162 default:
163 return {};
164 }
165}
166
167/**
168 * Gets the model scope for the source.
169 * @param {object} sourceSettings - The settings for the source
170 * @returns {string} The model scope for the source
171 */
172function getModelScope(sourceSettings) {
173 const scopesEnabled = getConfigValue('vectors.enableModelScopes', false);
174 const warningShown = global.process.env.VECTORS_MODEL_SCOPE_WARNING_SHOWN === 'true';
175
176 if (!scopesEnabled && !warningShown) {
177 console.log();
178 console.warn(color.red('[DEPRECATION NOTICE]'), 'Model scopes for Vectore Storage are disabled, but will soon be required.');
179 console.log(`To enable model scopes, set the ${color.cyan('vectors.enableModelScopes')} in config.yaml to ${color.green(true)}.`);
180 console.log('This message won\'t be shown again in the current session.');
181 console.log();
182 global.process.env.VECTORS_MODEL_SCOPE_WARNING_SHOWN = 'true';
183 }
184
185 return scopesEnabled ? (sourceSettings?.model || '') : '';
186}
187
188/**
113 * Gets the index for the vector collection189 * Gets the index for the vector collection
114 * @param {import('../users').UserDirectoryList} directories - User directories190 * @param {import('../users').UserDirectoryList} directories - User directories
115 * @param {string} collectionId - The collection ID191 * @param {string} collectionId - The collection ID
116 * @param {string} source - The source of the vector192 * @param {string} source - The source of the vector
117 * @param {boolean} create - Whether to create the index if it doesn't exist193 * @param {object} sourceSettings - The model for the source
118 * @returns {Promise<vectra.LocalIndex>} - The index for the collection194 * @returns {Promise<vectra.LocalIndex>} - The index for the collection
119 */195 */
120async function getIndex(directories, collectionId, source, create = true) {196async function getIndex(directories, collectionId, source, sourceSettings) {
121 const pathToFile = path.join(directories.vectors, sanitize(source), sanitize(collectionId));197 const model = getModelScope(sourceSettings);
198 const pathToFile = path.join(directories.vectors, sanitize(source), sanitize(collectionId), sanitize(model));
122 const store = new vectra.LocalIndex(pathToFile);199 const store = new vectra.LocalIndex(pathToFile);
123200
124 if (create && !await store.isIndexCreated()) {201 if (!await store.isIndexCreated()) {
125 await store.createIndex();202 await store.createIndex();
126 }203 }
127204
@@ -137,7 +214,7 @@ async function getIndex(directories, collectionId, source, create = true) {
137 * @param {{ hash: number; text: string; index: number; }[]} items - The items to insert214 * @param {{ hash: number; text: string; index: number; }[]} items - The items to insert
138 */215 */
139async function insertVectorItems(directories, collectionId, source, sourceSettings, items) {216async function insertVectorItems(directories, collectionId, source, sourceSettings, items) {
140 const store = await getIndex(directories, collectionId, source);217 const store = await getIndex(directories, collectionId, source, sourceSettings);
141218
142 await store.beginUpdate();219 await store.beginUpdate();
143220
@@ -157,10 +234,11 @@ async function insertVectorItems(directories, collectionId, source, sourceSettin
157 * @param {import('../users').UserDirectoryList} directories - User directories234 * @param {import('../users').UserDirectoryList} directories - User directories
158 * @param {string} collectionId - The collection ID235 * @param {string} collectionId - The collection ID
159 * @param {string} source - The source of the vector236 * @param {string} source - The source of the vector
237 * @param {Object} sourceSettings - Settings for the source, if it needs any
160 * @returns {Promise<number[]>} - The hashes of the items in the collection238 * @returns {Promise<number[]>} - The hashes of the items in the collection
161 */239 */
162async function getSavedHashes(directories, collectionId, source) {240async function getSavedHashes(directories, collectionId, source, sourceSettings) {
163 const store = await getIndex(directories, collectionId, source);241 const store = await getIndex(directories, collectionId, source, sourceSettings);
164242
165 const items = await store.listItems();243 const items = await store.listItems();
166 const hashes = items.map(x => Number(x.metadata.hash));244 const hashes = items.map(x => Number(x.metadata.hash));
@@ -173,10 +251,11 @@ async function getSavedHashes(directories, collectionId, source) {
173 * @param {import('../users').UserDirectoryList} directories - User directories251 * @param {import('../users').UserDirectoryList} directories - User directories
174 * @param {string} collectionId - The collection ID252 * @param {string} collectionId - The collection ID
175 * @param {string} source - The source of the vector253 * @param {string} source - The source of the vector
254 * @param {Object} sourceSettings - Settings for the source, if it needs any
176 * @param {number[]} hashes - The hashes of the items to delete255 * @param {number[]} hashes - The hashes of the items to delete
177 */256 */
178async function deleteVectorItems(directories, collectionId, source, hashes) {257async function deleteVectorItems(directories, collectionId, source, sourceSettings, hashes) {
179 const store = await getIndex(directories, collectionId, source);258 const store = await getIndex(directories, collectionId, source, sourceSettings);
180 const items = await store.listItemsByMetadata({ hash: { '$in': hashes } });259 const items = await store.listItemsByMetadata({ hash: { '$in': hashes } });
181260
182 await store.beginUpdate();261 await store.beginUpdate();
@@ -200,7 +279,7 @@ async function deleteVectorItems(directories, collectionId, source, hashes) {
200 * @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text279 * @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text
201 */280 */
202async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {281async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {
203 const store = await getIndex(directories, collectionId, source);282 const store = await getIndex(directories, collectionId, source, sourceSettings);
204 const vector = await getVector(source, sourceSettings, searchText, true, directories);283 const vector = await getVector(source, sourceSettings, searchText, true, directories);
205284
206 const result = await store.queryItems(vector, topK);285 const result = await store.queryItems(vector, topK);
@@ -226,7 +305,7 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
226 const results = [];305 const results = [];
227306
228 for (const collectionId of collectionIds) {307 for (const collectionId of collectionIds) {
229 const store = await getIndex(directories, collectionId, source);308 const store = await getIndex(directories, collectionId, source, sourceSettings);
230 const result = await store.queryItems(vector, topK);309 const result = await store.queryItems(vector, topK);
231 results.push(...result.map(result => ({ collectionId, result })));310 results.push(...result.map(result => ({ collectionId, result })));
232 }311 }
@@ -255,71 +334,6 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
255}334}
256335
257/**336/**
258 * Extracts settings for the vectorization sources from the HTTP request headers.
259 * @param {string} source - Which source to extract settings for.
260 * @param {object} request - The HTTP request object.
261 * @returns {object} - An object that can be used as `sourceSettings` in functions that take that parameter.
262 */
263function getSourceSettings(source, request) {
264 if (source === 'togetherai') {
265 const model = String(request.headers['x-togetherai-model']);
266
267 return {
268 model: model,
269 };
270 } else if (source === 'openai') {
271 const model = String(request.headers['x-openai-model']);
272
273 return {
274 model: model,
275 };
276 } else if (source === 'cohere') {
277 const model = String(request.headers['x-cohere-model']);
278
279 return {
280 model: model,
281 };
282 } else if (source === 'llamacpp') {
283 const apiUrl = String(request.headers['x-llamacpp-url']);
284
285 return {
286 apiUrl: apiUrl,
287 };
288 } else if (source === 'vllm') {
289 const apiUrl = String(request.headers['x-vllm-url']);
290 const model = String(request.headers['x-vllm-model']);
291
292 return {
293 apiUrl: apiUrl,
294 model: model,
295 };
296 } else if (source === 'ollama') {
297 const apiUrl = String(request.headers['x-ollama-url']);
298 const model = String(request.headers['x-ollama-model']);
299 const keep = Boolean(request.headers['x-ollama-keep']);
300
301 return {
302 apiUrl: apiUrl,
303 model: model,
304 keep: keep,
305 };
306 } else {
307 // Extras API settings to connect to the Extras embeddings provider
308 let extrasUrl = '';
309 let extrasKey = '';
310 if (source === 'extras') {
311 extrasUrl = String(request.headers['x-extras-url']);
312 extrasKey = String(request.headers['x-extras-key']);
313 }
314
315 return {
316 extrasUrl: extrasUrl,
317 extrasKey: extrasKey,
318 };
319 }
320}
321
322/**
323 * Performs a request to regenerate the index if it is corrupted.337 * Performs a request to regenerate the index if it is corrupted.
324 * @param {import('express').Request} req Express request object338 * @param {import('express').Request} req Express request object
325 * @param {import('express').Response} res Express response object339 * @param {import('express').Response} res Express response object
@@ -330,9 +344,10 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
330 if (error instanceof SyntaxError && !req.query.regenerated) {344 if (error instanceof SyntaxError && !req.query.regenerated) {
331 const collectionId = String(req.body.collectionId);345 const collectionId = String(req.body.collectionId);
332 const source = String(req.body.source) || 'transformers';346 const source = String(req.body.source) || 'transformers';
347 const sourceSettings = getSourceSettings(source, req);
333348
334 if (collectionId && source) {349 if (collectionId && source) {
335 const index = await getIndex(req.user.directories, collectionId, source, false);350 const index = await getIndex(req.user.directories, collectionId, source, sourceSettings);
336 const exists = await index.isIndexCreated();351 const exists = await index.isIndexCreated();
337352
338 if (exists) {353 if (exists) {
@@ -350,6 +365,11 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
350365
351const router = express.Router();366const router = express.Router();
352367
368router.get('/scopes-enabled', (_req, res) => {
369 const scopesEnabled = getConfigValue('vectors.enableModelScopes', false);
370 return res.json({ enabled: scopesEnabled });
371});
372
353router.post('/query', jsonParser, async (req, res) => {373router.post('/query', jsonParser, async (req, res) => {
354 try {374 try {
355 if (!req.body.collectionId || !req.body.searchText) {375 if (!req.body.collectionId || !req.body.searchText) {
@@ -416,8 +436,9 @@ router.post('/list', jsonParser, async (req, res) => {
416436
417 const collectionId = String(req.body.collectionId);437 const collectionId = String(req.body.collectionId);
418 const source = String(req.body.source) || 'transformers';438 const source = String(req.body.source) || 'transformers';
439 const sourceSettings = getSourceSettings(source, req);
419440
420 const hashes = await getSavedHashes(req.user.directories, collectionId, source);441 const hashes = await getSavedHashes(req.user.directories, collectionId, source, sourceSettings);
421 return res.json(hashes);442 return res.json(hashes);
422 } catch (error) {443 } catch (error) {
423 return regenerateCorruptedIndexErrorHandler(req, res, error);444 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -433,8 +454,9 @@ router.post('/delete', jsonParser, async (req, res) => {
433 const collectionId = String(req.body.collectionId);454 const collectionId = String(req.body.collectionId);
434 const hashes = req.body.hashes.map(x => Number(x));455 const hashes = req.body.hashes.map(x => Number(x));
435 const source = String(req.body.source) || 'transformers';456 const source = String(req.body.source) || 'transformers';
457 const sourceSettings = getSourceSettings(source, req);
436458
437 await deleteVectorItems(req.user.directories, collectionId, source, hashes);459 await deleteVectorItems(req.user.directories, collectionId, source, sourceSettings, hashes);
438 return res.sendStatus(200);460 return res.sendStatus(200);
439 } catch (error) {461 } catch (error) {
440 return regenerateCorruptedIndexErrorHandler(req, res, error);462 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -468,17 +490,12 @@ router.post('/purge', jsonParser, async (req, res) => {
468 const collectionId = String(req.body.collectionId);490 const collectionId = String(req.body.collectionId);
469491
470 for (const source of SOURCES) {492 for (const source of SOURCES) {
471 const index = await getIndex(req.user.directories, collectionId, source, false);493 const sourcePath = path.join(req.user.directories.vectors, sanitize(source), sanitize(collectionId));
472494 if (!fs.existsSync(sourcePath)) {
473 const exists = await index.isIndexCreated();
474
475 if (!exists) {
476 continue;495 continue;
477 }496 }
478497 await fs.promises.rm(sourcePath, { recursive: true });
479 const path = index.folderPath;498 console.log(`Deleted vector index at ${sourcePath}`);
480 await index.deleteIndex();
481 console.log(`Deleted vector index at ${path}`);
482 }499 }
483500
484 return res.sendStatus(200);501 return res.sendStatus(200);
src/prompt-converters.js+3 -0
@@ -267,11 +267,14 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
267 'gemini-1.5-flash',267 'gemini-1.5-flash',
268 'gemini-1.5-flash-latest',268 'gemini-1.5-flash-latest',
269 'gemini-1.5-flash-001',269 'gemini-1.5-flash-001',
270 'gemini-1.5-flash-002',
270 'gemini-1.5-flash-exp-0827',271 'gemini-1.5-flash-exp-0827',
271 'gemini-1.5-flash-8b-exp-0827',272 'gemini-1.5-flash-8b-exp-0827',
273 'gemini-1.5-flash-8b-exp-0924',
272 'gemini-1.5-pro',274 'gemini-1.5-pro',
273 'gemini-1.5-pro-latest',275 'gemini-1.5-pro-latest',
274 'gemini-1.5-pro-001',276 'gemini-1.5-pro-001',
277 'gemini-1.5-pro-002',
275 'gemini-1.5-pro-exp-0801',278 'gemini-1.5-pro-exp-0801',
276 'gemini-1.5-pro-exp-0827',279 'gemini-1.5-pro-exp-0827',
277 'gemini-1.0-pro-vision-latest',280 'gemini-1.0-pro-vision-latest',
src/users.js+63 -4
@@ -9,8 +9,10 @@ const storage = require('node-persist');
9const express = require('express');9const express = require('express');
10const mime = require('mime-types');10const mime = require('mime-types');
11const archiver = require('archiver');11const archiver = require('archiver');
12const writeFileAtomicSync = require('write-file-atomic').sync;
13const _ = require('lodash');
1214
13const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, DEFAULT_AVATAR, SETTINGS_FILE } = require('./constants');15const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } = require('./constants');
14const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util');16const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util');
15const { readSecret, writeSecret } = require('./endpoints/secrets');17const { readSecret, writeSecret } = require('./endpoints/secrets');
1618
@@ -24,6 +26,7 @@ const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
24 * @type {Map<string, UserDirectoryList>}26 * @type {Map<string, UserDirectoryList>}
25 */27 */
26const DIRECTORIES_CACHE = new Map();28const DIRECTORIES_CACHE = new Map();
29const PUBLIC_USER_AVATAR = '/img/default-user.png';
2730
28const STORAGE_KEYS = {31const STORAGE_KEYS = {
29 csrfSecret: 'csrfSecret',32 csrfSecret: 'csrfSecret',
@@ -82,6 +85,7 @@ const STORAGE_KEYS = {
82 * @property {string} files - The directory where the uploaded files are stored85 * @property {string} files - The directory where the uploaded files are stored
83 * @property {string} vectors - The directory where the vectors are stored86 * @property {string} vectors - The directory where the vectors are stored
84 * @property {string} backups - The directory where the backups are stored87 * @property {string} backups - The directory where the backups are stored
88 * @property {string} sysprompt - The directory where the system prompt data is stored
85 */89 */
8690
87/**91/**
@@ -322,6 +326,60 @@ async function migrateUserData() {
322 console.log(color.green('Migration completed!'));326 console.log(color.green('Migration completed!'));
323}327}
324328
329async function migrateSystemPrompts() {
330 /**
331 * Gets the default system prompts.
332 * @returns {Promise<any[]>} - The list of default system prompts
333 */
334 async function getDefaultSystemPrompts() {
335 try {
336 const { getContentOfType } = await import('./endpoints/content-manager.js');
337 return getContentOfType('sysprompt', 'json');
338 } catch {
339 return [];
340 }
341 }
342
343 const directories = await getUserDirectoriesList();
344 for (const directory of directories) {
345 try {
346 const migrateMarker = path.join(directory.sysprompt, '.migrated');
347 if (fs.existsSync(migrateMarker)) {
348 continue;
349 }
350 const defaultPrompts = await getDefaultSystemPrompts();
351 const instucts = fs.readdirSync(directory.instruct);
352 let migratedPrompts = [];
353 for (const instruct of instucts) {
354 const instructPath = path.join(directory.instruct, instruct);
355 const sysPromptPath = path.join(directory.sysprompt, instruct);
356 if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) {
357 const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8'));
358 if ('system_prompt' in instructData && 'name' in instructData) {
359 const syspromptData = { name: instructData.name, content: instructData.system_prompt };
360 migratedPrompts.push(syspromptData);
361 delete instructData.system_prompt;
362 writeFileAtomicSync(instructPath, JSON.stringify(instructData, null, 4));
363 }
364 }
365 }
366 // Only leave unique contents
367 migratedPrompts = _.uniqBy(migratedPrompts, 'content');
368 // Only leave contents that are not in the default prompts
369 migratedPrompts = migratedPrompts.filter(x => !defaultPrompts.some(y => y.content === x.content));
370 for (const sysPromptData of migratedPrompts) {
371 sysPromptData.name = `[Migrated] ${sysPromptData.name}`;
372 const syspromptPath = path.join(directory.sysprompt, `${sysPromptData.name}.json`);
373 writeFileAtomicSync(syspromptPath, JSON.stringify(sysPromptData, null, 4));
374 console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`);
375 }
376 writeFileAtomicSync(migrateMarker, '');
377 } catch {
378 // Ignore errors
379 }
380 }
381}
382
325/**383/**
326 * Converts a user handle to a storage key.384 * Converts a user handle to a storage key.
327 * @param {string} handle User handle385 * @param {string} handle User handle
@@ -478,11 +536,11 @@ async function getUserAvatar(handle) {
478 const settings = fs.existsSync(pathToSettings) ? JSON.parse(fs.readFileSync(pathToSettings, 'utf8')) : {};536 const settings = fs.existsSync(pathToSettings) ? JSON.parse(fs.readFileSync(pathToSettings, 'utf8')) : {};
479 const avatarFile = settings?.power_user?.default_persona || settings?.user_avatar;537 const avatarFile = settings?.power_user?.default_persona || settings?.user_avatar;
480 if (!avatarFile) {538 if (!avatarFile) {
481 return DEFAULT_AVATAR;539 return PUBLIC_USER_AVATAR;
482 }540 }
483 const avatarPath = path.join(directory.avatars, avatarFile);541 const avatarPath = path.join(directory.avatars, avatarFile);
484 if (!fs.existsSync(avatarPath)) {542 if (!fs.existsSync(avatarPath)) {
485 return DEFAULT_AVATAR;543 return PUBLIC_USER_AVATAR;
486 }544 }
487 const mimeType = mime.lookup(avatarPath);545 const mimeType = mime.lookup(avatarPath);
488 const base64Content = fs.readFileSync(avatarPath, 'base64');546 const base64Content = fs.readFileSync(avatarPath, 'base64');
@@ -490,7 +548,7 @@ async function getUserAvatar(handle) {
490 }548 }
491 catch {549 catch {
492 // Ignore errors550 // Ignore errors
493 return DEFAULT_AVATAR;551 return PUBLIC_USER_AVATAR;
494 }552 }
495}553}
496554
@@ -723,6 +781,7 @@ module.exports = {
723 requireLoginMiddleware,781 requireLoginMiddleware,
724 requireAdminMiddleware,782 requireAdminMiddleware,
725 migrateUserData,783 migrateUserData,
784 migrateSystemPrompts,
726 getPasswordSalt,785 getPasswordSalt,
727 getPasswordHash,786 getPasswordHash,
728 getCsrfSecret,787 getCsrfSecret,
start.sh+1 -1
@@ -26,7 +26,7 @@ fi
2626
27echo "Installing Node Modules..."27echo "Installing Node Modules..."
28export NODE_ENV=production28export NODE_ENV=production
29npm i --no-audit --no-fund --quiet --omit=dev29npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev
3030
31echo "Entering SillyTavern..."31echo "Entering SillyTavern..."
32node "server.js" "$@"32node "server.js" "$@"