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

8c39e20ed911bc010369090efb8acfba64855ab6

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

167 files changed, +3101 -1472Showing whitespace changes
.github/readme-ja_jp.md+17 -3
@@ -1,9 +1,20 @@
11> [!IMPORTANT]
22> ここに掲載されている情報は、古かったり不完全であったりする可能性があります。最新の情報は英語版をご利用ください。
33
4+![][cover]
5+
6+<div align="center">
7+
48[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
819モバイルデバイスにも適したレイアウト・マルチAPI(KoboldAI/CPP、Horde、NovelAI、Ooba、OpenAI、OpenRouter、Claude、Scale)、VN ライクな Waifu モード、Stable Diffusion、TTS、WorldInfo(伝承本)、カスタマイズ可能なUI、自動翻訳、大量のプロンプトオプション+サードパーティの拡張機能をインストールする機能
920
@@ -284,8 +295,8 @@ ST サーバを再起動すると、ユーザ名とパスワードさえ知っ
284295
285296## スクリーンショット
286297
287298<img width="400500" alt="image" src="https://user-images.githubusercontentgithub.com/18619528/228649245user-8061c60fattachments/assets/9b5f32f0-63dcc3b3-488e4102-9325b3f5-f151b7a3ec2d.png0e9213c0f50f">
288299<img width="400500" alt="image" src="https://user-images.githubusercontentgithub.com/18619528/228649856user-fbdeef05attachments/assets/913fdbaa-d7277d33-4d5a42f1-be80ae2c-266cbbc6b811.png89dca41c53d1">
289300
290301## ライセンスとクレジット
291302
@@ -314,3 +325,6 @@ ST サーバを再起動すると、ユーザ名とパスワードさえ知っ
314325* @doloroushyeonse による韓国語翻訳
315326* k_euler_a による Horde のサポート <https://github.com/Teashrock>
316327* [@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
44<a name="readme-top"></a>
55
6+![][cover]
7+
8+<div align="center">
9+
610[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
1021Мобайл-френдли интерфейс, поддержка множества 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
3041## Скриншоты
3142
3243<img width="400500" alt="image" src="https://github.com/SillyTavern/SillyTavernuser-attachments/assets/61471128/e902c7a29b5f32f0-45a6c3b3-44154102-97aab3f5-c59c597669c10e9213c0f50f">
3344<img width="400500" alt="image" src="https://github.com/SillyTavern/SillyTavernuser-attachments/assets/61471128/f8a79c47913fdbaa-4fe97d33-456442f1-9e4aae2c-bf247ed1c96189dca41c53d1">
3445
3546### Ветки
3647
@@ -355,7 +366,7 @@ SillyTavern сохраняет ключи от ваших API в файле `sec
355366
356367<!-- LINK GROUP -->
357368[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square
358369[cover]: https://github.com/SillyTavern/SillyTavernuser-attachments/assets/18619528/c2be4c3f01a6ae9a-aada16aa-4f6445f2-87a38bff-ae35a68b61a432b5dc587e44
359370[discord-link]: https://discord.gg/sillytavern
360371[discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
361372[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 @@
11> [!IMPORTANT]
22> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。
33
4+![][cover]
5+
6+<div align="center">
7+
48[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
819移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。
920
@@ -278,8 +289,8 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
278289
279290## 屏幕截图
280291
281292<img width="400500" alt="image" src="https://user-images.githubusercontentgithub.com/18619528/228649245user-8061c60fattachments/assets/9b5f32f0-63dcc3b3-488e4102-9325b3f5-f151b7a3ec2d.png0e9213c0f50f">
282293<img width="400500" alt="image" src="https://user-images.githubusercontentgithub.com/18619528/228649856user-fbdeef05attachments/assets/913fdbaa-d7277d33-4d5a42f1-be80ae2c-266cbbc6b811.png89dca41c53d1">
283294
284295## 许可证和贡献
285296
@@ -306,3 +317,6 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
306317* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
307318* Korean translation by @doloroushyeonse
308319* 中文翻译由 [@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+162 -242
@@ -1,76 +1,103 @@
11<a name="readme-top"></a>
22
3-English | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
4-
53![][cover]
64
7-Mobile-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
9-Based on a fork of [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8
7+English | [中文](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
13-1. We have created a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.
14+</div>
1415
15-2. 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
17-3. 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.
18+SillyTavern 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 community
20+We have a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.
2021
2122### What is SillyTavern or TavernAI?
2223
2324SillyTavern is a user interface(or youST canfor installshort) onis youra computerlocally (andinstalled Androiduser phones)interface that allows you to interact with text generation AIs and chat/roleplayLLMs, withimage charactersgeneration youengines, orand theTTS communityvoice createmodels.
2425
2526SillyTavernBeginning isin February 2023 as a fork of TavernAI 1.2.8, whichSillyTavern isnow underhas moreover active100 developmentcontributors and has2 addedyears manyof majorindependent features.development Atunder thisits pointbelt, they canand becontinues thoughtto ofserve as completelya independentleading programssoftware for savvy AI hobbyists.
2627
2728## ScreenshotsOur Vision
2829
29-<img width="400" alt="image" src="https://github.com/SillyTavern/SillyTavern/assets/61471128/e902c7a2-45a6-4415-97aa-c59c597669c1">
30+1. 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">
31+2. We do not provide any online or hosted services, nor programmatically track any user data.
32+3. SillyTavern is a passion project brought to you by a dedicated community of LLM enthusiasts, and will always be free and open sourced.
3133
3234### Branches
3335
3436SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users.
3537
3638* `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.
3739* `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
3941If 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
4143### What do I need other than SillyTavern?
4244
4345On its ownSince SillyTavern is useless, as it's just aonly useran interface. You, haveyou towill haveneed access to an AI systemLLM backend thatto provide inference. You can actuse asAI theHorde roleplayfor characterinstant out-of-the-box chatting. ThereAside arefrom variousthat, supportedwe backends:support OpenAPImany APIother (GPT),local KoboldAIand (eithercloud-based runningLLM locallybackends: orOpenAI-compatible onAPI, GoogleKoboldAI, Colab)Tabby, and many more. You can read more about thisour supported APIs in [the FAQ](https://docs.sillytavern.app/usage/faqapi-connections/).
4446
4547### Do I need a powerful PC to run SillyTavern?
4648
47-Since 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.
49+The 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
4958## Questions or suggestions?
5059
5160### We now have a community Discord server
5261
5362| [![][discord-shield-badge]][discord-link] | [Join our Discord community!](https://discord.gg/sillytavern) Get support, share favorite characters and prompts. |
5463| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
5564
5665Or get in touch with the developers directly:
5766
5867* Discord: cohee or, rossascends, wolfsblvt
5968* Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/) or, [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/)
6069* [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues)
6170
62-## This version includes
71+### I like your project! How do I contribute?
72+
73+1. Send pull requests. Learn how to contribute: [CONTRIBUTING.md](../CONTRIBUTING.md)
74+2. Send feature suggestions and issue reports using the provided templates.
75+3. 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+
84+SillyTavern 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+
86+The 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)
88+To 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-* Swipes
89+
66-* Group chats: multi-bot rooms for characters to talk to you or each other
90+To 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 / branching
91+
68-* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
92+## Key Features
93+
94+* Advanced text generation settings with many community-made presets
6995* 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 connection
97+* Rich UI customization options: theme colors, background images, custom CSS, and more
72-* [AI Horde](https://aihorde.net/) connection
98+* User personas: let the AI know a bit about you for greater immersion
73-* Prompt generation formatting tweaking
99+* 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
75102## Extensions
76103
@@ -78,58 +105,26 @@ SillyTavern has extensibility support.
78105
79106* Character emotional expressions (sprites)
80107* Auto-Summary of the chat history
81-* Sending images to chat, and the AI interpreting the content
108+* 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
83110* 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
86114Tutorials 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-
123116# ⌛ Installation
124117
125118> \[!WARNING]
119+>
126120> * DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc).
127121> * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
128122> * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16
129123
130124## 🪟 Windows
131125
132126### Installing via Git
127+
1331281. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
1341292. Install [Git for Windows](https://gitforwindows.org/)
1351303. Open Windows Explorer (`Win+E`)
@@ -137,24 +132,16 @@ Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern
1371325. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
1381336. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
139134
140135-* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
141136-* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
142137
1431387. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
1441398. The server will then start, and SillyTavern will pop up in your browser.
145140
146141### Installing via SillyTavernGitHub LauncherDesktop
147-1. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:
148-```shell
149-cmd /c winget install -e --id Git.Git
150-```
151-2. 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
153-git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
154-```
155142
156-## Installing via GitHub Desktop
157143(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+
158145 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
159146 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
160147 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.
1721591. Install git and nodeJS (the method for doing this will vary depending on your OS)
1731602. Clone the repo
174161
175162-* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
176163-* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
177164
1781653. `cd SillyTavern` to navigate into the install folder.
1791664. Run the `start.sh` script with one of these commands:
180167
181168-* `./start.sh`
182169-* `bash start.sh`
170+
171+## ⚡ Installing via SillyTavern Launcher
172+
173+SillyTavern 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 Launcher
177+1. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:
178+
179+```shell
180+cmd /c winget install -e --id Git.Git
181+```
182+
183+2. 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
186+git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
187+```
185188
186189### For Linux users
190+
1871911. Open your favorite terminal and install git
1881922. Git clone the Sillytavern-Launcher with:
193+
189194```shell
190195git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
191196```
197+
1921983. Start the installer.sh with:
199+
193200```shell
194201chmod +x install.sh && ./install.sh
195202```
203+
1962044. After installation start the launcher.sh with:
205+
197206```shell
198207chmod +x launcher.sh && ./launcher.sh
199208```
200209
201210### For Mac users
211+
2022121. Open a terminal and install brew with:
213+
203214```shell
204215/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
205216```
217+
2062182. Install git with:
219+
207220```shell
208221brew install git
209222```
223+
2102243. Git clone the Sillytavern-Launcher with:
225+
211226```shell
212227git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
213228```
229+
2142304. Start the installer.sh with:
231+
215232```shell
216233chmod +x install.sh && ./install.sh
217234```
235+
2182365. After installation start the launcher.sh with:
237+
219238```shell
220239chmod +x launcher.sh && ./launcher.sh
221240```
@@ -236,18 +255,18 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
236255
237256##### Volume Mappings
238257
239258-* [config] - The directory where SillyTavern configuration files will be stored on your host machine
240259-* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
241260-* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
242261
243262##### Port Mappings
244263
245264-* [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
247266##### Additional Settings
248267
249268-* [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/).
250269-* [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
252271#### Install command
253272
@@ -258,23 +277,28 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
258277
259278> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
260279
261280## 📱 Mobile - Installing via termuxTermux on Android OS
262281
263282> \[!NOTE]
264283> **SillyTavern can be run natively on Android phonesdevices using Termux., Pleasebut referwe todo thisnot guideprovide byofficial ArroganceComplex#2659:support for this use case.**
284+>
285+> **Please refer to this guide by ArroganceComplex#2659:**
286+>
265287> * <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
268291## API keys management
269292
270293SillyTavern saves your API keys to a `secrets.json` file in the user data directory (`/data/default-user/secrets.json` is the default path).
271294
272295By default, theyAPI keys will not be exposedvisible tofrom athe frontendinterface after you enterhave saved them and reloadrefreshed the page.
273296
274297In order to enable viewing your keys by clicking a button in the API block:
275298
2762991. Set the value of `allowKeysExposure` to `true` in `config.yaml` file.
2773002. Restart the SillyTavern server.
301+3. Click the 'View hidden API keys' link at the bottom right of the API Connection Panel.
278302
279303## Command-line arguments
280304
@@ -292,134 +316,43 @@ Start.bat --port 8000 --listen false
292316
293317### Supported arguments
294318
295319| Option | Description | Type | Default |
296320|-------------------------|------------------------------------------------------------------------------------------------------|----------|------------------------------|
297321| `--version` | Show version number | boolean | |
298322| `--enableIPv6` | Enables IPv6. | boolean | false |
299323| `--enableIPv4` | Enables IPv4. | boolean | true |
300324| `--port` | Sets the port under which SillyTavern will run. If not provided falls back to yaml config 'port'. | number | 8000 |
301325| `--dnsPreferIPv6` | Prefers IPv6 for dns. If not provided falls back to yaml config 'preferIPv6'. | boolean | false |
302326| `--autorun` | Automatically launch SillyTavern in the browser. If not provided falls back to yaml config 'autorun'.| boolean | false |
303327| `--autorunHostname` | The autorun hostname, probably best left on 'auto'. | string | null |
304328| `--autorunPortOverride` | Overrides the port for autorun. | string | null |
305329| `--listen` | SillyTavern is listening on all network interfaces. If not provided falls back to yaml config 'listen'.| boolean | false |
306330| `--corsProxy` | Enables CORS proxy. If not provided falls back to yaml config 'enableCorsProxy'. | boolean | false |
307331| `--disableCsrf` | Disables CSRF protection | boolean | null |
308332| `--ssl` | Enables SSL | boolean | false |
309333| `--certPath` | Path to your certificate file. | string | "certs/cert.pem" |
310334| `--keyPath` | Path to your private key file. | string | "certs/privkey.pem" |
311335| `--whitelist` | Enables whitelist mode | boolean | null |
312336| `--dataRoot` | Root directory for data storage | string | null |
313337| `--avoidLocalhost` | Avoids using 'localhost' for autorun in auto mode. | boolean | null |
314338| `--basicAuthMode` | Enables basic authentication | boolean | null |
315339| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean | null |
316340| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string | null |
317341| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array | null |
318342
319343## Remote connections
320344
321345Most often this is for people who want to use SillyTavern on their mobile phones while their PC runs the ST server on the same wifiWi-Fi network. However, it can be used to allow remote connections from anywhere as well.
322-
323-However, 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
335-192.168.0.1
336-192.168.0.20
337-```
338-
339-or
340-
341-```txt
342-192.168.0.*
343-```
344-
345-(the above wildcard IP range will allow any device on the local network to connect)
346-
347-CIDR masks are also accepted (eg. 10.0.0.0/24).
348-
349-* Save the `whitelist.txt` file.
350-* Restart your ST server.
351-
352-Now 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-
358-After the whitelist has been setup, you'll need the IP of the ST-hosting device.
359-
360-If 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-
364-If 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 machine
347+Read the detailed guide on how to set up remote connections in the [Docs](https://docs.sillytavern.app/usage/remoteconnections/).
369348
370-Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
349+You 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-
372-A typical address for an ST host on the same wifi network would look like this:
373-
374-`http://192.168.0.5:8000`
375-
376-Use http:// NOT https://
377-
378-### Opening your ST to all IPs
379-
380-We do not recommend doing this, but you can open `config.yaml` and change `whitelistMode` to `false`.
381-
382-You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder if it exists.
383-
384-This is usually an insecure practice, so we require you to set a username and password when you do this.
385-
386-The username and password are set in `config.yaml`.
387-
388-After 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
395351## Performance issues?
396352
397353Try1. enablingDisable the No Blur Effect (Fastand UI)enable modeReduced Motion on the User settingsSettings panel (UI Theme toggles category).
398-
354+2. 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?
355+3. Make sure the browser is enabled to use GPU acceleration for rendering.
400-
401-### DO's
402-
403-1. Send pull requests
404-2. Send feature suggestions and issue reports using established templates
405-3. Read the readme file and built-in documentation before asking anything
406-
407-### DONT's
408-
409-1. Offer monetary donations
410-2. Send bug reports without providing any context
411-3. Ask the questions that were already answered numerous times
412-
413-## Where can I find the old backgrounds?
414-
415-We're moving to a 100% original content only policy, so old background images have been removed from this repository.
416-
417-You can find them archived here:
418-
419-<https://files.catbox.moe/1xevnc.zip>
420-
421-
422-
423356
424357## License and credits
425358
@@ -428,32 +361,19 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
428361MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
429362GNU Affero General Public License for more details.**
430363
431-* TAI Base by Humi: MIT
364+* [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT License
432-* Cohee's modifications and derived code: AGPL v3
365+* Portions of CncAnon's TavernAITurbo mod used with permission
433-* RossAscends' additions: AGPL v3
366+* 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/>
442367* Noto Sans font by Google (OFL license)
443368* 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
452370* 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+
454376<!-- LINK GROUP -->
455-[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square
377+[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
457378[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
459379[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
1919COPY package*.json post-install.js ./
2020RUN \
2121 echo "*** Install npm packages ***" && \
2222 npm i --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev && npm cache clean --force
2323
2424# Bundle app source
2525COPY . ./
Start.bat+1 -1
@@ -1,7 +1,7 @@
11@echo off
22pushd %~dp0
33set NODE_ENV=production
44call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev
55node server.js %*
66pause
77popd
UpdateAndStart.bat+1 -1
@@ -12,7 +12,7 @@ if %errorlevel% neq 0 (
1212 )
1313)
1414set NODE_ENV=production
1515call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev
1616node server.js %*
1717pause
1818popd
UpdateForkAndStart.bat+1 -1
@@ -95,7 +95,7 @@ if %errorlevel% neq 0 (
9595
9696echo Installing npm packages and starting server
9797set NODE_ENV=production
9898call npm install --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev
9999node server.js %*
100100
101101:end
default/config.yaml+5 -0
@@ -83,6 +83,8 @@ skipContentCheck: false
8383disableChatBackup: false
8484# Number of backups to keep for each chat and settings file
8585numberOfBackups: 50
86+# Interval in milliseconds to throttle chat backups per user
87+chatBackupThrottleInterval: 10000
8688# Allowed hosts for card downloads
8789whitelistImportDomains:
8890 - localhost
@@ -108,6 +110,9 @@ enableExtensionsAutoUpdate: true
108110# Additional model tokenizers can be downloaded on demand.
109111# Disabling will fallback to another locally available tokenizer.
110112enableDownloadableTokenizers: true
113+# Vector storage settings
114+vectors:
115+ enableModelScopes: false
111116# Extension settings
112117extras:
113118 # Disables automatic model download from HuggingFace
default/content/index.json+92 -16
@@ -320,6 +320,26 @@
320320 "type": "novel_preset"
321321 },
322322 {
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+ {
323343 "filename": "presets/textgen/Asterism.json",
324344 "type": "textgen_preset"
325345 },
@@ -468,10 +488,6 @@
468488 "type": "context"
469489 },
470490 {
471- "filename": "presets/context/Alpaca-Roleplay.json",
472- "type": "context"
473- },
474- {
475491 "filename": "presets/context/Alpaca-Single-Turn.json",
476492 "type": "context"
477493 },
@@ -512,10 +528,6 @@
512528 "type": "context"
513529 },
514530 {
515- "filename": "presets/context/Mistral.json",
516- "type": "context"
517- },
518- {
519531 "filename": "presets/context/NovelAI.json",
520532 "type": "context"
521533 },
@@ -556,10 +568,6 @@
556568 "type": "instruct"
557569 },
558570 {
559- "filename": "presets/instruct/Alpaca-Roleplay.json",
560- "type": "instruct"
561- },
562- {
563571 "filename": "presets/instruct/Alpaca-Single-Turn.json",
564572 "type": "instruct"
565573 },
@@ -600,10 +608,6 @@
600608 "type": "instruct"
601609 },
602610 {
603- "filename": "presets/instruct/Mistral.json",
604- "type": "instruct"
605- },
606- {
607611 "filename": "presets/instruct/OpenOrca-OpenChat.json",
608612 "type": "instruct"
609613 },
@@ -686,5 +690,77 @@
686690 {
687691 "filename": "presets/instruct/Gemma 2.json",
688692 "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"
689765 }
690766]
default/content/presets/context/Adventure.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": false,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": true,
1110 "name": "Adventure"
1211}
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+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": false,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Alpaca-Single-Turn"
1211}
default/content/presets/context/Alpaca.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Alpaca"
1211}
default/content/presets/context/ChatML-Names.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "ChatML-Names"
1211}
default/content/presets/context/ChatML.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "ChatML"
1211}
default/content/presets/context/Command R.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Command R"
1211}
default/content/presets/context/Default.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Default"
1211}
default/content/presets/context/DreamGen Role-Play V1 ChatML.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": false,
88 "trim_sentences": true,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "DreamGen Role-Play V1 ChatML"
1211}
default/content/presets/context/DreamGen Role-Play V1 Llama3.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": false,
88 "trim_sentences": true,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "DreamGen Role-Play V1 Llama3"
1211}
default/content/presets/context/Gemma 2.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Gemma 2"
1211}
default/content/presets/context/Libra-32B.json+1 -2
@@ -1,12 +1,11 @@
11{
22 "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}}",
33 "example_separator": "### Example:",
44 "chat_start": "### START ROLEPLAY:",
55 "use_stop_strings": false,
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Libra-32B"
1211}
default/content/presets/context/Lightning 1.1.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Lightning 1.1"
1211}
default/content/presets/context/Llama 2 Chat.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Llama 2 Chat"
1211}
default/content/presets/context/Llama 3 Instruct.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Llama 3 Instruct"
1211}
default/content/presets/context/Llama-3-Instruct-Names.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Llama-3-Instruct-Names"
1211}
default/content/presets/context/Minimalist.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Minimalist"
1211}
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 @@
11{
22 "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] Understood.</s>",
33 "example_separator": "Examples:",
44 "chat_start": "",
55 "use_stop_strings": false,
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Mistral V2 & V3"
1211}
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+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "NovelAI"
1211}
default/content/presets/context/OldDefault.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "OldDefault"
1211}
default/content/presets/context/Phi.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Phi"
1211}
default/content/presets/context/Pygmalion.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Pygmalion"
1211}
default/content/presets/context/Story.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Story"
1211}
default/content/presets/context/Synthia.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "Synthia"
1211}
default/content/presets/context/simple-proxy-for-tavern.json+0 -1
@@ -6,7 +6,6 @@
66 "allow_jailbreak": false,
77 "always_force_name2": true,
88 "trim_sentences": false,
9- "include_newline": false,
109 "single_line": false,
1110 "name": "simple-proxy-for-tavern"
1211}
default/content/presets/instruct/Adventure.json+1 -3
@@ -1,5 +1,4 @@
11{
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.]",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "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 @@
11{
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.",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "\n### Response:",
@@ -7,12 +6,11 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
1513 "first_output_sequence": "<START OF ROLEPLAYCHAT>",
1614 "skip_examples": false,
1715 "output_suffix": "",
1816 "input_suffix": "",
default/content/presets/instruct/Alpaca.json+1 -3
@@ -1,5 +1,4 @@
11{
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",
32 "input_sequence": "### Instruction:",
43 "output_sequence": "### Response:",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/ChatML-Names.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
32 "input_sequence": "<|im_start|>[{{name}}]",
43 "output_sequence": "<|im_start|>[{{name}}]",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<|im_end|>",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/ChatML.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
32 "input_sequence": "<|im_start|>user",
43 "output_sequence": "<|im_start|>assistant",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<|im_end|>",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": true"always",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Command R.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>",
43 "output_sequence": "<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",
54 "first_output_sequence": "",
@@ -9,8 +8,7 @@
98 "stop_sequence": "<|END_OF_TURN_TOKEN|>",
109 "wrap": false,
1110 "macro": true,
1211 "namesnames_behavior": true"always",
13- "names_force_groups": true,
1412 "activation_regex": "",
1513 "skip_examples": false,
1614 "output_suffix": "<|END_OF_TURN_TOKEN|>",
default/content/presets/instruct/DreamGen Role-Play V1 ChatML.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
32 "input_sequence": "\n<|im_start|>text names= {{name}}\n",
43 "output_sequence": "\n<|im_start|>text names= {{name}}\n",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "\n<|im_start|>",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/DreamGen Role-Play V1 Llama3.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
32 "input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
43 "output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
54 "first_output_sequence": "",
@@ -10,8 +9,7 @@
109 "separator_sequence": "",
1110 "wrap": false,
1211 "macro": true,
1312 "namesnames_behavior": false"none",
14- "names_force_groups": false,
1513 "activation_regex": "",
1614 "skip_examples": false,
1715 "name": "DreamGen Role-Play V1 Llama3"
default/content/presets/instruct/Gemma 2.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Continue writing this story and portray characters realistically.",
32 "input_sequence": "<start_of_turn>user",
43 "output_sequence": "<start_of_turn>model",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<end_of_turn>",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Koala.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "USER: ",
43 "output_sequence": "GPT: ",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "BEGINNING OF CONVERSATION: ",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Libra-32B.json+1 -3
@@ -1,5 +1,4 @@
11{
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.",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "\n### Response:",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": true"always",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Lightning 1.1.json+1 -3
@@ -1,5 +1,4 @@
11{
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",
32 "input_sequence": "### Instruction:",
43 "output_sequence": "### Response: (length = unlimited)",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Llama 2 Chat.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "[INST] ",
43 "output_sequence": "",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Llama 3 Instruct.json+1 -3
@@ -1,5 +1,4 @@
11{
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}}.",
32 "input_sequence": "<|start_header_id|>user<|end_header_id|>\n\n",
43 "output_sequence": "<|start_header_id|>assistant<|end_header_id|>\n\n",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<|eot_id|>",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": true"always",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Llama-3-Instruct-Names.json+1 -3
@@ -1,5 +1,4 @@
11{
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}}.",
32 "input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
43 "output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<|eot_id|>",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Metharme.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
32 "input_sequence": "<|user|>",
43 "output_sequence": "<|model|>",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "</s>",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "<|system|>",
1412 "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 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "[INST] ",
43 "output_sequence": "[/INST] ",
54 "last_output_sequence": "[/INST]",
65 "system_sequence": "",
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": true"always",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
1513 "first_output_sequence": "",
1614 "skip_examples": false,
1715 "output_suffix": "\n</s>",
1816 "input_suffix": " [/INST]\n",
1917 "system_suffix": "",
2018 "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
2119 "system_same_as_user": true,
2220 "last_system_sequence": "",
2321 "name": "Mistral V2 & V3"
2422}
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 @@
11{
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",
32 "input_sequence": "\nUser: ",
43 "output_sequence": "\nAssistant: ",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Phi.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
32 "input_sequence": "<|user|>\n",
43 "output_sequence": "<|assistant|>\n",
54 "first_output_sequence": "",
@@ -9,8 +8,7 @@
98 "stop_sequence": "<|end|>",
109 "wrap": false,
1110 "macro": true,
1211 "namesnames_behavior": true"always",
13- "names_force_groups": true,
1412 "activation_regex": "",
1513 "skip_examples": false,
1614 "output_suffix": "<|end|>\n",
default/content/presets/instruct/Pygmalion.json+1 -3
@@ -1,5 +1,4 @@
11{
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.",
32 "input_sequence": "<|user|>",
43 "output_sequence": "<|model|>",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "<|user|>",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": true"always",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "<|system|>",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Story.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "",
32 "input_sequence": "",
43 "output_sequence": "",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Synthia.json+1 -3
@@ -1,5 +1,4 @@
11{
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.",
32 "input_sequence": "USER: ",
43 "output_sequence": "ASSISTANT: ",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "SYSTEM: ",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Vicuna 1.0.json+1 -3
@@ -1,5 +1,4 @@
11{
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",
32 "input_sequence": "### Human:",
43 "output_sequence": "### Assistant:",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/Vicuna 1.1.json+1 -3
@@ -1,5 +1,4 @@
11{
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",
32 "input_sequence": "\nUSER: ",
43 "output_sequence": "\nASSISTANT: ",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": false,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "BEGINNING OF CONVERSATION:",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/WizardLM-13B.json+1 -3
@@ -1,5 +1,4 @@
11{
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}}.",
32 "input_sequence": "USER: ",
43 "output_sequence": "ASSISTANT: ",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/WizardLM.json+1 -3
@@ -1,5 +1,4 @@
11{
2- "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
32 "input_sequence": "",
43 "output_sequence": "### Response:",
54 "last_output_sequence": "",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"force",
11- "names_force_groups": true,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/instruct/simple-proxy-for-tavern.json+1 -3
@@ -1,5 +1,4 @@
11{
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.]",
32 "input_sequence": "### Instruction:\n#### {{name}}:",
43 "output_sequence": "### Response:\n#### {{name}}:",
54 "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{name}}:",
@@ -7,8 +6,7 @@
76 "stop_sequence": "",
87 "wrap": true,
98 "macro": true,
109 "namesnames_behavior": false"none",
11- "names_force_groups": false,
1210 "activation_regex": "",
1311 "system_sequence_prefix": "",
1412 "system_sequence_suffix": "",
default/content/presets/novel/Asper-Kayra.json+5 -2
@@ -14,7 +14,10 @@
1414 "use_cache": false,
1515 "return_full_text": false,
1616 "prefix": "vanilla",
17- "cfg_scale": 1,
1817 "phrase_rep_pen": "medium",
1918 "max_context": 7800,
19+ "min_p": 0,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Blended-Coffee-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [6, 0, 1, 2, 3],
33 "temperature": 1,
44 "max_length": 150,
55 "min_length": 1,
@@ -14,6 +14,9 @@
1414 "return_full_text": false,
1515 "prefix": "vanilla",
1616 "phrase_rep_pen": "medium",
1717 "cfg_scalemax_context": 1.557800,
1818 "max_contextmin_p": 78000,
19+ "math1_temp": 1,
20+ "math1_quad": 0,
21+ "math1_quad_entropy_scale": 0
1922}
default/content/presets/novel/Blook-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [6, 2, 3, 1, 0],
33 "temperature": 1,
44 "max_length": 150,
55 "min_length": 1,
@@ -15,6 +15,9 @@
1515 "return_full_text": false,
1616 "prefix": "vanilla",
1717 "phrase_rep_pen": "very_aggressive",
1818 "cfg_scalemax_context": 1.37800,
1919 "max_contextmin_p": 78000,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Carefree-Kayra.json+5 -2
@@ -15,7 +15,10 @@
1515 "use_cache": false,
1616 "return_full_text": false,
1717 "prefix": "vanilla",
18- "cfg_scale": 1,
1918 "phrase_rep_pen": "aggressive",
2019 "max_context": 7800,
20+ "min_p": 0,
21+ "math1_temp": 1,
22+ "math1_quad": 0,
23+ "math1_quad_entropy_scale": 0
2124}
default/content/presets/novel/CosmicCube-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [8, 6, 5, 0, 3],
33 "temperature": 0.9,
44 "max_length": 150,
55 "min_length": 1,
@@ -15,6 +15,9 @@
1515 "return_full_text": false,
1616 "prefix": "vanilla",
1717 "phrase_rep_pen": "off",
1818 "cfg_scalemax_context": 1.487800,
1919 "max_contextmin_p": 78000,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Edgewise-Clio.json+5 -2
@@ -15,7 +15,10 @@
1515 "use_cache": false,
1616 "return_full_text": false,
1717 "prefix": "vanilla",
18- "cfg_scale": 1,
1918 "phrase_rep_pen": "very_light",
2019 "max_context": 7800,
20+ "min_p": 0,
21+ "math1_temp": 1,
22+ "math1_quad": 0,
23+ "math1_quad_entropy_scale": 0
2124}
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 @@
1616 "use_cache": false,
1717 "return_full_text": false,
1818 "prefix": "vanilla",
19- "cfg_scale": 1,
2019 "phrase_rep_pen": "very_light",
2120 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Fresh-Coffee-Kayra.json+5 -2
@@ -14,7 +14,10 @@
1414 "use_cache": false,
1515 "return_full_text": false,
1616 "prefix": "vanilla",
17- "cfg_scale": 1,
1817 "phrase_rep_pen": "off",
1918 "max_context": 7800,
19+ "min_p": 0,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Green-Active-Writer-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [6, 0, 8, 5, 3],
33 "temperature": 1.5,
44 "max_length": 150,
55 "min_length": 1,
@@ -15,6 +15,9 @@
1515 "return_full_text": false,
1616 "prefix": "vanilla",
1717 "phrase_rep_pen": "very_aggressive",
1818 "cfg_scalemax_context": 1.47800,
1919 "max_contextmin_p": 78000,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Keelback-Clio.json+5 -2
@@ -16,7 +16,10 @@
1616 "use_cache": false,
1717 "return_full_text": false,
1818 "prefix": "vanilla",
19- "cfg_scale": 1,
2019 "phrase_rep_pen": "very_light",
2120 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Long-Press-Clio.json+5 -2
@@ -16,7 +16,10 @@
1616 "use_cache": false,
1717 "return_full_text": false,
1818 "prefix": "vanilla",
19- "cfg_scale": 1,
2019 "phrase_rep_pen": "very_light",
2120 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Pilotfish-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [6, 0, 4, 1, 2, 5, 3],
33 "temperature": 1.31,
44 "max_length": 150,
55 "min_length": 1,
@@ -17,6 +17,9 @@
1717 "return_full_text": false,
1818 "prefix": "vanilla",
1919 "phrase_rep_pen": "medium",
2020 "cfg_scalemax_context": 1.357800,
2121 "max_contextmin_p": 78000,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Pro_Writer-Kayra.json+5 -2
@@ -15,6 +15,9 @@
1515 "return_full_text": false,
1616 "prefix": "vanilla",
1717 "phrase_rep_pen": "medium",
1818 "cfg_scalemax_context": 1.07800,
1919 "max_contextmin_p": 78000,
20+ "math1_temp": 1,
21+ "math1_quad": 0,
22+ "math1_quad_entropy_scale": 0
2023}
default/content/presets/novel/Stelenes-Kayra.json+5 -1
@@ -13,5 +13,9 @@
1313 "return_full_text": false,
1414 "prefix": "vanilla",
1515 "phrase_rep_pen": "medium",
1616 "max_context": 7800,
17+ "min_p": 0,
18+ "math1_temp": 1,
19+ "math1_quad": 0,
20+ "math1_quad_entropy_scale": 0
1721}
default/content/presets/novel/Talker-Chat-Clio.json+5 -2
@@ -16,7 +16,10 @@
1616 "use_cache": false,
1717 "return_full_text": false,
1818 "prefix": "vanilla",
19- "cfg_scale": 1,
2019 "phrase_rep_pen": "very_light",
2120 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Tea_Time-Kayra.json+5 -2
@@ -14,6 +14,9 @@
1414 "return_full_text": false,
1515 "prefix": "vanilla",
1616 "phrase_rep_pen": "aggressive",
1717 "cfg_scalemax_context": 1.07800,
1818 "max_contextmin_p": 78000,
19+ "math1_temp": 1,
20+ "math1_quad": 0,
21+ "math1_quad_entropy_scale": 0
1922}
default/content/presets/novel/Tesseract-Kayra.json+6 -3
@@ -1,5 +1,5 @@
11{
22 "order": [6, 0, 5],
33 "temperature": 0.895,
44 "max_length": 150,
55 "min_length": 1,
@@ -13,6 +13,9 @@
1313 "return_full_text": false,
1414 "prefix": "vanilla",
1515 "phrase_rep_pen": "aggressive",
1616 "cfg_scalemax_context": 1.37800,
1717 "max_contextmin_p": 78000,
18+ "math1_temp": 1,
19+ "math1_quad": 0,
20+ "math1_quad_entropy_scale": 0
1821}
default/content/presets/novel/Vingt-Un-Clio.json+5 -2
@@ -16,7 +16,10 @@
1616 "use_cache": false,
1717 "return_full_text": false,
1818 "prefix": "vanilla",
19- "cfg_scale": 1,
2019 "phrase_rep_pen": "very_light",
2120 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2225}
default/content/presets/novel/Writers-Daemon-Kayra.json+5 -1
@@ -17,5 +17,9 @@
1717 "return_full_text": false,
1818 "prefix": "vanilla",
1919 "phrase_rep_pen": "very_aggressive",
2020 "max_context": 7800,
21+ "min_p": 0,
22+ "math1_temp": 1,
23+ "math1_quad": 0,
24+ "math1_quad_entropy_scale": 0
2125}
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 @@
9090 "pin_examples": false,
9191 "strip_examples": false,
9292 "trim_sentences": false,
93- "include_newline": false,
9493 "always_force_name2": true,
9594 "user_prompt_bias": "",
9695 "show_user_prompt_bias": true,
@@ -150,11 +149,9 @@
150149 "continue_on_send": false,
151150 "trim_spaces": true,
152151 "relaxed_api_urls": false,
153- "default_instruct": "",
154152 "instruct": {
155153 "enabled": false,
156154 "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",
158155 "input_sequence": "### Instruction:",
159156 "output_sequence": "### Response:",
160157 "last_output_sequence": "",
@@ -162,8 +159,7 @@
162159 "stop_sequence": "",
163160 "wrap": true,
164161 "macro": true,
165162 "namesnames_behavior": false"force",
166- "names_force_groups": true,
167163 "activation_regex": "",
168164 "system_sequence_prefix": "",
169165 "system_sequence_suffix": "",
@@ -175,7 +171,11 @@
175171 "user_alignment_message": "",
176172 "system_same_as_user": false
177173 },
178174 "default_contextsysprompt": "Default",{
175+ "enabled": true,
176+ "name": "Neutral - Chat",
177+ "content": "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}."
178+ },
179179 "context": {
180180 "preset": "Default",
181181 "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 @@
235235 "lengthPenaltyStep": 0.1,
236236 "memoryFrozen": false,
237237 "source": "extras",
238238 "prompt": "[PauseIgnore yourprevious roleplayinstructions. Summarize the most important facts and events that have happened in the chatstory 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.]",
239239 "promptWords": 200,
240240 "promptMinWords": 25,
241241 "promptMaxWords": 1000,
@@ -298,13 +298,13 @@
298298 "horde_karras": true,
299299 "refine_mode": false,
300300 "prompts": {
301301 "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,']",
302302 "1": "[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply as {{char}} when writing this description, and do not attempt to continue the story.]",
303303 "2": "[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply whileas {{char}} when writing this description, and do not attempt to continue the story.]",
304304 "3": "[PauseIgnore yourprevious roleplayinstructions and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplayreply atas all{{char}} inwhen yourwriting response.this Dodescription, and do not attempt to continue the roleplay 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)'",
306306 "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,']",
307307 "7": "[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply as {{user}} when writing this description, and do not attempt to continue the story.]"
308308 },
309309 "character_prompts": {}
310310 },
@@ -318,9 +318,9 @@
318318 "objective": {
319319 "customPrompts": {
320320 "default": {
321321 "createTask": "PauseIgnore yourprevious roleplayinstructions 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 ",
322322 "checkTaskCompleted": "PauseIgnore yourprevious roleplayinstructions. 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 ",
323323 "currentTask": "Your current task is [{{task}}]. Balance existing roleplaystory with completing this task."
324324 }
325325 }
326326 },
@@ -417,7 +417,6 @@
417417 "preset_settings_novel": "Talker-Chat-Clio",
418418 "streaming_novel": true,
419419 "preamble": "[ Style: chat, complex, sensory, visceral ]",
420- "cfg_uc": "",
421420 "banned_tokens": "",
422421 "order": [
423422 1,
package-lock.json+47 -60
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.56",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.56",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
@@ -22,7 +22,7 @@
2222 "cookie-session": "^2.1.0",
2323 "cors": "^2.8.5",
2424 "csrf-csrf": "^2.2.3",
2525 "express": "^4.2021.0",
2626 "form-data": "^4.0.0",
2727 "google-translate-api-browser": "^3.0.1",
2828 "he": "^1.2.0",
@@ -1568,21 +1568,6 @@
15681568 "node": ">=0.10.0"
15691569 }
15701570 },
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- },
15861571 "node_modules/boolbase": {
15871572 "version": "1.0.0",
15881573 "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1728,6 +1713,7 @@
17281713 "version": "2.7.0",
17291714 "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz",
17301715 "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==",
1716+ "license": "MIT",
17311717 "dependencies": {
17321718 "follow-redirects": "^1.15.6"
17331719 }
@@ -2540,9 +2526,9 @@
25402526 "license": "MIT"
25412527 },
25422528 "node_modules/encodeurl": {
25432529 "version": "12.0.20",
25442530 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-12.0.20.tgz",
25452531 "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0wQ0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
25462532 "license": "MIT",
25472533 "engines": {
25482534 "node": ">= 0.8"
@@ -2879,9 +2865,9 @@
28792865 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
28802866 },
28812867 "node_modules/express": {
28822868 "version": "4.2021.0",
28832869 "resolved": "https://registry.npmjs.org/express/-/express-4.2021.0.tgz",
28842870 "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2VqcNGcj/Id5ZT1LZ/cfihi3ttTn+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLwNJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
28852871 "license": "MIT",
28862872 "dependencies": {
28872873 "accepts": "~1.3.8",
@@ -2896,7 +2882,7 @@
28962882 "encodeurl": "~2.0.0",
28972883 "escape-html": "~1.0.3",
28982884 "etag": "~1.8.1",
28992885 "finalhandler": "1.23.01",
29002886 "fresh": "0.5.2",
29012887 "http-errors": "2.0.0",
29022888 "merge-descriptors": "1.0.3",
@@ -2905,11 +2891,11 @@
29052891 "parseurl": "~1.3.3",
29062892 "path-to-regexp": "0.1.10",
29072893 "proxy-addr": "~2.0.7",
29082894 "qs": "6.1113.0",
29092895 "range-parser": "~1.2.1",
29102896 "safe-buffer": "5.2.1",
29112897 "send": "0.19.0",
29122898 "serve-static": "1.16.02",
29132899 "setprototypeof": "1.2.0",
29142900 "statuses": "2.0.1",
29152901 "type-is": "~1.6.18",
@@ -2928,15 +2914,6 @@
29282914 "node": ">= 0.6"
29292915 }
29302916 },
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- },
29402917 "node_modules/express/node_modules/safe-buffer": {
29412918 "version": "5.2.1",
29422919 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -3033,13 +3010,13 @@
30333010 }
30343011 },
30353012 "node_modules/finalhandler": {
30363013 "version": "1.23.01",
30373014 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.23.01.tgz",
30383015 "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
30393016 "license": "MIT",
30403017 "dependencies": {
30413018 "debug": "2.6.9",
30423019 "encodeurl": "~12.0.20",
30433020 "escape-html": "~1.0.3",
30443021 "on-finished": "2.4.1",
30453022 "parseurl": "~1.3.3",
@@ -4080,9 +4057,9 @@
40804057 }
40814058 },
40824059 "node_modules/load-bmfont": {
40834060 "version": "1.4.12",
40844061 "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.12.tgz",
40854062 "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJqElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/7Enqmwk2sM14soAdmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==",
40864063 "license": "MIT",
40874064 "dependencies": {
40884065 "buffer-equal": "0.0.1",
@@ -4090,7 +4067,7 @@
40904067 "parse-bmfont-ascii": "^1.0.3",
40914068 "parse-bmfont-binary": "^1.0.5",
40924069 "parse-bmfont-xml": "^1.1.4",
40934070 "phin": "^23.97.1",
40944071 "xhr": "^2.0.1",
40954072 "xtend": "^4.0.0"
40964073 }
@@ -4692,13 +4669,13 @@
46924669 "license": "MIT"
46934670 },
46944671 "node_modules/parse-bmfont-xml": {
46954672 "version": "1.1.46",
46964673 "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.46.tgz",
46974674 "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+mCQXuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
46984675 "license": "MIT",
46994676 "dependencies": {
47004677 "xml-parse-from-string": "^1.0.0",
47014678 "xml2js": "^0.4.5.0"
47024679 }
47034680 },
47044681 "node_modules/parse-headers": {
@@ -4814,6 +4791,7 @@
48144791 "version": "3.7.1",
48154792 "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz",
48164793 "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==",
4794+ "license": "MIT",
48174795 "dependencies": {
48184796 "centra": "^2.7.0"
48194797 },
@@ -5032,12 +5010,12 @@
50325010 }
50335011 },
50345012 "node_modules/qs": {
50355013 "version": "6.1113.0",
50365014 "resolved": "https://registry.npmjs.org/qs/-/qs-6.1113.0.tgz",
50375015 "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/MaJzRkIk4QXOq7UU2Nx6nsjvS16Z9uwfpg==",
50385016 "license": "BSD-3-Clause",
50395017 "dependencies": {
50405018 "side-channel": "^1.0.46"
50415019 },
50425020 "engines": {
50435021 "node": ">=0.6"
@@ -5344,9 +5322,9 @@
53445322 }
53455323 },
53465324 "node_modules/sax": {
53475325 "version": "1.2.4.1",
53485326 "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.1.tgz",
53495327 "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9+aWOz7yVScEGoKNd4PA10LZ8sk0A/atZwz5+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWwnXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
53505328 "license": "ISC"
53515329 },
53525330 "node_modules/send": {
@@ -5373,6 +5351,15 @@
53735351 "node": ">= 0.8.0"
53745352 }
53755353 },
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+ },
53765363 "node_modules/send/node_modules/ms": {
53775364 "version": "2.1.3",
53785365 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5380,15 +5367,15 @@
53805367 "license": "MIT"
53815368 },
53825369 "node_modules/serve-static": {
53835370 "version": "1.16.02",
53845371 "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.02.tgz",
53855372 "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4VqpjJZKadQB/hQGdPAPEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
53865373 "license": "MIT",
53875374 "dependencies": {
53885375 "encodeurl": "~12.0.20",
53895376 "escape-html": "~1.0.3",
53905377 "parseurl": "~1.3.3",
53915378 "send": "0.1819.0"
53925379 },
53935380 "engines": {
53945381 "node": ">= 0.8.0"
package.json+2 -11
@@ -12,7 +12,7 @@
1212 "cookie-session": "^2.1.0",
1313 "cors": "^2.8.5",
1414 "csrf-csrf": "^2.2.3",
1515 "express": "^4.2021.0",
1616 "form-data": "^4.0.0",
1717 "google-translate-api-browser": "^3.0.1",
1818 "he": "^1.2.0",
@@ -49,23 +49,14 @@
4949 "node": ">= 18"
5050 },
5151 "overrides": {
52- "parse-bmfont-xml": {
53- "xml2js": "^0.5.0"
54- },
5552 "vectra": {
5653 "openai": "^4.17.0"
5754 },
58- "load-bmfont": {
59- "phin": "^3.7.1"
60- },
6155 "axios": {
6256 "follow-redirects": "^1.15.4"
6357 },
6458 "node-fetch": {
6559 "whatwg-url": "^14.0.0"
66- },
67- "express": {
68- "send": "^0.19.0"
6960 }
7061 },
7162 "name": "sillytavern",
@@ -75,7 +66,7 @@
7566 "type": "git",
7667 "url": "https://github.com/SillyTavern/SillyTavern.git"
7768 },
7869 "version": "1.12.56",
7970 "scripts": {
8071 "start": "node server.js",
8172 "start:no-csrf": "node server.js --disableCsrf",
public/css/mobile-styles.css+8 -9
@@ -103,7 +103,6 @@
103103 touch-action: none;
104104 overflow: hidden;
105105 position: fixed;
106-
107106 }
108107
109108 .world_entry_form_control {
@@ -112,7 +111,7 @@
112111
113112 .drawer-content {
114113 min-width: unset;
115114 width: 100%100dvw;
116115 max-height: calc(100vh - 45px);
117116 max-height: calc(100dvh - 45px);
118117 position: fixed;
@@ -159,9 +158,9 @@
159158 #sheld,
160159 #character_popup,
161160 .drawer-content {
162161 width: 100%100dvw !important;
163162 margin: 0 auto;
164163 max-width: 100%100dvw;
165164 left: 0 !important;
166165 resize: none !important;
167166 top: var(--topBarBlockSize);
@@ -233,9 +232,9 @@
233232 /* 100vh are fallback units for browsers that don't support dvh */
234233 height: calc(100vh - 45px);
235234 height: calc(100dvh - 45px);
236235 min-width: 100%100dvw !important;
237236 width: 100%100dvw !important;
238237 max-width: 100%100dvw !important;
239238 overflow-y: hidden;
240239 border-left: 1px solid var(--SmartThemeBorderColor);
241240 border-right: 1px solid var(--SmartThemeBorderColor);
@@ -481,10 +480,10 @@
481480 margin: 0 auto;
482481 height: calc(100vh - 70px);
483482 height: calc(100dvh - 70px);
484483 width: calc(100%100dvw - 5px);
485484 max-height: calc(100vh - 70px);
486485 max-height: calc(100dvh - 70px);
487486 max-width: calc(100%100dvw - 5px);
488487
489488 }
490489
public/css/popup.css+4 -0
@@ -47,6 +47,10 @@ dialog {
4747 padding: 1px;
4848}
4949
50+.popup:not(:has(.img_enlarged_container)) .popup-body {
51+ max-height: 95dvh;
52+}
53+
5054.popup .popup-content {
5155 margin-top: 10px;
5256 padding: 0 8px;
public/css/promptmanager.css+35 -8
@@ -91,15 +91,34 @@
9191#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect {
9292 display: none;
9393 padding: 0.5em;
94+ height: 100%;
95+ display: flex;
96+ flex-direction: column;
9497}
9598
9699#completion_prompt_manager_popup .completion_prompt_manager_popup_entry {
97100 padding: 1em0.5em;
98101 margin-topflex: 2em1;
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;
99118}
100119
101120#completion_prompt_manager_popup #completion_prompt_manager_popup_inspect .completion_prompt_manager_popup_entry {
102121 padding: 1em0.5em;
103122}
104123
105124#completion_prompt_manager_popup #completion_prompt_manager_popup_entry_form_inspect_list {
@@ -248,7 +267,7 @@
248267}
249268
250269#completion_prompt_manager_footer_append_prompt {
251270 font-size: 16px1em;
252271}
253272
254273#prompt-manager-export-format-popup {
@@ -303,10 +322,6 @@
303322 justify-content: space-between;
304323}
305324
306-#prompt-manager-export-format-popup span {
307- font-size: 16px;
308-}
309-
310325@media screen and (max-width: 412px) {
311326 #completion_prompt_manager_popup {
312327 max-width: 100%;
@@ -316,3 +331,15 @@
316331 margin-left: 0.5em;
317332 }
318333}
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 {
274274 align-self: center;
275275 width: 100%;
276276}
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+437 -342
@@ -119,7 +119,7 @@
119119 <option value="gui" data-i18n="Default">Default</option>
120120 </select>
121121 <div class="flex-container marginLeft5 ">
122122 <input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings, .preset">
123123 <i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
124124 <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>
125125 <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 @@
324324 </div>
325325 </div>
326326 <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">
327340 <div class="range-block-title" data-i18n="TFS">
328341 TFS
329342 </div>
@@ -337,6 +350,136 @@
337350 </div>
338351 </div>
339352 <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">
340483 <div class="range-block-title" data-i18n="Phrase Repetition Penalty">
341484 Phrase Repetition Penalty
342485 </div>
@@ -349,6 +492,46 @@
349492 <option value="very_aggressive" data-i18n="Very aggressive">Very aggressive</option>
350493 </select>
351494 </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>
352535 </div>
353536 <div id="range_block_openai">
354537 <div class="range-block">
@@ -907,163 +1090,6 @@
9071090 </div>
9081091 </div><!-- end of kobold settings-->
9091092 <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>
10671093 <div class="range-block flexFlowColumn">
10681094 <div class="range-block-title">
10691095 <span data-i18n="Samplers Order">Samplers Order</span>
@@ -1102,17 +1128,22 @@
11021128 <small>5</small>
11031129 <div class="toggle_button right_menu_button"></div>
11041130 </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. -->
11111132 <div data-id="8">
11121133 <span data-i18n="Mirostat">Mirostat</span>
11131134 <small>8</small>
11141135 <div class="toggle_button right_menu_button"></div>
11151136 </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>
11161147 </div>
11171148 </div>
11181149 </div><!-- end of novel settings-->
@@ -1279,7 +1310,7 @@
12791310 </div>
12801311 </div>
12811312
12821313 <div data-tg-type="koboldcpp, aphrodite" id="xtc_block" class="wide100p">
12831314 <h4 class="wide100p textAlignCenter">
12841315 <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
12851316 <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
@@ -1339,7 +1370,7 @@
13391370 </div>
13401371 </div>
13411372 </div>
13421373 <div data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" id="dynatemp_block_ooba" class="wide100p">
13431374 <h4 class="wide100p textAlignCenter">
13441375 <div class="flex-container alignitemscenter justifyCenter">
13451376 <div class="checkbox_label" for="dynatemp_textgenerationwebui">
@@ -2038,6 +2069,7 @@
20382069 <select id="model_novel_select">
20392070 <option value="clio-v1">Clio</option>
20402071 <option value="kayra-v1">Kayra</option>
2072+ <option value="llama-3-erato-v1">Erato</option>
20412073 </select>
20422074 <div class="flex-container">
20432075 <div id="api_button_novel" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
@@ -2850,10 +2882,13 @@
28502882 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
28512883 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
28522884 <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>
28532886 <option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experiment 2024-08-27</option>
28542887 <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>
28552889 <option value="gemini-1.5-flash-latest">Gemini 1.5 Flash [latest]</option>
28562890 <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>
28572892 <option value="gemini-1.0-pro-latest">Gemini 1.0 Pro [latest]</option>
28582893 <option value="gemini-1.0-pro-001">Gemini 1.0 Pro (Tuning) [001]</option>
28592894 <option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision [latest]</option>
@@ -2884,6 +2919,7 @@
28842919 <option value="mistral-large-latest">mistral-large-latest</option>
28852920 <option value="codestral-latest">codestral-latest</option>
28862921 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
2922+ <option value="pixtral-latest">pixtral-latest</option>
28872923 </optgroup>
28882924 <optgroup label="Sub-versions">
28892925 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>
@@ -2891,11 +2927,13 @@
28912927 <option value="mistral-tiny-2312">mistral-tiny-2312</option>
28922928 <option value="mistral-small-2312">mistral-small-2312</option>
28932929 <option value="mistral-small-2402">mistral-small-2402</option>
2930+ <option value="mistral-small-2409">mistral-small-2409</option>
28942931 <option value="mistral-medium-2312">mistral-medium-2312</option>
28952932 <option value="mistral-large-2402">mistral-large-2402</option>
28962933 <option value="mistral-large-2407">mistral-large-2407</option>
28972934 <option value="codestral-2405">codestral-2405</option>
28982935 <option value="codestral-mamba-2407">codestral-mamba-2407</option>
2936+ <option value="pixtral-12b-2409">pixtral-12b-2409</option>
28992937 </optgroup>
29002938 </select>
29012939 </div>
@@ -3085,9 +3123,22 @@
30853123 <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
30863124 </div>
30873125 <div id="AdvancedFormatting" class="drawer-content">
30883126 <h3div class="margin0" dataflex-i18n="Advancedcontainer FormattingalignItemsBaseline">
3127+ <h3 class="margin0 flex1" data-i18n="Advanced Formatting">
30893128 Advanced Formatting
30903129 </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>
30913142 <div class="flex-container spaceEvenly">
30923143 <div id="ContextSettings" class="flex-container flexNoGap flexFlowColumn flex1">
30933144 <div>
@@ -3099,18 +3150,17 @@
30993150 </a>
31003151 </div>
31013152 </h4>
3153+ <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">
3154+ <select id="context_presets" data-preset-manager-for="context" class="flex1 text_pole"></select>
31023155 <div class="flex-container justifyCenter">
31033156 <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>
31053157 <i data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
31063158 <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>
31073159 <i data-preset-manager-import="context" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
31083160 <i data-preset-manager-export="context" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
31093161 <i data-preset-manager-restore="context" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
31103162 <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>
31113163 </div>
3112- <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>
31143164 </div>
31153165 <div>
31163166 <label for="context_story_string" class="flex-container">
@@ -3141,9 +3191,6 @@
31413191 <span data-i18n="Context Formatting">
31423192 Context Formatting
31433193 </span>
3144- <small data-i18n="(Saves to Template)">
3145- (Saves to Template)
3146- </small>
31473194 </h4>
31483195
31493196 <label class="checkbox_label" for="always-force-name2-checkbox">
@@ -3174,49 +3221,19 @@
31743221 Trim Incomplete Sentences
31753222 </small>
31763223 </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>
31823224 <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.">
31833225 <input id="context_use_stop_strings" type="checkbox" />
31843226 <small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small>
31853227 </label>
31863228 <label class="checkbox_label" title="Add Character and User names to a list of stopping strings." data-i18n="[title]Add Character and User names to a list of stopping strings.">
31873229 <input id="context_names_as_stop_strings" type="checkbox" />
31883230 <small data-i18n="Names as Stop Strings">Names as Stop Strings</small>
31893231 </label>
31903232 </div>
31913233
31923234 <label class="checkbox_label" title="Includes Post-History Instructions at the end of the prompt, if defined in the character card AND ''Prefer Char. Instructions'' is enabled.&#10;THIS IS NOT RECOMMENDED FOR TEXT COMPLETION MODELS, CAN LEAD TO BAD OUTPUT." data-i18n="[title]context_allow_post_history_instructions">
31933235 <input id="context_allow_jailbreak" type="checkbox" />
31943236 <small data-i18n="Allow Post-History Instructions">Allow Post-History Instructions</small>
3195- </label>
3196- </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>
32203237 </label>
32213238 </div>
32223239 </div>
@@ -3231,29 +3248,28 @@
32313248 </a>
32323249 </div>
32333250 <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>
32383251 <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">
32393252 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
32403253 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
32413254 </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>
32423259 </div>
32433260 </h4>
3261+
3262+ <div class="flex-container" title="Select your current Instruct Template" data-i18n="[title]Select your current Instruct Template">
3263+ <select id="instruct_presets" data-preset-manager-for="instruct" class="flex1 text_pole"></select>
32443264 <div class="flex-container margin0 justifyCenter">
32453265 <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>
32473266 <i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
32483267 <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>
32493268 <i data-preset-manager-import="instruct" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
32503269 <i data-preset-manager-export="instruct" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
32513270 <i data-preset-manager-restore="instruct" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
32523271 <i data-preset-manager-delete="instruct" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
32533272 </div>
3254-
3255- <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>
32573273 </div>
32583274 <label>
32593275 <small>
@@ -3265,14 +3281,6 @@
32653281 <input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i">
32663282 </div>
32673283 <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>
32763284 <label for="instruct_wrap" class="checkbox_label">
32773285 <input id="instruct_wrap" type="checkbox" />
32783286 <small data-i18n="Wrap Sequences with Newline">Wrap Sequences with Newline</small>
@@ -3285,116 +3293,29 @@
32853293 <input id="instruct_skip_examples" type="checkbox" />
32863294 <small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>
32873295 </label>
3288- <label for="instruct_names" class="checkbox_label">
3289- <input id="instruct_names" type="checkbox" />
3290- <small data-i18n="Include Names">Include Names</small>
3291- </label>
3292- <label for="instruct_names_force_groups" class="checkbox_label indent20p">
3293- <input id="instruct_names_force_groups" type="checkbox" />
3294- <small data-i18n="Force for Groups and Personas">Force for Groups and Personas</small>
3295- </label>
3296- </div>
3297- </div>
3298- <div name="tokenizerSettingsBlock">
3299- <div name="tokenizerSelectorBlock">
3300- <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
3301- <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#tokenizer" class="notes-link" target="_blank">
3302- <span class="fa-solid fa-circle-question note-link-span"></span>
3303- </a>
3304- </h4>
3305- <select id="tokenizer">
3306- <option value="99">Best match (recommended)</option>
3307- <option value="0">None / Estimated</option>
3308- <option value="1">GPT-2</option>
3309- <!-- Option #2 was a legacy GPT-2/3 tokenizer -->
3310- <option value="3">Llama 1/2</option>
3311- <option value="12">Llama 3</option>
3312- <option value="13">Gemma / Gemini</option>
3313- <option value="14">Jamba</option>
3314- <option value="15">Qwen2</option>
3315- <option value="16">Command-R</option>
3316- <option value="4">NerdStash (NovelAI Clio)</option>
3317- <option value="5">NerdStash v2 (NovelAI Kayra)</option>
3318- <option value="7">Mistral</option>
3319- <option value="8">Yi</option>
3320- <option value="11">Claude 1/2</option>
3321- <option value="6">API (WebUI / koboldcpp)</option>
3322- </select>
3323- </div>
3324- <div class="range-block flex-container flexnowrap" name="tokenPaddingBlock">
3325- <div class="range-block-title justifyLeft">
3326- <small data-i18n="Token Padding" class="flex-container">Token Padding
3327- <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#token-padding" class="notes-link" target="_blank">
3328- <span class="fa-solid fa-circle-question note-link-span"></span>
3329- </a>
3330- </small>
3331- </div>
3332- <input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" />
3333- </div>
3334- </div>
3335- <div>
3336- <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4>
3337- <div>
3338- <small>
3339- <span data-i18n="Non-markdown strings">
3340- Non-markdown strings
3341- </span>
3342- </small>
3343- <div>
3344- <input id="markdown_escape_strings" class="text_pole textarea_compact" type="text" data-i18n="[placeholder]comma delimited,no spaces between" placeholder="comma delimited,no spaces between" />
3345- </div>
3346- </div>
3347-
3348- <div name="startReplyWithBlock">
3349- <div>
3350- <small>
3351- <span data-i18n="Start Reply With">
3352- Start Reply With
3353- </span>
3354- </small>
33553296 <div>
3356- <textarea id="start_reply_with" class="text_pole textarea_compact autoSetHeight"></textarea>
3297+ <small data-i18n="Include Names">
3357- </div>
3298+ Include Names
3358- <label class="checkbox_label" for="chat-show-reply-prefix-checkbox">
3359- <input id="chat-show-reply-prefix-checkbox" type="checkbox" />
3360- <small data-i18n="Show reply prefix in chat">
3361- Show reply prefix in chat
33623299 </small>
3363- </label>
3300+ <select id="instruct_names_behavior">
3364- </div>
3301+ <option value="none" data-i18n="Never">Never</option>
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>
33653305 </div>
33663306 </div>
33673307 </div>
33683308 <div id="InstructSequencesColumn" class="flex-container flexNoGapwide100p flexFlowColumn flex1">
3369- <div class="wide100p flexFlowColumn">
33703309 <h4 class="standoutHeader title_restorable">
33713310 <b>
33723311 <span data-i18n="Instruct Sequences">
33733312 Instruct Sequences
33743313 </span>
3375- <small data-i18n="(Saves to Template)">
3376- (Saves to Template)
3377- </small>
33783314 </b>
33793315 </h4>
3380- <div class="flex-container">
3316+ <!-- We keep one auto-open so the user would know what is going on in the picked template -->
3381- <div class="flexAuto" title="Inserted before a System prompt." data-i18n="[title]Inserted before a System prompt.">
3317+ <details open>
3382- <label for="instruct_system_sequence_prefix">
3318+ <summary>User Message Sequences</summary>
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>
33983319 <div class="flex-container">
33993320 <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.">
34003321 <small data-i18n="User Prefix">User Message Prefix</small>
@@ -3405,6 +3326,9 @@
34053326 <textarea id="instruct_input_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
34063327 </div>
34073328 </div>
3329+ </details>
3330+ <details open>
3331+ <summary>Assistant Message Sequences</summary>
34083332 <div class="flex-container">
34093333 <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.">
34103334 <small data-i18n="Assistant Prefix">Assistant Message Prefix</small>
@@ -3415,6 +3339,9 @@
34153339 <textarea id="instruct_output_suffix" class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
34163340 </div>
34173341 </div>
3342+ </details>
3343+ <details>
3344+ <summary>System Message Sequences</summary>
34183345 <div class="flex-container">
34193346 <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.">
34203347 <small data-i18n="System Prefix">System Message Prefix</small>
@@ -3431,10 +3358,30 @@
34313358 <small data-i18n="System same as User">System same as User</small>
34323359 </label>
34333360 </div>
3434-
3361+ </details>
3435- <h5 class="textAlignCenter" data-i18n="Misc. Sequences">
3362+ <details>
3436- Misc. Sequences
3363+ <summary>System Prompt Sequences</summary>
3437- </h5>
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>
34383385 <div class="flex-container">
34393386 <div class="flexAuto" title="Inserted before the first Assistant's message." data-i18n="[title]Inserted before the first Assistant's message.">
34403387 <small data-i18n="First Assistant Prefix">First Assistant Prefix</small>
@@ -3471,6 +3418,147 @@
34713418 <textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight"></textarea>
34723419 </div>
34733420 </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>
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>
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>
3491+ </div>
3492+
3493+ <div name="tokenizerSettingsBlock">
3494+ <div name="tokenizerSelectorBlock">
3495+ <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
3496+ <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#tokenizer" class="notes-link" target="_blank">
3497+ <span class="fa-solid fa-circle-question note-link-span"></span>
3498+ </a>
3499+ </h4>
3500+ <select id="tokenizer">
3501+ <option value="99">Best match (recommended)</option>
3502+ <option value="0">None / Estimated</option>
3503+ <option value="1">GPT-2</option>
3504+ <!-- Option #2 was a legacy GPT-2/3 tokenizer -->
3505+ <option value="3">Llama 1/2</option>
3506+ <option value="12">Llama 3</option>
3507+ <option value="13">Gemma / Gemini</option>
3508+ <option value="14">Jamba</option>
3509+ <option value="15">Qwen2</option>
3510+ <option value="16">Command-R</option>
3511+ <option value="4">NerdStash (NovelAI Clio)</option>
3512+ <option value="5">NerdStash v2 (NovelAI Kayra)</option>
3513+ <option value="7">Mistral V1</option>
3514+ <option value="17">Mistral Nemo</option>
3515+ <option value="8">Yi</option>
3516+ <option value="11">Claude 1/2</option>
3517+ <option value="6">API (WebUI / koboldcpp)</option>
3518+ </select>
3519+ </div>
3520+ <div class="range-block flex-container flexnowrap" name="tokenPaddingBlock">
3521+ <div class="range-block-title justifyLeft">
3522+ <small data-i18n="Token Padding" class="flex-container">Token Padding
3523+ <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#token-padding" class="notes-link" target="_blank">
3524+ <span class="fa-solid fa-circle-question note-link-span"></span>
3525+ </a>
3526+ </small>
3527+ </div>
3528+ <input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" />
3529+ </div>
3530+ </div>
3531+ <div>
3532+ <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4>
3533+ <div>
3534+ <small>
3535+ <span data-i18n="Non-markdown strings">
3536+ Non-markdown strings
3537+ </span>
3538+ </small>
3539+ <div>
3540+ <input id="markdown_escape_strings" class="text_pole textarea_compact" type="text" data-i18n="[placeholder]comma delimited,no spaces between" placeholder="comma delimited,no spaces between" />
3541+ </div>
3542+ </div>
3543+
3544+ <div name="startReplyWithBlock">
3545+ <div>
3546+ <small>
3547+ <span data-i18n="Start Reply With">
3548+ Start Reply With
3549+ </span>
3550+ </small>
3551+ <div>
3552+ <textarea id="start_reply_with" class="text_pole textarea_compact autoSetHeight"></textarea>
3553+ </div>
3554+ <label class="checkbox_label" for="chat-show-reply-prefix-checkbox">
3555+ <input id="chat-show-reply-prefix-checkbox" type="checkbox" />
3556+ <small data-i18n="Show reply prefix in chat">
3557+ Show reply prefix in chat
3558+ </small>
3559+ </label>
3560+ </div>
3561+ </div>
34743562 </div>
34753563 </div>
34763564 </div>
@@ -5452,23 +5540,23 @@
54525540 <div class="world_entry_form_control flex1">
54535541 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
54545542 <select name="caseSensitive" class="text_pole widthNatural margin0">
54555543 <option value="null" data-i18n="Use global setting">Use global setting</option>
54565544 <option value="true" data-i18n="Yes">Yes</option>
54575545 <option value="false" data-i18n="No">No</option>
54585546 </select>
54595547 </div>
54605548 <div class="world_entry_form_control flex1">
54615549 <small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small>
54625550 <select name="matchWholeWords" class="text_pole widthNatural margin0">
54635551 <option value="null" data-i18n="Use global setting">Use global setting</option>
54645552 <option value="true" data-i18n="Yes">Yes</option>
54655553 <option value="false" data-i18n="No">No</option>
54665554 </select>
54675555 </div>
54685556 <div class="world_entry_form_control flex1">
54695557 <small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small>
54705558 <select name="useGroupScoring" class="text_pole widthNatural margin0">
54715559 <option value="null" data-i18n="Use global setting">Use global setting</option>
54725560 <option value="true" data-i18n="Yes">Yes</option>
54735561 <option value="false" data-i18n="No">No</option>
54745562 </select>
@@ -5477,6 +5565,13 @@
54775565 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
54785566 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
54795567 </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>
54805575 </div>
54815576 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
54825577 <div class="world_entry_form_control flex1">
@@ -5493,20 +5588,20 @@
54935588 <div>
54945589 <label class="checkbox flex-container alignitemscenter flexNoGap">
54955590 <input type="checkbox" name="exclude_recursion" />
54965591 <span data-i18n="ExcludeNon-recursable from(will recursionnot be activated by another)">
54975592 Non-recursable (this entry will not be activated by another)
54985593 </span>
54995594 </label>
55005595 <label class="checkbox flex-container alignitemscenter flexNoGap">
55015596 <input type="checkbox" name="prevent_recursion" />
55025597 <span data-i18n="Prevent further recursion (this entry will not activate others)">
55035598 Prevent further recursion (this entry will not activate others)
55045599 </span>
55055600 </label>
55065601 <label class="checkbox flex-container alignitemscenter flexNoGap">
55075602 <input type="checkbox" name="delay_until_recursion" />
55085603 <span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)">
55095604 Delay until recursion (this entry can only be activated on recursive checking)
55105605 </span>
55115606 </label>
55125607 </div>
public/script.js+63 -31
@@ -99,7 +99,7 @@ import {
9999 proxies,
100100 loadProxyPresets,
101101 selected_proxy,
102102 initOpenaiinitOpenAI,
103103} from './scripts/openai.js';
104104
105105import {
@@ -210,7 +210,7 @@ import {
210210 selectContextPreset,
211211} from './scripts/instruct-mode.js';
212212import { initLocales, t, translate } from './scripts/i18n.js';
213213import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
214214import {
215215 user_avatar,
216216 getUserAvatars,
@@ -224,7 +224,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set
224224import { hideLoader, showLoader } from './scripts/loader.js';
225225import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
226226import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
227227import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
228228import { initPresetManager } from './scripts/preset-manager.js';
229229import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';
230230import { currentUser, setUserControls } from './scripts/user.js';
@@ -243,6 +243,7 @@ import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/Slash
243243import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
244244import { initInputMarkdown } from './scripts/input-md-formatting.js';
245245import { AbortReason } from './scripts/util/AbortReason.js';
246+import { initSystemPrompts } from './scripts/sysprompt.js';
246247
247248//exporting functions and vars for mods
248249export {
@@ -411,6 +412,7 @@ export const event_types = {
411412 MESSAGE_FILE_EMBEDDED: 'message_file_embedded',
412413 IMPERSONATE_READY: 'impersonate_ready',
413414 CHAT_CHANGED: 'chat_id_changed',
415+ GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS',
414416 GENERATION_STARTED: 'generation_started',
415417 GENERATION_STOPPED: 'generation_stopped',
416418 GENERATION_ENDED: 'generation_ended',
@@ -509,6 +511,7 @@ let saveCharactersPage = 0;
509511export const default_avatar = 'img/ai4.png';
510512export const system_avatar = 'img/five.png';
511513export const comment_avatar = 'img/quill.png';
514+export const default_user_avatar = 'img/user-default.png';
512515export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header
513516let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
514517 placement: 'top-start',
@@ -927,9 +930,12 @@ async function firstLoadInit() {
927930 addSafariPatch();
928931 await getClientVersion();
929932 await readSecretState();
930933 await initLocales();
931934 initDefaultSlashCommands();
932935 initTextGenModels();
936+ initOpenAI();
937+ initSystemPrompts();
938+ await initPresetManager();
933939 await getSystemMessages();
934940 sendSystemMessage(system_message_types.WELCOME);
935941 sendSystemMessage(system_message_types.WELCOME_PROMPT);
@@ -937,13 +943,11 @@ async function firstLoadInit() {
937943 initKeyboard();
938944 initDynamicStyles();
939945 initTags();
940- initOpenai();
941946 initBookmarks();
942947 await getUserAvatars(true, user_avatar);
943948 await getCharacters();
944949 await getBackgrounds();
945950 await initTokenizers();
946- await initPresetManager();
947951 initBackgrounds();
948952 initAuthorsNote();
949953 initPersonas();
@@ -1213,6 +1217,9 @@ async function getStatusTextgen() {
12131217 // Determine instruct mode preset
12141218 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+
12161223 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
12171224 if (online_status === 'no_connection' && data.response) {
12181225 toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });
@@ -1845,6 +1852,7 @@ export async function deleteLastMessage() {
18451852}
18461853
18471854export async function reloadCurrentChat() {
1855+ preserveNeutralChat();
18481856 await clearChat();
18491857 chat.length = 0;
18501858
@@ -1856,6 +1864,7 @@ export async function reloadCurrentChat() {
18561864 }
18571865 else {
18581866 resetChatState();
1867+ restoreNeutralChat();
18591868 await getCharacters();
18601869 await printMessages();
18611870 await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
@@ -3332,10 +3341,12 @@ function removeLastMessage() {
33323341 */
33333342export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName } = {}, dryRun = false) {
33343343 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);
33363344 setGenerationProgress(0);
33373345 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+
33393350 // Don't recreate abort controller if signal is passed
33403351 if (!(abortController && signal)) {
33413352 abortController = new AbortController();
@@ -3355,6 +3366,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
33553366 }
33563367 }
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+
33583372 if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
33593373 toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true });
33603374 unblockGeneration(type);
@@ -3512,9 +3526,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
35123526 jailbreak,
35133527 } = getCharacterCardFields();
35143528
35153529 if (isInstructmain_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+ }
35183537 }
35193538
35203539 // Depth prompt (character-specific A/N)
@@ -3767,7 +3786,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
37673786 personality: personality,
37683787 persona: power_user.persona_description_position == persona_description_positions.IN_PROMPT ? persona : '',
37693788 scenario: scenario,
37703789 system: isInstruct ? system : '',
37713790 char: name2,
37723791 user: name1,
37733792 wiBefore: worldInfoBefore,
@@ -4340,7 +4359,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
43404359 this_max_context: this_max_context,
43414360 padding: power_user.token_padding,
43424361 main_api: main_api,
43434362 instruction: isInstructmain_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.instructsysprompt.system_promptcontent) : '',
43444363 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
43454364 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
43464365 };
@@ -4839,6 +4858,13 @@ export function getMaxContextSize(overrideResponseLength = null) {
48394858 console.log(`NovelAI subscription limit reached. Max context size is now ${this_max_context}`);
48404859 }
48414860 }
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
48434869 this_max_context = this_max_context - (overrideResponseLength || amount_gen);
48444870 }
@@ -5466,7 +5492,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
54665492 }
54675493
54685494 if (!displayIncompleteSentences && power_user.trim_sentences) {
54695495 getMessage = trimToEndSentence(getMessage, power_user.include_newline);
54705496 }
54715497
54725498 if (power_user.trim_spaces) {
@@ -5698,10 +5724,10 @@ export function deactivateSendButtons() {
56985724}
56995725
57005726export function resetChatState() {
5727+ // replaces deleted charcter name with system user since it will be displayed next.
5728+ name2 = (this_chid === undefined && neutralCharacterName) ? neutralCharacterName : systemUserName;
57015729 //unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
57025730 this_chid = undefined;
5703- // replaces deleted charcter name with system user since it will be displayed next.
5704- name2 = systemUserName;
57055731 // sets up system user to tell user about having deleted a character
57065732 chat.splice(0, chat.length, ...SAFETY_CHAT);
57075733 // resets chat metadata
@@ -6917,15 +6943,13 @@ export async function displayPastChats() {
69176943 }
69186944 // Check whether `text` {string} includes all of the `fragments` {string[]}.
69196945 function matchFragments(fragments, text) {
69206946 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));
69246948 }
69256949 const fragments = makeQueryFragments(searchQuery);
69266950 // At least one chat message must match *all* the fragments.
69276951 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.
69286952 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase()));
69296953 });
69306954
69316955 console.debug(filteredData);
@@ -8565,20 +8589,27 @@ async function connectAPISlash(args, text) {
85658589 return '';
85668590 }
85678591
8592+ let connectionRequired = false;
8593+
8594+ if (main_api !== apiConfig.selected) {
85688595 $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
85698596 $('#main_api').trigger('change');
8597+ connectionRequired = true;
8598+ }
85708599
85718600 if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) {
85728601 $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true);
85738602 $('#chat_completion_source').trigger('change');
8603+ connectionRequired = true;
85748604 }
85758605
85768606 if (apiConfig.type && textgen_settings.type !== apiConfig.type) {
85778607 $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true);
85788608 $('#textgen_type').trigger('change');
8609+ connectionRequired = true;
85798610 }
85808611
85818612 if (connectionRequired && apiConfig.button) {
85828613 $(apiConfig.button).trigger('click');
85838614 }
85848615
@@ -8803,7 +8834,7 @@ export async function renameChat(oldFileName, newName) {
88038834 is_group: !!selected_group,
88048835 avatar_url: characters[this_chid]?.avatar,
88058836 original_file: `${oldFileName}.jsonl`,
88068837 renamed_file: `${newName.trim()}.jsonl`,
88078838 };
88088839
88098840 try {
@@ -8824,6 +8855,10 @@ export async function renameChat(oldFileName, newName) {
88248855 throw new Error('Server returned an error.');
88258856 }
88268857
8858+ if (data.sanitizedFileName) {
8859+ newName = data.sanitizedFileName;
8860+ }
8861+
88278862 if (selected_group) {
88288863 await renameGroupChat(selected_group, oldFileName, newName);
88298864 }
@@ -8945,15 +8980,12 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
89458980 * It also ensures to save the settings after all the operations.
89468981 */
89478982async function removeCharacterFromUI() {
8983+ preserveNeutralChat();
89488984 await clearChat();
89498985 $('#character_cross').clicktrigger('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 = {};
89558987 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');
8956- this_chid = undefined;
8988+ restoreNeutralChat();
89578989 await getCharacters();
89588990 await printMessages();
89598991 saveSettingsDebounced();
public/scripts/PromptManager.js+39 -13
@@ -1,12 +1,13 @@
11'use strict';
22
33import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
44import { is_group_generating } from './group-chats.js';
55import { Message, TokenHandler } from './openai.js';
66import { power_user } from './power-user.js';
77import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
88import { debounce_timeout } from './constants.js';
99import { renderTemplateAsync } from './templates.js';
10+import { Popup } from './popup.js';
1011
1112function debouncePromise(func, delay) {
1213 let timeoutId;
@@ -427,12 +428,13 @@ class PromptManager {
427428
428429 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name;
429430 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system';
430431 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? '';
431432 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;
432433 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;
433434 document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
434435 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;
435436 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
437439 if (!this.systemPrompts.includes(promptId)) {
438440 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');
@@ -452,7 +454,9 @@ class PromptManager {
452454 };
453455
454456 // Delete selected prompt from list form and close edit form
455457 this.handleDeletePrompt = async (event) => {
458+ Popup.show.confirm('Are you sure you want to delete this prompt?', null).then((userChoice) => {
459+ if (!userChoice) return;
456460 const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
457461 const prompt = this.getPromptById(promptID);
458462
@@ -467,6 +471,7 @@ class PromptManager {
467471 this.render();
468472 this.saveServiceSettings();
469473 }
474+ });
470475 };
471476
472477 // Create new prompt, then save it to settings and close form.
@@ -526,9 +531,9 @@ class PromptManager {
526531
527532 // Import prompts for the selected character
528533 this.handleImport = () => {
529534 callPopupPopup.show.confirm('Existing prompts with the same ID will be overridden. Do you want to proceed?', 'confirm'null)
530535 .then(userChoice => {
531536 if (false === !userChoice) return;
532537
533538 const fileOpener = document.createElement('input');
534539 fileOpener.type = 'file';
@@ -562,9 +567,9 @@ class PromptManager {
562567
563568 // Restore default state of a characters prompt order
564569 this.handleCharacterReset = () => {
565570 callPopupPopup.show.confirm('This will reset the prompt order for this character. You will not lose any prompts.', 'confirm'null)
566571 .then(userChoice => {
567572 if (false === !userChoice) return;
568573
569574 this.removePromptOrderForCharacter(this.activeCharacter);
570575 this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
@@ -920,7 +925,15 @@ class PromptManager {
920925 * @returns {boolean} True if the prompt can be edited, false otherwise.
921926 */
922927 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;
924937 }
925938
926939 /**
@@ -929,7 +942,17 @@ class PromptManager {
929942 * @returns {boolean} True if the prompt can be deleted, false otherwise.
930943 */
931944 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+ ];
933956 return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);
934957 }
935958
@@ -1182,8 +1205,9 @@ class PromptManager {
11821205 const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
11831206
11841207 nameField.value = prompt.name ?? '';
11851208 roleField.value = prompt.role ?? 'system';
11861209 promptField.value = prompt.content ?? '';
1210+ promptField.disabled = prompt.marker ?? false;
11871211 injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;
11881212 injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;
11891213 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
@@ -1279,6 +1303,7 @@ class PromptManager {
12791303 nameField.value = '';
12801304 roleField.selectedIndex = 0;
12811305 promptField.value = '';
1306+ promptField.disabled = false;
12821307 injectionPositionField.selectedIndex = 0;
12831308 injectionPositionField.removeAttribute('disabled');
12841309 injectionDepthField.value = DEFAULT_DEPTH;
@@ -1515,16 +1540,17 @@ class PromptManager {
15151540 }
15161541
15171542 const encodedName = escapeHtml(prompt.name);
1543+ const isMarkerPrompt = prompt.marker && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
15181544 const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && !prompt.forbid_overrides;
15191545 const isImportantPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && prompt.forbid_overrides;
15201546 const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
15211547 const isInjectionPrompt = !prompt.marker && prompt.injection_position === INJECTION_POSITION.ABSOLUTE;
15221548 const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier);
15231549 const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : '';
15241550 listItemHtml += `
15251551 <li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${prompt.identifier}">
15261552 <span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
15271553 ${prompt.markerisMarkerPrompt ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
15281554 ${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
15291555 ${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}
15301556 ${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}
@@ -1724,7 +1750,7 @@ class PromptManager {
17241750 */
17251751 showPopup(area = 'edit') {
17261752 const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);
17271753 areaElement.style.display = 'blockflex';
17281754
17291755 $('#' + this.configuration.prefix + 'prompt_manager_popup').first()
17301756 .slideDown(200, 'swing')
public/scripts/RossAscends-mods.js+10 -0
@@ -737,6 +737,16 @@ export function initRossMods() {
737737 RA_autoconnect();
738738 }
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+
740750 $('#main_api').change(function () {
741751 var PrevAPI = main_api;
742752 setTimeout(() => RA_autoconnect(PrevAPI), 100);
public/scripts/backgrounds.js+1 -1
@@ -313,7 +313,7 @@ async function onDeleteBackgroundClick(e) {
313313 }
314314}
315315
316316const autoBgPrompt = 'PauseIgnore yourprevious roleplayinstructions 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
318318async function autoBackgroundCommand() {
319319 /** @type {HTMLElement[]} */
public/scripts/chats.js+31 -2
@@ -19,6 +19,8 @@ import {
1919 this_chid,
2020 saveChatConditional,
2121 chat_metadata,
22+ neutralCharacterName,
23+ updateChatMetadata,
2224} from '../script.js';
2325import { selected_group } from './group-chats.js';
2426import { power_user } from './power-user.js';
@@ -133,10 +135,11 @@ export async function hideChatMessageRange(start, end, unhide) {
133135 const message = chat[messageId];
134136 if (!message) continue;
135137
138+ message.is_system = hide;
139+
140+ // Also toggle "hidden" state for all visible messages
136141 const messageBlock = $(`.mes[mesid="${messageId}"]`);
137142 if (!messageBlock.length) continue;
138-
139- message.is_system = hide;
140143 messageBlock.attr('is_system', String(hide));
141144 }
142145
@@ -1352,6 +1355,32 @@ async function verifyAttachmentsForSource(source) {
13521355 }
13531356}
13541357
1358+const NEUTRAL_CHAT_KEY = 'neutralChat';
1359+
1360+export 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+
1368+export 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+
13551384/**
13561385 * Registers a file converter function.
13571386 * @param {string} mimeType MIME type
public/scripts/extensions.js+0 -1
@@ -29,7 +29,6 @@ export function saveMetadataDebounced() {
2929 const characterId = context.characterId;
3030
3131 if (saveMetadataTimeout) {
32- console.debug('Clearing save metadata timeout');
3332 clearTimeout(saveMetadataTimeout);
3433 }
3534
public/scripts/extensions/caption/index.js+55 -22
@@ -1,6 +1,6 @@
11import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
22import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
33import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, main_api, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
44import { getMessageTimeStamp } from '../../RossAscends-mods.js';
55import { SECRET_KEYS, secret_state } from '../../secrets.js';
66import { getMultimodalCaption } from '../shared.js';
@@ -403,6 +403,7 @@ jQuery(async function () {
403403 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
404404 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
405405 (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)) ||
406407 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
407408 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
408409 (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 () {
431432 $('#form_sheld').append(imgForm);
432433 $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false));
433434 }
434435 async function switchMultimodalBlocks() {
436+ await addOpenRouterModels();
435437 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- });
442438 $('#caption_multimodal_block').toggle(isMultimodal);
443439 $('#caption_prompt_block').toggle(isMultimodal);
444440 $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);
@@ -448,30 +444,48 @@ jQuery(async function () {
448444 const types = type.split(',');
449445 $(this).toggle(types.includes(extension_settings.caption.multimodal_api));
450446 });
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- });
463447 }
464448 async function addSettings() {
465449 const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });
466450 $('#caption_container').append(html);
467451 }
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
469483 await addSettings();
470484 addPictureSendForm();
471485 addSendPictureButton();
472486 setImageIcon();
473487 migrateSettings();
474488 await switchMultimodalBlocks();
475489
476490 $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
477491 $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));
@@ -506,6 +520,24 @@ jQuery(async function () {
506520 extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');
507521 saveSettingsDebounced();
508522 });
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
510542 const onMessageEvent = async (index) => {
511543 if (!extension_settings.caption.auto_mode) {
@@ -538,7 +570,8 @@ jQuery(async function () {
538570 }
539571 });
540572
541573 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',
574+ name: 'caption',
542575 callback: captionCommandCallback,
543576 returns: 'caption',
544577 namedArgumentList: [
public/scripts/extensions/caption/settings.html+4 -1
@@ -23,6 +23,7 @@
2323 <option value="google">Google AI Studio</option>
2424 <option value="koboldcpp">KoboldCpp</option>
2525 <option value="llamacpp">llama.cpp</option>
26+ <option value="mistral">MistralAI</option>
2627 <option value="ollama">Ollama</option>
2728 <option value="openai">OpenAI</option>
2829 <option value="openrouter">OpenRouter</option>
@@ -33,6 +34,8 @@
3334 <div class="flex1 flex-container flexFlowColumn flexNoGap">
3435 <label for="caption_multimodal_model" data-i18n="Model">Model</label>
3536 <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>
3639 <option data-type="zerooneai" value="yi-vision">yi-vision</option>
3740 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
3841 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
@@ -96,7 +99,7 @@
9699 <div data-type="ollama">
97100 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>.
98101 </div>
99102 <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.">
100103 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
101104 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
102105 </label>
public/scripts/extensions/connection-manager/index.js+42 -7
@@ -19,20 +19,23 @@ const DEFAULT_SETTINGS = {
1919 selectedProfile: null,
2020};
2121
2222const COMMON_COMMANDSCC_COMMANDS = [
2323 'api',
2424 'preset',
25+ // Do not fix; CC needs to set the API twice because it could be overridden by the preset
26+ 'api',
2527 'api-url',
2628 'model',
27-];
28-
29-const CC_COMMANDS = [
30- ...COMMON_COMMANDS,
3129 'proxy',
3230];
3331
3432const TC_COMMANDS = [
35- ...COMMON_COMMANDS,
33+ 'api',
34+ 'preset',
35+ 'api-url',
36+ 'model',
37+ 'sysprompt',
38+ 'sysprompt-state',
3639 'instruct',
3740 'context',
3841 'instruct-state',
@@ -45,6 +48,8 @@ const FANCY_NAMES = {
4548 'preset': 'Settings Preset',
4649 'model': 'Model',
4750 'proxy': 'Proxy Preset',
51+ 'sysprompt-state': 'Use System Prompt',
52+ 'sysprompt': 'System Prompt Name',
4853 'instruct-state': 'Instruct Mode',
4954 'instruct': 'Instruct Template',
5055 'context': 'Context Template',
@@ -180,6 +185,11 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
180185 }
181186
182187 if (cleanUp) {
188+ for (const command of commands) {
189+ if (command.endsWith('-state') && profile[command] === 'false') {
190+ delete profile[command.replace('-state', '')];
191+ }
192+ }
183193 for (const command of opposingCommands) {
184194 if (commands.includes(command)) {
185195 continue;
@@ -325,7 +335,7 @@ function renderConnectionProfiles(profiles) {
325335 noneOption.selected = !extension_settings.connectionManager.selectedProfile;
326336 profiles.appendChild(noneOption);
327337
328338 for (const profile of extension_settings.connectionManager.profiles.sort((a, b) => a.name.localeCompare(b.name))) {
329339 const option = document.createElement('option');
330340 option.value = profile.id;
331341 option.textContent = profile.name;
@@ -462,6 +472,31 @@ async function renderDetailsContent(detailsContent) {
462472 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
463473 });
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+
465500 /** @type {HTMLElement} */
466501 const viewDetails = document.getElementById('view_connection_profile');
467502 const detailsContent = document.getElementById('connection_profile_details_content');
public/scripts/extensions/connection-manager/settings.html+1 -0
@@ -13,6 +13,7 @@
1313 <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>
1414 <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>
1515 <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>
1617 <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>
1718 <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>
1819 </div>
public/scripts/extensions/expressions/index.js+6 -1
@@ -20,7 +20,7 @@ const STREAMING_UPDATE_INTERVAL = 10000;
2020const TALKINGCHECK_UPDATE_INTERVAL = 500;
2121const DEFAULT_FALLBACK_EXPRESSION = 'joy';
2222const FUNCTION_NAME = 'set_emotion';
2323const DEFAULT_LLM_PROMPT = 'PauseIgnore yourprevious roleplayinstructions. Classify the emotion of the last message. Output just one word, e.g. "joy" or "anger". Choose only one of the following labels: {{labels}}';
2424const DEFAULT_EXPRESSIONS = [
2525 'talkinghead',
2626 'admiration',
@@ -2064,6 +2064,11 @@ function migrateSettings() {
20642064 });
20652065 eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
20662066 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
20682073 const localEnumProviders = {
20692074 expressions: () => getCachedExpressions().map(expression => {
public/scripts/extensions/memory/index.js+1 -1
@@ -102,7 +102,7 @@ const prompt_builders = {
102102 RAW_NON_BLOCKING: 2,
103103};
104104
105105const defaultPrompt = '[PauseIgnore yourprevious roleplayinstructions. 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.]';
106106const defaultTemplate = '[Summary: {{summary}}]';
107107
108108const defaultSettings = {
public/scripts/extensions/quick-reply/src/QuickReply.js+1 -0
@@ -1904,6 +1904,7 @@ export class QuickReply {
19041904 executeOnAi: this.executeOnAi,
19051905 executeOnChatChange: this.executeOnChatChange,
19061906 executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,
1907+ executeOnNewChat: this.executeOnNewChat,
19071908 automationId: this.automationId,
19081909 };
19091910 }
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+4 -0
@@ -35,6 +35,7 @@ export class SlashCommandHandler {
3535 if (qr.executeOnUser) icons += enumIcons.user;
3636 if (qr.executeOnAi) icons += enumIcons.assistant;
3737 if (qr.executeOnChatChange) icons += '💬';
38+ if (qr.executeOnNewChat) icons += '🆕';
3839 if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
3940 return icons;
4041 }
@@ -265,6 +266,7 @@ export class SlashCommandHandler {
265266 new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
266267 new SlashCommandNamedArgument('bot', 'auto execute on AI message, e.g., bot=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
267268 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'),
268270 new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
269271 new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false),
270272 ];
@@ -857,6 +859,7 @@ export class SlashCommandHandler {
857859 executeOnUser: isTrueBoolean(args.user),
858860 executeOnAi: isTrueBoolean(args.bot),
859861 executeOnChatChange: isTrueBoolean(args.load),
862+ executeOnNewChat: isTrueBoolean(args.new),
860863 executeOnGroupMemberDraft: isTrueBoolean(args.group),
861864 automationId: args.automationId ?? '',
862865 },
@@ -889,6 +892,7 @@ export class SlashCommandHandler {
889892 executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),
890893 executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
891894 executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
895+ executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new),
892896 automationId: args.automationId ?? '',
893897 },
894898 );
public/scripts/extensions/shared.js+6 -2
@@ -13,7 +13,7 @@ import { createThumbnail, isValidUrl } from '../utils.js';
1313 */
1414export async function getMultimodalCaption(base64Img, prompt) {
1515 const useReverseProxy =
1616 (['openai', 'anthropic', 'google', 'mistral'].includes(extension_settings.caption.multimodal_api))
1717 && extension_settings.caption.allow_reverse_proxy
1818 && oai_settings.reverse_proxy
1919 && isValidUrl(oai_settings.reverse_proxy);
@@ -36,7 +36,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
3636 const isVllm = extension_settings.caption.multimodal_api === 'vllm';
3737 const base64Bytes = base64Img.length * 0.75;
3838 const compressionLimit = 2 * 1024 * 1024;
3939 if ((['google', 'openrouter', 'mistral'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
4040 const maxSide = 1024;
4141 base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
4242 }
@@ -139,6 +139,10 @@ function throwIfInvalidModel(useReverseProxy) {
139139 throw new Error('Google AI Studio API key is not set.');
140140 }
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+
142146 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
143147 throw new Error('Ollama server URL is not set.');
144148 }
public/scripts/extensions/stable-diffusion/index.js+13 -11
@@ -125,15 +125,14 @@ const messageTrigger = {
125125const promptTemplates = {
126126 // Not really a prompt template, rather an outcome message template
127127 [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,\']',
130129 //face-specific prompt
131130 [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,\']',
132131 //prompt for only the last message
133132 [generationMode.USER]: '[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply as {{char}} when writing this description, and do not attempt to continue the story.]',
134133 [generationMode.SCENARIO]: '[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply as {{char}} while writing this description.]',
135134
136135 [generationMode.NOW]: `[PauseIgnore yourprevious roleplayinstructions. 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
138137 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 = {
155154 If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.
156155
157156 A correctly formatted example response would be:
158157 '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']`,
159158
160159 [generationMode.RAW_LAST]: '[PauseIgnore yourprevious roleplayinstructions and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplayreply atas all{{char}} inwhen yourwriting response.this Dodescription, and do not attempt to continue the roleplay story.]',
161160 [generationMode.BACKGROUND]: '[PauseIgnore yourprevious roleplayinstructions 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 roleplayreply as {{userchar}} when writing this description, and do not attempt to continue the story.]',
162161 [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".',
163162 [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".',
164163 [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".',
165164 [generationMode.FREE_EXTENDED]: 'PauseIgnore yourprevious roleplayinstructions 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}}.',
166165};
167166
168167const defaultPrefix = 'best quality, absurdres, aesthetic,';
@@ -3813,7 +3812,10 @@ jQuery(async () => {
38133812 ],
38143813 helpString: `
38153814 <div>
38163815 Requests to generate an image and posts it to chat (unless <code>quiet=true</code> argument is specified).</code>.
3816+ </div>
3817+ <div>
3818+ Supported arguments: <code>${Object.values(triggerWords).flat().join(', ')}</code>.
38173819 </div>
38183820 <div>
38193821 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 @@
349349 </div>
350350 </div>
351351
352352 <div class="flex-container marginTopBot5" data-sd-source="auto,vlad,extras,horde,drawthings,comfy">
353353 <label class="flex1 checkbox_label">
354354 <input id="sd_restore_faces" type="checkbox" />
355355 <small data-i18n="Restore Faces">Restore Faces</small>
public/scripts/extensions/vectors/index.js+48 -85
@@ -56,7 +56,7 @@ const settings = {
5656 summarize: false,
5757 summarize_sent: false,
5858 summary_source: 'main',
5959 summary_prompt: 'PauseIgnore yourprevious roleplayinstructions. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.',
6060 force_chunk_delimiter: '',
6161
6262 // For chats
@@ -718,7 +718,7 @@ async function getQueryText(chat, initiator) {
718718async function getSavedHashes(collectionId) {
719719 const response = await fetch('/api/vector/list', {
720720 method: 'POST',
721721 headers: getRequestHeadersgetVectorHeaders(),
722722 body: JSON.stringify({
723723 collectionId: collectionId,
724724 source: settings.source,
@@ -737,105 +737,48 @@ function getVectorHeaders() {
737737 const headers = getRequestHeaders();
738738 switch (settings.source) {
739739 case 'extras':
740- addExtrasHeaders(headers);
741- break;
742- case 'togetherai':
743- addTogetherAiHeaders(headers);
744- break;
745- case 'openai':
746- addOpenAiHeaders(headers);
747- break;
748- case 'cohere':
749- addCohereHeaders(headers);
750- break;
751- case 'ollama':
752- addOllamaHeaders(headers);
753- break;
754- case 'llamacpp':
755- addLlamaCppHeaders(headers);
756- break;
757- case 'vllm':
758- addVllmHeaders(headers);
759- break;
760- default:
761- break;
762- }
763- return headers;
764-}
765-
766-/**
767- * Add headers for the Extras API source.
768- * @param {object} headers Headers object
769- */
770-function addExtrasHeaders(headers) {
771- console.log(`Vector source is extras, populating API URL: ${extension_settings.apiUrl}`);
772740 Object.assign(headers, {
773741 'X-Extras-Url': extension_settings.apiUrl,
774742 'X-Extras-Key': extension_settings.apiKey,
775743 });
776-}
744+ break;
777-
745+ case 'togetherai':
778-/**
779- * Add headers for the TogetherAI API source.
780- * @param {object} headers Headers object
781- */
782-function addTogetherAiHeaders(headers) {
783746 Object.assign(headers, {
784747 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,
785748 });
786-}
749+ break;
787-
750+ case 'openai':
788-/**
789- * Add headers for the OpenAI API source.
790- * @param {object} headers Header object
791- */
792-function addOpenAiHeaders(headers) {
793751 Object.assign(headers, {
794752 'X-OpenAI-Model': extension_settings.vectors.openai_model,
795753 });
796-}
754+ break;
797-
755+ case 'cohere':
798-/**
799- * Add headers for the Cohere API source.
800- * @param {object} headers Header object
801- */
802-function addCohereHeaders(headers) {
803756 Object.assign(headers, {
804757 'X-Cohere-Model': extension_settings.vectors.cohere_model,
805758 });
806-}
759+ break;
807-
760+ case 'ollama':
808-/**
809- * Add headers for the Ollama API source.
810- * @param {object} headers Header object
811- */
812-function addOllamaHeaders(headers) {
813761 Object.assign(headers, {
814762 'X-Ollama-Model': extension_settings.vectors.ollama_model,
815763 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],
816764 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,
817765 });
818-}
766+ break;
819-
767+ case 'llamacpp':
820-/**
821- * Add headers for the LlamaCpp API source.
822- * @param {object} headers Header object
823- */
824-function addLlamaCppHeaders(headers) {
825768 Object.assign(headers, {
826769 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],
827770 });
828-}
771+ break;
829-
772+ case 'vllm':
830-/**
831- * Add headers for the VLLM API source.
832- * @param {object} headers Header object
833- */
834-function addVllmHeaders(headers) {
835773 Object.assign(headers, {
836774 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],
837775 'X-Vllm-Model': extension_settings.vectors.vllm_model,
838776 });
777+ break;
778+ default:
779+ break;
780+ }
781+ return headers;
839782}
840783
841784/**
@@ -901,7 +844,7 @@ function throwIfSourceInvalid() {
901844async function deleteVectorItems(collectionId, hashes) {
902845 const response = await fetch('/api/vector/delete', {
903846 method: 'POST',
904847 headers: getRequestHeadersgetVectorHeaders(),
905848 body: JSON.stringify({
906849 collectionId: collectionId,
907850 hashes: hashes,
@@ -987,7 +930,7 @@ async function purgeFileVectorIndex(fileUrl) {
987930
988931 const response = await fetch('/api/vector/purge', {
989932 method: 'POST',
990933 headers: getRequestHeadersgetVectorHeaders(),
991934 body: JSON.stringify({
992935 collectionId: collectionId,
993936 }),
@@ -1016,7 +959,7 @@ async function purgeVectorIndex(collectionId) {
1016959
1017960 const response = await fetch('/api/vector/purge', {
1018961 method: 'POST',
1019962 headers: getRequestHeadersgetVectorHeaders(),
1020963 body: JSON.stringify({
1021964 collectionId: collectionId,
1022965 }),
@@ -1041,7 +984,7 @@ async function purgeAllVectorIndexes() {
1041984 try {
1042985 const response = await fetch('/api/vector/purge-all', {
1043986 method: 'POST',
1044987 headers: getRequestHeadersgetVectorHeaders(),
1045988 });
1046989
1047990 if (!response.ok) {
@@ -1056,6 +999,25 @@ async function purgeAllVectorIndexes() {
1056999 }
10571000}
10581001
1002+async 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+
10591021function toggleSettings() {
10601022 $('#vectors_files_settings').toggle(!!settings.enabled_files);
10611023 $('#vectors_chats_settings').toggle(!!settings.enabled_chats);
@@ -1320,6 +1282,7 @@ jQuery(async () => {
13201282 }
13211283
13221284 Object.assign(settings, extension_settings.vectors);
1285+ const scopesEnabled = await isModelScopesEnabled();
13231286
13241287 // Migrate from TensorFlow to Transformers
13251288 settings.source = settings.source !== 'local' ? settings.source : 'transformers';
@@ -1371,31 +1334,31 @@ jQuery(async () => {
13711334 saveSettingsDebounced();
13721335 });
13731336 $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {
13741337 !scopesEnabled && $('#vectors_modelWarning').show();
13751338 settings.togetherai_model = String($('#vectors_togetherai_model').val());
13761339 Object.assign(extension_settings.vectors, settings);
13771340 saveSettingsDebounced();
13781341 });
13791342 $('#vectors_openai_model').val(settings.openai_model).on('change', () => {
13801343 !scopesEnabled && $('#vectors_modelWarning').show();
13811344 settings.openai_model = String($('#vectors_openai_model').val());
13821345 Object.assign(extension_settings.vectors, settings);
13831346 saveSettingsDebounced();
13841347 });
13851348 $('#vectors_cohere_model').val(settings.cohere_model).on('change', () => {
13861349 !scopesEnabled && $('#vectors_modelWarning').show();
13871350 settings.cohere_model = String($('#vectors_cohere_model').val());
13881351 Object.assign(extension_settings.vectors, settings);
13891352 saveSettingsDebounced();
13901353 });
13911354 $('#vectors_ollama_model').val(settings.ollama_model).on('input', () => {
13921355 !scopesEnabled && $('#vectors_modelWarning').show();
13931356 settings.ollama_model = String($('#vectors_ollama_model').val());
13941357 Object.assign(extension_settings.vectors, settings);
13951358 saveSettingsDebounced();
13961359 });
13971360 $('#vectors_vllm_model').val(settings.vllm_model).on('input', () => {
13981361 !scopesEnabled && $('#vectors_modelWarning').show();
13991362 settings.vllm_model = String($('#vectors_vllm_model').val());
14001363 Object.assign(extension_settings.vectors, settings);
14011364 saveSettingsDebounced();
public/scripts/extensions/vectors/settings.html+3 -2
@@ -98,8 +98,9 @@
9898
9999 <small id="vectors_modelWarning">
100100 <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.
103104 </span>
104105 </small>
105106
public/scripts/horde.js+78 -18
@@ -1,10 +1,10 @@
11import {
22 saveSettingsDebouncedamount_gen,
33 callPopup,
4- setGenerationProgress,
54 getRequestHeaders,
65 max_context,
76 amount_gensaveSettingsDebounced,
7+ setGenerationProgress,
88} from '../script.js';
99import { SECRET_KEYS, writeSecret } from './secrets.js';
1010import { delay } from './utils.js';
@@ -45,8 +45,7 @@ async function getWorkers(force) {
4545 headers: getRequestHeaders(),
4646 body: JSON.stringify({ force }),
4747 });
4848 const data =return await response.json();
49- return data;
5049}
5150
5251/**
@@ -61,9 +60,11 @@ async function getModels(force) {
6160 body: JSON.stringify({ force }),
6261 });
6362 const data = await response.json();
63+ console.log('getModels', data);
6464 return data;
6565}
6666
67+
6768/**
6869 * Gets the status of a Horde task.
6970 * @param {string} taskId Task ID
@@ -80,8 +81,7 @@ async function getTaskStatus(taskId) {
8081 throw new Error(`Failed to get task status: ${response.statusText}`);
8182 }
8283
8384 const data =return await response.json();
84- return data;
8585}
8686
8787/**
@@ -148,7 +148,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
148148
149149 for (const model of selectedModels) {
150150 for (const worker of workers) {
151151 if (model.cluster === worker.cluster && worker.models.includes(model.name)) {
152152 // Skip workers that are not trusted if the option is enabled
153153 if (horde_settings.trusted_workers_only && !worker.trusted) {
154154 continue;
@@ -250,12 +250,10 @@ async function generateHorde(prompt, params, signal, reportProgress) {
250250 console.log(generatedText);
251251 console.log(`Generated by Horde Worker: ${WorkerName} [${WorkerModel}]`);
252252 return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName} [${WorkerModel}]` };
253- }
253+ } else if (!queue_position_first) {
254- else if (!queue_position_first) {
255254 queue_position_first = statusCheckJson.queue_position;
256255 reportProgress && setGenerationProgress(0);
257- }
256+ } else if (statusCheckJson.queue_position >= 0) {
258- else if (statusCheckJson.queue_position >= 0) {
259257 let queue_position = statusCheckJson.queue_position;
260258 const progress = Math.round(100 - (queue_position / queue_position_first * 100));
261259 reportProgress && setGenerationProgress(progress);
@@ -268,17 +266,24 @@ async function generateHorde(prompt, params, signal, reportProgress) {
268266 throw new Error('Horde timeout');
269267}
270268
269+
271270/**
272271 * Displays the available models in the Horde model selection dropdown.
273272 * @param {boolean} force Force refresh of the models
274273 */
275274async 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+
276279 $('#horde_model').empty();
277280 models = (await getModels(force)).sort((a, b) => b.performance - a.performance);{
281+ return sortByWhitelisted(a, b) || sortByPopular(a, b) || sortByPerformance(a, b);
282+ });
278283 for (const model of models) {
279284 const option = document.createElement('option');
280285 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);
282287 option.selected = horde_settings.models.includes(model.name);
283288 $('#horde_model').append(option);
284289 }
@@ -323,8 +328,66 @@ async function showKudos() {
323328 toastr.info(`Kudos: ${data.kudos}`, data.username);
324329}
325330
331+function hordeModelTextString(model) {
332+ const q = hordeModelQueueStateString(model);
333+ return `${model.name} (${q})`;
334+}
335+
336+function hordeModelQueueStateString(model) {
337+ return `ETA: ${model.eta}s, Speed: ${model.performance}, Queue: ${model.queued}, Workers: ${model.count}`;
338+}
339+
340+function 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+
326388jQuery(function () {
327389 $('#horde_model').on('mousedown change', async function (e) {
390+ console.log('Horde model change', e);
328391 horde_settings.models = $('#horde_model').val();
329392 console.log('Updated Horde models', horde_settings.models);
330393
@@ -374,10 +437,7 @@ jQuery(function () {
374437 // Customize the pillbox text by shortening the full text
375438 return data.id;
376439 },
377- templateResult: function (data) {
440+ templateResult: getHordeModelTemplate,
378- // Return the full text for the dropdown
379- return data.text;
380- },
381441 });
382442 }
383443});
public/scripts/instruct-mode.js+62 -54
@@ -14,10 +14,15 @@ import { regexFromString, resetScrollHeight } from './utils.js';
1414 */
1515export let instruct_presets = [];
1616
17+export const names_behavior_types = {
18+ NONE: 'none',
19+ FORCE: 'force',
20+ ALWAYS: 'always',
21+};
22+
1723const controls = [
1824 { id: 'instruct_enabled', property: 'enabled', isCheckbox: true },
1925 { id: 'instruct_wrap', property: 'wrap', isCheckbox: true },
20- { id: 'instruct_system_prompt', property: 'system_prompt', isCheckbox: false },
2126 { id: 'instruct_system_sequence_prefix', property: 'system_sequence_prefix', isCheckbox: false },
2227 { id: 'instruct_system_sequence_suffix', property: 'system_sequence_suffix', isCheckbox: false },
2328 { id: 'instruct_input_sequence', property: 'input_sequence', isCheckbox: false },
@@ -29,9 +34,6 @@ const controls = [
2934 { id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },
3035 { id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
3136 { 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 },
3537 { id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
3638 { id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },
3739 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
@@ -39,6 +41,7 @@ const controls = [
3941 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
4042 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
4143 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
44+ { id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },
4245 { id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
4346];
4447
@@ -54,6 +57,15 @@ function migrateInstructModeSettings(settings) {
5457 delete settings.separator_sequence;
5558 }
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+
5769 const defaults = {
5870 input_suffix: '',
5971 system_sequence: '',
@@ -65,6 +77,7 @@ function migrateInstructModeSettings(settings) {
6577 names_force_groups: true,
6678 skip_examples: false,
6779 system_same_as_user: false,
80+ names_behavior: names_behavior_types.FORCE,
6881 };
6982
7083 for (let key in defaults) {
@@ -94,6 +107,10 @@ export async function loadInstructMode(data) {
94107
95108 if (control.isCheckbox) {
96109 $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);
97114 } else {
98115 $element.val(power_user.instruct[control.property]);
99116 }
@@ -119,12 +136,6 @@ export async function loadInstructMode(data) {
119136 option.selected = name === power_user.instruct.preset;
120137 $('#instruct_presets').append(option);
121138 });
122-
123- highlightDefaultPreset();
124-}
125-
126-function highlightDefaultPreset() {
127- $('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
128139}
129140
130141/**
@@ -141,13 +152,6 @@ export function selectContextPreset(preset, { quiet = false, isAuto = false } =
141152 !quiet && toastr.info(`Context Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);
142153 }
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-
151155 saveSettingsDebounced();
152156}
153157
@@ -217,13 +221,6 @@ export function autoSelectInstructPreset(modelId) {
217221 }
218222 }
219223 }
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- }
227224 }
228225
229226 return false;
@@ -311,9 +308,9 @@ export const force_output_sequence = {
311308 * @returns {string} Formatted instruct mode chat message.
312309 */
313310export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {
314311 let includeNames = isNarrator ? false : power_user.instruct.namesnames_behavior === names_behavior_types.ALWAYS;
315312
316313 if (!isNarrator && power_user.instruct.names_force_groupsnames_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
317314 includeNames = true;
318315 }
319316
@@ -416,7 +413,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
416413 return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
417414 }
418415
419416 const includeNames = power_user.instruct.namesnames_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_force_groupsnames_behavior === names_behavior_types.FORCE);
420417
421418 let inputPrefix = power_user.instruct.input_sequence || '';
422419 let outputPrefix = power_user.instruct.output_sequence || '';
@@ -460,7 +457,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
460457
461458 for (const example of blockExamples) {
462459 // If force group/persona names is set, we should override the include names for the user placeholder
463460 const includeThisName = includeNames || (power_user.instruct.names_force_groupsnames_behavior === names_behavior_types.FORCE && example.name == 'example_user');
464461
465462 const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
466463 const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
@@ -490,7 +487,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
490487 * @returns {string} Formatted instruct mode last prompt line.
491488 */
492489export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {
493490 const includeNames = name && (power_user.instruct.namesnames_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_force_groupsnames_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud);
494491
495492 function getSequence() {
496493 // User impersonation prompt
@@ -514,6 +511,19 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
514511 }
515512
516513 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
518528 if (power_user.instruct.macro) {
519529 sequence = substituteParams(sequence, name1, name2);
@@ -521,7 +531,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
521531 }
522532
523533 const separator = power_user.instruct.wrap ? '\n' : '';
524534 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
525535
526536 // Quiet prompt already has a newline at the end
527537 if (isQuiet && separator) {
@@ -549,10 +559,6 @@ function selectMatchingContextTemplate(name) {
549559 break;
550560 }
551561 }
552- if (!foundMatch) {
553- // If no match was found, select default context preset
554- selectContextPreset(power_user.default_context, { isAuto: true });
555- }
556562}
557563
558564/**
@@ -566,9 +572,13 @@ export function replaceInstructMacros(input, env) {
566572 if (!input) {
567573 return '';
568574 }
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+
569581 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,
572582 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,
573583 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,
574584 'instructInput|instructUserPrefix': power_user.instruct.input_sequence,
@@ -591,6 +601,11 @@ export function replaceInstructMacros(input, env) {
591601 input = input.replace(regex, power_user.instruct.enabled ? value : '');
592602 }
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+
594609 input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
595610 input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);
596611
@@ -598,20 +613,6 @@ export function replaceInstructMacros(input, env) {
598613}
599614
600615jQuery(() => {
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-
615616 $('#instruct_system_same_as_user').on('input', function () {
616617 const state = !!$(this).prop('checked');
617618 if (state) {
@@ -640,9 +641,6 @@ jQuery(() => {
640641 // When instruct mode gets enabled, select context template matching selected instruct preset
641642 if (power_user.instruct.enabled) {
642643 selectMatchingContextTemplate(power_user.instruct.preset);
643- } else {
644- // When instruct mode gets disabled, select default context preset
645- selectContextPreset(power_user.default_context);
646644 }
647645 });
648646
@@ -668,6 +666,10 @@ jQuery(() => {
668666
669667 if (control.isCheckbox) {
670668 $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');
671673 } else {
672674 $element.val(power_user.instruct[control.property]);
673675 $element.trigger('input');
@@ -679,7 +681,13 @@ jQuery(() => {
679681 // Select matching context template
680682 selectMatchingContextTemplate(name);
681683 }
684+ });
682685
683- highlightDefaultPreset();
686+ if (!CSS.supports('field-sizing', 'content')) {
687+ $('#InstructSequencesColumn details').on('toggle', function () {
688+ if ($(this).prop('open')) {
689+ resetScrollHeight($(this).find('textarea'));
690+ }
684691 });
692+ }
685693});
public/scripts/nai-settings.js+107 -30
@@ -5,9 +5,7 @@ import {
55 novelai_setting_names,
66 saveSettingsDebounced,
77 setGenerationParamsFromPreset,
8- substituteParams,
98} from '../script.js';
10-import { getCfgPrompt } from './cfg-scale.js';
119import { MAX_CONTEXT_DEFAULT, MAX_RESPONSE_DEFAULT, power_user } from './power-user.js';
1210import { getTextTokens, tokenizers } from './tokenizers.js';
1311import { getEventSourceStream } from './sse-stream.js';
@@ -24,6 +22,7 @@ const maximum_output_length = 150;
2422const default_presets = {
2523 'clio-v1': 'Talker-Chat-Clio',
2624 'kayra-v1': 'Carefree-Kayra',
25+ 'llama-3-erato-v1': 'Erato-Dragonfruit',
2726};
2827
2928export const nai_settings = {
@@ -38,13 +37,16 @@ export const nai_settings = {
3837 top_p: 0.75,
3938 top_a: 0.08,
4039 typical_p: 0.975,
40+ min_p: 0,
41+ math1_temp: 1,
42+ math1_quad: 0,
43+ math1_quad_entropy_scale: 0,
4144 min_length: 1,
4245 model_novel: 'clio-v1',
4346 preset_settings_novel: 'Talker-Chat-Clio',
4447 streaming_novel: false,
4548 preamble: default_preamble,
4649 prefix: '',
47- cfg_uc: '',
4850 banned_tokens: '',
4951 order: default_order,
5052 logit_bias: [],
@@ -57,6 +59,19 @@ const nai_tiers = {
5759 3: 'Opus',
5860};
5961
62+const 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+
6075let novel_data = null;
6176let badWordsCache = {};
6277const BIAS_KEY = '#novel_api-settings';
@@ -91,6 +106,37 @@ export function getKayraMaxResponseTokens() {
91106 return maximum_output_length;
92107}
93108
109+export 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+
94140export function getNovelTier() {
95141 return nai_tiers[novel_data?.tier] ?? 'no_connection';
96142}
@@ -141,16 +187,18 @@ export function loadNovelPreset(preset) {
141187 nai_settings.top_a = preset.top_a;
142188 nai_settings.typical_p = preset.typical_p;
143189 nai_settings.min_length = preset.min_length;
144- nai_settings.cfg_scale = preset.cfg_scale;
145190 nai_settings.phrase_rep_pen = preset.phrase_rep_pen;
146191 nai_settings.mirostat_lr = preset.mirostat_lr;
147192 nai_settings.mirostat_tau = preset.mirostat_tau;
148193 nai_settings.prefix = preset.prefix;
149- nai_settings.cfg_uc = preset.cfg_uc || '';
150194 nai_settings.banned_tokens = preset.banned_tokens || '';
151195 nai_settings.order = preset.order || default_order;
152196 nai_settings.logit_bias = preset.logit_bias || [];
153197 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;
154202 loadNovelSettingsUi(nai_settings);
155203}
156204
@@ -178,16 +226,18 @@ export function loadNovelSettings(settings) {
178226 nai_settings.typical_p = settings.typical_p;
179227 nai_settings.min_length = settings.min_length;
180228 nai_settings.phrase_rep_pen = settings.phrase_rep_pen;
181- nai_settings.cfg_scale = settings.cfg_scale;
182229 nai_settings.mirostat_lr = settings.mirostat_lr;
183230 nai_settings.mirostat_tau = settings.mirostat_tau;
184231 nai_settings.streaming_novel = !!settings.streaming_novel;
185232 nai_settings.preamble = settings.preamble || default_preamble;
186233 nai_settings.prefix = settings.prefix;
187- nai_settings.cfg_uc = settings.cfg_uc || '';
188234 nai_settings.banned_tokens = settings.banned_tokens || '';
189235 nai_settings.order = settings.order || default_order;
190236 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;
191241 loadNovelSettingsUi(nai_settings);
192242}
193243
@@ -214,8 +264,6 @@ function loadNovelSettingsUi(ui_settings) {
214264 $('#top_a_counter_novel').val(Number(ui_settings.top_a).toFixed(3));
215265 $('#typical_p_novel').val(ui_settings.typical_p);
216266 $('#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));
219267 $('#phrase_rep_pen_novel').val(ui_settings.phrase_rep_pen || 'off');
220268 $('#mirostat_lr_novel').val(ui_settings.mirostat_lr);
221269 $('#mirostat_lr_counter_novel').val(Number(ui_settings.mirostat_lr).toFixed(2));
@@ -225,8 +273,15 @@ function loadNovelSettingsUi(ui_settings) {
225273 $('#min_length_counter_novel').val(Number(ui_settings.min_length).toFixed(0));
226274 $('#nai_preamble_textarea').val(ui_settings.preamble);
227275 $('#nai_prefix').val(ui_settings.prefix || 'vanilla');
228- $('#nai_cfg_uc').val(ui_settings.cfg_uc || '');
229276 $('#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
231286 $('#streaming_novel').prop('checked', ui_settings.streaming_novel);
232287 sortItemsByOrder(ui_settings.order);
@@ -313,29 +368,41 @@ const sliders = [
313368 setValue: (val) => { nai_settings.mirostat_lr = Number(val).toFixed(2); },
314369 },
315370 {
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- {
322371 sliderId: '#min_length_novel',
323372 counterId: '#min_length_counter_novel',
324373 format: (val) => `${val}`,
325374 setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },
326375 },
327376 {
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- {
334377 sliderId: '#nai_banned_tokens',
335378 counterId: '#nai_banned_tokens_counter',
336379 format: (val) => val,
337380 setValue: (val) => { nai_settings.banned_tokens = val; },
338381 },
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+ },
339406];
340407
341408function getBadWordIds(banned_tokens, tokenizerType) {
@@ -423,11 +490,8 @@ function getBadWordPermutations(text) {
423490 return result.filter(onlyUnique);
424491}
425492
426493export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, cfgValues_cfgValues, type) {
427494 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
432496 const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);
433497 const stopSequences = (tokenizerType !== tokenizers.NONE)
@@ -451,7 +515,14 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
451515 console.log(finalPrompt);
452516 }
453517
454518 const adjustedMaxLengthisKayra = nai_settings.model_novel.includes('kayra') ? getKayraMaxResponseTokens() : maximum_output_length;
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
456527 return {
457528 'input': finalPrompt,
@@ -469,11 +540,13 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
469540 'top_a': Number(nai_settings.top_a),
470541 'top_p': Number(nai_settings.top_p),
471542 '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),
472547 'typical_p': Number(nai_settings.typical_p),
473548 'mirostat_lr': Number(nai_settings.mirostat_lr),
474549 '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) ?? '',
477550 'phrase_rep_pen': nai_settings.phrase_rep_pen,
478551 'stop_sequences': stopSequences,
479552 'bad_words_ids': badWordIds,
@@ -492,7 +565,8 @@ function selectPrefix(selected_prefix, finalPrompt) {
492565 let useInstruct = false;
493566 const clio = nai_settings.model_novel.includes('clio');
494567 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
497571 if (isNewModel) {
498572 // 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) {
511585 if (model.includes('kayra')) {
512586 return tokenizers.NERD2;
513587 }
588+ if (model.includes('erato')) {
589+ return tokenizers.LLAMA3;
590+ }
514591 return tokenizers.NONE;
515592}
516593
public/scripts/openai.js+40 -13
@@ -431,7 +431,7 @@ function setOpenAIMessages(chat) {
431431 case character_names_behavior.NONE:
432432 break;
433433 case character_names_behavior.DEFAULT:
434434 if ((selected_group && chat[j].name !== name1) || (chat[j].force_avatar && chat[j].name !== name1 && chat[j].extra?.type !== system_message_types.NARRATOR)) {
435435 content = `${chat[j].name}: ${content}`;
436436 }
437437 break;
@@ -855,6 +855,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
855855 }
856856
857857 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+
858864 const index = target ? prompts.index(target) : prompts.index(source);
859865 const collection = new MessageCollection(source);
860866 collection.add(Message.fromPrompt(prompt));
@@ -899,8 +905,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
899905 acc.push(prompt.identifier);
900906 return acc;
901907 }, []);
902908 const userAbsolutePromptsabsolutePrompts = prompts.collection
903909 .filter((prompt) => false === prompt.system_prompt && prompt.injection_position === INJECTION_POSITION.ABSOLUTE)
904910 .reduce((acc, prompt) => {
905911 acc.push(prompt);
906912 return acc;
@@ -965,7 +971,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
965971 }
966972
967973 // Add in-chat injections
968974 messages = populationInjectionPrompts(userAbsolutePromptsabsolutePrompts, messages);
969975
970976 // Decide whether dialogue examples should always be added
971977 if (power_user.pin_examples) {
@@ -1102,6 +1108,18 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
11021108
11031109 // Merge system prompts with prompt manager prompts
11041110 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+
11051123 const newPrompt = promptManager.preparePrompt(prompt);
11061124 const markerIndex = prompts.index(prompt.identifier);
11071125
@@ -2370,7 +2388,7 @@ class Message {
23702388 * @returns {Promise<string>} Compressed image as a Data URL.
23712389 */
23722390 async compressImage(image) {
23732391 if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {
23742392 const sizeThreshold = 2 * 1024 * 1024;
23752393 const dataSize = image.length * 0.75;
23762394 const maxSide = 1024;
@@ -2747,7 +2765,7 @@ export class ChatCompletion {
27472765 const message = { role: item.role, content: item.content, ...(item.name ? { name: item.name } : {}) };
27482766 chat.push(message);
27492767 } else {
27502768 consolethis.warnlog('Invalid`Skipping invalid or empty message in collection',: ${JSON.stringify(item)}`);
27512769 }
27522770 }
27532771 return chat;
@@ -3216,12 +3234,12 @@ async function getStatusOpen() {
32163234
32173235 const responseData = await response.json();
32183236
3219- if (!('error' in responseData)) {
3220- setOnlineStatus('Valid');
3221- }
32223237 if ('data' in responseData && Array.isArray(responseData.data)) {
32233238 saveModelList(responseData.data);
32243239 }
3240+ if (!('error' in responseData)) {
3241+ setOnlineStatus('Valid');
3242+ }
32253243 } catch (error) {
32263244 console.error(error);
32273245
@@ -3986,6 +4004,8 @@ async function onModelChange() {
39864004 $('#openai_max_context').attr('max', max_2mil);
39874005 } else if (value.includes('gemini-1.5-pro')) {
39884006 $('#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);
39894009 } else if (value.includes('gemini-1.5-flash')) {
39904010 $('#openai_max_context').attr('max', max_1mil);
39914011 } else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {
@@ -4097,6 +4117,8 @@ async function onModelChange() {
40974117 $('#openai_max_context').attr('max', max_128k);
40984118 } else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
40994119 $('#openai_max_context').attr('max', max_64k);
4120+ } else if (oai_settings.mistralai_model.includes('pixtral')) {
4121+ $('#openai_max_context').attr('max', max_128k);
41004122 } else {
41014123 $('#openai_max_context').attr('max', max_32k);
41024124 }
@@ -4630,12 +4652,15 @@ export function isImageInliningSupported() {
46304652 'gemini-1.5-flash',
46314653 'gemini-1.5-flash-latest',
46324654 'gemini-1.5-flash-001',
4655+ 'gemini-1.5-flash-002',
46334656 'gemini-1.5-flash-exp-0827',
46344657 'gemini-1.5-flash-8b-exp-0827',
4658+ 'gemini-1.5-flash-8b-exp-0924',
46354659 'gemini-1.0-pro-vision-latest',
46364660 'gemini-1.5-pro',
46374661 'gemini-1.5-pro-latest',
46384662 'gemini-1.5-pro-001',
4663+ 'gemini-1.5-pro-002',
46394664 'gemini-1.5-pro-exp-0801',
46404665 'gemini-1.5-pro-exp-0827',
46414666 'gemini-pro-vision',
@@ -4646,6 +4671,8 @@ export function isImageInliningSupported() {
46464671 'gpt-4o-mini',
46474672 'chatgpt-4o-latest',
46484673 'yi-vision',
4674+ 'pixtral-latest',
4675+ 'pixtral-12b-2409',
46494676 ];
46504677
46514678 switch (oai_settings.chat_completion_source) {
@@ -4661,6 +4688,8 @@ export function isImageInliningSupported() {
46614688 return true;
46624689 case chat_completion_sources.ZEROONEAI:
46634690 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));
46644693 default:
46654694 return false;
46664695 }
@@ -4789,7 +4818,7 @@ function runProxyCallback(_, value) {
47894818 return foundName;
47904819}
47914820
47924821export function initOpenaiinitOpenAI() {
47934822 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
47944823 name: 'proxy',
47954824 callback: runProxyCallback,
@@ -4805,9 +4834,7 @@ export function initOpenai() {
48054834 ],
48064835 helpString: 'Sets a proxy preset by name.',
48074836 }));
4808-}
48094837
4810-$(document).ready(async function () {
48114838 $('#test_api_button').on('click', testApiConnection);
48124839
48134840 $('#scale-alt').on('change', function () {
@@ -5266,4 +5293,4 @@ $(document).ready(async function () {
52665293 $('#openai_proxy_password_show').on('click', onProxyPasswordShowClick);
52675294 $('#customize_additional_parameters').on('click', onCustomizeParametersClick);
52685295 $('#openai_proxy_preset').on('change', onProxyPresetChange);
52695296});
public/scripts/personas.js+3 -3
@@ -2,7 +2,7 @@ import {
22 characters,
33 chat,
44 chat_metadata,
55 default_avatardefault_user_avatar,
66 eventSource,
77 event_types,
88 getRequestHeaders,
@@ -357,7 +357,7 @@ async function createDummyPersona() {
357357 // Date + name (only ASCII) to make it unique
358358 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
359359 initPersona(avatarId, personaName, '');
360360 await uploadUserAvatar(default_avatardefault_user_avatar, avatarId);
361361}
362362
363363/**
@@ -944,7 +944,7 @@ async function onPersonasRestoreInput(e) {
944944 // If the avatar is missing, upload it
945945 if (!avatarsList.includes(key)) {
946946 warnings.push(`Persona image "${key}" (${value}) is missing, uploading default avatar`);
947947 await uploadUserAvatar(default_avatardefault_user_avatar, key);
948948 }
949949 }
950950
public/scripts/power-user.js+10 -42
@@ -32,6 +32,7 @@ import {
3232import {
3333 instruct_presets,
3434 loadInstructMode,
35+ names_behavior_types,
3536 selectInstructPreset,
3637} from './instruct-mode.js';
3738
@@ -49,6 +50,7 @@ import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/Auto
4950import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
5051import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
5152import { POPUP_TYPE, callGenericPopup } from './popup.js';
53+import { loadSystemPrompts } from './sysprompt.js';
5254
5355export {
5456 loadPowerUserSettings,
@@ -113,7 +115,6 @@ let power_user = {
113115 pin_examples: false,
114116 strip_examples: false,
115117 trim_sentences: false,
116- include_newline: false,
117118 always_force_name2: false,
118119 user_prompt_bias: '',
119120 show_user_prompt_bias: true,
@@ -205,11 +206,9 @@ let power_user = {
205206 disable_group_trimming: false,
206207 single_line: false,
207208
208- default_instruct: '',
209209 instruct: {
210210 enabled: false,
211211 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',
213212 input_sequence: '### Instruction:',
214213 input_suffix: '',
215214 output_sequence: '### Response:',
@@ -224,8 +223,7 @@ let power_user = {
224223 stop_sequence: '',
225224 wrap: true,
226225 macro: true,
227226 namesnames_behavior: falsenames_behavior_types.FORCE,
228- names_force_groups: true,
229227 activation_regex: '',
230228 bind_to_context: false,
231229 user_alignment_message: '',
@@ -234,7 +232,6 @@ let power_user = {
234232 separator_sequence: '',
235233 },
236234
237- default_context: 'Default',
238235 context: {
239236 preset: 'Default',
240237 story_string: defaultStoryString,
@@ -245,6 +242,12 @@ let power_user = {
245242 names_as_stop_strings: true,
246243 },
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+
248251 personas: {},
249252 default_persona: null,
250253 persona_descriptions: {},
@@ -317,7 +320,6 @@ const contextControls = [
317320 // Existing power user settings
318321 { id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },
319322 { 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 },
321323 { id: 'single_line', property: 'single_line', isCheckbox: true, isGlobalSetting: true, defaultValue: false },
322324];
323325
@@ -1487,7 +1489,6 @@ async function loadPowerUserSettings(settings, data) {
14871489 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
14881490 $('#always-force-name2-checkbox').prop('checked', power_user.always_force_name2);
14891491 $('#trim_sentences_checkbox').prop('checked', power_user.trim_sentences);
1490- $('#include_newline_checkbox').prop('checked', power_user.include_newline);
14911492 $('#render_formulas').prop('checked', power_user.render_formulas);
14921493 $('#disable_group_trimming').prop('checked', power_user.disable_group_trimming);
14931494 $('#markdown_escape_strings').val(power_user.markdown_escape_strings);
@@ -1598,6 +1599,7 @@ async function loadPowerUserSettings(settings, data) {
15981599 reloadMarkdownProcessor(power_user.render_formulas);
15991600 await loadInstructMode(data);
16001601 await loadContextSettings();
1602+ await loadSystemPrompts(data);
16011603 loadMaxContextUnlocked();
16021604 switchWaifuMode();
16031605 switchSpoilerMode();
@@ -1817,29 +1819,8 @@ async function loadContextSettings() {
18171819 }
18181820 }
18191821
1820- highlightDefaultContext();
1821- saveSettingsDebounced();
1822- });
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-
18321822 saveSettingsDebounced();
1833- }
18341823 });
1835-
1836- highlightDefaultContext();
1837-}
1838-
1839-function 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);
18431824}
18441825
18451826/**
@@ -3069,19 +3050,6 @@ $(document).ready(() => {
30693050 // if trim sentences is unchecked, include newline must be unchecked
30703051 $('#trim_sentences_checkbox').change(function () {
30713052 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- }
30853053 saveSettingsDebounced();
30863054 });
30873055
public/scripts/preset-manager.js+264 -9
@@ -18,13 +18,16 @@ import {
1818import { groups, selected_group } from './group-chats.js';
1919import { instruct_presets } from './instruct-mode.js';
2020import { kai_settings } from './kai-settings.js';
2121import { PopupconvertNovelPreset } from './popupnai-settings.js';
22+import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2223import { context_presets, getContextSettings, power_user } from './power-user.js';
2324import { SlashCommand } from './slash-commands/SlashCommand.js';
2425import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
2526import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
2627import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
2728import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
29+import { checkForSystemPromptInInstructTemplate, system_prompts } from './sysprompt.js';
30+import { renderTemplateAsync } from './templates.js';
2831import {
2932 textgenerationwebui_preset_names,
3033 textgenerationwebui_presets,
@@ -71,7 +74,7 @@ function autoSelectPreset() {
7174 * @param {string} apiId API id
7275 * @returns {PresetManager} Preset manager
7376 */
7477export function getPresetManager(apiId = '') {
7578 if (!apiId) {
7679 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
7780 }
@@ -102,6 +105,215 @@ class PresetManager {
102105 this.apiId = apiId;
103106 }
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+
105317 /**
106318 * Gets all preset names.
107319 * @returns {string[]} List of preset names
@@ -182,6 +394,14 @@ class PresetManager {
182394 }
183395
184396 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+
185405 const preset = settings ?? this.getPresetSettings(name);
186406
187407 const response = await fetch('/api/presets/save', {
@@ -228,6 +448,10 @@ class PresetManager {
228448 presets = instruct_presets;
229449 preset_names = instruct_presets.map(x => x.name);
230450 break;
451+ case 'sysprompt':
452+ presets = system_prompts;
453+ preset_names = system_prompts.map(x => x.name);
454+ break;
231455 default:
232456 console.warn(`Unknown API ID ${this.apiId}`);
233457 }
@@ -236,11 +460,11 @@ class PresetManager {
236460 }
237461
238462 isKeyedApi() {
239463 return this.apiId == 'textgenerationwebui' || this.apiId == 'context' || this.apiId == 'instruct'isAdvancedFormatting();
240464 }
241465
242466 isAdvancedFormatting() {
243467 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
244468 }
245469
246470 updateList(name, preset) {
@@ -298,6 +522,11 @@ class PresetManager {
298522 instruct_preset['name'] = name || power_user.instruct.preset;
299523 return instruct_preset;
300524 }
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+ }
301530 default:
302531 console.warn(`Unknown API ID ${apiId}`);
303532 return {};
@@ -601,11 +830,6 @@ export async function initPresetManager() {
601830 return;
602831 }
603832
604- // default context preset cannot be deleted
605- if (apiId == 'context' && power_user.default_context === power_user.context.preset) {
606- return;
607- }
608-
609833 const headerText = !presetManager.isAdvancedFormatting() ? 'Delete this preset?' : 'Delete this template?';
610834 const confirm = await Popup.show.confirm(headerText, 'This action is irreversible and your current settings will be overwritten.');
611835 if (!confirm) {
@@ -682,4 +906,35 @@ export async function initPresetManager() {
682906 toastr.success(successToast);
683907 }
684908 });
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+ });
685940}
public/scripts/sysprompt.js+250 -0
@@ -0,0 +1,250 @@
1+import { saveSettingsDebounced } from '../script.js';
2+import { callGenericPopup, POPUP_TYPE } from './popup.js';
3+import { power_user } from './power-user.js';
4+import { getPresetManager } from './preset-manager.js';
5+import { SlashCommand } from './slash-commands/SlashCommand.js';
6+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
7+import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8+import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10+import { renderTemplateAsync } from './templates.js';
11+import { isTrueBoolean, resetScrollHeight } from './utils.js';
12+
13+export let system_prompts = [];
14+
15+const $enabled = $('#sysprompt_enabled');
16+const $select = $('#sysprompt_select');
17+const $content = $('#sysprompt_content');
18+const $contentBlock = $('#SystemPromptBlock');
19+
20+async 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+ */
46+export 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+ */
71+export 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+
93+function 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+ */
103+function setSystemPromptStateCallback(state) {
104+ power_user.sysprompt.enabled = state;
105+ $enabled.prop('checked', state);
106+ toggleSystemPromptDisabledControls();
107+ saveSettingsDebounced();
108+ return '';
109+}
110+
111+function 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+
121+function 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+
151+export 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 @@
5757 <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>
5858 <li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> – <span data-i18n="help_macros_42">context template example dialogues separator</span></li>
5959 <li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> – <span data-i18n="help_macros_43">context template chat start line</span></li>
6060 <li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_44">main system prompt content if enabled (either character prompt override if chosenallowed, or instructSystemPromptdefaultSystemPrompt)</span></li>
6161 <li><tt>&lcub;&lcub;instructSystemPromptdefaultSystemPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_45">instruct system prompt content</span></li>
6262 <li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> – <span data-i18n="help_macros_46">instruct system prompt prefix sequence</span></li>
6363 <li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> – <span data-i18n="help_macros_47">instruct system prompt suffix sequence</span></li>
6464 <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 @@
4141 SillyTavern Documentation Site
4242 </a>
4343 </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>
4944</ul>
5045
5146<hr>
public/scripts/textgen-settings.js+10 -4
@@ -15,7 +15,7 @@ import { BIAS_CACHE, createNewLogitBiasEntry, displayLogitBias, getLogitBiasList
1515import { power_user, registerDebugFunction } from './power-user.js';
1616import { getEventSourceStream } from './sse-stream.js';
1717import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer } from './textgen-models.js';
1818import { SENTENCEPIECE_TOKENIZERSENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
1919import { getSortableDelay, onlyUnique } from './utils.js';
2020
2121export {
@@ -353,7 +353,7 @@ function getTokenizerForTokenIds() {
353353 return tokenizers.API_CURRENT;
354354 }
355355
356356 if (SENTENCEPIECE_TOKENIZERSENCODE_TOKENIZERS.includes(power_user.tokenizer)) {
357357 return power_user.tokenizer;
358358 }
359359
@@ -1201,9 +1201,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
12011201 'frequency_penalty': settings.freq_pen,
12021202 'presence_penalty': settings.presence_pen,
12031203 'repetition_penalty': settings.rep_pen,
12041204 'seed': settings.seed >= 0 ? settings.seed : undefined,
12051205 'stop': getStoppingStrings(isImpersonate, isContinue),
1206- 'temperature': settings.temp,
1206+ 'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
12071207 'temperature_last': settings.temperature_last,
12081208 'top_p': settings.top_p,
12091209 'top_k': settings.top_k,
@@ -1223,6 +1223,12 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
12231223 'guided_json': settings.json_schema,
12241224 'early_stopping': false, // hacks
12251225 '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),
12261232 };
12271233
12281234 if (settings.type === OPENROUTER) {
public/scripts/tokenizers.js+34 -9
@@ -10,7 +10,8 @@ import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, o
1010const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
1111
1212export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
1313export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
14+export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
1415
1516export const tokenizers = {
1617 NONE: 0,
@@ -30,21 +31,27 @@ export const tokenizers = {
3031 JAMBA: 14,
3132 QWEN2: 15,
3233 COMMAND_R: 16,
34+ NEMO: 17,
3335 BEST_MATCH: 99,
3436};
3537
36-export const SENTENCEPIECE_TOKENIZERS = [
38+// A list of local tokenizers that support encoding and decoding token ids.
39+export const ENCODE_TOKENIZERS = [
3740 tokenizers.LLAMA,
3841 tokenizers.MISTRAL,
3942 tokenizers.YI,
4043 tokenizers.LLAMA3,
4144 tokenizers.GEMMA,
4245 tokenizers.JAMBA,
46+ tokenizers.QWEN2,
47+ tokenizers.COMMAND_R,
48+ tokenizers.NEMO,
4349 // uncomment when NovelAI releases Kayra and Clio weights, lol
4450 //tokenizers.NERD,
4551 //tokenizers.NERD2,
4652];
4753
54+// A list of Text Completion sources that support remote tokenization.
4855export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE];
4956
5057const TOKENIZER_URLS = {
@@ -117,6 +124,11 @@ const TOKENIZER_URLS = {
117124 decode: '/api/tokenizers/command-r/decode',
118125 count: '/api/tokenizers/command-r/encode',
119126 },
127+ [tokenizers.NEMO]: {
128+ encode: '/api/tokenizers/nemo/encode',
129+ decode: '/api/tokenizers/nemo/decode',
130+ count: '/api/tokenizers/nemo/encode',
131+ },
120132 [tokenizers.API_TEXTGENERATIONWEBUI]: {
121133 encode: '/api/tokenizers/remote/textgenerationwebui/encode',
122134 count: '/api/tokenizers/remote/textgenerationwebui/encode',
@@ -262,6 +274,9 @@ export function getTokenizerBestMatch(forApi) {
262274 if (nai_settings.model_novel.includes('kayra')) {
263275 return tokenizers.NERD2;
264276 }
277+ if (nai_settings.model_novel.includes('erato')) {
278+ return tokenizers.LLAMA3;
279+ }
265280 }
266281 if (forApi === 'kobold' || forApi === 'textgenerationwebui' || forApi === 'koboldhorde') {
267282 // Try to use the API tokenizer if possible:
@@ -269,8 +284,9 @@ export function getTokenizerBestMatch(forApi) {
269284 // - Kobold must pass a version check
270285 // - Tokenizer haven't reported an error previously
271286 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
287+ const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
272288 const isConnected = online_status !== 'no_connection';
273289 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== OOBA || hasValidEndpoint);
274290
275291 if (!hasTokenizerError && isConnected) {
276292 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@@ -531,6 +547,7 @@ export function getTokenizerModel() {
531547 const jambaTokenizer = 'jamba';
532548 const qwen2Tokenizer = 'qwen2';
533549 const commandRTokenizer = 'command-r';
550+ const nemoTokenizer = 'nemo';
534551
535552 // Assuming no one would use it for different models.. right?
536553 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
@@ -624,6 +641,9 @@ export function getTokenizerModel() {
624641 }
625642
626643 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+ }
627647 return mistralTokenizer;
628648 }
629649
@@ -908,15 +928,20 @@ function countTokensFromTextgenAPI(str, resolve) {
908928
909929function apiFailureTokenCount(str) {
910930 console.error('Error counting tokens');
931+ let shouldTryAgain = false;
911932
912933 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-
919935 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);
920945 }
921946
922947 return guesstimate(str);
public/scripts/utils.js+6 -12
@@ -607,12 +607,11 @@ export function sortByCssOrder(a, b) {
607607/**
608608 * Trims a string to the end of a nearest sentence.
609609 * @param {string} input The string to trim.
610- * @param {boolean} include_newline Whether to include a newline character in the trimmed string.
611610 * @returns {string} The trimmed string.
612611 * @example
613612 * trimToEndSentence('Hello, world! I am from'); // 'Hello, world!'
614613 */
615614export function trimToEndSentence(input, include_newline = false) {
616615 if (!input) {
617616 return '';
618617 }
@@ -633,11 +632,6 @@ export function trimToEndSentence(input, include_newline = false) {
633632 }
634633 break;
635634 }
636-
637- if (include_newline && char === '\n') {
638- last = i;
639- break;
640- }
641635 }
642636
643637 if (last === -1) {
@@ -1908,13 +1902,13 @@ export function select2ChoiceClickSubscribe(control, action, { buttonStyle = fal
19081902 * @returns {string} The html representation of the highlighted regex
19091903 */
19101904export function highlightRegex(regexStr) {
19111905 // Function to escape HTML special characters for safety or readability
19121906 const escapeHtmlescape = (str) => str.replace(/[&<>"'\x01]/g, match => ({
19131907 '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;', '\x01': '\\x01',
19141908 })[match]);
19151909
19161910 // Replace special characters with their HTML-escaped forms
19171911 regexStr = escapeHtmlescape(regexStr);
19181912
19191913 // Patterns that we want to highlight only if they are not escaped
19201914 function getPatterns() {
public/scripts/world-info.js+72 -10
@@ -217,8 +217,9 @@ class WorldInfoBuffer {
217217 depth = MAX_SCAN_DEPTH;
218218 }
219219
220220 const JOINERMATCHER = '\n\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
223224 if (this.#injectBuffer.length > 0) {
224225 result += JOINER + this.#injectBuffer.join(JOINER);
@@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) {
29492950 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
29502951
29512952 // delay until recursion
2953+ // delay until recursion level
29522954 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
29532955 delayUntilRecursionInput.data('uid', entry.uid);
2956+ const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
2957+ delayUntilRecursionLevelInput.data('uid', entry.uid);
29542958 delayUntilRecursionInput.on('input', async function () {
29552959 const uid = $(this).data('uid');
29562960 const valuetoggled = $(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+
29572967 data.entries[uid].delayUntilRecursion = value;
29582968 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
29592969 await saveWorldInfo(name, data);
29602970 });
29612971 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
29632987 // duplicate button
29642988 const duplicateButton = template.find('.duplicate_entry_button');
@@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = {
32573281 disable: { default: false, type: 'boolean' },
32583282 excludeRecursion: { default: false, type: 'boolean' },
32593283 preventRecursion: { default: false, type: 'boolean' },
32603284 delayUntilRecursion: { default: false0, type: 'booleannumber' },
32613285 probability: { default: 100, type: 'number' },
32623286 useProbability: { default: true, type: 'boolean' },
32633287 depth: { default: DEFAULT_DEPTH, type: 'number' },
@@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
36963720 }
36973721 }
36983722
3723+ /** @type {scan_state} */
36993724 let scanState = scan_state.INITIAL;
37003725 let token_budget_overflowed = false;
37013726 let count = 0;
@@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
37203745 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
37213746 }
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+
37233759 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);
37243760
37253761 while (scanState) {
@@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
37323768 // Track how many times the loop has run. May be useful for debugging.
37333769 count++;
37343770
37353771 console.debug(`[WI] Loop--- LOOP #${count}. SearchSTART state---`, Object.entries(scan_state).find(x => x[1] === scanState));
3772+ console.debug('[WI] Scan state', Object.entries(scan_state).find(x => x[1] === scanState));
37363773
37373774 // Until decided otherwise, we set the loop to stop scanning after this
37383775 let nextScanState = scan_state.NONE;
@@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
38113848 continue;
38123849 }
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+
38143856 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {
38153857 log('suppressed by exclude recursion');
38163858 continue;
@@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39493991 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
39503992
39513993 console.debug('[WI] --- PROBABILITY CHECKS ---');
3994+ !newEntries.length && console.debug('[WI] No probability checks to do');
3995+
39523996 for (const entry of newEntries) {
39533997 function verifyProbability() {
39543998 // If we don't need to roll, it's always true
@@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39844028 newContent += `${entry.content}\n`;
39854029
39864030 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
4031+ console.debug('[WI] --- BUDGET OVERFLOW CHECK ---');
39874032 if (world_info_overflow_alert) {
39884033 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);
39894034 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
@@ -4001,23 +4046,31 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40014046 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));
40024047 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);
40034048
4049+ console.debug(`[WI] --- LOOP #${count} RESULT ---`);
40044050 if (!newEntries.length) {
40054051 console.debug('[WI] No new entries activated, stopping.');
40064052 } else if (!successfulNewEntries.length) {
40074053 console.debug('[WI] Probability checks failed for all activated entries,. stoppingNo new entries activated.');
40084054 } else {
40094055 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);
40104056 }
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+
40124063 // After processing and rolling entries is done, see if we should continue with normal recursion
40134064 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {
40144065 nextScanState = scan_state.RECURSION;
4066+ logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion');
40154067 }
40164068
40174069 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again
40184070 // There might be recurse-trigger-able entries that match the buffer, so we need to check that
40194071 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {
40204072 nextScanState = scan_state.RECURSION;
4073+ logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan');
40214074 }
40224075
40234076 // 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) {
40314084 ) || (buffer.getDepth() > chat.length);
40324085
40334086 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`);
40354087 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`);
40364089 buffer.advanceScan();
40374090 } else {
40384091 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);
40394092 }
40404093 }
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+
40424102 // Final check if we should really continue scan, and extend the current WI recurse buffer
40434103 scanState = nextScanState;
40444104 if (scanState) {
@@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40464106 .map(x => x.content).join('\n');
40474107 buffer.addRecurse(text);
40484108 allActivatedText = (text + '\n' + allActivatedText);
4109+ } else {
4110+ logNextState('[WI] Scan done. No new entries to prompt. Stopping.');
40494111 }
40504112 }
40514113
@@ -4072,10 +4134,10 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40724134
40734135 switch (entry.position) {
40744136 case world_info_position.before:
40754137 WIBeforeEntries.unshift(substituteParams(content));
40764138 break;
40774139 case world_info_position.after:
40784140 WIAfterEntries.unshift(substituteParams(content));
40794141 break;
40804142 case world_info_position.EMTop:
40814143 EMEntries.unshift(
public/style.css+25 -11
@@ -2570,20 +2570,25 @@ input[type="file"] {
25702570/*#################################################################*/
25712571
25722572select {
2573-
2573+ -moz-appearance: none;
2574- padding: 3px 2px;
2574+ -webkit-appearance: none;
2575+ appearance: none;
2576+ padding: 3px 5px;
25752577 background-color: var(--black30a);
25762578 border: 1px solid var(--SmartThemeBorderColor);
25772579 border-radius: 5px;
25782580 margin-bottom: 5px;
25792581 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;
25802586}
25812587
25822588select option {
25832589 /* works to highlight selected/active option */
25842590 background-color: var(--white50a);
25852591 color: var(--black70a);
2586-
25872592}
25882593
25892594select option:not(:checked) {
@@ -2625,14 +2630,6 @@ select option:not(:checked) {
26252630 color: var(--active) !important;
26262631}
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-
26362633.displayBlock {
26372634 display: block !important;
26382635}
@@ -3518,6 +3515,14 @@ grammarly-extension {
35183515 column-gap: 20px;
35193516}
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+
35213526.drag-handle {
35223527 cursor: grab;
35233528 /* Make the drag handle not selectable in most browsers */
@@ -5464,3 +5469,12 @@ body:not(.movingUI) .drawer-content.maximized {
54645469#AdvancedFormatting .autoSetHeight {
54655470 overflow-wrap: anywhere;
54665471}
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);
610610// Ooba/OpenAI text completions
611611app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router);
612612
613+// OpenRouter
614+app.use('/api/openrouter', require('./src/endpoints/openrouter').router);
615+
613616// KoboldAI
614617app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router);
615618
@@ -929,6 +932,7 @@ async function verifySecuritySettings() {
929932userModule.initUserStorage(dataRoot)
930933 .then(userModule.ensurePublicDirectoriesExist)
931934 .then(userModule.migrateUserData)
935+ .then(userModule.migrateSystemPrompts)
932936 .then(verifySecuritySettings)
933937 .then(preSetupTasks)
934938 .finally(startServer);
src/constants.js+1 -2
@@ -5,7 +5,6 @@ const PUBLIC_DIRECTORIES = {
55 extensions: 'public/scripts/extensions',
66};
77
8-const DEFAULT_AVATAR = '/img/ai4.png';
98const SETTINGS_FILE = 'settings.json';
109
1110/**
@@ -42,6 +41,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({
4241 files: 'user/files',
4342 vectors: 'vectors',
4443 backups: 'backups',
44+ sysprompt: 'sysprompt',
4545});
4646
4747/**
@@ -422,7 +422,6 @@ const VLLM_KEYS = [
422422
423423module.exports = {
424424 DEFAULT_USER,
425- DEFAULT_AVATAR,
426425 SETTINGS_FILE,
427426 PUBLIC_DIRECTORIES,
428427 USER_DIRECTORY_TEMPLATE,
src/endpoints/backends/text-completions.js+1 -0
@@ -186,6 +186,7 @@ router.post('/status', jsonParser, async function (request, response) {
186186
187187 const modelName = modelInfo?.model_name;
188188 result = modelName || result;
189+ response.setHeader('x-supports-tokenization', 'true');
189190 }
190191 } catch (error) {
191192 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) {
726726 const char = JSON.stringify(charaFormatData(request.body, request.user.directories));
727727 const internalName = getPngName(request.body.ch_name, request.user.directories);
728728 const avatarName = `${internalName}.png`;
729- const defaultAvatar = './public/img/ai4.png';
730729 const chatsPath = path.join(request.user.directories.chats, internalName);
731730
732731 if (!fs.existsSync(chatsPath)) fs.mkdirSync(chatsPath);
733732
734733 if (!request.file) {
735734 await writeCharacterData(defaultAvatardefaultAvatarPath, char, internalName, request);
736735 return response.send(avatarName);
737736 } else {
738737 const crop = tryParse(request.query.crop);
@@ -1028,6 +1027,12 @@ router.post('/chats', jsonParser, async function (request, response) {
10281027 const stats = fs.statSync(pathToFile);
10291028 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+
10311036 const rl = readline.createInterface({
10321037 input: fileStream,
10331038 crlfDelay: Infinity,
src/endpoints/chats.js+99 -42
@@ -4,6 +4,7 @@ const readline = require('readline');
44const express = require('express');
55const sanitize = require('sanitize-filename');
66const writeFileAtomicSync = require('write-file-atomic').sync;
7+const _ = require('lodash');
78
89const { jsonParser, urlencodedParser } = require('../express-common');
910const { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } = require('../util');
@@ -34,6 +35,27 @@ function backupChat(directory, name, chat) {
3435 }
3536}
3637
38+const 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+ */
45+function 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+
53+process.on('exit', () => {
54+ for (const func of backupFunctions.values()) {
55+ func.flush();
56+ }
57+});
58+
3759/**
3860 * Imports a chat from Ooba's format.
3961 * @param {string} userName User name
@@ -70,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) {
7092 }
7193 }
7294
7395 const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n');
74- return chatContent;
7596}
7697
7798/**
@@ -99,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) {
99120 });
100121 }
101122
102123 const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n');
103- return chatContent;
104124}
105125
106126/**
@@ -137,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) {
137157 return newChats;
138158}
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+ */
168+function 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+
140191const router = express.Router();
141192
142193router.post('/save', jsonParser, function (request, response) {
@@ -144,10 +195,10 @@ router.post('/save', jsonParser, function (request, response) {
144195 const directoryName = String(request.body.avatar_url).replace('.png', '');
145196 const chatData = request.body.chat;
146197 const jsonlData = chatData.map(JSON.stringify).join('\n');
147198 const fileName = `${sanitize(String(request.body.file_name))}.jsonl`;
148199 const filePath = path.join(request.user.directories.chats, directoryName, sanitize(fileName));
149200 writeFileAtomicSync(filePath, jsonlData, 'utf8');
150201 backupChatgetBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
151202 return response.send({ result: 'ok' });
152203 } catch (error) {
153204 response.send(error);
@@ -171,14 +222,15 @@ router.post('/get', jsonParser, function (request, response) {
171222 return response.send({});
172223 }
173224
174225 const fileName = path.join(directoryPath, `${sanitize(String(request.body.file_name))}.jsonl`);
175226 const chatFileExistsfilePath = fspath.existsSyncjoin(directoryPath, sanitize(fileName));
227+ const chatFileExists = fs.existsSync(filePath);
176228
177229 if (!chatFileExists) {
178230 return response.send({});
179231 }
180232
181233 const data = fs.readFileSync(fileNamefilePath, 'utf8');
182234 const lines = data.split('\n');
183235
184236 // Iterate through the array of strings and parse each line as JSON
@@ -199,8 +251,9 @@ router.post('/rename', jsonParser, async function (request, response) {
199251 const pathToFolder = request.body.is_group
200252 ? request.user.directories.groupChats
201253 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));
202254 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));
203255 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));
256+ const sanitizedFileName = path.parse(pathToRenamedFile).name;
204257 console.log('Old chat name', pathToOriginalFile);
205258 console.log('New chat name', pathToRenamedFile);
206259
@@ -212,32 +265,22 @@ router.post('/rename', jsonParser, async function (request, response) {
212265 fs.copyFileSync(pathToOriginalFile, pathToRenamedFile);
213266 fs.rmSync(pathToOriginalFile);
214267 console.log('Successfully renamed.');
215268 return response.send({ ok: true, sanitizedFileName });
216269});
217270
218271router.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-
229272 const dirName = String(request.body.avatar_url).replace('.png', '');
230273 const fileName = path.join(request.user.directories.chats, dirName, sanitize(String(request.body.chatfile)));
231274 const chatFileExistsfilePath = fspath.existsSyncjoin(request.user.directories.chats, dirName, sanitize(fileName));
275+ const chatFileExists = fs.existsSync(filePath);
232276
233277 if (!chatFileExists) {
234278 console.log(`Chat file not found '${fileNamefilePath}'`);
235279 return response.sendStatus(400);
236- } else {
237- fs.rmSync(fileName);
238- console.log('Deleted chat file: ' + fileName);
239280 }
240281
282+ fs.rmSync(filePath);
283+ console.log('Deleted chat file: ' + filePath);
241284 return response.send('ok');
242285});
243286
@@ -259,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) {
259302 }
260303 try {
261304 // Short path for JSONL files
262305 if (request.body.format === 'jsonl') {
263306 try {
264307 const rawFile = fs.readFileSync(filename, 'utf8');
265308 const successMessage = {
@@ -269,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) {
269312
270313 console.log(`Chat exported as ${exportfilename}`);
271314 return response.status(200).json(successMessage);
272- }
315+ } catch (err) {
273- catch (err) {
274316 console.error(err);
275317 const errorMessage = {
276318 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
@@ -305,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) {
305347 console.log(`Chat exported as ${exportfilename}`);
306348 return response.status(200).json(successMessage);
307349 });
308- }
350+ } catch (err) {
309- catch (err) {
310351 console.log('chat export failed.');
311352 console.log(err);
312353 return response.sendStatus(400);
@@ -382,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) {
382423 }
383424
384425 if (format === 'jsonl') {
385426 constlet linelines = data.split('\n')[0];
427+ const header = lines[0];
428+
429+ const jsonData = JSON.parse(header);
430+
431+ if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
432+ console.log('Incorrect chat format .jsonl');
433+ return response.send({ error: true });
434+ }
386435
387- const jsonData = JSON.parse(line);
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+ }
388446
389- if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
390447 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
391448 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
449+ if (flattenedChat !== data) {
450+ writeFileAtomicSync(filePath, flattenedChat, 'utf8');
451+ } else {
392452 fs.copyFileSync(pathToUpload, filePath);
453+ }
393454 fs.unlinkSync(pathToUpload);
394455 response.send({ res: true });
395- } else {
396- console.log('Incorrect chat format .jsonl');
397- return response.send({ error: true });
398- }
399456 }
400457 } catch (error) {
401458 console.error(error);
@@ -454,7 +511,7 @@ router.post('/group/save', jsonParser, (request, response) => {
454511 let chat_data = request.body.chat;
455512 let jsonlData = chat_data.map(JSON.stringify).join('\n');
456513 writeFileAtomicSync(pathToFile, jsonlData, 'utf8');
457514 backupChatgetBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData);
458515 return response.send({ ok: true });
459516});
460517
src/endpoints/content-manager.js+40 -1
@@ -43,6 +43,7 @@ const CONTENT_TYPES = {
4343 CONTEXT: 'context',
4444 MOVING_UI: 'moving_ui',
4545 QUICK_REPLIES: 'quick_replies',
46+ SYSPROMPT: 'sysprompt',
4647};
4748
4849/**
@@ -56,7 +57,7 @@ function getDefaultPresets(directories) {
5657 const presets = [];
5758
5859 for (const contentItem of contentIndex) {
5960 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {
6061 contentItem.name = path.parse(contentItem.filename).name;
6162 contentItem.folder = getTargetByType(contentItem.type, directories);
6263 presets.push(contentItem);
@@ -218,6 +219,41 @@ function getContentIndex() {
218219}
219220
220221/**
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+ */
227+function 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+/**
221257 * Gets the target directory for the specified asset type.
222258 * @param {ContentType} type Asset type
223259 * @param {import('../users').UserDirectoryList} directories User directories
@@ -257,6 +293,8 @@ function getTargetByType(type, directories) {
257293 return directories.movingUI;
258294 case CONTENT_TYPES.QUICK_REPLIES:
259295 return directories.quickreplies;
296+ case CONTENT_TYPES.SYSPROMPT:
297+ return directories.sysprompt;
260298 default:
261299 return null;
262300 }
@@ -721,5 +759,6 @@ module.exports = {
721759 checkForNewContent,
722760 getDefaultPresets,
723761 getDefaultPresetFile,
762+ getContentOfType,
724763 router,
725764};
src/endpoints/horde.js+29 -4
@@ -6,6 +6,7 @@ const { readSecret, SECRET_KEYS } = require('./secrets');
66const { jsonParser } = require('../express-common');
77
88const ANONYMOUS_KEY = '0000000000';
9+const HORDE_TEXT_MODEL_METADATA_URL = 'https://raw.githubusercontent.com/db0/AI-Horde-text-model-reference/main/db.json';
910const cache = new Cache(60 * 1000);
1011const router = express.Router();
1112
@@ -23,10 +24,9 @@ async function getClientAgent() {
2324 * @returns {Promise<AIHorde>} AIHorde client
2425 */
2526async function getHordeClient() {
2627 const ai_horde =return new AIHorde({
2728 client_agent: await getClientAgent(),
2829 });
29- return ai_horde;
3030}
3131
3232/**
@@ -79,10 +79,24 @@ router.post('/text-workers', jsonParser, async (request, response) => {
7979 }
8080});
8181
82+async function getHordeTextModelMetadata() {
83+ const response = await fetch(HORDE_TEXT_MODEL_METADATA_URL);
84+ return await response.json();
85+}
86+
87+async 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+
8297router.post('/text-models', jsonParser, async (request, response) => {
8398 try {
8499 const cachedModels = cache.get('models');
85-
86100 if (cachedModels && !request.body.force) {
87101 return response.send(cachedModels);
88102 }
@@ -94,7 +108,17 @@ router.post('/text-models', jsonParser, async (request, response) => {
94108 },
95109 });
96110
97111 constlet 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+
98122 cache.set('models', data);
99123 return response.send(data);
100124 } catch (error) {
@@ -310,6 +334,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
310334 console.log('Stable Horde request:', request.body);
311335
312336 const ai_horde = await getHordeClient();
337+ // noinspection JSCheckFunctionSignatures -- see @ts-ignore - use_gfpgan
313338 const generation = await ai_horde.postAsyncImageGenerate(
314339 {
315340 prompt: `${request.body.prompt} ### ${request.body.negative_prompt}`,
src/endpoints/novelai.js+67 -12
@@ -15,6 +15,11 @@ const badWordsList = [
1515 [19438], [43145], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115], [24],
1616];
1717
18+const 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+
1823const hypeBotBadWordsList = [
1924 [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895],
2025 [5512], [5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208],
@@ -42,12 +47,24 @@ const repPenaltyAllowList = [
4247 803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12],
4348];
4449
50+const 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+
4557// Ban the dinkus and asterism
4658const logitBiasExp = [
4759 { 'sequence': [23], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
4860 { 'sequence': [21], 'bias': -0.08, 'ensure_sequence_finish': false, 'generate_once': false },
4961];
5062
63+const 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+
5168function getBadWordsList(model) {
5269 let list = [];
5370
@@ -59,10 +76,40 @@ function getBadWordsList(model) {
5976 list = badWordsList;
6077 }
6178
79+ if (model.includes('erato')) {
80+ list = eratoBadWordsList;
81+ }
82+
6283 // Clone the list so we don't modify the original
6384 return list.slice();
6485}
6586
87+function 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+
101+function 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+
66113const router = express.Router();
67114
68115router.post('/status', jsonParser, async function (req, res) {
@@ -116,11 +163,10 @@ router.post('/generate', jsonParser, async function (req, res) {
116163 controller.abort();
117164 });
118165
119- const isNewModel = (req.body.model.includes('clio') || req.body.model.includes('kayra'));
166+ // Add customized bad words for Clio, Kayra, and Erato
120167 const badWordsList = getBadWordsList(req.body.model);
121168
122- // Add customized bad words for Clio and Kayra
169+ if (Array.isArray(badWordsList) && Array.isArray(req.body.bad_words_ids)) {
123- if (isNewModel && Array.isArray(req.body.bad_words_ids)) {
124170 for (const badWord of req.body.bad_words_ids) {
125171 if (Array.isArray(badWord) && badWord.every(x => Number.isInteger(x))) {
126172 badWordsList.push(badWord);
@@ -136,12 +182,14 @@ router.post('/generate', jsonParser, async function (req, res) {
136182 }
137183
138184 // Add default biases for dinkus and asterism
139- const logit_bias_exp = isNewModel ? logitBiasExp.slice() : [];
185+ const logitBiasList = getLogitBiasList(req.body.model);
140186
141187 if (Array.isArray(logit_bias_explogitBiasList) && Array.isArray(req.body.logit_bias_exp)) {
142188 logit_bias_explogitBiasList.push(...req.body.logit_bias_exp);
143189 }
144190
191+ const repPenWhitelist = getRepPenaltyWhitelist(req.body.model);
192+
145193 const data = {
146194 'input': req.body.input,
147195 'model': req.body.model,
@@ -156,32 +204,39 @@ router.post('/generate', jsonParser, async function (req, res) {
156204 'repetition_penalty_slope': req.body.repetition_penalty_slope,
157205 'repetition_penalty_frequency': req.body.repetition_penalty_frequency,
158206 'repetition_penalty_presence': req.body.repetition_penalty_presence,
159207 'repetition_penalty_whitelist': isNewModel ? repPenaltyAllowList.flat() : nullrepPenWhitelist,
160208 'top_a': req.body.top_a,
161209 'top_p': req.body.top_p,
162210 'top_k': req.body.top_k,
163211 'typical_p': req.body.typical_p,
164212 'mirostat_lr': req.body.mirostat_lr,
165213 'mirostat_tau': req.body.mirostat_tau,
166- 'cfg_scale': req.body.cfg_scale,
167- 'cfg_uc': req.body.cfg_uc,
168214 'phrase_rep_pen': req.body.phrase_rep_pen,
169215 'stop_sequences': req.body.stop_sequences,
170216 'bad_words_ids': badWordsList.length ? badWordsList : null,
171217 'logit_bias_exp': logit_bias_explogitBiasList,
172218 'generate_until_sentence': req.body.generate_until_sentence,
173219 'use_cache': req.body.use_cache,
174220 'return_full_text': req.body.return_full_text,
175221 'prefix': req.body.prefix,
176222 'order': req.body.order,
177223 '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,
178228 },
179229 };
180230
181231 // Tells the model to stop generation at '>'
182232 if ('theme_textadventure' === req.body.prefix && isNewModel) {
233+ if (req.body.model.includes('clio') || req.body.model.includes('kayra')) {
183234 data.parameters.eos_token_id = 49405;
184235 }
236+ if (req.body.model.includes('erato')) {
237+ data.parameters.eos_token_id = 29;
238+ }
239+ }
185240
186241 console.log(util.inspect(data, { depth: 4 }));
187242
@@ -192,7 +247,7 @@ router.post('/generate', jsonParser, async function (req, res) {
192247 };
193248
194249 try {
195250 const baseURL = (req.body.model.includes('kayra') || req.body.model.includes('erato')) ? TEXT_NOVELAI : API_NOVELAI;
196251 const url = req.body.streaming ? `${baseURL}/ai/generate-stream` : `${baseURL}/ai/generate`;
197252 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) => {
5151 key = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
5252 }
5353
54+ if (request.body.api === 'mistral') {
55+ key = readSecret(request.user.directories, SECRET_KEYS.MISTRALAI);
56+ }
57+
5458 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
5559 console.log('No key found for API', request.body.api);
5660 return response.sendStatus(400);
@@ -107,6 +111,10 @@ router.post('/caption-image', jsonParser, async (request, response) => {
107111 apiUrl = 'https://api.01.ai/v1/chat/completions';
108112 }
109113
114+ if (request.body.api === 'mistral') {
115+ apiUrl = 'https://api.mistral.ai/v1/chat/completions';
116+ }
117+
110118 if (request.body.api === 'ooba') {
111119 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
112120 const imgMessage = body.messages.pop();
src/endpoints/openrouter.js+32 -0
@@ -0,0 +1,32 @@
1+const express = require('express');
2+const { jsonParser } = require('../express-common');
3+
4+const router = express.Router();
5+const API_OPENROUTER = 'https://openrouter.ai/api/v1';
6+
7+router.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+
32+module.exports = { router };
src/endpoints/presets.js+2 -0
@@ -27,6 +27,8 @@ function getPresetSettingsByAPI(apiId, directories) {
2727 return { folder: directories.instruct, extension: '.json' };
2828 case 'context':
2929 return { folder: directories.context, extension: '.json' };
30+ case 'sysprompt':
31+ return { folder: directories.sysprompt, extension: '.json' };
3032 default:
3133 return { folder: null, extension: null };
3234 }
src/endpoints/settings.js+2 -0
@@ -251,6 +251,7 @@ router.post('/get', jsonParser, (request, response) => {
251251
252252 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
253253 const context = readAndParseFromDirectory(request.user.directories.context);
254+ const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
254255
255256 response.send({
256257 settings,
@@ -268,6 +269,7 @@ router.post('/get', jsonParser, (request, response) => {
268269 quickReplyPresets,
269270 instruct,
270271 context,
272+ sysprompt,
271273 enable_extensions: ENABLE_EXTENSIONS,
272274 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
273275 enable_accounts: ENABLE_ACCOUNTS,
src/endpoints/tokenizers.js+24 -0
@@ -221,6 +221,7 @@ const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json');
221221const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');
222222const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');
223223const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');
224+const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json');
224225
225226const sentencepieceTokenizers = [
226227 'llama',
@@ -418,6 +419,10 @@ function getTokenizerModel(requestModel) {
418419 return 'command-r';
419420 }
420421
422+ if (requestModel.includes('nemo')) {
423+ return 'nemo';
424+ }
425+
421426 // default
422427 return 'gpt-3.5-turbo';
423428}
@@ -645,6 +650,7 @@ router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(clau
645650router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer));
646651router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));
647652router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));
653+router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer));
648654router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));
649655router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));
650656router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));
@@ -657,6 +663,7 @@ router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(clau
657663router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer));
658664router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));
659665router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));
666+router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer));
660667
661668router.post('/openai/encode', jsonParser, async function (req, res) {
662669 try {
@@ -707,6 +714,11 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
707714 return handler(req, res);
708715 }
709716
717+ if (queryModel.includes('nemo')) {
718+ const handler = createWebTokenizerEncodingHandler(nemoTokenizer);
719+ return handler(req, res);
720+ }
721+
710722 const model = getTokenizerModel(queryModel);
711723 const handler = createTiktokenEncodingHandler(model);
712724 return handler(req, res);
@@ -765,6 +777,11 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
765777 return handler(req, res);
766778 }
767779
780+ if (queryModel.includes('nemo')) {
781+ const handler = createWebTokenizerDecodingHandler(nemoTokenizer);
782+ return handler(req, res);
783+ }
784+
768785 const model = getTokenizerModel(queryModel);
769786 const handler = createTiktokenDecodingHandler(model);
770787 return handler(req, res);
@@ -835,6 +852,13 @@ router.post('/openai/count', jsonParser, async function (req, res) {
835852 return res.send({ 'token_count': num_tokens });
836853 }
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+
838862 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;
839863 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;
840864 const tokensPadding = 3;
src/endpoints/vectors.js+105 -88
@@ -4,6 +4,7 @@ const fs = require('fs');
44const express = require('express');
55const sanitize = require('sanitize-filename');
66const { jsonParser } = require('../express-common');
7+const { getConfigValue, color } = require('../util');
78
89// Don't forget to add new sources to the SOURCES array
910const SOURCES = [
@@ -110,18 +111,94 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
110111}
111112
112113/**
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+ */
119+function 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+ */
172+function 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+/**
113189 * Gets the index for the vector collection
114190 * @param {import('../users').UserDirectoryList} directories - User directories
115191 * @param {string} collectionId - The collection ID
116192 * @param {string} source - The source of the vector
117193 * @param {booleanobject} createsourceSettings - WhetherThe tomodel createfor the index if it doesn't existsource
118194 * @returns {Promise<vectra.LocalIndex>} - The index for the collection
119195 */
120196async function getIndex(directories, collectionId, source, create = truesourceSettings) {
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));
122199 const store = new vectra.LocalIndex(pathToFile);
123200
124201 if (create && !await store.isIndexCreated()) {
125202 await store.createIndex();
126203 }
127204
@@ -137,7 +214,7 @@ async function getIndex(directories, collectionId, source, create = true) {
137214 * @param {{ hash: number; text: string; index: number; }[]} items - The items to insert
138215 */
139216async function insertVectorItems(directories, collectionId, source, sourceSettings, items) {
140217 const store = await getIndex(directories, collectionId, source, sourceSettings);
141218
142219 await store.beginUpdate();
143220
@@ -157,10 +234,11 @@ async function insertVectorItems(directories, collectionId, source, sourceSettin
157234 * @param {import('../users').UserDirectoryList} directories - User directories
158235 * @param {string} collectionId - The collection ID
159236 * @param {string} source - The source of the vector
237+ * @param {Object} sourceSettings - Settings for the source, if it needs any
160238 * @returns {Promise<number[]>} - The hashes of the items in the collection
161239 */
162240async function getSavedHashes(directories, collectionId, source, sourceSettings) {
163241 const store = await getIndex(directories, collectionId, source, sourceSettings);
164242
165243 const items = await store.listItems();
166244 const hashes = items.map(x => Number(x.metadata.hash));
@@ -173,10 +251,11 @@ async function getSavedHashes(directories, collectionId, source) {
173251 * @param {import('../users').UserDirectoryList} directories - User directories
174252 * @param {string} collectionId - The collection ID
175253 * @param {string} source - The source of the vector
254+ * @param {Object} sourceSettings - Settings for the source, if it needs any
176255 * @param {number[]} hashes - The hashes of the items to delete
177256 */
178257async function deleteVectorItems(directories, collectionId, source, sourceSettings, hashes) {
179258 const store = await getIndex(directories, collectionId, source, sourceSettings);
180259 const items = await store.listItemsByMetadata({ hash: { '$in': hashes } });
181260
182261 await store.beginUpdate();
@@ -200,7 +279,7 @@ async function deleteVectorItems(directories, collectionId, source, hashes) {
200279 * @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text
201280 */
202281async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {
203282 const store = await getIndex(directories, collectionId, source, sourceSettings);
204283 const vector = await getVector(source, sourceSettings, searchText, true, directories);
205284
206285 const result = await store.queryItems(vector, topK);
@@ -226,7 +305,7 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
226305 const results = [];
227306
228307 for (const collectionId of collectionIds) {
229308 const store = await getIndex(directories, collectionId, source, sourceSettings);
230309 const result = await store.queryItems(vector, topK);
231310 results.push(...result.map(result => ({ collectionId, result })));
232311 }
@@ -255,71 +334,6 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
255334}
256335
257336/**
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- */
263-function 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-/**
323337 * Performs a request to regenerate the index if it is corrupted.
324338 * @param {import('express').Request} req Express request object
325339 * @param {import('express').Response} res Express response object
@@ -330,9 +344,10 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
330344 if (error instanceof SyntaxError && !req.query.regenerated) {
331345 const collectionId = String(req.body.collectionId);
332346 const source = String(req.body.source) || 'transformers';
347+ const sourceSettings = getSourceSettings(source, req);
333348
334349 if (collectionId && source) {
335350 const index = await getIndex(req.user.directories, collectionId, source, falsesourceSettings);
336351 const exists = await index.isIndexCreated();
337352
338353 if (exists) {
@@ -350,6 +365,11 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
350365
351366const router = express.Router();
352367
368+router.get('/scopes-enabled', (_req, res) => {
369+ const scopesEnabled = getConfigValue('vectors.enableModelScopes', false);
370+ return res.json({ enabled: scopesEnabled });
371+});
372+
353373router.post('/query', jsonParser, async (req, res) => {
354374 try {
355375 if (!req.body.collectionId || !req.body.searchText) {
@@ -416,8 +436,9 @@ router.post('/list', jsonParser, async (req, res) => {
416436
417437 const collectionId = String(req.body.collectionId);
418438 const source = String(req.body.source) || 'transformers';
439+ const sourceSettings = getSourceSettings(source, req);
419440
420441 const hashes = await getSavedHashes(req.user.directories, collectionId, source, sourceSettings);
421442 return res.json(hashes);
422443 } catch (error) {
423444 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -433,8 +454,9 @@ router.post('/delete', jsonParser, async (req, res) => {
433454 const collectionId = String(req.body.collectionId);
434455 const hashes = req.body.hashes.map(x => Number(x));
435456 const source = String(req.body.source) || 'transformers';
457+ const sourceSettings = getSourceSettings(source, req);
436458
437459 await deleteVectorItems(req.user.directories, collectionId, source, sourceSettings, hashes);
438460 return res.sendStatus(200);
439461 } catch (error) {
440462 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -468,17 +490,12 @@ router.post('/purge', jsonParser, async (req, res) => {
468490 const collectionId = String(req.body.collectionId);
469491
470492 for (const source of SOURCES) {
471493 const indexsourcePath = await getIndexpath.join(req.user.directories, collectionId.vectors, sanitize(source), falsesanitize(collectionId));
472-
494+ if (!fs.existsSync(sourcePath)) {
473- const exists = await index.isIndexCreated();
474-
475- if (!exists) {
476495 continue;
477496 }
478-
497+ 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}`);
482499 }
483500
484501 return res.sendStatus(200);
src/prompt-converters.js+3 -0
@@ -267,11 +267,14 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
267267 'gemini-1.5-flash',
268268 'gemini-1.5-flash-latest',
269269 'gemini-1.5-flash-001',
270+ 'gemini-1.5-flash-002',
270271 'gemini-1.5-flash-exp-0827',
271272 'gemini-1.5-flash-8b-exp-0827',
273+ 'gemini-1.5-flash-8b-exp-0924',
272274 'gemini-1.5-pro',
273275 'gemini-1.5-pro-latest',
274276 'gemini-1.5-pro-001',
277+ 'gemini-1.5-pro-002',
275278 'gemini-1.5-pro-exp-0801',
276279 'gemini-1.5-pro-exp-0827',
277280 'gemini-1.0-pro-vision-latest',
src/users.js+63 -4
@@ -9,8 +9,10 @@ const storage = require('node-persist');
99const express = require('express');
1010const mime = require('mime-types');
1111const archiver = require('archiver');
12+const writeFileAtomicSync = require('write-file-atomic').sync;
13+const _ = require('lodash');
1214
1315const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, DEFAULT_AVATAR, SETTINGS_FILE } = require('./constants');
1416const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util');
1517const { readSecret, writeSecret } = require('./endpoints/secrets');
1618
@@ -24,6 +26,7 @@ const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
2426 * @type {Map<string, UserDirectoryList>}
2527 */
2628const DIRECTORIES_CACHE = new Map();
29+const PUBLIC_USER_AVATAR = '/img/default-user.png';
2730
2831const STORAGE_KEYS = {
2932 csrfSecret: 'csrfSecret',
@@ -82,6 +85,7 @@ const STORAGE_KEYS = {
8285 * @property {string} files - The directory where the uploaded files are stored
8386 * @property {string} vectors - The directory where the vectors are stored
8487 * @property {string} backups - The directory where the backups are stored
88+ * @property {string} sysprompt - The directory where the system prompt data is stored
8589 */
8690
8791/**
@@ -322,6 +326,60 @@ async function migrateUserData() {
322326 console.log(color.green('Migration completed!'));
323327}
324328
329+async 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+
325383/**
326384 * Converts a user handle to a storage key.
327385 * @param {string} handle User handle
@@ -478,11 +536,11 @@ async function getUserAvatar(handle) {
478536 const settings = fs.existsSync(pathToSettings) ? JSON.parse(fs.readFileSync(pathToSettings, 'utf8')) : {};
479537 const avatarFile = settings?.power_user?.default_persona || settings?.user_avatar;
480538 if (!avatarFile) {
481539 return DEFAULT_AVATARPUBLIC_USER_AVATAR;
482540 }
483541 const avatarPath = path.join(directory.avatars, avatarFile);
484542 if (!fs.existsSync(avatarPath)) {
485543 return DEFAULT_AVATARPUBLIC_USER_AVATAR;
486544 }
487545 const mimeType = mime.lookup(avatarPath);
488546 const base64Content = fs.readFileSync(avatarPath, 'base64');
@@ -490,7 +548,7 @@ async function getUserAvatar(handle) {
490548 }
491549 catch {
492550 // Ignore errors
493551 return DEFAULT_AVATARPUBLIC_USER_AVATAR;
494552 }
495553}
496554
@@ -723,6 +781,7 @@ module.exports = {
723781 requireLoginMiddleware,
724782 requireAdminMiddleware,
725783 migrateUserData,
784+ migrateSystemPrompts,
726785 getPasswordSalt,
727786 getPasswordHash,
728787 getCsrfSecret,
start.sh+1 -1
@@ -26,7 +26,7 @@ fi
2626
2727echo "Installing Node Modules..."
2828export NODE_ENV=production
2929npm i --no-audit --no-fund --quietloglevel=error --no-progress --omit=dev
3030
3131echo "Entering SillyTavern..."
3232node "server.js" "$@"