Merge pull request #4189 from SillyTavern/staging Staging

a2efd8792c1cd0791c63bf6fa39ab03dada5d4bd

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

Signed
148 files changed, +4262 -2245Ignore whitespace
.github/readme-de_de.md+23 -23
@@ -320,29 +320,29 @@ Start.bat --port 8000 --listen false
320320
321321### Unterstützte Argumente
322322
323323| Option | Beschreibung | Typ |
324324|-------------------------|---------|--------------------------------------------------------------------------------------------------|----------|
325325| `--version` | Versionsnummer anzeigen | boolean |
326326| `--enableIPv6` | Aktiviert IPv6. | boolean |
327327| `--enableIPv4` | Aktiviert IPv4. | boolean |
328328| `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. Wenn nicht angegeben, wird auf YAML-Konfiguration „Port“ zurückgegriffen. | number |
329329| `--dnsPreferIPv6` | Bevorzugt IPv6 für DNS. Wenn nicht angegeben, wird auf YAML-Konfiguration „preferIPv6“ zurückgegriffen. | boolean |
330330| `--autorun“ browserLaunchEnabled` | Startet SillyTavern automatisch im Browser. Wenn nicht angegeben, wird auf YAML-Konfiguration „autorun“ zurückgegriffen. | boolean |
331331| `--autorunHostname“ browserLaunchHostname` | Der Autorun-Hostname, am besten auf „auto" belassen. | string |
332332| `--autorunPortOverride“ browserLaunchPort` | Überschreibt den Port für Autorun. | string |
333-| „--listen“ | SillyTavern lauscht auf allen Netzwerkschnittstellen. Wenn nicht angegeben, wird auf YAML-Konfiguration „listen“ zurückgegriffen.| boolean |
333+| `--browserLaunchAvoidLocalhost` | Vermeidet die Verwendung von „localhost" für Autorun im Auto-Modus. | boolean |
334-| „--corsProxy“ | Aktiviert CORS-Proxy. Wenn nicht angegeben, wird auf YAML-Konfiguration „enableCorsProxy“ zurückgegriffen. | boolean |
334+| `--listen` | SillyTavern lauscht auf allen Netzwerkschnittstellen. | boolean |
335335| `--disableCsrfcorsProxy` | DeaktiviertAktiviert CSRFCORS-SchutzProxy. Wenn nicht angegeben. | boolean |
336336| `--ssldisableCsrf` | Aktiviert SSLDeaktiviert CSRF-Schutz | boolean |
337337| `--certPathssl` | Pfad zu Ihrer Zertifikatsdatei.Aktiviert SSL | string boolean |
338338| `--keyPathcertPath` | Pfad zu Ihrer privaten SchlüsseldateiZertifikatsdatei. | string |
339339| `--whitelistkeyPath` | AktiviertPfad denzu Whitelist-ModusIhrer |privaten booleanSchlüsseldatei. | string |
340340| `--dataRootwhitelist` | Stammverzeichnis fürAktiviert Datenspeicherungden Whitelist-Modus | string boolean |
341-| `--avoidLocalhost` | Vermeidet die Verwendung von „localhost“ für Autorun im Auto-Modus. | boolean |
341+| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |
342342| `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean |
343343| `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean |
344344| `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string |
345345| `--requestProxyBypass` | Proxy-Bypass-Liste anfordern (durch Leerzeichen getrennte Liste von Hosts) | Array |
346346
347347## Remoteverbindungen
348348
.github/readme-ko_kr.md+23 -23
@@ -334,29 +334,29 @@ Start.bat --port 8000 --listen false
334334
335335### 지원되는 인수
336336
337337| 옵션 | 설명 | 타입 |
338338|-------------------------|----------------|--------------------------------------------------------------------------------------|----------|
339339| `--version` | 버전 표시 | boolean |
340340| `--enableIPv6` | IPv6 활성화 | boolean |
341341| `--enableIPv4` | IPv4 활성화 | boolean |
342342| `--port` | SillyTavern이 실행될 포트를 설정합니다. 설정되지 않은 경우 yaml config 'port'를 불러옵니다. | number |
343343| `--dnsPreferIPv6` | DNS에 IPv6를 우선으로 할당합니다. 설정되지 않은 경우 yaml config를 불러옵니다. | boolean |
344344| `--autorunbrowserLaunchEnabled` | 브라우저에서 SillyTavern을 자동으로 실행합니다. 설정되지 않은 경우 yaml config 'autorun'를 불러옵니다. | boolean |
345345| `--autorunHostnamebrowserLaunchHostname` | 자동 실행 호스트 이름, 'auto'가 최적의 설정일 것입니다. | string |
346346| `--autorunPortOverridebrowserLaunchPort` | 자동 실행 포트 덮어쓰기 | string |
347347| `--listenbrowserLaunchAvoidLocalhost` | SillyTavern이 모든 네트워크 인터페이스에서 수신 대기합니다. 설정되지자동 않은모드에서 경우자동 yaml실행 구성 'listenlocalhost' 불러옵니다. 사용 방지 | boolean |
348348| `--corsProxylisten` | CORS 프록시 활성화. 설정되지 않은SillyTavern이 경우모든 yaml네트워크 구성인터페이스에서 'enableCorsProxy'을수신 불러옵니다대기합니다. | boolean |
349349| `--disableCsrfcorsProxy` | CSRFCORS 보호프록시 비활성화 활성화. | boolean |
350350| `--ssldisableCsrf` | SSLCSRF 활성화 보호 비활성화 | boolean |
351351| `--certPathssl` | 인증서 파일SSL 경로 활성화 | string boolean |
352352| `--keyPathcertPath` | 프라이빗 키인증서 파일 경로 | string |
353353| `--whitelistkeyPath` | 화이트리스트프라이빗 모드 활성화 파일 경로 | boolean string |
354354| `--dataRootwhitelist` | 데이터 스토리지의화이트리스트 루트모드 디렉토리 활성화 | string boolean |
355355| `--avoidLocalhostdataRoot` | 자동 모드에서 자동 실행 시데이터 'localhost'스토리지의 사용루트 방지 디렉토리 | boolean string |
356356| `--basicAuthMode` | 기본 인증 활성화 | boolean |
357357| `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean |
358358| `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string |
359359| `--requestProxyBypass` | 프록시 바이패스 리스트 리퀘스트 (공백으로 구분된 호스트 목록) | array |
360360
361361## 원격 연결
362362
.github/readme-ru_ru.md+23 -23
@@ -283,29 +283,29 @@ Start.bat --port 8000 --listen false
283283
284284### Поддерживаемые аргументы
285285
286286| Аргумент | Описание | Тип |
287287|-------------------------|-----|-----------------------------------------------------------------------------------------------------------|----------|
288288| `--version` | Показывает номер версии. | boolean |
289289| `--enableIPv6` | Включает IPv6. | boolean |
290290| `--enableIPv4` | Включает IPv4. | boolean |
291291| `--port` | Устанавливает порт, котрыйкоторый будет использовать SillyTavern. Если не указан, то используется yaml-конфиг 'port'. | number |
292292| `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. Если не указан, то используется yaml-конфиг 'preferIPv6'. | boolean |
293293| `--autorunbrowserLaunchEnabled` | Автоматический запуск SillyTavern в браузере. Если не указан, то используется yaml-конфиг 'autorun'. | boolean |
294294| `--autorunHostnamebrowserLaunchHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string |
295295| `--autorunPortOverridebrowserLaunchPort` | Переопределяет порт для автозапуска. | string |
296296| `--listenbrowserLaunchAvoidLocalhost` | SillyTavern будет прослушивать все сетевые интерфейсы.Избегает Еслииспользования не'localhost' указан,для тоавтозапуска используетсяв yaml-конфигрежиме 'listenauto'. | boolean |
297297| `--corsProxylisten` | Включает CORS-прокси. Если неSillyTavern указан,будет топрослушивать используетсявсе yaml-конфигсетевые 'enableCorsProxy'интерфейсы. | boolean |
298298| `--disableCsrfcorsProxy` | Отключает защиту отВключает CSRFCORS-прокси. | boolean |
299299| `--ssldisableCsrf` | ВключаетОтключает SSLзащиту от CSRF. | boolean |
300300| `--certPathssl` | Путь к файлу cВключает сертификатомSSL. | string boolean |
301301| `--keyPathcertPath` | Путь к файлу с закрытымc ключомсертификатом. | string |
302302| `--whitelistkeyPath` | ВключаетПуть режимк белогофайлу спискас закрытым ключом. | booleanstring |
303303| `--dataRootwhitelist` | Корневой каталогВключает длярежим хранениябелого данныхсписка. | string boolean |
304304| `--avoidLocalhostdataRoot` | Избегает использованияКорневой 'localhost'каталог для автозапуска в режимехранения 'auto'данных. | booleanstring |
305305| `--basicAuthMode` | Включает простую аутентификацию. | boolean |
306306| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |
307307| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |
308308| `--requestProxyBypass` | Bypass список прокси (список хостов, разделенных пробелами). | array |
309309
310310## Удалённое подключение
311311
.github/readme-zh_cn.md+269 -195
@@ -1,6 +1,8 @@
11> [!IMPORTANT]
22> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。
33
4+<a name="readme-top"></a>
5+
46![][cover]
57
68<div align="center">
@@ -8,7 +10,7 @@
810[English](readme.md) | [German](readme-de_de.md) | 中文 | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md)
911
1012[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
1113[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/networkforks)
1214[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
1315[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
1416
@@ -16,307 +18,379 @@
1618
1719---
1820
19-移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。
21+SillyTavern 为众多 LLM API(KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)提供统一界面,拥有移动设备友好的布局、视觉小说模式、Automatic1111 & ComfyUI API 图像生成集成、TTS、世界书(lorebooks)、可自定义的 UI、自动翻译、超乎您想象的丰富 Prompt 选项,以及通过第三方扩展实现的无限增长潜力。
2022
21-基于 TavernAI 1.2.8 的分叉版本
23+我们有一个[文档网站](https://docs.sillytavern.app/)来回答您的大部分问题并帮助您入门。
2224
23-### 由 Cohee、RossAscends 和 SillyTavern 社区为您呈现
25+## SillyTavern 是什么?
2426
25-注意:我们创建了一个 [帮助文档](https://docs.sillytavern.app/) 网站来回答各类问题与帮助您开始使用。
27+SillyTavern(简称 ST)是一个本地安装的用户界面,允许您与文本生成 LLM、图像生成引擎和 TTS 语音模型进行交互。
2628
27-### SillyTavern 或 TavernAI 是什么?
29+SillyTavern 于 2023 年 2 月作为 TavernAI 1.2.8 的一个分支开始,如今已拥有超过 200 名贡献者和 2 年的独立开发经验,并继续作为资深 AI 爱好者领先的软件。
2830
29-SillyTavern 是一个可以安装在电脑(和安卓手机)上的用户界面,让您可以与文本生成的人工智能互动,并与您或社区创建的角色聊天/玩角色扮演游戏。
31+## 我们的愿景
3032
31-SillyTavern 是 TavernAI 1.2.8 的一个分支,正在进行更积极地开发,并添加了许多重要功能。在这一点上,它可以被视为完全独立的程序。
33+1. 我们的目标是尽可能为用户提供 LLM Prompt 的最大效用和控制权。陡峭的学习曲线是乐趣的一部分!
34+2. 我们不提供任何在线或托管服务,也不会以编程方式跟踪任何用户数据。
35+3. SillyTavern 是一个由专注的 LLM 爱好者社区为您带来的充满激情的项目,并且将永远是免费和开源的。
3236
3337### 分支
3438
3539SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。
3640
3741*- `release` -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。通常每月更新一次。
3842*- `staging` - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。每天更新数次。
3943
4044如果你不熟悉使用 Gitgit 命令命令行,或者不了解什么是分支,别担心!`release` 分支始终是您的首选。
4145
4246### 除了 SillyTavern,我还需要什么?
4347
4448由于 SillyTavern 本身并无用处只是一个界面因为它只是一个用户聊天界面您需要接入一个 LLM 后端来进行推理你必须接入一个能充当角色扮演的人工智能系统您可以使用 AI Horde 进行开箱即用的聊天支持的人工智能系统有多种除此之外,我们还支持许多其他本地和基于云的 LLM 后端OpenAPIOpenAI API兼容 (GPT)API、KoboldAI(可在本地或 Google Colab、Tabby 上运行)等等等。您可以在 [常见问题文档](https://docs.sillytavern.app/usage/faqapi-connections/)中阅读更多关于我们支持的 中阅读更多相关信息API 的信息
4549
4650### 我需要一台性能强大的电脑来运行 SillyTavern 吗?
4751
48-由于 SillyTavern 只是一个用户聊天界面,它对硬件性能的要求很低,可以在任何电脑上运行。需要强大性能的是人工智能系统。
52+硬件要求很低:任何可以运行 NodeJS 18 或更高版本的设备都可以运行它。如果您打算在本地计算机上进行 LLM 推理,我们建议使用至少具有 6GB VRAM 的 3000 系列 NVIDIA 显卡。有关更多详细信息,请查看您后端的文档。
4953
5054### 移动设备支持建议的后端(非附属)
5155
52-> 注意
56+- [AI Horde](https://aihorde.net/) - 使用志愿者托管的模型。无需进一步设置。
57+- [KoboldCpp](https://github.com/LostRuins/koboldcpp) - 社区最喜欢的在本地运行 GGUF 模型的工具。
58+- [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - 一款流行的、轻量级的、本地托管的 exl2 推理 API。
59+- [OpenRouter](https://openrouter.ai) - 一个适用于许多云提供商(OpenAI、Claude、Meta Llama 等)以及流行社区模型的单一 API。
5360
54-> **此分叉可使用 Termux 在安卓手机上原生运行。请参考 ArroganceComplex#2659 编写的指南:**
61+## 有问题或建议?
5562
56-<https://rentry.org/STAI-Termux>
63+### Discord 服务器
5764
58-## 有问题或建议?
65+| [![][discord-shield-badge]][discord-link] | [加入我们的 Discord 社区!](https://discord.gg/sillytavern) 获取支持,分享喜爱的角色和 Prompt。 |
66+| :---------------------------------------- | :---------------------------------------------------------------------------------------------- |
5967
60-### 我们现在有了 Discord 社区
68+或者直接与开发人员联系:
6169
62-获取支持,或分享喜爱的角色和 Prompt:
70+- Discord: cohee, rossascends, wolfsblvt
71+- Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/)
72+- [提交 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues)
6373
64-### [加入 Discord 社区](https://discord.gg/sillytavern)
74+### 我喜欢你的项目!我该如何贡献自己的力量?
6575
66-***
76+1. 发送 Pull Request。学习如何贡献:[CONTRIBUTING.md](../CONTRIBUTING.md)
77+2. 使用提供的模板发送功能建议和问题报告。
78+3. 请先阅读整个 readme 文件并查看文档网站,以避免提交重复的问题。
6779
68-直接与开发人员联系:
80+## 屏幕截图
6981
70-* Discord: cohee 或 rossascends
82+<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
71-* Reddit:/u/RossAscends 或 /u/sillylossy
83+<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
72-* [发布 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues)
7384
7485## 此版本包括角色卡
7586
76-* 经过大量修改的 TavernAI 1.2.8(超过 50% 的代码经过重写或优化)
87+SillyTavern 围绕“角色卡”的概念构建。角色卡是设定 LLM 行为的 Prompt 集合,是在 SillyTavern 中进行持久对话所必需的。它们的功能类似于 ChatGPT 的 GPTs 或 Poe 的 bots。角色卡的内容可以是任何东西:一个抽象的场景、一个为特定任务量身定制的助手、一个著名人物或一个虚构角色。
77-* 根据自定义规则自动重新生成消息
78-* 群聊:多机器人房间,供角色与你或彼此交谈
79-* 聊天书签/分支(复制当前状态下的对话)
80-* 先进的 KoboldAI / TextGen 生成设置,包含大量社区预设
81-* 支持世界信息(Lorebooks):创建丰富的传说
82-* 支持 Window AI 浏览器扩展(运行 Claude、GPT 4 等模型):<https://windowai.io/>
83-* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API 连接
84-* 连接 [AI Horde](https://aihorde.net/)
85-* Prompt 生成格式调整
86-* Webp 角色卡支持(PNG 仍是内部格式)
8788
88-## 扩展
89+要在不选择角色卡的情况下进行快速对话或仅测试 LLM 连接,只需在打开 SillyTavern 后在欢迎屏幕的输入栏中键入您的 Prompt 输入。这将创建一个空的“助手”角色卡,您可以稍后自定义。
8990
90-SillyTavern 支持扩展服务。
91+要大致了解如何定义角色卡,请参阅默认角色(Seraphina)或从“下载扩展和资源”菜单中下载选定的社区制作卡片。
9192
92-* 角色情绪识别
93+## 主要功能
93-* 聊天记录自动摘要
94-* 在聊天窗口发送图片,并由人工智能解释图片内容
95-* 文本图像生成(5 预设,以及 "自由模式")
96-* 聊天信息的文字转语音(通过 ElevenLabs、Silero 或操作系统的语音生成)
9794
98-扩展服务的完整功能介绍和使用教程,请参阅 [Docs](https://docs.sillytavern.app/)。
95+- 高级文本生成设置,包含许多社区制作的预设
96+- 世界书支持:创建丰富的传说或节省角色卡上的 Token
97+- 群聊:多机器人房间,供角色与您或彼此交谈
98+- 丰富的 UI 自定义选项:主题颜色、背景图片、自定义 CSS 等
99+- 用户角色:让 AI 了解一些关于您的信息,以获得更强的沉浸感
100+- 内置 RAG 支持:将文档添加到您的聊天中供 AI 参考
101+- 广泛的聊天命令子系统和自己的[脚本引擎](https://docs.sillytavern.app/usage/st-script/)
99102
100-## 界面/CSS/性能,由 RossAscends 调整并优化
103+## 扩展
101104
102-* 针对 iOS 系统优化了界面,并支持将快捷方式保存到主屏幕,在全屏模式下打开。
105+SillyTavern 支持扩展。
103-* 热键
104- * 上 = 编辑聊天中的最后一条信息
105- * Ctrl+P = 编辑聊天中最后一条用户信息
106- * 左 = 向左滑动
107- * 右 = 向右滑动(注意:当聊天窗口输入内容时,轻扫快捷键将被禁用)
108- * Ctrl+左 = 查看本地存储的变量(在浏览器控制台窗口中)
109- * 回车(选择聊天栏)= 向人工智能发送信息
110- * Ctrl+Enter = 重新生成人工智能最后的回复
111106
112-* 用户名更改和角色删除不再强制重新刷新页面。
107+- 角色情绪表达
108+- 聊天记录自动摘要
109+- 自动 UI 和聊天翻译
110+- Stable Diffusion/FLUX/DALL-E 图像生成
111+- AI 回复消息的文本转语音(通过 ElevenLabs、Silero 或操作系统的 TTS)
112+- 网络搜索功能,为您的 Prompt 添加额外的现实世界背景信息
113+- 更多扩展可从“下载扩展和资源”菜单中下载。
113114
114-* 增加在页面加载时自动连接 API 的选项。
115+有关如何使用它们的使用教程,请参阅[文档](https://docs.sillytavern.app/)。
115-* 增加选项,在页面加载时自动加载最近的聊天信息。
116-* 更好的 Tokens 计算器 - 适用于未保存的文字,并显示永久和临时 Tokens 数量
117116
118-* 更好的聊天历史查询窗口
117+## ⌛ 安装
119- * 聊天的文件名以"(角色卡名称)+(创建时间)"的可读格式保存
120- * 聊天历史预览从 40 个字符增加到 300 个字符。
121- * 聊天历史排序有多种选择(按名称、创建日期、聊天记录大小)。
122118
123-* 默认情况下,左侧和右侧弹出的设置面板会在点击其他区域时自动关闭。
119+### 🪟 Windows
124-* 点击导航面板上的 "锁按钮" 将保持弹出面板打开,并在不同聊天中记住此设置。
125-* 导航面板的打开或关闭状态也会跨聊天保存。
126120
127-* 自定义聊天界面:
121+> \[!WARNING]
128- * 收到新消息时播放提示音
122+>
129- * 切换圆形或长方形头像样式
123+> - 请勿安装到任何受 Windows 控制的文件夹(Program Files、System32 等)中。
130- * 在台式电脑上拥有更宽的聊天窗口
124+> - 请勿以管理员权限运行 Start.bat
131- * 可选的半透明玻璃效果聊天窗口
125+> - 无法在 Windows 7 上安装,因为它无法运行 NodeJS 18.16
132- * 可定制 "主文本"、"引用文本 "和 "斜体文本 "的字体颜色。
133- * 可定制聊天界面的背景颜色和透明模糊程度
134126
135-## 安装
127+#### 通过 Git 安装(推荐)
136128
137-*注意:SillyTavern 用于本地安装,尚未在 Colab 或其他云服务上进行全面测试。
129+1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
130+2. 安装 [Git for Windows](https://gitforwindows.org/)
131+3. 打开 Windows 资源管理器 (`Win+E`)
132+4. 浏览或创建一个不受 Windows 控制或监控的文件夹(例如:C:\MySpecialFolder\)
133+5. 通过点击顶部的“地址栏”,输入 `cmd`,然后按 Enter,在该文件夹内打开命令提示符。
134+6. 弹出黑框(命令提示符)后,键入以下其中一项并按 Enter:
138135
139-> **警告**
136+- Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
137+- Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
140138
141-> 切勿安装到任何受 Windows 控制的系统文件夹(Program Files, System32, etc)中。
139+7. 等待所有内容克隆完成后,双击 `Start.bat` 以使 NodeJS 安装其依赖项。
140+8. 然后服务器将启动,SillyTavern 将在您的浏览器中弹出。
142141
143-> 不要以管理员权限运行 start.bat
142+#### 通过 GitHub Desktop 安装
144143
145-### Windows
144+(这**仅**允许在 GitHub Desktop 中使用 git,如果您也想在命令行上使用 `git`,则还需要安装 [Git for Windows](https://gitforwindows.org/))
146145
147-通过 Git 安装(推荐使用,便于更新)
146+1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
147+2. 安装 [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
148+3. 安装 GitHub Desktop 后,点击 `Clone a repository from the internet....`(注意:此步骤**无需**创建 GitHub 帐户)
149+4. 在菜单中,点击 URL 选项卡,输入此 URL `https://github.com/SillyTavern/SillyTavern`,然后点击 Clone。您可以更改本地路径以更改 SillyTavern 的下载位置。
150+5. 要打开 SillyTavern,请使用 Windows 资源管理器浏览到克隆存储库的文件夹。默认情况下,存储库将克隆到此处:`C:\Users\[您的 Windows 用户名]\Documents\GitHub\SillyTavern`
151+6. 双击 `start.bat` 文件。(注意:文件名的 `.bat` 部分可能被您的操作系统隐藏,在这种情况下,它将显示为一个名为“`Start`”的文件。双击此文件以运行 SillyTavern)
152+7. 双击后,应打开一个大的黑色命令控制台窗口,SillyTavern 将开始安装其运行所需的组件。
153+8. 安装过程完成后,如果一切正常,命令控制台窗口应如下所示,并且您的浏览器中应打开一个 SillyTavern 选项卡:
154+9. 连接到任何[支持的 API](https://docs.sillytavern.app/usage/api-connections/) 并开始聊天!
148155
149-附有精美图片示例的简易指南:
156+### 🐧 Linux & 🍎 MacOS
150-<https://docs.sillytavern.app/installation/windows/>
151157
152- 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
158+对于 MacOS / Linux,所有这些都将在终端中完成。
153- 2. 安装 [GitHub 客户端](https://central.github.com/deployments/desktop/desktop/latest/win32)
154- 3. 打开 Windows 资源管理器 (`Win+E`)
155- 4. 浏览或创建一个不受 Windows 控制或监控的文件夹。(例如:C:\MySpecialFolder\)
156- 5. 点击顶部的 "地址栏",在该文件夹内打开命令提示符,输入 `cmd`,然后按回车。
157- 6. 弹出黑框(CMD 命令提示符)后,键入以下其中一项并按 Enter:
158159
159-* 稳定分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
160+1. 安装 git 和 nodeJS(具体方法取决于您的操作系统)
160-* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
161+2. 克隆仓库
161162
162- 7. 等待 Git 克隆完成后,双击文件夹中的 `Start.bat` 将启动 NodeJS 并开始自动安装需要的软件包。
163+- Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
163- 8. 然后 SillyTavern 服务就会自动启动,同时在浏览器新标签页中自动打开。
164+- Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
164165
165-通过压缩包下载安装(不推荐)
166+3. `cd SillyTavern` 导航到安装文件夹。
167+4. 使用以下命令之一运行 `start.sh` 脚本:
166168
167- 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
169+- `./start.sh`
168- 2. 从该 GitHub 仓库下载压缩包。(从 [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest) 获取 "Source code(zip)")。
170+- `bash start.sh`
169- 3. 将压缩包解压到您选择的文件夹中
170- 4. 双击或在命令行中运行 `Start.bat`。
171- 5. SillyTavern 服务自动为你准备好一切后,会在你的浏览器中打开一个新标签页。
172171
173-### Linux
172+## 🐋 通过 Docker 安装
174173
175- 1.运行 `start.sh` 脚本。
174+这些说明假定您已安装 Docker,能够访问命令行以安装容器,并熟悉其常规操作。
176- 2.等待自动完成,然后开始享受
177175
178-## API 密钥管理
176+### 使用 GitHub Container Registry
179177
180-SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
178+#### Docker Compose (最简单)
181179
182-默认情况下,输入密钥并重新加载页面后,密钥会自动隐藏以保证安全。
180+从 [GitHub 仓库](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) 获取 `docker-compose.yml` 文件,并在文件所在目录中运行以下命令。这将从 GitHub Container Registry 中拉取最新的 release 镜像并启动容器,自动创建必要的卷。
183181
184-如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置:
182+```shell
183+docker-compose up
184+```
185185
186-1. 打开 `config.yaml` 文件,将里面的 `allowKeysExposure` 设置为 `true`。
186+根据您的需求自定义 `docker-compose.yml` 文件。默认端口为 8000。如果您想使用环境变量调整服务器配置,请在此处阅读文档:[链接](https://docs.sillytavern.app/administration/config-yaml/#environment-variables)。
187-2. 然后重启 SillyTavern 服务。
188187
189-## 远程访问
188+#### Docker CLI (高级)
190189
191-这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。
190+您将需要两个强制性的目录映射和一个端口映射才能使 SillyTavern 正常运行。在命令中,替换以下位置中的选项:
192191
193-不过,SillyTavern 也可以被设置为允许从任何地方进行远程访问。
192+#### 容器变量
194193
195-**重要提示:SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。
194+##### 卷映射
196195
197-### 1.管理白名单 IP
196+- `CONFIG_PATH` - SillyTavern 配置文件将存储在主机上的目录
197+- `DATA_PATH` - SillyTavern 用户数据(包括角色)将存储在主机上的目录
198+- `PLUGINS_PATH` - (可选) SillyTavern 服务器插件将存储在主机上的目录
199+- `EXTENSIONS_PATH` - (可选) 全局 UI 扩展将存储在主机上的目录
198200
199-* 在你的 SillyTavern 文件夹中新建一个文本文件,名为 `whitelist.txt`。
201+##### 端口映射
200-* 用文本编辑器打开该文件,添加你希望允许连接的 IP 地址列表。
201-* 接受单个 IP 地址和 IP 范围,示例:
202202
203-```
203+- `PUBLIC_PORT` - 暴露流量的端口。这是强制性的,因为您将从其虚拟机容器外部访问实例。**在未实现单独的安全服务的情况下,请勿将其暴露给互联网。**
204-192.168.0.1
205-192.168.0.20
206-```
207204
208-或者
205+##### 附加设置
209206
210-```
207+- `SILLYTAVERN_VERSION` - 在此 GitHub 页面的右侧,您会看到“Packages”。选择“sillytavern”包,您将看到镜像版本。镜像标签“latest”将使您与当前 release 保持同步。您还可以使用指向相应分支的每日镜像的“staging”标签。
211-192.168.0.*
212-```
213208
214-(上述 IP 范围将允许局域网中的任何设备连接)
209+#### 运行容器
215210
216-也接受子网掩码设置(如 10.0.0.0/24)。
211+1. 打开您的命令行
212+2. 在您要存储配置和数据文件的文件夹中运行以下命令:
217213
218-* 保存`whitelist.txt`文件。
214+```bash
219-* 重启 SillyTavern 服务。
215+SILLYTAVERN_VERSION="latest"
216+PUBLIC_PORT="8000"
217+CONFIG_PATH="./config"
218+DATA_PATH="./data"
219+PLUGINS_PATH="./plugins"
220+EXTENSIONS_PATH="./extensions"
220221
221-然后,文件中设置的 IP 就可以访问 SillyTavern 了。
222+docker run \
223+ --name="sillytavern" \
224+ -p "$PUBLIC_PORT:8000/tcp" \
225+ -v "$CONFIG_PATH:/home/node/app/config:rw" \
226+ -v "$DATA_PATH:/home/node/app/data:rw" \
227+ -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
228+ -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
229+ ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
230+```
222231
223-*注意:"config.yaml" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。
232+> 默认情况下,容器将在前台运行。如果要在后台运行它,请将 `-d` 标志添加到 `docker run` 命令中。
224233
225-### 2.获取 SillyTavern 服务的 IP 地址
234+### 自己构建镜像
226235
227-白名单设置完成后,您需要 SillyTavern 服务的 IP 地址。
236+我们有一个关于在 Docker 中使用 SillyTavern 的综合指南[在此处](http://docs.sillytavern.app/installation/docker/),涵盖了 Windows、macOS 和 Linux 上的安装!如果您希望自己构建镜像,请阅读它。
228237
229-如果 SillyTavern 服务设备在同一个局域网上,则使用安装 SillyTavern 服务的电脑的局域网 IP 地址:
238+## ⚡ 通过 SillyTavern Launcher 安装
230239
231-* Windows:Windows 按钮 > 在搜索栏中输入 `cmd.exe` > 在打开的控制台中输入 `ipconfig`,回车 > 然后在输出中查找 `IPv4` 地址。
240+SillyTavern Launcher 是一个安装向导,可帮助您进行多种选项的设置,包括为本地推理安装后端。
232241
233-如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。
242+### 对于 Windows 用户
234243
235-* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。
244+1. 在键盘上:按 **`WINDOWS + R`** 打开“运行”对话框。然后,运行以下命令安装 git:
236245
237-### 3. 使用其他设备访问 SillyTavern 服务
246+```shell
247+cmd /c winget install -e --id Git.Git
248+```
238249
239-无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。
250+2. 在键盘上:按 **`WINDOWS + E`** 打开文件资源管理器,然后导航到要安装启动器的文件夹。进入所需文件夹后,在地址栏中键入 `cmd` 并按 Enter。然后,运行以下命令:
240251
241-同一局域网中的 SillyTavern 服务的典型默认地址如下:
252+```shell
253+git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
254+```
242255
243-`http://192.168.0.5:8000`
256+### 对于 Linux 用户
244257
245-使用 http:// 而不是 https://
258+1. 打开您喜欢的终端并安装 git
259+2. 使用以下命令克隆 SillyTavern-Launcher:
246260
247-### 向所有 IP 开放您的 SillyTavern 服务
261+```shell
262+git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
263+```
248264
249-我们不建议这样做,但您可以打开 `config.yaml` 并将里面的 `whitelistMode` 设置改为 `false`。
265+3. 使用以下命令启动 installer.sh:
250266
251-你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。
267+```shell
268+chmod +x install.sh && ./install.sh
269+```
252270
253-这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。
271+4. 安装后使用以下命令启动 launcher.sh:
254272
255-用户名和密码在`config.yaml`文件中设置。
273+```shell
274+chmod +x launcher.sh && ./launcher.sh
275+```
256276
257-重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。
277+### 对于 Mac 用户
258278
259-### 还是无法访问?
279+1. 打开终端并使用以下命令安装 brew:
260280
261-* 为 `config.yaml` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。
281+```shell
262-* 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。
282+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
283+```
263284
264-### 性能问题?
285+2. 使用以下命令安装 git:
265286
266-尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。
287+```shell
288+brew install git
289+```
267290
268-## 我喜欢你的项目!我该如何贡献自己的力量?
291+3. 使用以下命令克隆 SillyTavern-Launcher:
269292
270-### 应该
293+```shell
294+git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
295+```
271296
272-1. 发送 Fork 请求
297+4. 使用以下命令启动 installer.sh:
273-2. 使用规定的模板发送功能建议和问题报告
274-3. 在提出任何问题之前,请先阅读 Readme 文件和文档
275298
276-#### 不应该
299+```shell
300+chmod +x install.sh && ./install.sh
301+```
277302
278-1. 提供金钱捐赠
303+5. 安装后使用以下命令启动 launcher.sh:
279-2. 发送错误报告而不提供任何详细信息
280-3. 提出已经回答过无数次的问题
281304
282-## 我在哪里可以找到以前的聊天背景图片?
305+```shell
306+chmod +x launcher.sh && ./launcher.sh
307+```
283308
284-我们正在实行 100% 原创内容的政策,因此旧的背景图片已从该资源库中删除。
309+## 📱 通过 Termux 在 Android OS 上安装
285310
286-不过你可以在这里找到它们的存档:
311+> \[!NOTE]
312+> **SillyTavern 可以在 Android 设备上使用 Termux 原生运行,但我们不为此用例提供官方支持。**
313+>
314+> **请参阅 ArroganceComplex#2659 编写的本指南:**
315+>
316+> - <https://rentry.org/STAI-Termux>
287317
288-<https://files.catbox.moe/1xevnc.zip>
318+**不支持的平台:android arm LEtime-web。** 32 位 Android 需要一个无法通过 npm 安装的外部依赖项。使用以下命令安装它:`pkg install esbuild`。然后运行常规安装步骤。
289319
290320## 屏幕截图命令行参数
291321
292-<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
322+您可以将命令行参数传递给 SillyTavern 服务器启动脚本,以覆盖 `config.yaml` 中的某些设置。
293-<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
294323
295324### 许可证和贡献示例
296325
297-**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。**
326+```shell
298-
327+node server.js --port 8000 --listen false
299-**This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.**
328+# 或
300-
329+npm run start -- --port 8000 --listen false
301-* TAI Base by Humi: Unknown license
330+# 或 (仅限 Windows)
302-* Cohee's modifications and derived code: AGPL v3
331+Start.bat --port 8000 --listen false
303-* RossAscends' additions: AGPL v3
332+```
304-* Portions of CncAnon's TavernAITurbo mod: Unknown license
333+
305-* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
334+### 支持的参数
306-* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
335+
307-* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
336+> \[!TIP]
308-* Thanks Pygmalion University for being awesome testers and suggesting cool features!
337+> 所有参数都不是必需的。如果您不提供它们,SillyTavern 将使用 `config.yaml` 中的设置。
309-* Thanks oobabooga for compiling presets for TextGen
338+
310-* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
339+| 选项 | 描述 | 类型 |
311-* Noto Sans font by Google (OFL license)
340+| -------------------------------- | ---------------------------------------------- | ------- |
312-* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
341+| `--version` | 显示版本号 | boolean |
313-* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>
342+| `--configPath` | 覆盖 config.yaml 文件的路径 | string |
314-* Linux startup script by AlpinDale
343+| `--dataRoot` | 数据存储的根目录 | string |
315-* Thanks paniphons for providing a FAQ document
344+| `--port` | 设置 SillyTavern 将在其下运行的端口 | number |
316-* 10K Discord Users Celebratory Background by @kallmeflocc
345+| `--listen` | SillyTavern 将侦听所有网络接口 | boolean |
317-* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
346+| `--whitelist` | 启用白名单模式 | boolean |
318-* Korean translation by @doloroushyeonse
347+| `--basicAuthMode` | 启用基本身份验证 | boolean |
319-* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3
348+| `--enableIPv4` | 启用 IPv4 协议 | boolean |
349+| `--enableIPv6` | 启用 IPv6 协议 | boolean |
350+| `--listenAddressIPv4` | 要侦听的特定 IPv4 地址 | string |
351+| `--listenAddressIPv6` | 要侦听的特定 IPv6 地址 | string |
352+| `--dnsPreferIPv6` | DNS 首选 IPv6 | boolean |
353+| `--ssl` | 启用 SSL | boolean |
354+| `--certPath` | 您的证书文件路径 | string |
355+| `--keyPath` | 您的私钥文件路径 | string |
356+| `--browserLaunchEnabled` | 自动在浏览器中启动 SillyTavern | boolean |
357+| `--browserLaunchHostname` | 自动运行主机名 | string |
358+| `--browserLaunchPort` | 覆盖自动运行的端口 | string |
359+| `--browserLaunchAvoidLocalhost` | 在自动模式下避免使用 'localhost' 进行自动运行 | boolean |
360+| `--corsProxy` | 启用 CORS 代理 | boolean |
361+| `--requestProxyEnabled` | 为传出请求启用代理 | boolean |
362+| `--requestProxyUrl` | 请求代理 URL(HTTP 或 SOCKS 协议) | string |
363+| `--requestProxyBypass` | 请求代理绕过列表(以空格分隔的主机列表) | array |
364+| `--disableCsrf` | 禁用 CSRF 保护(不推荐) | boolean |
365+
366+## 远程连接
367+
368+这通常适用于那些想在手机上使用 SillyTavern,而他们的电脑在同一 Wi-Fi 网络上运行 ST 服务器的人。但是,它也可以用于允许从任何地方进行远程连接。
369+
370+请在[文档](https://docs.sillytavern.app/usage/remoteconnections/)中阅读有关如何设置远程连接的详细指南。
371+
372+您可能还需要配置 SillyTavern 用户配置文件(可选密码保护):[用户](https://docs.sillytavern.app/administration/multi-user/)。
373+
374+## 许可证和致谢
375+
376+**本程序的分发是希望它能有用,但不提供任何保证;甚至没有对适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU Affero 通用公共许可证。**
377+
378+- [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT 许可证
379+- CncAnon 的 TavernAITurbo mod 的部分内容经许可使用
380+- 视觉小说模式的灵感来自 PepperTaco 的工作 (<https://github.com/peppertaco/Tavern/>)
381+- Noto Sans 字体 by Google (OFL 许可证)
382+- 图标主题 by Font Awesome <https://fontawesome.com> (图标: CC BY 4.0, 字体: SIL OFL 1.1, 代码: MIT 许可证)
383+- 默认内容由 @OtisAlejandro (Seraphina 角色和世界书) 和 @kallmeflocc (10K Discord 用户庆祝背景) 提供
384+- Docker 指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 提供
385+- kokoro-js 库由 [@hexgrad](https://github.com/hexgrad) 提供 (Apache-2.0 许可证)
386+- 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3
387+
388+## 主要贡献者
389+
390+[![Contributors](https://contrib.rocks/image?repo=SillyTavern/SillyTavern)](https://github.com/SillyTavern/SillyTavern/graphs/contributors)
320391
321392<!-- LINK GROUP -->
393+
322394[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
395+[discord-link]: https://discord.gg/sillytavern
396+[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_tw.md+23 -23
@@ -319,29 +319,29 @@ Start.bat --port 8000 --listen false
319319
320320### Supported arguments
321321
322322| Option | Description | Type |
323323|-------------------------|---------------------------------------------------------|---------------------------------------------|----------|
324324| `--version` | 顯示版本序號 | boolean |
325325| `--enableIPv6` | 啟用 IPv6 | boolean |
326326| `--enableIPv4` | 啟用 IPv4 | boolean |
327327| `--port` | 設定 SillyTavern 運行的端口。若未提供,則預設使用 `config.yaml` 中的 'port' | number |
328328| `--dnsPreferIPv6` | 偏好使用 IPv6 解析 DNS。未提供則默認使用 `config.yaml` 中的 'preferIPv6' | boolean |
329329| `--autorunbrowserLaunchEnabled` | 自動在瀏覽器中啟動 SillyTavern。未提供則默認使用 `config.yaml` 中的 'autorun' | boolean |
330330| `--autorunHostnamebrowserLaunchHostname` | 自動啟動時的主機名稱,通常建議保持為 'auto' | string |
331331| `--autorunPortOverridebrowserLaunchPort` | 覆蓋自動啟動的端口設定 | string |
332332| `--listenbrowserLaunchAvoidLocalhost` | SillyTavern 是否可監聽所有網路接口。若未提供,則默認使用 `config.yaml` 中的在自動模式下避免使用 'listenlocalhost' | boolean |
333-| `--corsProxy` | 啟用 CORS 代理。若未提供,則默認使用 `config.yaml` 中的 'enableCorsProxy' | boolean |
333+| `--listen` | SillyTavern 是否可監聽所有網路接口。 | boolean |
334334| `--disableCsrfcorsProxy` | 停用啟用 CSRFCORS 保護 代理。 | boolean |
335335| `--ssldisableCsrf` | 啟用停用 SSL CSRF 保護 | boolean |
336336| `--certPathssl` | 設定您證書文件的路徑 啟用 SSL | string boolean |
337337| `--keyPathcertPath` | 設定您私人金鑰文件的路徑 設定您證書文件的路徑 | string |
338338| `--whitelistkeyPath` | 啟用白名單模式 設定您私人金鑰文件的路徑 | booleanstring |
339339| `--dataRootwhitelist` | 設定數據儲存的根目錄 啟用白名單模式 | string boolean |
340340| `--avoidLocalhostdataRoot` | 在自動模式下避免使用 'localhost' 設定數據儲存的根目錄 | booleanstring |
341341| `--basicAuthMode` | 啟用基本身份驗證模式 | boolean |
342342| `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean |
343343| `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議) | string |
344344| `--requestProxyBypass` | 請求代理的例外主機清單(主機列表需以空格分隔) | array |
345345
346346## 遠端連線
347347
.github/readme.md+48 -26
@@ -232,6 +232,28 @@ docker run \
232232
233233We have a comprehensive guide on using SillyTavern in Docker [here](http://docs.sillytavern.app/installation/docker/) which covers installations on Windows, macOS and Linux! Give it a read if you wish to build the image yourself.
234234
235+### Common issues with Docker
236+
237+#### SELinux Permission Issues with Mounted Volumes
238+
239+Linux distributions with SELinux enabled (such as RHEL, CentOS, Fedora, etc.) may prevent Docker containers from accessing mounted volumes due to security policies. This can result in permission denied errors when the container tries to read or write to the mounted directories.
240+
241+Two suffixes `:z` or `:Z` can be added to the volume mount. These suffixes tell Docker to relabel file objects on the shared volumes.
242+
243+* The `z` option is used when the volume content will be shared between containers.
244+* The `Z` option is used when the volume content should only be used by the current container.
245+
246+Example:
247+
248+```yaml
249+# docker-compose.yml
250+volumes:
251+ ## Shared volume
252+ - ./config:/home/node/app/config:z
253+ ## Private volume
254+ - ./data:/home/node/app/data:Z
255+```
256+
235257## ⚡ Installing via SillyTavern Launcher
236258
237259SillyTavern Launcher is an installation wizard that will help you get setup with many options, including installing a backend for local inference.
@@ -333,32 +355,32 @@ Start.bat --port 8000 --listen false
333355> \[!TIP]
334356> None of the arguments are required. If you don't provide them, SillyTavern will use the settings in `config.yaml`.
335357
336358| Option | Description | Type |
337359|---------------------------------|----------------------------------------------------------------------|----------|
338360| `--version` | Show version number | boolean |
339361| `--configPath` | Override the path to the config.yaml file | string |
340362| `--dataRoot` | Root directory for data storage | string |
341363| `--port` | Sets the port under which SillyTavern will run | number |
342364| `--listen` | SillyTavern will listen on all network interfaces | boolean |
343365| `--whitelist` | Enables whitelist mode | boolean |
344366| `--basicAuthMode` | Enables basic authentication | boolean |
345367| `--enableIPv4` | Enables IPv4 protocol | boolean |
346368| `--enableIPv6` | Enables IPv6 protocol | boolean |
347369| `--listenAddressIPv4` | Specific IPv4 address to listen to | string |
348370| `--listenAddressIPv6` | Specific IPv6 address to listen to | string |
349371| `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean |
350372| `--ssl` | Enables SSL | boolean |
351373| `--certPath` | Path to your certificate file | string |
352374| `--keyPath` | Path to your private key file | string |
353375| `--autorunbrowserLaunchEnabled` | Automatically launch SillyTavern in the browser | boolean |
354376| `--autorunHostnamebrowserLaunchHostname` | AutorunBrowser launch hostname | string |
355377| `--autorunPortOverridebrowserLaunchPort` | Overrides the port for autorun browser launch | string |
356378| `--avoidLocalhostbrowserLaunchAvoidLocalhost` | Avoids using 'localhost' for autorunbrowser launch in auto mode | boolean |
357379| `--corsProxy` | Enables CORS proxy | boolean |
358380| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean |
359381| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string |
360382| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array |
361383| `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean |
362384
363385## Remote connections
364386
Dockerfile+3 -3
@@ -1,4 +1,4 @@
11FROM node:lts-alpine3.1921
22
33# Arguments
44ARG APP_HOME=/home/node/app
@@ -19,7 +19,7 @@ RUN \
1919 echo "*** Install npm packages ***" && \
2020 npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force
2121
22-# Copy default chats, characters and user avatars to <folder>.default folder
22+# Create config directory and link config.yaml
2323RUN \
2424 rm -f "config.yaml" || true && \
2525 ln -s "./config/config.yaml" "config.yaml" || true && \
@@ -30,7 +30,7 @@ RUN \
3030 echo "*** Run Webpack ***" && \
3131 node "./docker/build-lib.js"
3232
3333# CleanupSet unnecessarythe filesentrypoint script
3434RUN \
3535 echo "*** Cleanup ***" && \
3636 mv "./docker/docker-entrypoint.sh" "./" && \
default/config.yaml+21 -15
@@ -16,16 +16,28 @@ protocol:
1616 ipv6: false
1717# Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6
1818dnsPreferIPv6: false
19-# The hostname that autorun opens.
19+# -- BROWSER LAUNCH CONFIGURATION --
20-# - Use "auto" to let the server decide
20+browserLaunch:
21-# - Use options like 'localhost', 'st.example.com'
21+ # Open the browser automatically on server startup.
22-autorunHostname: "auto"
22+ enabled: true
23+ # Browser to use for opening the URL.
24+ # NOT SUPPORTED ON ANDROID DEVICES.
25+ # - Use "default" to use the system default browser
26+ # - Use "firefox", "chrome", "edge"
27+ browser: 'default'
28+ # Overrides the hostname that opens in the browser.
29+ # - Use "auto" to let the server decide
30+ # - Use options like 'localhost', 'st.example.com'
31+ hostname: 'auto'
32+ # Overrides the port for run in the browser.
33+ # - Use -1 to use the server port.
34+ # - Specify a port to override the default.
35+ port: -1
36+ # Avoids using 'localhost' as the hostname in auto mode.
37+ # Use if you don't have 'localhost' in your hosts file
38+ avoidLocalhost: false
2339# Server port
2440port: 8000
25-# Overrides the port for autorun in browser.
26-# - Use -1 to use the server port.
27-# - Specify a port to override the default.
28-autorunPortOverride: -1
2941# -- SSL options --
3042ssl:
3143 enabled: false
@@ -70,7 +82,7 @@ enableDiscreetLogin: false
7082# https://www.authelia.com/
7183# This will use auto login to an account with the same username
7284# as that used for authlia. (Ensure the username in authlia
7385# is an exact match in lowercase with that in sillytavern)
7486autheliaAuth: false
7587# If `basicAuthMode` and this are enabled then
7688# the username and passwords for basic auth are the same as those
@@ -98,12 +110,6 @@ rateLimiting:
98110 # Use X-Real-IP header instead of socket IP for rate limiting
99111 # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy)
100112 preferRealIpHeader: false
101-# -- ADVANCED CONFIGURATION --
102-# Open the browser automatically
103-autorun: true
104-# Avoids using 'localhost' for autorun in auto mode.
105-# use if you don't have 'localhost' in your hosts file
106-avoidLocalhost: false
107113
108114## BACKUP CONFIGURATION
109115backups:
default/content/index.json+8 -0
@@ -810,5 +810,13 @@
810810 {
811811 "filename": "presets/reasoning/Blank.json",
812812 "type": "reasoning"
813+ },
814+ {
815+ "filename": "presets/instruct/Dots1.json",
816+ "type": "instruct"
817+ },
818+ {
819+ "filename": "presets/context/Dots1.json",
820+ "type": "context"
813821 }
814822]
default/content/presets/context/Dots1.json+11 -0
@@ -0,0 +1,11 @@
1+{
2+ "story_string": "<|system|>\n{{#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}}<|endofsystem|>",
3+ "example_separator": "",
4+ "chat_start": "",
5+ "use_stop_strings": false,
6+ "names_as_stop_strings": true,
7+ "always_force_name2": true,
8+ "trim_sentences": false,
9+ "single_line": false,
10+ "name": "Dots1"
11+}
11 \ No newline at end of file
default/content/presets/instruct/Dots1.json+24 -0
@@ -0,0 +1,24 @@
1+{
2+ "input_sequence": "<|userprompt|>",
3+ "output_sequence": "<|response|>",
4+ "last_output_sequence": "",
5+ "system_sequence": "<|system|>",
6+ "stop_sequence": "<|endofresponse|>",
7+ "wrap": true,
8+ "macro": true,
9+ "names_behavior": "force",
10+ "activation_regex": "",
11+ "system_sequence_prefix": "",
12+ "system_sequence_suffix": "",
13+ "first_output_sequence": "",
14+ "skip_examples": false,
15+ "output_suffix": "<|endofresponse|>\n",
16+ "input_suffix": "<|endofuserprompt|>\n",
17+ "system_suffix": "<|endofsystem|>\n",
18+ "user_alignment_message": "",
19+ "system_same_as_user": false,
20+ "last_system_sequence": "",
21+ "first_input_sequence": "",
22+ "last_input_sequence": "",
23+ "name": "Dots1"
24+}
package-lock.json+330 -207
@@ -1,16 +1,16 @@
11{
22 "name": "sillytavern",
33 "version": "1.13.01",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.13.01",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
1313 "@adobe/css-tools": "^4.4.23",
1414 "@agnai/sentencepiece-js": "^1.1.1",
1515 "@agnai/web-tokenizers": "^0.1.3",
1616 "@iconfu/svg-inject": "^1.2.3",
@@ -51,19 +51,19 @@
5151 "cookie-session": "^2.1.0",
5252 "cors": "^2.8.5",
5353 "crc": "^4.3.2",
5454 "csrf-sync": "^4.02.31",
5555 "diff-match-patch": "^1.0.5",
5656 "dompurify": "^3.2.46",
5757 "droll": "^0.2.1",
5858 "express": "^4.21.0",
5959 "form-data": "^4.0.23",
6060 "fuse.js": "^7.1.0",
6161 "google-translate-api-browser": "^3.0.1",
6262 "google-translate-api-x": "^10.7.2",
6363 "handlebars": "^4.7.8",
6464 "helmet": "^78.21.0",
6565 "highlight.js": "^11.11.1",
6666 "html-entities": "^2.56.20",
6767 "iconv-lite": "^0.6.3",
6868 "ip-matching": "^2.1.2",
6969 "ip-regex": "^5.0.0",
@@ -71,13 +71,13 @@
7171 "is-docker": "^3.0.0",
7272 "localforage": "^1.10.0",
7373 "lodash": "^4.17.21",
7474 "mime-types": "^23.10.351",
7575 "moment": "^2.30.1",
7676 "morphdom": "^2.7.45",
7777 "multer": "^2.0.1",
7878 "node-fetch": "^3.3.2",
7979 "node-persist": "^4.0.4",
8080 "open": "^810.41.2",
8181 "png-chunk-text": "^1.0.0",
8282 "png-chunks-extract": "^1.0.0",
8383 "proxy-agent": "^6.5.0",
@@ -87,18 +87,18 @@
8787 "seedrandom": "^3.0.5",
8888 "showdown": "^2.1.0",
8989 "sillytavern-transformers": "2.14.6",
9090 "simple-git": "^3.2728.0",
9191 "slidetoggle": "^4.0.0",
9292 "tiktoken": "^1.0.2021",
9393 "url-join": "^5.0.0",
9494 "vectra": "^0.2.2",
9595 "wavefile": "^11.0.0",
9696 "webpack": "^5.9899.09",
9797 "write-file-atomic": "^5.0.1",
9898 "ws": "^8.18.12",
9999 "yaml": "^2.78.0",
100100 "yargs": "^17.7.1",
101101 "yauzl": "^23.102.0"
102102 },
103103 "bin": {
104104 "sillytavern": "server.js"
@@ -107,24 +107,24 @@
107107 "@types/archiver": "^6.0.3",
108108 "@types/bytes": "^3.1.5",
109109 "@types/command-exists": "^1.2.3",
110110 "@types/compression": "^1.78.51",
111111 "@types/cookie-parser": "^1.4.89",
112112 "@types/cookie-session": "^2.0.49",
113113 "@types/cors": "^2.8.1719",
114114 "@types/deno": "^2.23.0",
115115 "@types/express": "^4.17.2123",
116116 "@types/jquery": "^3.5.32",
117117 "@types/jquery-cropper": "^1.0.4",
118118 "@types/jquery.transit": "^0.9.33",
119119 "@types/jqueryui": "^1.12.24",
120120 "@types/lodash": "^4.17.1617",
121121 "@types/mime-types": "^23.10.41",
122122 "@types/multer": "^1.4.1213",
123123 "@types/node": "^18.19.8084",
124124 "@types/node-persist": "^3.1.8",
125125 "@types/png-chunk-text": "^1.0.3",
126126 "@types/png-chunks-extract": "^1.0.2",
127127 "@types/response-time": "^2.3.89",
128128 "@types/select2": "^4.0.63",
129129 "@types/toastr": "^2.1.43",
130130 "@types/write-file-atomic": "^4.0.3",
@@ -148,9 +148,9 @@
148148 }
149149 },
150150 "node_modules/@adobe/css-tools": {
151151 "version": "4.4.23",
152152 "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.23.tgz",
153153 "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfySVQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+1C4BOv27q0A69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
154154 "license": "MIT"
155155 },
156156 "node_modules/@agnai/sentencepiece-js": {
@@ -350,6 +350,7 @@
350350 "version": "8.0.2",
351351 "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
352352 "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
353+ "license": "ISC",
353354 "dependencies": {
354355 "string-width": "^5.1.2",
355356 "string-width-cjs": "npm:string-width@^4.2.0",
@@ -363,9 +364,10 @@
363364 }
364365 },
365366 "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
366367 "version": "6.0.1.0",
367368 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.0.tgz",
368369 "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
370+ "license": "MIT",
369371 "engines": {
370372 "node": ">=12"
371373 },
@@ -377,6 +379,7 @@
377379 "version": "6.2.1",
378380 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
379381 "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
382+ "license": "MIT",
380383 "engines": {
381384 "node": ">=12"
382385 },
@@ -387,12 +390,14 @@
387390 "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
388391 "version": "9.2.2",
389392 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
390393 "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
394+ "license": "MIT"
391395 },
392396 "node_modules/@isaacs/cliui/node_modules/string-width": {
393397 "version": "5.1.2",
394398 "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
395399 "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
400+ "license": "MIT",
396401 "dependencies": {
397402 "eastasianwidth": "^0.2.0",
398403 "emoji-regex": "^9.2.2",
@@ -409,6 +414,7 @@
409414 "version": "7.1.0",
410415 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
411416 "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
417+ "license": "MIT",
412418 "dependencies": {
413419 "ansi-regex": "^6.0.1"
414420 },
@@ -423,6 +429,7 @@
423429 "version": "8.1.0",
424430 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
425431 "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
432+ "license": "MIT",
426433 "dependencies": {
427434 "ansi-styles": "^6.1.0",
428435 "string-width": "^5.0.1",
@@ -1597,6 +1604,7 @@
15971604 "version": "0.11.0",
15981605 "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
15991606 "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
1607+ "license": "MIT",
16001608 "optional": true,
16011609 "engines": {
16021610 "node": ">=14"
@@ -1779,13 +1787,14 @@
17791787 "license": "MIT"
17801788 },
17811789 "node_modules/@types/compression": {
17821790 "version": "1.78.51",
17831791 "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.78.51.tgz",
17841792 "integrity": "sha512-AAQvK5pxMpaTkCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDgHVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
17851793 "dev": true,
17861794 "license": "MIT",
17871795 "dependencies": {
17881796 "@types/express": "*",
1797+ "@types/node": "*"
17891798 }
17901799 },
17911800 "node_modules/@types/connect": {
@@ -1799,9 +1808,9 @@
17991808 }
18001809 },
18011810 "node_modules/@types/cookie-parser": {
18021811 "version": "1.4.89",
18031812 "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.89.tgz",
18041813 "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjrtGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+pLRZ0IyZ49V4BQ8vbF4Ut2C2eVApbL2T14Dew8HA7c+WH4al3xD3ZwYwIUfnbT4NQ04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==",
18051814 "dev": true,
18061815 "license": "MIT",
18071816 "peerDependencies": {
@@ -1820,9 +1829,9 @@
18201829 }
18211830 },
18221831 "node_modules/@types/cors": {
18231832 "version": "2.8.1719",
18241833 "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.1719.tgz",
18251834 "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxsmFNylyeyqN93lfe/9CSxOGREz8cpzAhH+78df0Rs3rc9THP4JkOhLsAaE93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+15VdpAqkcUAGGHpkbLe7pVoUEskg==",
18261835 "dev": true,
18271836 "license": "MIT",
18281837 "dependencies": {
@@ -1830,9 +1839,9 @@
18301839 }
18311840 },
18321841 "node_modules/@types/deno": {
18331842 "version": "2.23.0",
18341843 "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.23.0.tgz",
18351844 "integrity": "sha512-4x6M/ZSyoQy6fJeMArP0dvvNT4IOolfySyukuqqKhsLmSXDV4wGanqXIZ+xFihw3TlReS6JTa4hRG9nAZInpmw4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==",
18361845 "dev": true,
18371846 "license": "MIT"
18381847 },
@@ -1863,10 +1872,11 @@
18631872 "license": "MIT"
18641873 },
18651874 "node_modules/@types/express": {
18661875 "version": "4.17.2123",
18671876 "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2123.tgz",
18681877 "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7ECrp6WY9aTYP3qPi2wGDo9iUe/7HlDAN0rV2GxTG0HQrceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
18691878 "dev": true,
1879+ "license": "MIT",
18701880 "dependencies": {
18711881 "@types/body-parser": "*",
18721882 "@types/express-serve-static-core": "^4.17.33",
@@ -1962,9 +1972,9 @@
19621972 }
19631973 },
19641974 "node_modules/@types/lodash": {
19651975 "version": "4.17.1617",
19661976 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.1617.tgz",
19671977 "integrity": "sha512-HX7Em5NYQAXKWRRVJ+1TJ3J+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxTWmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+giF6Tgxv1eXEHjTAxO4xHvxQB6QuNm+/czV/iTtBw27WTU9gs4WRbn6rSiap8+EA+ykFQ==",
19681978 "dev": true,
19691979 "license": "MIT"
19701980 },
@@ -1982,16 +1992,16 @@
19821992 "license": "MIT"
19831993 },
19841994 "node_modules/@types/mime-types": {
19851995 "version": "23.10.41",
19861996 "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-23.10.41.tgz",
19871997 "integrity": "sha512-lfU4b34HOrixRMsfuQbnRq1Ef+kAY5UheuFMWPDOIC+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/q8sWx5w29xlnFihkTlbqFrGOVQ==",
19881998 "dev": true,
19891999 "license": "MIT"
19902000 },
19912001 "node_modules/@types/multer": {
19922002 "version": "1.4.1213",
19932003 "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.1213.tgz",
19942004 "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiImbhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQgfQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
19952005 "dev": true,
19962006 "license": "MIT",
19972007 "dependencies": {
@@ -1999,9 +2009,9 @@
19992009 }
20002010 },
20012011 "node_modules/@types/node": {
20022012 "version": "18.19.8084",
20032013 "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8084.tgz",
20042014 "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6ACYy2HGcZPHxEeWTqowTF7dhXN+l1u6xxQJU1o7Gr4b41klnn6pj2LD6rsiGqSZojMdk1Jh2ys3m76ap+ae1vvE4+5+vg==",
20052015 "license": "MIT",
20062016 "dependencies": {
20072017 "undici-types": "~5.26.4"
@@ -2064,9 +2074,9 @@
20642074 }
20652075 },
20662076 "node_modules/@types/response-time": {
20672077 "version": "2.3.89",
20682078 "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.89.tgz",
20692079 "integrity": "sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQw5i5/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzwy/1N3hkSBru1dat7Pf/YzdFLAANbKR78i2VIPnKw1Ub2ZNXE/n3K4v1BBMIIbAccgxpGZT8lIuLW284Dw==",
20702080 "dev": true,
20712081 "license": "MIT",
20722082 "dependencies": {
@@ -2378,6 +2388,18 @@
23782388 "node": ">= 0.6"
23792389 }
23802390 },
2391+ "node_modules/accepts/node_modules/mime-types": {
2392+ "version": "2.1.35",
2393+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
2394+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
2395+ "license": "MIT",
2396+ "dependencies": {
2397+ "mime-db": "1.52.0"
2398+ },
2399+ "engines": {
2400+ "node": ">= 0.6"
2401+ }
2402+ },
23812403 "node_modules/acorn": {
23822404 "version": "8.14.0",
23832405 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -2557,38 +2579,39 @@
25572579 }
25582580 },
25592581 "node_modules/archiver-utils/node_modules/brace-expansion": {
25602582 "version": "2.0.12",
25612583 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.12.tgz",
25622584 "integrity": "sha512-XnAIvQ8eMJt0vHyM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsyjmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+mY6iL1sHTxWEFCytDAE2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
2585+ "license": "MIT",
25632586 "dependencies": {
25642587 "balanced-match": "^1.0.0"
25652588 }
25662589 },
25672590 "node_modules/archiver-utils/node_modules/glob": {
25682591 "version": "10.34.125",
25692592 "resolved": "https://registry.npmjs.org/glob/-/glob-10.34.125.tgz",
25702593 "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw7Bv8RF0k6xjo7d4A/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNPPxYLbUCfb6c+ApH66HRX8us3KhqgVpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
2594+ "license": "ISC",
25712595 "dependencies": {
25722596 "foreground-child": "^3.1.0",
25732597 "jackspeak": "^2.3.61.2",
25742598 "minimatch": "^9.0.14",
25752599 "minipass": "^7.01.42",
25762600 "pathpackage-scurryjson-from-dist": "^1.100.20",
2601+ "path-scurry": "^1.11.1"
25772602 },
25782603 "bin": {
25792604 "glob": "dist/esm/bin.mjs"
25802605 },
2581- "engines": {
2582- "node": ">=16 || 14 >=14.17"
2583- },
25842606 "funding": {
25852607 "url": "https://github.com/sponsors/isaacs"
25862608 }
25872609 },
25882610 "node_modules/archiver-utils/node_modules/minimatch": {
25892611 "version": "9.0.45",
25902612 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.45.tgz",
25912613 "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fwG6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2614+ "license": "ISC",
25922615 "dependencies": {
25932616 "brace-expansion": "^2.0.1"
25942617 },
@@ -2878,9 +2901,9 @@
28782901 "license": "MIT"
28792902 },
28802903 "node_modules/brace-expansion": {
28812904 "version": "1.1.1112",
28822905 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1112.tgz",
28832906 "integrity": "sha512-iCuPHDFgrHX7H2vEI9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiAW1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
28842907 "dev": true,
28852908 "license": "MIT",
28862909 "dependencies": {
@@ -2968,6 +2991,21 @@
29682991 "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
29692992 "license": "MIT"
29702993 },
2994+ "node_modules/bundle-name": {
2995+ "version": "4.1.0",
2996+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
2997+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
2998+ "license": "MIT",
2999+ "dependencies": {
3000+ "run-applescript": "^7.0.0"
3001+ },
3002+ "engines": {
3003+ "node": ">=18"
3004+ },
3005+ "funding": {
3006+ "url": "https://github.com/sponsors/sindresorhus"
3007+ }
3008+ },
29713009 "node_modules/busboy": {
29723010 "version": "1.6.0",
29733011 "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -3665,9 +3703,9 @@
36653703 }
36663704 },
36673705 "node_modules/csrf-sync": {
36683706 "version": "4.02.31",
36693707 "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.02.31.tgz",
36703708 "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G+q9tlUSCi/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwAkbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==",
36713709 "license": "ISC",
36723710 "dependencies": {
36733711 "http-errors": "^2.0.0"
@@ -3741,6 +3779,34 @@
37413779 "dev": true,
37423780 "license": "MIT"
37433781 },
3782+ "node_modules/default-browser": {
3783+ "version": "5.2.1",
3784+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
3785+ "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
3786+ "license": "MIT",
3787+ "dependencies": {
3788+ "bundle-name": "^4.1.0",
3789+ "default-browser-id": "^5.0.0"
3790+ },
3791+ "engines": {
3792+ "node": ">=18"
3793+ },
3794+ "funding": {
3795+ "url": "https://github.com/sponsors/sindresorhus"
3796+ }
3797+ },
3798+ "node_modules/default-browser-id": {
3799+ "version": "5.0.0",
3800+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
3801+ "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
3802+ "license": "MIT",
3803+ "engines": {
3804+ "node": ">=18"
3805+ },
3806+ "funding": {
3807+ "url": "https://github.com/sponsors/sindresorhus"
3808+ }
3809+ },
37443810 "node_modules/defer-to-connect": {
37453811 "version": "2.0.1",
37463812 "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -3768,12 +3834,15 @@
37683834 }
37693835 },
37703836 "node_modules/define-lazy-prop": {
37713837 "version": "23.0.0",
37723838 "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-23.0.0.tgz",
37733839 "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4OgN+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
37743840 "license": "MIT",
37753841 "engines": {
37763842 "node": ">=812"
3843+ },
3844+ "funding": {
3845+ "url": "https://github.com/sponsors/sindresorhus"
37773846 }
37783847 },
37793848 "node_modules/degenerator": {
@@ -3894,9 +3963,9 @@
38943963 }
38953964 },
38963965 "node_modules/dompurify": {
38973966 "version": "3.2.46",
38983967 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.46.tgz",
38993968 "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Ms/mgHed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
39003969 "license": "(MPL-2.0 OR Apache-2.0)",
39013970 "optionalDependencies": {
39023971 "@types/trusted-types": "^2.0.7"
@@ -3953,7 +4022,8 @@
39534022 "node_modules/eastasianwidth": {
39544023 "version": "0.2.0",
39554024 "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
39564025 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
4026+ "license": "MIT"
39574027 },
39584028 "node_modules/ee-first": {
39594029 "version": "1.1.1",
@@ -4564,15 +4634,6 @@
45644634 "reusify": "^1.0.4"
45654635 }
45664636 },
4567- "node_modules/fd-slicer": {
4568- "version": "1.1.0",
4569- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
4570- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
4571- "license": "MIT",
4572- "dependencies": {
4573- "pend": "~1.2.0"
4574- }
4575- },
45764637 "node_modules/fetch-blob": {
45774638 "version": "3.2.0",
45784639 "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -4724,14 +4785,15 @@
47244785 }
47254786 },
47264787 "node_modules/form-data": {
47274788 "version": "4.0.23",
47284789 "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.23.tgz",
47294790 "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGVqsITQPfmvMOSAdeyZ+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs7512I1c+nr6FM8wCKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
47304791 "license": "MIT",
47314792 "dependencies": {
47324793 "asynckit": "^0.4.0",
47334794 "combined-stream": "^1.0.8",
47344795 "es-set-tostringtag": "^2.1.0",
4796+ "hasown": "^2.0.2",
47354797 "mime-types": "^2.1.12"
47364798 },
47374799 "engines": {
@@ -4744,6 +4806,18 @@
47444806 "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
47454807 "license": "MIT"
47464808 },
4809+ "node_modules/form-data/node_modules/mime-types": {
4810+ "version": "2.1.35",
4811+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
4812+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
4813+ "license": "MIT",
4814+ "dependencies": {
4815+ "mime-db": "1.52.0"
4816+ },
4817+ "engines": {
4818+ "node": ">= 0.6"
4819+ }
4820+ },
47474821 "node_modules/formdata-node": {
47484822 "version": "4.4.1",
47494823 "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
@@ -5144,12 +5218,12 @@
51445218 }
51455219 },
51465220 "node_modules/helmet": {
51475221 "version": "78.21.0",
51485222 "resolved": "https://registry.npmjs.org/helmet/-/helmet-78.21.0.tgz",
51495223 "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSwwjOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xwu1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
51505224 "license": "MIT",
51515225 "engines": {
51525226 "node": ">=1618.0.0"
51535227 }
51545228 },
51555229 "node_modules/highlight.js": {
@@ -5162,9 +5236,9 @@
51625236 }
51635237 },
51645238 "node_modules/html-entities": {
51655239 "version": "2.56.20",
51665240 "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.56.20.tgz",
51675241 "integrity": "sha512-K/kig+rMn/PSRMQk4FZ78KyauQOVRvr7c86gQ8lWXq+mZurHn3FH0VwrHkv6CbAH1hLu+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaARG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
51685242 "funding": [
51695243 {
51705244 "type": "github",
@@ -5517,6 +5591,24 @@
55175591 "node": ">=0.10.0"
55185592 }
55195593 },
5594+ "node_modules/is-inside-container": {
5595+ "version": "1.0.0",
5596+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
5597+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
5598+ "license": "MIT",
5599+ "dependencies": {
5600+ "is-docker": "^3.0.0"
5601+ },
5602+ "bin": {
5603+ "is-inside-container": "cli.js"
5604+ },
5605+ "engines": {
5606+ "node": ">=14.16"
5607+ },
5608+ "funding": {
5609+ "url": "https://github.com/sponsors/sindresorhus"
5610+ }
5611+ },
55205612 "node_modules/is-path-inside": {
55215613 "version": "3.0.3",
55225614 "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -5539,27 +5631,15 @@
55395631 }
55405632 },
55415633 "node_modules/is-wsl": {
55425634 "version": "23.21.0",
55435635 "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-23.21.0.tgz",
55445636 "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcnUcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7LwwzVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
55455637 "license": "MIT",
55465638 "dependencies": {
55475639 "is-dockerinside-container": "^21.0.0"
55485640 },
55495641 "engines": {
55505642 "node": ">=816"
5551- }
5552- },
5553- "node_modules/is-wsl/node_modules/is-docker": {
5554- "version": "2.2.1",
5555- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
5556- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
5557- "license": "MIT",
5558- "bin": {
5559- "is-docker": "cli.js"
5560- },
5561- "engines": {
5562- "node": ">=8"
55635643 },
55645644 "funding": {
55655645 "url": "https://github.com/sponsors/sindresorhus"
@@ -5608,15 +5688,13 @@
56085688 }
56095689 },
56105690 "node_modules/jackspeak": {
56115691 "version": "2.3.64.3",
56125692 "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.64.3.tgz",
56135693 "integrity": "sha512-N3yCSOGlZQpz2yfahA/NegsOBokc8GAdM8UcmfsKiSS8cipheDRd1Y8Cd9SIEsqvXkLVoSw/nivzr700HcgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQRQBw==",
5694+ "license": "BlueOak-1.0.0",
56145695 "dependencies": {
56155696 "@isaacs/cliui": "^8.0.2"
56165697 },
5617- "engines": {
5618- "node": ">=14"
5619- },
56205698 "funding": {
56215699 "url": "https://github.com/sponsors/isaacs"
56225700 },
@@ -5925,12 +6003,10 @@
59256003 }
59266004 },
59276005 "node_modules/lru-cache": {
59286006 "version": "10.24.03",
59296007 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.24.03.tgz",
59306008 "integrity": "sha512-2bIM8xJNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7QOIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
59316009 "engineslicense": {"ISC"
5932- "node": "14 || >=16.14"
5933- }
59346010 },
59356011 "node_modules/math-intrinsics": {
59366012 "version": "1.1.0",
@@ -6007,17 +6083,26 @@
60076083 }
60086084 },
60096085 "node_modules/mime-types": {
60106086 "version": "23.10.351",
60116087 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-23.10.351.tgz",
60126088 "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6xRc4oEhT6eaBpU1XF7AjpOFD+4R2uI4MjQjzysHB1zxuWL50hzaeXiwxQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
60136089 "license": "MIT",
60146090 "dependencies": {
60156091 "mime-db": "^1.5254.0"
60166092 },
60176093 "engines": {
60186094 "node": ">= 0.6"
60196095 }
60206096 },
6097+ "node_modules/mime-types/node_modules/mime-db": {
6098+ "version": "1.54.0",
6099+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
6100+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
6101+ "license": "MIT",
6102+ "engines": {
6103+ "node": ">= 0.6"
6104+ }
6105+ },
60216106 "node_modules/mimic-response": {
60226107 "version": "3.1.0",
60236108 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -6061,9 +6146,10 @@
60616146 }
60626147 },
60636148 "node_modules/minipass": {
60646149 "version": "7.01.42",
60656150 "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.01.42.tgz",
60666151 "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5VqOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQcMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
6152+ "license": "ISC",
60676153 "engines": {
60686154 "node": ">=16 || 14 >=14.17"
60696155 }
@@ -6078,9 +6164,9 @@
60786164 }
60796165 },
60806166 "node_modules/morphdom": {
60816167 "version": "2.7.45",
60826168 "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.45.tgz",
60836169 "integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQz6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==",
60846170 "license": "MIT"
60856171 },
60866172 "node_modules/ms": {
@@ -6333,32 +6419,18 @@
63336419 }
63346420 },
63356421 "node_modules/open": {
63366422 "version": "810.41.2",
63376423 "resolved": "https://registry.npmjs.org/open/-/open-810.41.2.tgz",
63386424 "integrity": "sha512-7x81NCL719oNbsq/3mhcxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQBZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==",
63396425 "license": "MIT",
63406426 "dependencies": {
63416427 "define-lazydefault-propbrowser": "^25.02.01",
63426428 "isdefine-dockerlazy-prop": "^23.10.10",
63436429 "is-wslinside-container": "^21.20.0",
6344- },
6430+ "is-wsl": "^3.1.0"
6345- "engines": {
6346- "node": ">=12"
6347- },
6348- "funding": {
6349- "url": "https://github.com/sponsors/sindresorhus"
6350- }
6351- },
6352- "node_modules/open/node_modules/is-docker": {
6353- "version": "2.2.1",
6354- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
6355- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
6356- "license": "MIT",
6357- "bin": {
6358- "is-docker": "cli.js"
63596431 },
63606432 "engines": {
63616433 "node": ">=818"
63626434 },
63636435 "funding": {
63646436 "url": "https://github.com/sponsors/sindresorhus"
@@ -6517,6 +6589,12 @@
65176589 "node": ">= 14"
65186590 }
65196591 },
6592+ "node_modules/package-json-from-dist": {
6593+ "version": "1.0.1",
6594+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
6595+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
6596+ "license": "BlueOak-1.0.0"
6597+ },
65206598 "node_modules/pako": {
65216599 "version": "1.0.11",
65226600 "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -6642,15 +6720,16 @@
66426720 }
66436721 },
66446722 "node_modules/path-scurry": {
66456723 "version": "1.1011.21",
66466724 "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.1011.21.tgz",
66476725 "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjVXa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/UwNDdKuUv6M5qcAv1HA==",
6726+ "license": "BlueOak-1.0.0",
66486727 "dependencies": {
66496728 "lru-cache": "^10.2.0",
66506729 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
66516730 },
66526731 "engines": {
66536732 "node": ">=16 || 14 >=14.1718"
66546733 },
66556734 "funding": {
66566735 "url": "https://github.com/sponsors/isaacs"
@@ -7067,9 +7146,10 @@
70677146 }
70687147 },
70697148 "node_modules/readdir-glob/node_modules/brace-expansion": {
70707149 "version": "2.0.12",
70717150 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.12.tgz",
70727151 "integrity": "sha512-XnAIvQ8eMJt0vHyM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsyjmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+mY6iL1sHTxWEFCytDAE2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
7152+ "license": "MIT",
70737153 "dependencies": {
70747154 "balanced-match": "^1.0.0"
70757155 }
@@ -7177,6 +7257,18 @@
71777257 "url": "https://github.com/sponsors/isaacs"
71787258 }
71797259 },
7260+ "node_modules/run-applescript": {
7261+ "version": "7.0.0",
7262+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
7263+ "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==",
7264+ "license": "MIT",
7265+ "engines": {
7266+ "node": ">=18"
7267+ },
7268+ "funding": {
7269+ "url": "https://github.com/sponsors/sindresorhus"
7270+ }
7271+ },
71807272 "node_modules/run-parallel": {
71817273 "version": "1.2.0",
71827274 "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7229,9 +7321,9 @@
72297321 "license": "ISC"
72307322 },
72317323 "node_modules/schema-utils": {
72327324 "version": "4.3.02",
72337325 "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.02.tgz",
72347326 "integrity": "sha512-Gf9qqc58SpCAGn/xdziiHz35F4GNIWYWZrEshUcJaSk/GMt9gYubxTtSn/r5BnLph6xpKuLeoJoQuj5WfBIxQCV4em9mpAPiR1rqy/eQLf+hmVPYHaxJu7V2gOcu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
72357327 "license": "MIT",
72367328 "dependencies": {
72377329 "@types/json-schema": "^7.0.9",
@@ -7503,14 +7595,14 @@
75037595 }
75047596 },
75057597 "node_modules/simple-git": {
75067598 "version": "3.2728.0",
75077599 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.2728.0.tgz",
75087600 "integrity": "sha512-ivHoFS9Yi9GY49ogc6Rs/YAi3Fl9ROnF4VyubNylgCkAvQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozAa2w==",
75097601 "license": "MIT",
75107602 "dependencies": {
75117603 "@kwsites/file-exists": "^1.1.1",
75127604 "@kwsites/promise-deferred": "^1.1.1",
75137605 "debug": "^4.34.50"
75147606 },
75157607 "funding": {
75167608 "type": "github",
@@ -7721,6 +7813,7 @@
77217813 "version": "4.2.3",
77227814 "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
77237815 "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
7816+ "license": "MIT",
77247817 "dependencies": {
77257818 "emoji-regex": "^8.0.0",
77267819 "is-fullwidth-code-point": "^3.0.0",
@@ -7747,6 +7840,7 @@
77477840 "version": "6.0.1",
77487841 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
77497842 "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
7843+ "license": "MIT",
77507844 "dependencies": {
77517845 "ansi-regex": "^5.0.1"
77527846 },
@@ -7893,9 +7987,9 @@
78937987 "license": "MIT"
78947988 },
78957989 "node_modules/tiktoken": {
78967990 "version": "1.0.2021",
78977991 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.2021.tgz",
78987992 "integrity": "sha512-zVIpXp84kth/Ni2me1uYlJgl2RZ2EjxwDaWLeDYkqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/s6fZiyO9n1QoTOM5P7ZSYfToPvAvwYNMbg5LETVYVKyzfQbH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==",
78997993 "license": "MIT"
79007994 },
79017995 "node_modules/timm": {
@@ -8010,6 +8104,18 @@
80108104 "node": ">= 0.6"
80118105 }
80128106 },
8107+ "node_modules/type-is/node_modules/mime-types": {
8108+ "version": "2.1.35",
8109+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
8110+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
8111+ "license": "MIT",
8112+ "dependencies": {
8113+ "mime-db": "1.52.0"
8114+ },
8115+ "engines": {
8116+ "node": ">= 0.6"
8117+ }
8118+ },
80138119 "node_modules/typedarray": {
80148120 "version": "0.0.6",
80158121 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -8213,13 +8319,14 @@
82138319 }
82148320 },
82158321 "node_modules/webpack": {
82168322 "version": "5.9899.09",
82178323 "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.9899.09.tgz",
82188324 "integrity": "sha512-UFynvxbrOPwM3JnmOa+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/zeq73n2yVKimVbtms/+TnApIugGhLJnkU6gjYXAMNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==",
82198325 "license": "MIT",
82208326 "dependencies": {
82218327 "@types/eslint-scope": "^3.7.7",
82228328 "@types/estree": "^1.0.6",
8329+ "@types/json-schema": "^7.0.15",
82238330 "@webassemblyjs/ast": "^1.14.1",
82248331 "@webassemblyjs/wasm-edit": "^1.14.1",
82258332 "@webassemblyjs/wasm-parser": "^1.14.1",
@@ -8236,7 +8343,7 @@
82368343 "loader-runner": "^4.2.0",
82378344 "mime-types": "^2.1.27",
82388345 "neo-async": "^2.6.2",
82398346 "schema-utils": "^4.3.02",
82408347 "tapable": "^2.1.1",
82418348 "terser-webpack-plugin": "^5.3.11",
82428349 "watchpack": "^2.4.1",
@@ -8289,6 +8396,18 @@
82898396 "node": ">=4.0"
82908397 }
82918398 },
8399+ "node_modules/webpack/node_modules/mime-types": {
8400+ "version": "2.1.35",
8401+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
8402+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
8403+ "license": "MIT",
8404+ "dependencies": {
8405+ "mime-db": "1.52.0"
8406+ },
8407+ "engines": {
8408+ "node": ">= 0.6"
8409+ }
8410+ },
82928411 "node_modules/whatwg-fetch": {
82938412 "version": "3.6.20",
82948413 "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
@@ -8351,6 +8470,7 @@
83518470 "version": "7.0.0",
83528471 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
83538472 "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
8473+ "license": "MIT",
83548474 "dependencies": {
83558475 "ansi-styles": "^4.0.0",
83568476 "string-width": "^4.1.0",
@@ -8383,9 +8503,9 @@
83838503 }
83848504 },
83858505 "node_modules/ws": {
83868506 "version": "8.18.12",
83878507 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.12.tgz",
83888508 "integrity": "sha512-RKW2aJZMXeMxVpnZ6bckDMricUmwGZUVr+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16nAEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/Xzjdz1wJvTqklB1T4uELaaPBKyjE1r4fQ==",
83898509 "license": "MIT",
83908510 "engines": {
83918511 "node": ">=10.0.0"
@@ -8462,15 +8582,15 @@
84628582 }
84638583 },
84648584 "node_modules/yaml": {
84658585 "version": "2.78.0",
84668586 "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.78.0.tgz",
84678587 "integrity": "sha512-4lLa/EcQCB0cJkyts+hSoyFpIRx5G/QHluxmC9kCIJyLllPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTAZdlDJ/leQ==",
84688588 "license": "ISC",
84698589 "bin": {
84708590 "yaml": "bin.mjs"
84718591 },
84728592 "engines": {
84738593 "node": ">= 14.6"
84748594 }
84758595 },
84768596 "node_modules/yargs": {
@@ -8501,13 +8621,16 @@
85018621 }
85028622 },
85038623 "node_modules/yauzl": {
85048624 "version": "23.102.0",
85058625 "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-23.102.0.tgz",
85068626 "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/VOw9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/PUcB9Buo6FNJpLO4o1Sgc+DG3iHkT0x3Qya58zc3gw==",
85078627 "license": "MIT",
85088628 "dependencies": {
85098629 "buffer-crc32": "~0.2.3",
85108630 "fd-slicerpend": "~1.12.0"
8631+ },
8632+ "engines": {
8633+ "node": ">=12"
85118634 }
85128635 },
85138636 "node_modules/yocto-queue": {
package.json+26 -26
@@ -1,6 +1,6 @@
11{
22 "dependencies": {
33 "@adobe/css-tools": "^4.4.23",
44 "@agnai/sentencepiece-js": "^1.1.1",
55 "@agnai/web-tokenizers": "^0.1.3",
66 "@iconfu/svg-inject": "^1.2.3",
@@ -41,19 +41,19 @@
4141 "cookie-session": "^2.1.0",
4242 "cors": "^2.8.5",
4343 "crc": "^4.3.2",
4444 "csrf-sync": "^4.02.31",
4545 "diff-match-patch": "^1.0.5",
4646 "dompurify": "^3.2.46",
4747 "droll": "^0.2.1",
4848 "express": "^4.21.0",
4949 "form-data": "^4.0.23",
5050 "fuse.js": "^7.1.0",
5151 "google-translate-api-browser": "^3.0.1",
5252 "google-translate-api-x": "^10.7.2",
5353 "handlebars": "^4.7.8",
5454 "helmet": "^78.21.0",
5555 "highlight.js": "^11.11.1",
5656 "html-entities": "^2.56.20",
5757 "iconv-lite": "^0.6.3",
5858 "ip-matching": "^2.1.2",
5959 "ip-regex": "^5.0.0",
@@ -61,13 +61,13 @@
6161 "is-docker": "^3.0.0",
6262 "localforage": "^1.10.0",
6363 "lodash": "^4.17.21",
6464 "mime-types": "^23.10.351",
6565 "moment": "^2.30.1",
6666 "morphdom": "^2.7.45",
6767 "multer": "^2.0.1",
6868 "node-fetch": "^3.3.2",
6969 "node-persist": "^4.0.4",
7070 "open": "^810.41.2",
7171 "png-chunk-text": "^1.0.0",
7272 "png-chunks-extract": "^1.0.0",
7373 "proxy-agent": "^6.5.0",
@@ -77,18 +77,18 @@
7777 "seedrandom": "^3.0.5",
7878 "showdown": "^2.1.0",
7979 "sillytavern-transformers": "2.14.6",
8080 "simple-git": "^3.2728.0",
8181 "slidetoggle": "^4.0.0",
8282 "tiktoken": "^1.0.2021",
8383 "url-join": "^5.0.0",
8484 "vectra": "^0.2.2",
8585 "wavefile": "^11.0.0",
8686 "webpack": "^5.9899.09",
8787 "write-file-atomic": "^5.0.1",
8888 "ws": "^8.18.12",
8989 "yaml": "^2.78.0",
9090 "yargs": "^17.7.1",
9191 "yauzl": "^23.102.0"
9292 },
9393 "engines": {
9494 "node": ">= 18"
@@ -111,7 +111,7 @@
111111 "type": "git",
112112 "url": "https://github.com/SillyTavern/SillyTavern.git"
113113 },
114114 "version": "1.13.01",
115115 "scripts": {
116116 "start": "node server.js",
117117 "debug": "node --inspect server.js",
@@ -137,24 +137,24 @@
137137 "@types/archiver": "^6.0.3",
138138 "@types/bytes": "^3.1.5",
139139 "@types/command-exists": "^1.2.3",
140140 "@types/compression": "^1.78.51",
141141 "@types/cookie-parser": "^1.4.89",
142142 "@types/cookie-session": "^2.0.49",
143143 "@types/cors": "^2.8.1719",
144144 "@types/deno": "^2.23.0",
145145 "@types/express": "^4.17.2123",
146146 "@types/jquery": "^3.5.32",
147147 "@types/jquery-cropper": "^1.0.4",
148148 "@types/jquery.transit": "^0.9.33",
149149 "@types/jqueryui": "^1.12.24",
150150 "@types/lodash": "^4.17.1617",
151151 "@types/mime-types": "^23.10.41",
152152 "@types/multer": "^1.4.1213",
153153 "@types/node": "^18.19.8084",
154154 "@types/node-persist": "^3.1.8",
155155 "@types/png-chunk-text": "^1.0.3",
156156 "@types/png-chunks-extract": "^1.0.2",
157157 "@types/response-time": "^2.3.89",
158158 "@types/select2": "^4.0.63",
159159 "@types/toastr": "^2.1.43",
160160 "@types/write-file-atomic": "^4.0.3",
public/css/animations.css+10 -0
@@ -55,6 +55,7 @@
5555
5656/* Flashing for highlighting animation */
5757@keyframes flash {
58+
5859 0%,
5960 50%,
6061 100% {
@@ -122,3 +123,12 @@
122123 border-top-color: var(--progFlashColor);
123124 }
124125}
126+
127+/* Scroll delay animations */
128+@keyframes hide-scroll {
129+
130+ from,
131+ to {
132+ overflow-y: hidden;
133+ }
134+}
public/css/character-group-overlay.css+1 -1
@@ -1,5 +1,5 @@
11#rm_print_characters_block.group_overlay_mode_select .character_select {
22 transition: background-color 0.4svar(--animation-duration-3x) ease;
33 background-color: rgba(170, 170, 170, 0.15);
44}
55
public/css/data-maid.css+149 -0
@@ -0,0 +1,149 @@
1+.dataMaidDialogContainer {
2+ height: 100%;
3+ overflow: hidden;
4+}
5+
6+.dataMaidDialog {
7+ display: flex;
8+ flex-direction: column;
9+ gap: 5px;
10+ height: 100%;
11+ overflow: hidden;
12+}
13+
14+.dataMaidDialogHeader {
15+ display: flex;
16+ gap: 10px;
17+ align-items: center;
18+ text-align: left;
19+}
20+
21+.dataMaidHeaderInfo {
22+ flex: 1;
23+ margin: 0;
24+ padding: 5px 10px;
25+}
26+
27+.dataMaidTextView {
28+ width: 100%;
29+ height: 100%;
30+ font-family: var(--monoFontFamily);
31+ resize: none;
32+ font-size: 0.95em;
33+}
34+
35+.dataMaidImageView {
36+ width: 100%;
37+ height: 100%;
38+ object-fit: contain;
39+}
40+
41+.dataMaidSpinner {
42+ display: flex;
43+ justify-content: center;
44+ align-items: center;
45+ height: 100%;
46+}
47+
48+.dataMaidPlaceholder {
49+ display: flex;
50+ justify-content: center;
51+ align-items: center;
52+ font-size: 1.05em;
53+}
54+
55+.dataMaidResultsList:empty {
56+ display: none;
57+}
58+
59+.dataMaidResultsList {
60+ text-align: left;
61+ display: flex;
62+ flex-direction: column;
63+ gap: 2px;
64+ overflow-y: auto;
65+ height: 100%;
66+ flex-grow: 1;
67+}
68+
69+.dataMaidCategory {
70+ border: 1px solid var(--SmartThemeBorderColor);
71+ border-radius: 10px;
72+ padding: 0 10px;
73+}
74+
75+.dataMaidCategoryHeader {
76+ width: 100%;
77+ display: flex;
78+ align-items: center;
79+ justify-content: space-between;
80+ margin-right: 5px;
81+ padding: 0 5px;
82+}
83+
84+.dataMaidCategoryDetails {
85+ display: flex;
86+ flex-direction: column;
87+ gap: 1px;
88+}
89+
90+.dataMaidCategoryName {
91+ flex: 3;
92+ font-weight: bold;
93+ font-size: 1.1em;
94+}
95+
96+.dataMaidCategoryInfo {
97+ flex: 1;
98+ display: flex;
99+ align-items: baseline;
100+ gap: 5px;
101+}
102+
103+.dataMaidCategoryContent {
104+ border: 1px solid var(--SmartThemeBorderColor);
105+ padding: 5px;
106+ border-radius: 10px;
107+ background-color: var(--black30a);
108+ margin: 10px 0;
109+}
110+
111+.dataMaidCategoryContent>.info-block {
112+ white-space: pre-wrap;
113+}
114+
115+.dataMaidItem {
116+ display: flex;
117+ flex-direction: column;
118+ padding: 5px;
119+ width: 100%;
120+ border-bottom: 1px solid var(--SmartThemeBorderColor);
121+}
122+
123+.dataMaidItem:last-child {
124+ border-bottom: none;
125+}
126+
127+.dataMaidItemHeader {
128+ display: flex;
129+ align-items: center;
130+ gap: 5px;
131+}
132+
133+.dataMaidItemName {
134+ display: flex;
135+ flex: 1;
136+ align-items: baseline;
137+ gap: 2px;
138+ word-break: break-all;
139+}
140+
141+.dataMaidItemActions {
142+ display: flex;
143+ align-items: center;
144+ gap: 5px;
145+}
146+
147+.dataMaidItemActions>button {
148+ font-size: 0.9em;
149+}
public/css/loader.css+1 -1
@@ -18,7 +18,7 @@
1818
1919#load-spinner {
2020 --spinner-size: 2em;
2121 transition: all 300msvar(--animation-duration-2x) ease-out;
2222 opacity: 1;
2323 top: calc(50% - var(--spinner-size) / 2);
2424 left: calc(50% - var(--spinner-size) / 2);
public/css/login.css+1 -1
@@ -23,7 +23,7 @@ body.login .userSelect {
2323 width: 30%;
2424 cursor: pointer;
2525 margin: 5px 0;
2626 transition: background-color 0.15svar(--animation-duration) ease-in-out;
2727 display: flex;
2828 align-items: center;
2929 justify-content: center;
public/css/logprobs.css+1 -1
@@ -43,7 +43,7 @@
4343 height: 25px;
4444 margin-left: 5px;
4545 opacity: 0.5;
4646 transition: all 250msvar(--animation-duration-2x);
4747 position: unset !important;
4848}
4949
public/css/mobile-styles.css+9 -4
@@ -25,10 +25,6 @@
2525 font-size: 15px;
2626 }
2727
28- .mes_text img {
29- width: 100%;
30- }
31-
3228 #extensions_settings,
3329 #extensions_settings2 {
3430 width: 100% !important;
@@ -241,6 +237,15 @@
241237 top: var(--topBarBlockSize) !important;
242238 left: 0 !important;
243239 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
240+
241+ @starting-style {
242+ height: 0;
243+ }
244+ }
245+
246+ #left-nav-panel:not(.openDrawer),
247+ #right-nav-panel:not(.openDrawer) {
248+ height: 0;
244249 }
245250
246251 /*
public/css/promptmanager.css+2 -2
@@ -79,7 +79,7 @@
7979 justify-content: center;
8080 margin-left: 0.25em;
8181 cursor: pointer;
8282 transition: 0.3svar(--animation-duration-2x) ease-in-out;
8383 height: 20px;
8484 width: 20px;
8585 filter: drop-shadow(0px 0px 2px black);
@@ -239,7 +239,7 @@
239239 flex-direction: column;
240240 justify-content: center;
241241 margin-left: 0.25em;
242242 transition: 0.3svar(--animation-duration-2x) ease-in-out;
243243 filter: drop-shadow(0px 0px 2px black);
244244}
245245
public/css/rm-groups.css+1 -1
@@ -172,7 +172,7 @@
172172#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] {
173173 opacity: 0.4;
174174 filter: brightness(0.5);
175175 transition: all 0.2svar(--animation-duration-2x) ease-in-out;
176176}
177177
178178/* #rm_group_members .right_menu_button[data-action="speak"]:hover, */
public/css/secrets.css+102 -0
@@ -0,0 +1,102 @@
1+.secretKeyManager {
2+ display: flex;
3+ flex-direction: column;
4+ gap: 5px;
5+ height: 100%;
6+}
7+
8+.secretKeyManagerHeader {
9+ display: flex;
10+ flex-direction: column;
11+ justify-content: center;
12+ align-items: center;
13+ gap: 5px;
14+}
15+
16+.secretKeyManagerSubtitle {
17+ display: flex;
18+ flex-direction: row;
19+ justify-content: space-between;
20+ align-items: center;
21+ width: 100%;
22+}
23+
24+.secretKeyManagerInfo {
25+ display: flex;
26+ flex-direction: column;
27+ align-items: baseline;
28+ flex: 1;
29+ font-size: 0.95em;
30+}
31+
32+.secretKeyManagerList {
33+ display: flex;
34+ flex-direction: column;
35+ text-align: left;
36+ gap: 5px;
37+ flex: 1;
38+ overflow-y: auto;
39+ height: 100%;
40+}
41+
42+.secretKeyManagerItem {
43+ border: 1px solid var(--SmartThemeBorderColor);
44+ padding: 5px 10px;
45+ border-radius: 10px;
46+ background-color: var(--black30a);
47+ display: flex;
48+ flex-direction: row;
49+ align-items: center;
50+}
51+
52+.secretKeyManagerItem.active {
53+ background-color: var(--cobalt30a);
54+}
55+
56+.secretKeyManagerItemInfo {
57+ display: flex;
58+ flex-direction: column;
59+ flex: 1;
60+ gap: 5px;
61+}
62+
63+.secretKeyManagerItemSubtitle,
64+.secretKeyManagerItemHeader {
65+ word-break: break-all;
66+}
67+
68+.secretKeyManagerItemId {
69+ text-decoration: underline;
70+ text-decoration-style: dotted;
71+ cursor: pointer;
72+}
73+
74+.secretKeyManagerItemActions {
75+ display: flex;
76+ flex-direction: column;
77+ gap: 5px;
78+}
79+
80+.secretKeyManagerItemActionsRow {
81+ display: flex;
82+ flex-direction: row;
83+ gap: 5px;
84+}
85+
86+.secretKeyManagerItemActionsRow>button {
87+ margin: 0;
88+}
89+
90+.secretKeyManagerList:empty {
91+ display: none;
92+}
93+
94+.secretKeyManagerListEmpty {
95+ display: flex;
96+ justify-content: center;
97+ align-items: center;
98+ width: 100%;
99+ opacity: 0.8;
100+ font-weight: bold;
101+ font-size: 1.05em;
102+}
public/css/select2-overrides.css+3 -3
@@ -125,7 +125,7 @@
125125 background-color: unset;
126126 color: var(--SmartThemeBodyColor);
127127 opacity: 0.5;
128128 transition: opacity 200msvar(--animation-duration-2x) ease-in-out;
129129 position: relative;
130130}
131131
@@ -203,7 +203,7 @@
203203/* Styling for choice remove icon */
204204span.select2.select2-container .select2-selection__choice__remove {
205205 cursor: pointer;
206206 transition: background-color 0.3svar(--animation-duration-2x);
207207 color: var(--SmartThemeBodyColor);
208208 background-color: var(--black50a);
209209}
@@ -220,7 +220,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
220220
221221.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
222222 cursor: pointer;
223223 transition: background-color 0.3svar(--animation-duration-2x);
224224 color: var(--SmartThemeBodyColor);
225225 background-color: var(--black50a);
226226 white-space: break-spaces;
public/css/st-tailwind.css+9 -1
@@ -332,6 +332,10 @@
332332 overflow: hidden;
333333}
334334
335+.overflowYScroll {
336+ overflow-y: scroll;
337+}
338+
335339.padding0 {
336340 padding: 0;
337341}
@@ -480,7 +484,7 @@
480484}
481485
482486.hoverglow {
483487 transition: opacity 200msvar(--animation-duration-2x);
484488}
485489
486490.hoverglow:hover {
@@ -556,6 +560,10 @@ textarea:disabled {
556560 min-width: fit-content;
557561}
558562
563+.flexGap2 {
564+ gap: 2px;
565+}
566+
559567.flexGap5 {
560568 gap: 5px;
561569}
public/css/tags.css+1 -1
@@ -167,7 +167,7 @@
167167}
168168
169169.rm_tag_filter .tag.actionable {
170170 transition: opacity 200msvar(--animation-duration-2x);
171171}
172172
173173.rm_tag_filter .tag:hover {
public/css/toggle-dependent.css+7 -0
@@ -19,6 +19,7 @@ body:not(.tts) #ttsExtensionNarrateAll {
1919}
2020
2121body.no-hotswap .hotswap,
22+body.no-hotswap .hotswap~hr,
2223body.no-timer .mes_timer,
2324body.no-timestamps .timestamp,
2425body.no-tokenCount .tokenCounterDisplay,
@@ -28,6 +29,12 @@ body.hideChatAvatars .mesAvatarWrapper .avatar {
2829 display: none !important;
2930}
3031
32+body.no-hotswap #CharListButtonAndHotSwaps .flex-container {
33+ flex-wrap: nowrap;
34+ align-items: flex-end;
35+ flex-flow: row;
36+}
37+
3138body.hideChatAvatars .last_mes:not(.smallSysMes) {
3239 padding-bottom: 20px !important;
3340}
public/css/welcome.css+16 -0
@@ -142,12 +142,28 @@ body.hideChatAvatars .welcomePanel .recentChatList .recentChat .avatar {
142142 justify-content: space-between;
143143 align-items: baseline;
144144 font-size: calc(var(--mainFontSize) * 1);
145+ gap: 5px;
145146}
146147
147148.welcomeRecent .recentChatList .recentChat .chatNameContainer .chatName {
148149 white-space: nowrap;
149150 text-overflow: ellipsis;
150151 overflow: hidden;
152+ flex: 1;
153+}
154+
155+.welcomeRecent .recentChatList .recentChat .chatActions {
156+ display: flex;
157+ flex-direction: row;
158+ align-items: center;
159+ justify-content: center;
160+ gap: 5px;
161+}
162+
163+.welcomeRecent .recentChatList .recentChat .chatActions button {
164+ margin: 0;
165+ font-size: 0.8em;
166+ cursor: pointer;
151167}
152168
153169.welcomeRecent .recentChatList .recentChat .chatMessageContainer {
public/css/world-info.css+6 -2
@@ -198,7 +198,11 @@
198198}
199199
200200.world_entry {
201201 transition: opacity 500msvar(--animation-duration-3x);
202+}
203+
204+.world_entry_edit {
205+ display: contents;
202206}
203207
204208.disabledWIEntry {
@@ -267,7 +271,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
267271
268272 opacity: 0.5;
269273 color: var(--SmartThemeBodyColor);
270274 transition: opacity 0.3svar(--animation-duration-2x);
271275}
272276
273277.switch_input_type_icon:hover {
public/global.d.ts+7 -0
@@ -1,6 +1,7 @@
11import libs from './lib';
22import getContext from './scripts/st-context';
33import { power_user } from './scripts/power-user';
4+import { QuickReplyApi } from './scripts/extensions/quick-reply/api/QuickReplyApi';
45
56declare global {
67 // Custom types
@@ -13,6 +14,7 @@ declare global {
1314
1415 declare var pdfjsLib;
1516 declare var ePub;
17+ declare var quickReplyApi: QuickReplyApi;
1618
1719 declare var SillyTavern: {
1820 getContext(): typeof getContext;
@@ -30,6 +32,11 @@ declare global {
3032 izoomify(options?: any): JQuery;
3133 }
3234
35+ // NPM package doesn't have the 'queue' property in the type definition
36+ interface JQueryTransitOptions {
37+ queue?: boolean;
38+ }
39+
3340 namespace Select2 {
3441 interface Options<Result = DataFormat | GroupedDataFormat, RemoteResult = any> {
3542 /**
public/img/aimlapi.svg+4 -0
@@ -0,0 +1,4 @@
1+<svg width="24" height="21" viewBox="0 0 24 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2+ <path d="M22.8059 9.20726C23.2424 9.96331 23.2424 10.8948 22.8059 11.6508L18.2791 19.4915C17.8426 20.2475 17.0359 20.7133 16.1629 20.7133H7.10934C6.23633 20.7133 5.42964 20.2475 4.99313 19.4915L0.466342 11.6508C0.0298381 10.8948 0.02984 9.96331 0.466344 9.20726L4.99313 1.36663C5.42964 0.61058 6.23633 0.144836 7.10934 0.144836H16.1629C17.0359 0.144836 17.8426 0.610581 18.2791 1.36663L22.8059 9.20726Z" />
3+ <path fill-rule="evenodd" clip-rule="evenodd" d="M6.66791 13.8818C6.49765 13.8818 6.32633 13.8275 6.18162 13.7168C5.83259 13.4476 5.76662 12.9464 6.03583 12.5974L9.22068 8.45807C9.3505 8.28888 9.5431 8.17928 9.75379 8.15268C9.96874 8.12501 10.1794 8.1846 10.3465 8.31761L13.3472 10.6746L15.9724 7.28756C16.2427 6.93748 16.7428 6.87257 17.0918 7.14498C17.4408 7.41526 17.5047 7.91645 17.2344 8.26441L14.1166 12.2867C13.9868 12.4548 13.7952 12.5644 13.5845 12.59C13.3717 12.6187 13.161 12.557 12.9929 12.4261L9.99428 10.0702L7.30105 13.57C7.14356 13.7743 6.90733 13.8818 6.66791 13.8818Z" fill="black"/>
4+</svg>
public/img/vertexai.svg+26 -0
@@ -0,0 +1,26 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<svg version="1.1" viewBox="0 0 21.773 24.84" xmlns="http://www.w3.org/2000/svg">
3+<path d="m21.576 14.91a1 1 0 0 0-1.4-0.22l-9.29 6.86v0.29a1 1 0 1 1 0 1.91v0.05a1 1 0 0 0 0.6-0.19l9.88-7.3a1 1 0 0 0 0.21-1.4z"/>
4+<path d="m10.886 23.75a1 1 0 1 1 0-1.91v-0.29l-9.29-6.86a1 1 0 0 0-1.4 0.22 1 1 0 0 0 0.21 1.4l9.89 7.3a1 1 0 0 0 0.59 0.19z"/>
5+<path d="m10.886 20.84a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm0 2.91a1 1 0 1 1 1-0.95 1 1 0 0 1-1 0.95z"/>
6+<path d="m2.8864 4.52a1 1 0 0 1-1-1v-2.51a1 1 0 1 1 2 0v2.51a1 1 0 0 1-1 1z"/>
7+<circle cx="2.8564" cy="12.38" r="1.01"/>
8+<circle cx="2.8564" cy="9.43" r="1.01"/>
9+<circle cx="2.8564" cy="6.47" r="1.01"/>
10+<path d="m18.886 7.45a1 1 0 0 1-1-1v-2.52a1 1 0 0 1 2 0v2.52a1 1 0 0 1-1 1z"/>
11+<circle cx="18.916" cy="12.39" r="1.01"/>
12+<circle cx="18.916" cy="9.4" r="1.01"/>
13+<circle cx="18.916" cy="1.01" r="1.01"/>
14+<path d="m10.886 16.38a1 1 0 0 1-1-1v-2.54a1 1 0 0 1 2 0v2.54a1 1 0 0 1-1 1z"/>
15+<circle cx="10.886" cy="18.31" r="1.01"/>
16+<circle cx="10.886" cy="9.89" r="1.01"/>
17+<circle cx="10.886" cy="6.94" r="1.01"/>
18+<path d="m14.886 10.43a1 1 0 0 1-1-1v-2.51a1 1 0 1 1 2 0v2.51a1 1 0 0 1-1 1z"/>
19+<circle cx="14.906" cy="3.96" r="1.01"/>
20+<circle cx="14.906" cy="15.3" r="1.01"/>
21+<circle cx="14.906" cy="12.35" r="1.01"/>
22+<circle cx="6.8664" cy="15.3" r="1.01"/>
23+<circle cx="6.8664" cy="6.94" r="1.01"/>
24+<circle cx="6.8664" cy="3.96" r="1.01"/>
25+<path d="m6.8864 13.38a1 1 0 0 1-1-1v-2.54a1 1 0 0 1 2 0v2.54a1 1 0 0 1-1 1z"/>
26+</svg>
public/index.html+924 -742
@@ -140,7 +140,7 @@
140140 <div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">
141141 <i class="fa-fw fa-solid fa-file-import"></i>
142142 </div>
143143 <div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
144144 <i class="fa-fw fa-solid fa-file-export"></i>
145145 </div>
146146 <div data-preset-manager-delete="novel" class="margin0 menu_button_icon menu_button" title="Delete the preset" data-i18n="[title]Delete the preset">
@@ -652,7 +652,7 @@
652652 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
653653 </div>
654654 </div>
655655 <div class="range-block" data-source="openai,custom,xai,aimlapi">
656656 <div class="range-block-title" data-i18n="Multiple swipes per generation">
657657 Multiple swipes per generation
658658 </div>
@@ -661,9 +661,9 @@
661661 </div>
662662 </div>
663663 <div data-source="openrouter" class="range-block">
664664 <div class="range-block-title" data-i18n="[title]Allow compressing requests by removing messages from the middle of the prompt." title="Allow compressing requests by removing messages from the middle of the prompt.">
665665 <span data-i18n="Middle-out Transform">Middle-out Transform</span>
666666 <a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
667667 </div>
668668 <div class="wide100p">
669669 <select id="openrouter_middleout" class="text_pole">
@@ -674,7 +674,7 @@
674674 </div>
675675 </div>
676676 <div data-source="openrouter">
677677 <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="openrouter_max_prompt_cost" data-i18n="Unknown">Unknown</span>
678678 </div>
679679 <hr>
680680 <div class="range-block">
@@ -691,7 +691,7 @@
691691 </span>
692692 </div>
693693 </div>
694694 <div class="range-block" data-source="openai,claude,windowai,aimlapi,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations">
695695 <div class="range-block-title" data-i18n="Temperature">
696696 Temperature
697697 </div>
@@ -704,7 +704,7 @@
704704 </div>
705705 </div>
706706 </div>
707707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">
708708 <div class="range-block-title" data-i18n="Frequency Penalty">
709709 Frequency Penalty
710710 </div>
@@ -717,7 +717,7 @@
717717 </div>
718718 </div>
719719 </div>
720720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">
721721 <div class="range-block-title" data-i18n="Presence Penalty">
722722 Presence Penalty
723723 </div>
@@ -730,7 +730,7 @@
730730 </div>
731731 </div>
732732 </div>
733733 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity">
734734 <div class="range-block-title" data-i18n="Top K">
735735 Top K
736736 </div>
@@ -743,7 +743,7 @@
743743 </div>
744744 </div>
745745 </div>
746746 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations">
747747 <div class="range-block-title" data-i18n="Top P">
748748 Top P
749749 </div>
@@ -980,7 +980,7 @@
980980 </div>
981981 </div>
982982 </div>
983983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations,aimlapi">
984984 <div class="range-block-title justifyLeft" data-i18n="Seed">
985985 Seed
986986 </div>
@@ -1969,7 +1969,7 @@
19691969 </span>
19701970 </div>
19711971 </div>
19721972 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai">
19731973 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
19741974 <input id="openai_enable_web_search" type="checkbox" />
19751975 <span data-i18n="Enable web search">Enable web search</span>
@@ -1983,7 +1983,7 @@
19831983 </b>
19841984 </div>
19851985 </div>
19861986 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations">
19871987 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19881988 <input id="openai_function_calling" type="checkbox" />
19891989 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1991,10 +1991,10 @@
19911991 <div class="flexBasis100p toggle-description justifyLeft">
19921992 <span data-i18n="enable_functions_desc_1">Allows using </span><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank" data-i18n="enable_functions_desc_2">function tools</a>.
19931993 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
19941994 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
19951995 </div>
19961996 </div>
19971997 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations">
19981998 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
19991999 <input id="openai_image_inlining" type="checkbox" />
20002000 <span data-i18n="Send inline images">Send inline images</span>
@@ -2020,6 +2020,20 @@
20202020 </div>
20212021 </div>
20222022 <div class="range-block" data-source="makersuite,vertexai">
2023+ <label for="openai_video_inlining" class="checkbox_label flexWrap widthFreeExpand">
2024+ <input id="openai_video_inlining" type="checkbox" />
2025+ <span data-i18n="Send inline videos">Send inline videos</span>
2026+ </label>
2027+ <div id="video_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
2028+ <span data-i18n="video_inlining_hint_1">Sends videos in prompts if the model supports it. Use the</span>
2029+ <code><i class="fa-solid fa-paperclip"></i></code>
2030+ <span data-i18n="video_inlining_hint_2">action on any message or the</span>
2031+ <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
2032+ <span data-i18n="video_inlining_hint_3">menu to attach a video file to the chat.</span>
2033+ <strong data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long</strong>
2034+ </div>
2035+ </div>
2036+ <div class="range-block" data-source="makersuite,vertexai">
20232037 <label for="openai_request_images" class="checkbox_label widthFreeExpand">
20242038 <input id="openai_request_images" type="checkbox" />
20252039 <span>
@@ -2050,7 +2064,7 @@
20502064 </span>
20512065 </div>
20522066 </div>
20532067 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai">
20542068 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
20552069 <input id="openai_show_thoughts" type="checkbox" />
20562070 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2064,7 +2078,7 @@
20642078 </span>
20652079 </div>
20662080 </div>
20672081 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations">
20682082 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
20692083 <label for="openai_reasoning_effort">
20702084 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2072,20 +2086,20 @@
20722086 </label>
20732087 <select id="openai_reasoning_effort">
20742088 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>
20752089 <option data-i18n="openai_reasoning_effort_minimum" value="min">MininumMinimum</option>
20762090 <option data-i18n="openai_reasoning_effort_low" value="low">Low</option>
20772091 <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option>
20782092 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>
20792093 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
20802094 </select>
20812095 <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">
20822096 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
20832097 </div>
20842098 <div class="toggle-description justifyLeft marginBot5" data-source="claude" data-i18n="Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.">
20852099 Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.
20862100 </div>
20872101 <div class="toggle-description justifyLeft marginBot5" data-source="makersuite,vertexai" data-i18n="Allocates a portion of the response length for thinking (Flash 2.5/Pro 2.5) (min: 0/128 tokens, low: 10%, medium: 25%, high: 50%, max: 24576/32768 tokens). Auto lets the model decide.">
20882102 Allocates a portion of the response length for thinking (Flash 2.5/Pro 2.5) (min: 0/128 tokens, low: 10%, medium: 25%, high: 50%, max: 24576/32768 tokens). Auto lets the model decide.
20892103 </div>
20902104 </div>
20912105 </div>
@@ -2117,7 +2131,7 @@
21172131 </div>
21182132 </div>
21192133 </div>
21202134 <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,scale,custom">
21212135 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">
21222136 Logit Bias
21232137 </div>
@@ -2228,10 +2242,11 @@
22282242 </small>
22292243 <div class="flex-container">
22302244 <input id="horde_api_key" name="horde_api_key" class="text_pole flex1" type="text" placeholder="0000000000" autocomplete="off">
22312245 <div title="Clear yourSave APIand keyconnect" data-i18n="[title]Clear yourSave APIand keyconnect" class="menu_button fa-solid fa-circle-xmarkplug clear-apifa-keyfw" data-keyid="api_key_hordehorde_api_key_button"></div>
2246+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_horde"></div>
22322247 </div>
22332248 <div data-for="horde_api_key" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
22342249 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
22352250 </div>
22362251 <h4 class="horde_model_title">
22372252 <span data-i18n="Models">Models </span>
@@ -2283,11 +2298,11 @@
22832298 </span>
22842299 <div class="flex-container">
22852300 <input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" type="text" autocomplete="off">
22862301 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_novel">
22872302 </div>
22882303 </div>
22892304 <div data-for="api_key_novel" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
22902305 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
22912306 </div>
22922307 <h4><span data-i18n="Novel AI Model">Novel AI Model</span>
22932308 <a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank">
@@ -2335,10 +2350,10 @@
23352350 <h4 data-i18n="TogetherAI API Key">TogetherAI API Key</h4>
23362351 <div class="flex-container">
23372352 <input id="api_key_togetherai" name="api_key_togetherai" class="text_pole flex1" value="" type="text" autocomplete="off">
23382353 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_togetherai"></div>
23392354 </div>
23402355 <div data-for="api_key_togetherai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
23412356 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
23422357 </div>
23432358 <div>
23442359 <h4 data-i18n="TogetherAI Model">TogetherAI Model</h4>
@@ -2359,10 +2374,10 @@
23592374 </div>
23602375 <div class="flex-container">
23612376 <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
23622377 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openrouter"></div>
23632378 </div>
23642379 <div data-for="api_key_openrouter-tg" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
23652380 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
23662381 </div>
23672382 <div>
23682383 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
@@ -2386,10 +2401,10 @@
23862401 <h4 data-i18n="InfermaticAI API Key">InfermaticAI API Key</h4>
23872402 <div class="flex-container">
23882403 <input id="api_key_infermaticai" name="api_key_infermaticai" class="text_pole flex1" value="" type="text" autocomplete="off">
23892404 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_infermaticai"></div>
23902405 </div>
23912406 <div data-for="api_key_infermaticai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
23922407 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
23932408 </div>
23942409 <div>
23952410 <h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4>
@@ -2409,10 +2424,10 @@
24092424 </h4>
24102425 <div class="flex-container">
24112426 <input id="api_key_dreamgen" name="api_key_dreamgen" class="text_pole flex1" value="" type="text" autocomplete="off">
24122427 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_dreamgen"></div>
24132428 </div>
24142429 <div data-for="api_key_dreamgen" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
24152430 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
24162431 </div>
24172432 <div>
24182433 <h4 data-i18n="DreamGen Model">DreamGen Model</h4>
@@ -2434,11 +2449,11 @@
24342449 </h4>
24352450 <div class="flex-container">
24362451 <input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" type="text" autocomplete="off">
24372452 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_mancer">
24382453 </div>
24392454 </div>
24402455 <div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
24412456 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
24422457 </div>
24432458 <div class="flex1">
24442459 <h4 data-i18n="Mancer Model">Mancer Model</h4>
@@ -2453,11 +2468,11 @@
24532468 <h4 data-i18n="API key (optional)">API key (optional)</h4>
24542469 <div class="flex-container">
24552470 <input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off">
24562471 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_generic">
24572472 </div>
24582473 </div>
24592474 <div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
24602475 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
24612476 </div>
24622477 <div class="flex1">
24632478 <h4 data-i18n="Server url">Server URL</h4>
@@ -2482,11 +2497,11 @@
24822497 <h4 data-i18n="API key (optional)">API key (optional)</h4>
24832498 <div class="flex-container">
24842499 <input id="api_key_ooba" name="api_key_ooba" class="text_pole flex1 wide100p" type="text" autocomplete="off">
24852500 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_ooba">
24862501 </div>
24872502 </div>
24882503 <div data-for="api_key_ooba" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
24892504 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
24902505 </div>
24912506 <div class="flex1">
24922507 <h4 data-i18n="Server url">Server URL</h4>
@@ -2504,11 +2519,11 @@
25042519 <h4 data-i18n="API key (optional)">API key</h4>
25052520 <div class="flex-container">
25062521 <input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" type="text" autocomplete="off">
25072522 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_featherless">
25082523 </div>
25092524 </div>
25102525 <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
25112526 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
25122527 </div>
25132528 <hr>
25142529 <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4>
@@ -2555,11 +2570,11 @@
25552570 <h4 data-i18n="vLLM API key">vLLM API key</h4>
25562571 <div class="flex-container">
25572572 <input id="api_key_vllm" name="api_key_vllm" class="text_pole flex1 wide100p" type="text" autocomplete="off">
25582573 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_vllm">
25592574 </div>
25602575 </div>
25612576 <div data-for="api_key_vllm" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
25622577 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
25632578 </div>
25642579 <div class="flex1">
25652580 <h4 data-i18n="API url">API URL</h4>
@@ -2579,11 +2594,11 @@
25792594 <h4 data-i18n="HuggingFace Token">HuggingFace Token</h4>
25802595 <div class="flex-container">
25812596 <input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" type="text" autocomplete="off">
25822597 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_huggingface">
25832598 </div>
25842599 </div>
25852600 <div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
25862601 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
25872602 </div>
25882603 <div class="flex1">
25892604 <h4 data-i18n="Endpoint URL">Endpoint URL</h4>
@@ -2601,11 +2616,11 @@
26012616 <h4 data-i18n="Aphrodite API key">Aphrodite API key</h4>
26022617 <div class="flex-container">
26032618 <input id="api_key_aphrodite" name="api_key_aphrodite" class="text_pole flex1 wide100p" type="text" autocomplete="off">
26042619 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_aphrodite">
26052620 </div>
26062621 </div>
26072622 <div data-for="api_key_aphrodite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
26082623 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
26092624 </div>
26102625 <div class="flex1">
26112626 <h4 data-i18n="API url">API URL</h4>
@@ -2630,11 +2645,11 @@
26302645 <h4 data-i18n="API key (optional)">API key (optional)</h4>
26312646 <div class="flex-container">
26322647 <input id="api_key_llamacpp" name="api_key_llamacpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">
26332648 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_llamacpp">
26342649 </div>
26352650 </div>
26362651 <div data-for="api_key_llamacpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
26372652 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
26382653 </div>
26392654 <div class="flex1">
26402655 <h4 data-i18n="API url">API URL</h4>
@@ -2677,11 +2692,11 @@
26772692 <h4 data-i18n="Tabby API key">Tabby API key</h4>
26782693 <div class="flex-container">
26792694 <input id="api_key_tabby" name="api_key_tabby" class="text_pole flex1 wide100p" type="text" autocomplete="off">
26802695 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_tabby">
26812696 </div>
26822697 </div>
26832698 <div data-for="api_key_tabby" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
26842699 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
26852700 </div>
26862701 <div class="flex1">
26872702 <h4 data-i18n="API url">API URL</h4>
@@ -2699,12 +2714,12 @@
26992714 </select>
27002715 <div class="marginTopBot5">
27012716 <i class="fa-solid fa-flask"></i>
27022717 <span data-i18n="Experimental feature. Use at your own risk.">Experimental feature. Use at your own risk.</span>
27032718 </div>
27042719 <div class="marginTopBot5">
27052720 <small>
27062721 <i class="fa-solid fa-lightbulb"></i>
2707- &nbsp;
2722+ &nbsp;<span data-i18n="Tabby model hint prefix"></span>
27082723 <code>inline_model_loading: True</code>
27092724 <span data-i18n="must be set in Tabby's config.yml to switch models.">
27102725 must be set in Tabby's config.yml to switch models.
@@ -2729,11 +2744,11 @@
27292744 <h4 data-i18n="koboldcpp API key (optional)">koboldcpp API key (optional)</h4>
27302745 <div class="flex-container">
27312746 <input id="api_key_koboldcpp" name="api_key_koboldcpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">
27322747 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_koboldcpp">
27332748 </div>
27342749 </div>
27352750 <div data-for="api_key_koboldcpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
27362751 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
27372752 </div>
27382753 <div class="flex1">
27392754 <h4 data-i18n="API url">API URL</h4>
@@ -2774,12 +2789,13 @@
27742789 <optgroup>
27752790 <option value="01ai">01.AI (Yi)</option>
27762791 <option value="ai21">AI21</option>
2792+ <option value="aimlapi">AI/ML API</option>
27772793 <option value="claude">Claude</option>
27782794 <option value="cohere">Cohere</option>
27792795 <option value="deepseek">DeepSeek</option>
27802796 <option value="groq">Groq</option>
27812797 <option value="makersuite">Google AI Studio</option>
27822798 <option value="vertexai">Google Vertex AI (Express mode)</option>
27832799 <option value="mistralai">MistralAI</option>
27842800 <option value="nanogpt">NanoGPT</option>
27852801 <option value="openrouter">OpenRouter</option>
@@ -2836,7 +2852,7 @@
28362852 <input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" />
28372853 <small class="reverse_proxy_warning">
28382854 <span data-i18n="Doesn't work? Try adding">Doesn't work? Try adding</span> <code>/v1</code> <span data-i18n="at the end!">at the end!</span>
28392855 <span data-i18n="completions note prefix"></span><code>/chat/completions</code> <b data-i18n="suffix will be added automatically.">suffix will be added automatically.</b>
28402856 </small>
28412857 </div>
28422858 <div class="range-block-title justifyLeft" data-i18n="Proxy Password">
@@ -2891,15 +2907,15 @@
28912907 </span>
28922908 <div class="flex-container">
28932909 <input id="api_key_openai" name="api_key_openai" class="text_pole flex1" value="" type="text" autocomplete="off">
28942910 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openai"></div>
28952911 </div>
28962912 <div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning">
28972913 <b data-i18n="Use Proxy password field instead. This input will be ignored.">
28982914 Use "Proxy password" field instead. This input will be ignored.
28992915 </b>
29002916 </div>
29012917 <div data-for="api_key_openai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
29022918 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
29032919 </div>
29042920 <div>
29052921 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>
@@ -2985,10 +3001,10 @@
29853001 </div>
29863002 <div class="flex-container">
29873003 <input id="api_key_claude" name="api_key_claude" class="text_pole flex1" value="" type="text" autocomplete="off">
29883004 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_claude"></div>
29893005 </div>
29903006 <div data-for="api_key_claude" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
29913007 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
29923008 </div>
29933009 <div>
29943010 <h4 data-i18n="Claude Model">Claude Model</h4>
@@ -3044,10 +3060,10 @@
30443060 </div>
30453061 <div class="flex-container">
30463062 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
30473063 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openrouter"></div>
30483064 </div>
30493065 <div data-for="api_key_openrouter" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
30503066 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
30513067 </div>
30523068 <div>
30533069 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
@@ -3108,10 +3124,10 @@
31083124 <h4 data-i18n="Scale API Key">Scale API Key</h4>
31093125 <div class="flex-container">
31103126 <input id="api_key_scale" name="api_key_scale" class="text_pole flex1" value="" autocomplete="off">
31113127 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_scale"></div>
31123128 </div>
31133129 <div data-for="api_key_scale" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
31143130 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
31153131 </div>
31163132 <h4>Scale API URL</h4>
31173133 <input id="api_url_scale" name="api_url_scale" class="text_pole" value="" autocomplete="off" placeholder="https://dashboard.scale.com/spellbook/api/v2/deploy/xxxxxxx">
@@ -3120,10 +3136,10 @@
31203136 <h4>Scale Cookie (_jwt)</h4>
31213137 <div class="flex-container">
31223138 <input id="scale_cookie" name="scale_cookie" class="text_pole flex1" value="" autocomplete="off">
31233139 <div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="scale_cookie"></div>
31243140 </div>
31253141 <div data-for="scale_cookie" class="neutral_warning">
31263142 For privacy reasons, your cookie will be hidden after you reload theclick page'Connect'.
31273143 </div>
31283144 </div>
31293145 <!-- Its only purpose is to trigger max context size check -->
@@ -3137,10 +3153,10 @@
31373153 <h4 data-i18n="AI21 API Key">AI21 API Key</h4>
31383154 <div class="flex-container">
31393155 <input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" value="" type="text" autocomplete="off">
31403156 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_ai21"></div>
31413157 </div>
31423158 <div data-for="api_key_ai21" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
31433159 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
31443160 </div>
31453161 <div>
31463162 <h4 data-i18n="AI21 Model">AI21 Model</h4>
@@ -3167,20 +3183,24 @@
31673183 <h4 data-i18n="Google AI Studio API Key">Google AI Studio API Key</h4>
31683184 <div class="flex-container">
31693185 <input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" value="" type="text" autocomplete="off">
31703186 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_makersuite"></div>
31713187 </div>
31723188 <div data-for="api_key_makersuite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
31733189 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
31743190 </div>
31753191 <div>
31763192 <h4 data-i18n="Google Model">Google Model</h4>
31773193 <select id="model_google_select">
31783194 <optgroup label="Gemini 2.5">
3195+ <option value="gemini-2.5-pro">gemini-2.5-pro</option>
3196+ <option value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option>
31793197 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
31803198 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
31813199 <option value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
3200+ <option value="gemini-2.5-flash">gemini-2.5-flash</option>
31823201 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
31833202 <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
3203+ <option value="gemini-2.5-flash-lite-preview-06-17">gemini-2.5-flash-lite-preview-06-17</option>
31843204 </optgroup>
31853205 <optgroup label="Gemini 2.0">
31863206 <option value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option>
@@ -3212,6 +3232,7 @@
32123232 <option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option>
32133233 </optgroup>
32143234 <optgroup label="Gemma">
3235+ <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>
32153236 <option value="gemma-3-27b-it">gemma-3-27b-it</option>
32163237 <option value="gemma-3-12b-it">gemma-3-12b-it</option>
32173238 <option value="gemma-3-4b-it">gemma-3-4b-it</option>
@@ -3219,37 +3240,145 @@
32193240 </optgroup>
32203241 <optgroup label="LearnLM">
32213242 <option value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
3222- <option value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>
32233243 </optgroup>
3244+ <optgroup id="google_other_models" label="Other"></optgroup>
32243245 </select>
32253246 </div>
32263247 </form>
32273248 <div id="vertexai_form" data-source="vertexai">
3228- <h4>
3249+ <h4 data-i18n="Google Vertex AI Configuration">Google Vertex AI Configuration</h4>
3229- <span data-i18n="Google Vertex AI API Key">
3250+
3230- Google Vertex AI API Key
3251+ <!-- Authentication Mode Selection -->
3231- </span>
3232- <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview" data-i18n="(Express mode keys only)" target="_blank" rel="noopener noreferrer">
3233- (Express mode keys only)
3234- </a>
3235- </h4>
32363252 <div class="flex-container">
3237- <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off">
3253+ <label for="vertexai_auth_mode" data-i18n="Authentication Mode">Authentication Mode:</label>
3238- <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_vertexai"></div>
3254+ <select id="vertexai_auth_mode" class="text_pole">
3255+ <option value="express" data-i18n="Express Mode (API Key)">Express Mode (API Key)</option>
3256+ <option value="full" data-i18n="Full Version (Service Account)">Full Version (Service Account)</option>
3257+ </select>
3258+ </div>
3259+
3260+ <!-- Express Mode Configuration -->
3261+ <div id="vertexai_express_config" class="vertexai-auth-section" data-mode="express">
3262+ <h4>
3263+ <span data-i18n="Google Vertex AI Configuration">
3264+ Google Vertex AI Configuration
3265+ </span>
3266+ <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview" data-i18n="(Express mode)" target="_blank" rel="noopener noreferrer">
3267+ (Express mode)
3268+ </a>
3269+ </h4>
3270+ <!-- API Key -->
3271+ <div class="flex-container flexFlowColumn">
3272+ <label for="api_key_vertexai" data-i18n="API Key">API Key:</label>
3273+ <div class="flex-container">
3274+ <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off">
3275+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_vertexai"></div>
3276+ </div>
3277+ <div data-for="api_key_vertexai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3278+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3279+ </div>
3280+ </div>
3281+ <!-- Project ID -->
3282+ <div class="flex-container flexFlowColumn marginTop10">
3283+ <label for="vertexai_express_project_id">
3284+ <span data-i18n="Project ID">Project ID:</span>
3285+ </label>
3286+ <div class="flex-container">
3287+ <input id="vertexai_express_project_id" name="vertexai_express_project_id" class="text_pole flex1" value="" type="text" autocomplete="off" placeholder="Enter your Project ID">
3288+ </div>
3289+ <div class="toggle-description justifyLeft marginBot5">
3290+ <small data-i18n="Project ID is required when selecting regions other than the default (us-central1). You can find this in a model 404 error message.">
3291+ `Project ID` is only required when selecting regions other than the default (us-central1).<br>
3292+ You can find this in a model 404 error message.
3293+ </small>
3294+ </div>
3295+ </div>
3296+ </div>
3297+
3298+ <!-- Full Version Configuration -->
3299+ <div id="vertexai_full_config" class="vertexai-auth-section" data-mode="full">
3300+ <h4>
3301+ <span data-i18n="Service Account Configuration">
3302+ Service Account Configuration
3303+ </span>
3304+ <a href="https://cloud.google.com/vertex-ai/docs/authentication" target="_blank" rel="noopener noreferrer">
3305+ <i class="fa-solid fa-circle-question"></i>
3306+ </a>
3307+ </h4>
3308+ <!-- Service Account JSON Content -->
3309+ <div class="flex-container flexFlowColumn">
3310+ <label for="vertexai_service_account_json" data-i18n="Service Account JSON Content">Service Account JSON Content:</label>
3311+ <div id="vertexai_service_account_status" class="info-block marginTopBot5" style="display: none;">
3312+ <span id="vertexai_service_account_info"></span>
3313+ </div>
3314+ <div class="flex-container">
3315+ <input id="vertexai_service_account_json" name="vertexai_service_account_json" class="text_pole flex1" value="" type="text" autocomplete="off">
3316+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="vertexai_service_account_json"></div>
3317+ </div>
3318+ <div data-for="vertexai_service_account_json" class="neutral_warning" data-i18n="For privacy reasons, your Service Account JSON content will be hidden after you click 'Validate JSON'.">
3319+ For privacy reasons, your Service Account JSON content will be hidden after you click 'Validate JSON'.
3320+ </div>
3321+ <div class="flex-container">
3322+ <button type="button" id="vertexai_validate_service_account" class="menu_button menu_button_icon" data-i18n="Validate JSON">Validate JSON</button>
3323+ </div>
3324+ </div>
32393325 </div>
3240- <div data-for="api_key_vertexai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3326+
3241- For privacy reasons, your API key will be hidden after you reload the page.
3327+ <!-- Region -->
3328+ <div class="flex-container flexFlowColumn">
3329+ <label for="vertexai_region">
3330+ <span data-i18n="Region">
3331+ Region:
3332+ </span>
3333+ <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations" target="_blank" rel="noopener noreferrer" title="View available regions and models" data-i18n="[title]View available regions and models" class="notes-link">
3334+ <span class="fa-solid fa-circle-question note-link-span"></span>
3335+ </a>
3336+ </label>
3337+ <input id="vertexai_region" name="vertexai_region" class="text_pole flex1" value="us-central1" type="text" autocomplete="off" placeholder="e.g., global, us-central1, europe-west1, asia-northeast1" list="vertexai_region_suggestions">
3338+
3339+ <datalist id="vertexai_region_suggestions">
3340+ <option value="global">global</option>
3341+ <option value="us-central1">us-central1</option>
3342+ <option value="us-east1">us-east1</option>
3343+ <option value="us-east4">us-east4</option>
3344+ <option value="us-west1">us-west1</option>
3345+ <option value="us-west2">us-west2</option>
3346+ <option value="us-west3">us-west3</option>
3347+ <option value="us-west4">us-west4</option>
3348+ <option value="europe-west1">europe-west1</option>
3349+ <option value="europe-west2">europe-west2</option>
3350+ <option value="europe-west3">europe-west3</option>
3351+ <option value="europe-west4">europe-west4</option>
3352+ <option value="europe-west6">europe-west6</option>
3353+ <option value="europe-central2">europe-central2</option>
3354+ <option value="asia-northeast1">asia-northeast1</option>
3355+ <option value="asia-northeast3">asia-northeast3</option>
3356+ <option value="asia-southeast1">asia-southeast1</option>
3357+ <option value="asia-south1">asia-south1</option>
3358+ <option value="australia-southeast1">australia-southeast1</option>
3359+ </datalist>
32423360 </div>
3361+
3362+ <!-- Model Selection -->
32433363 <div>
32443364 <h4 data-i18n="Google Model">Google Model</h4>
32453365 <select id="model_vertexai_select">
3366+ <!-- data-mode="full" is for models that require a service account -->
32463367 <optgroup label="Gemini 2.5">
3368+ <option value="gemini-2.5-pro">gemini-2.5-pro</option>
3369+ <option value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option>
32473370 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
32483371 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
3372+ <option value="gemini-2.5-pro-exp-03-25" data-mode="full">gemini-2.5-pro-exp-03-25</option>
3373+ <option value="gemini-2.5-flash">gemini-2.5-flash</option>
32493374 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
32503375 <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
3376+ <option value="gemini-2.5-flash-lite-preview-06-17">gemini-2.5-flash-lite-preview-06-17</option>
32513377 </optgroup>
32523378 <optgroup label="Gemini 2.0">
3379+ <option value="gemini-2.0-flash-exp" data-mode="full">gemini-2.0-flash-exp</option>
3380+ <option value="gemini-2.0-flash-preview-image-generation" data-mode="full">gemini-2.0-flash-preview-image-generation</option>
3381+ <option value="gemini-2.0-flash">gemini-2.0-flash</option>
32533382 <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
32543383 <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
32553384 </optgroup>
@@ -3260,10 +3389,10 @@
32603389 <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4>
32613390 <div class="flex-container">
32623391 <input id="api_key_mistralai" name="api_key_mistralai" class="text_pole flex1" value="" type="text" autocomplete="off">
32633392 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_mistralai"></div>
32643393 </div>
32653394 <div data-for="api_key_mistralai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
32663395 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
32673396 </div>
32683397 <div>
32693398 <h4 data-i18n="MistralAI Model">MistralAI Model</h4>
@@ -3286,6 +3415,8 @@
32863415 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
32873416 <option value="pixtral-large-latest">pixtral-large-latest</option>
32883417 <option value="devstral-small-latest">devstral-small-latest</option>
3418+ <option value="magistral-small-latest">magistral-small-latest</option>
3419+ <option value="magistral-medium-latest">magistral-medium-latest</option>
32893420 </optgroup>
32903421 <optgroup label="Sub-versions">
32913422 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>
@@ -3315,6 +3446,8 @@
33153446 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
33163447 <option value="pixtral-large-2411">pixtral-large-2411</option>
33173448 <option value="devstral-small-2505">devstral-small-2505</option>
3449+ <option value="magistral-small-2506">magistral-small-2506</option>
3450+ <option value="magistral-medium-2506">magistral-medium-2506</option>
33183451 </optgroup>
33193452 <optgroup id="mistralai_other_models" label="Other"></optgroup>
33203453 </select>
@@ -3324,10 +3457,10 @@
33243457 <h4 data-i18n="Groq API Key">Groq API Key</h4>
33253458 <div class="flex-container">
33263459 <input id="api_key_groq" name="api_key_groq" class="text_pole flex1" value="" type="text" autocomplete="off">
33273460 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_groq"></div>
33283461 </div>
33293462 <div data-for="api_key_groq" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
33303463 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
33313464 </div>
33323465 <h4 data-i18n="Groq Model">Groq Model</h4>
33333466 <select id="model_groq_select">
@@ -3366,10 +3499,10 @@
33663499 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
33673500 <div class="flex-container">
33683501 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">
33693502 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_nanogpt"></div>
33703503 </div>
33713504 <div data-for="api_key_nanogpt" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
33723505 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
33733506 </div>
33743507 <div>
33753508 <h4 data-i18n="NanoGPT Model">NanoGPT Model</h4>
@@ -3382,10 +3515,10 @@
33823515 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
33833516 <div class="flex-container">
33843517 <input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off">
33853518 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_deepseek"></div>
33863519 </div>
33873520 <div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
33883521 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
33893522 </div>
33903523 <div>
33913524 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
@@ -3400,10 +3533,10 @@
34003533 <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>
34013534 <div class="flex-container">
34023535 <input id="api_key_perplexity" name="api_key_perplexity" class="text_pole flex1" value="" type="text" autocomplete="off">
34033536 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_perplexity"></div>
34043537 </div>
34053538 <div data-for="api_key_perplexity" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
34063539 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
34073540 </div>
34083541 <h4 data-i18n="Perplexity Model">Perplexity Model</h4>
34093542 <select id="model_perplexity_select">
@@ -3431,10 +3564,10 @@
34313564 <h4 data-i18n="Cohere API Key">Cohere API Key</h4>
34323565 <div class="flex-container">
34333566 <input id="api_key_cohere" name="api_key_cohere" class="text_pole flex1" value="" type="text" autocomplete="off">
34343567 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_cohere"></div>
34353568 </div>
34363569 <div data-for="api_key_cohere" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
34373570 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
34383571 </div>
34393572 <div>
34403573 <h4 data-i18n="Cohere Model">Cohere Model</h4>
@@ -3470,7 +3603,7 @@
34703603 <div>
34713604 <small>
34723605 <span data-i18n="Doesn't work? Try adding">Doesn't work? Try adding</span> <code>/v1</code> <span data-i18n="at the end!">at the end!</span>
34733606 <b data-i18n="completions note prefix"></b><code>/chat/completions</code> <b data-i18n="suffix will be added automatically.">suffix will be added automatically.</b>
34743607 </small>
34753608 </div>
34763609 <h4>
@@ -3479,10 +3612,10 @@
34793612 </h4>
34803613 <div class="flex-container">
34813614 <input id="api_key_custom" name="api_key_custom" class="text_pole flex1" value="" type="text" autocomplete="off">
34823615 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_custom"></div>
34833616 </div>
34843617 <div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
34853618 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
34863619 </div>
34873620 <h4 data-i18n="Enter a Model ID">Enter a Model ID</h4>
34883621 <div class="flex-container">
@@ -3502,10 +3635,10 @@
35023635 </h4>
35033636 <div class="flex-container">
35043637 <input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" value="" type="text" autocomplete="off">
35053638 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_01ai"></div>
35063639 </div>
35073640 <div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
35083641 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
35093642 </div>
35103643 <h4 data-i18n="01.AI Model">01.AI Model</h4>
35113644 <select id="model_01ai_select">
@@ -3519,10 +3652,10 @@
35193652 </h4>
35203653 <div class="flex-container">
35213654 <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off">
35223655 <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_xai"></div>
35233656 </div>
35243657 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.">
35253658 For privacy reasons, your API key will be hidden after you reload theclick page'Connect'.
35263659 </div>
35273660 <h4 data-i18n="xAI Model">xAI Model</h4>
35283661 <select id="model_xai_select">
@@ -3536,6 +3669,21 @@
35363669 <option value="grok-beta">grok-beta</option>
35373670 </select>
35383671 </div>
3672+ <div id="aimlapi_form" data-source="aimlapi">
3673+ <h4>
3674+ <span>AI/ML API Key</span>
3675+ </h4>
3676+ <div class="flex-container">
3677+ <input id="api_key_aimlapi" name="api_key_aimlapi" class="text_pole flex1" value="" type="text" autocomplete="off">
3678+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_aimlapi"></div>
3679+ </div>
3680+ <div data-for="api_key_aimlapi" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3681+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3682+ </div>
3683+ <h4>AI/ML Model</h4>
3684+ <select id="model_aimlapi_select">
3685+ </select>
3686+ </div>
35393687 <div id="pollinations_form" data-source="pollinations">
35403688 <h4 data-i18n="Pollinations Model">Pollinations Model</h4>
35413689 <select id="model_pollinations_select">
@@ -3562,10 +3710,17 @@
35623710 </h4>
35633711 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
35643712 <option data-i18n="prompt_post_processing_none" value="">None</option>
3565- <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
3713+ <optgroup label="With Tools">
35663714 <option data-i18n="prompt_post_processing_semiprompt_post_processing_merge_tools" value="semimerge_tools">Semi-strictMerge (alternatingconsecutive roles (with tools)</option>
35673715 <option data-i18n="prompt_post_processing_strictprompt_post_processing_semi_tools" value="strictsemi_tools">StrictSemi-strict (user first, alternating roles; with tools)</option>
35683716 <option data-i18n="prompt_post_processing_singleprompt_post_processing_strict_tools" value="singlestrict_tools">SingleStrict (user messagefirst, alternating roles; with tools)</option>
3717+ </optgroup>
3718+ <optgroup label="No Tools">
3719+ <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles (no tools)</option>
3720+ <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles; no tools)</option>
3721+ <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles; no tools)</option>
3722+ <option data-i18n="prompt_post_processing_single" value="single">Single user message (no tools)</option>
3723+ </optgroup>
35693724 </select>
35703725 </div>
35713726 <div class="flex-container flex">
@@ -3585,7 +3740,9 @@
35853740 <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" />
35863741 <span data-i18n="Auto-connect to Last Server">Auto-connect to Last Server</span>
35873742 </label>
35883743 <a id="viewSecrets" href="javascript:void(0);" data-i18n="[missing_key_text]Missing key;[key_saved_text]Key saved" missing_key_text="❌ Missing key" key_saved_text="✔️ Key saved"><span data-i18n="View hidden API keys">View hidden API keys</span></a>
3744+ <span data-i18n="View hidden API keys">View hidden API keys</span>
3745+ </a>
35893746 </div>
35903747 </div>
35913748 </div>
@@ -3725,11 +3882,11 @@
37253882 <input id="instruct_derived" type="checkbox" style="display:none;" />
37263883 <small><i class="fa-solid fa-bolt menu_button margin0"></i></small>
37273884 </label>
37283885 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If10;If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">
37293886 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
37303887 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
37313888 </label>
37323889 <label id="instruct_enabled_label" for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
37333890 <input id="instruct_enabled" type="checkbox" style="display:none;" />
37343891 <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
37353892 </label>
@@ -4042,7 +4199,7 @@
40424199 </small>
40434200 </label>
40444201 <div class="flex1 flex-container alignItemsBaseline" title="Maximum number of reasoning blocks to be added per prompt, counting from the last message." data-i18n="[title]reasoning_max_additions">
40454202 <input id="reasoning_max_additions" class="text_pole textarea_compact widthUnset" type="number" min="0" max="999">< /textarea>
40464203 <small data-i18n="Max">Max</small>
40474204 </div>
40484205 </div>
@@ -4173,8 +4330,8 @@
41734330 <small>
41744331 <span data-i18n="Scan Depth">Scan Depth</span>
41754332 </small>
41764333 <input class="neo-range-slider" type="range" id="world_info_depth" name="world_info_depth" min="0" max="1001000" step="1">
41774334 <input class="neo-range-input" type="number" min="0" max="1001000" step="1" data-for="world_info_depth" id="world_info_depth_counter">
41784335 </div>
41794336
41804337 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
@@ -4190,8 +4347,8 @@
41904347 <span data-i18n="Budget Cap">Budget Cap</span>
41914348 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title](0 = disabled)" title="(0 = disabled)"></div>
41924349 </small>
41934350 <input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="819265536" step="1">
41944351 <input class="neo-range-input" type="number" min="0" max="819265536" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter">
41954352 </div>
41964353
41974354 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" title="Scan chronologically until reached min entries or token budget." data-i18n="[title]Scan chronologically until reached min entries or token budget.">
@@ -4643,6 +4800,10 @@
46434800 <input id="never_resize_avatars" type="checkbox" />
46444801 <small data-i18n="Never resize avatars">Never resize avatars</small>
46454802 </label>
4803+ <label data-i18n="[title]Allow animations for WEBP backgrounds. This is only a change for the selection menu." for="background_thumbnails_animation" class="checkbox_label" title="Allow animations for WEBP backgrounds. This is only a change for the selection menu.">
4804+ <input id="background_thumbnails_animation" type="checkbox" />
4805+ <small data-i18n="Animated background thumbnails">Animated background thumbnails</small>
4806+ </label>
46464807 <label class="checkbox_label" for="show_card_avatar_urls" title="Show actual file names on the disk, in the characters list display only." data-i18n="[title]Show actual file names on the disk, in the characters list display only">
46474808 <input id="show_card_avatar_urls" type="checkbox" />
46484809 <small data-i18n="Show avatar filenames">Show avatar filenames</small>
@@ -4656,13 +4817,16 @@
46564817
46574818 <div name="MiscellaneousToggles">
46584819 <h4><span data-i18n="Miscellaneous">Miscellaneous</span></h4>
46594820 <div class="flex-container flexGap2">
46604821 <div id="reload_chat" class="menu_button whitespacenowrap" data-i18n="[title]Reload and redraw the currently open chat" title="Reload and redraw the currently open chat.">
46614822 <small data-i18n="Reload Chat">Reload Chat</small>
46624823 </div>
46634824 <div id="debug_menu" class="menu_button whitespacenowrap">
46644825 <small data-i18n="Debug Menu">Debug Menu</small>
46654826 </div>
4827+ <div id="data_maid_button" class="menu_button whitespacenowrap" title="Find and delete backups, unused chats, files, images, etc." data-i18n="[title]Find and delete backups, unused chats, files, images, etc.">
4828+ <small data-i18n="Clean-Up">Clean-Up</small>
4829+ </div>
46664830 </div>
46674831 <label class="checkbox_label flexWrap" for="smooth_streaming">
46684832 <input id="smooth_streaming" type="checkbox" />
@@ -4942,6 +5106,14 @@
49425106 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
49435107 </div>
49445108 <div class="inline-drawer-content">
5109+ <label for="stscript_autocomplete_state">
5110+ <small data-i18n="Visibility">Visibility</small>
5111+ </label>
5112+ <select id="stscript_autocomplete_state">
5113+ <option value="0" data-i18n="Don't show">Don't show</option>
5114+ <option value="1" data-i18n="Input length > 1">Input length > 1</option>
5115+ <option value="2" data-i18n="Always show">Always show</option>
5116+ </select>
49455117 <label class="checkbox_label" for="stscript_autocomplete_autoHide">
49465118 <input id="stscript_autocomplete_autoHide" type="checkbox" />
49475119 <small data-i18n="Automatically hide details">
@@ -5177,7 +5349,7 @@
51775349 <div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
51785350 <div class="flex-container alignItemsBaseline wide100p">
51795351 <div class="flex1 flex-container alignItemsBaseline">
51805352 <h3 class="margin0" >
51815353 <span data-i18n="Persona Management">Persona Management</span>
51825354 <a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
51835355 <span class="fa-solid fa-circle-question note-link-span"></span>
@@ -5348,10 +5520,12 @@
53485520 <div class="right_menu_button fa-solid fa-list-ul" id="rm_button_characters" title="Select/Create Characters" data-i18n="[title]Select/Create Characters"></div>
53495521 </div>
53505522 <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p">
53515523 <div class="hotswap avatars_inline scroll-reset-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"></div>
5524+ </div>
5525+ <hr class="wide100p">
53525526 </div>
53535527 </div>
5354- <hr>
5528+
53555529 <!-- this div structure must be preserved until group peeking can adjust -->
53565530 <div id="rm_PinAndTabs">
53575531 <div id="right-nav-panel-tabs" class="">
@@ -5376,340 +5550,343 @@
53765550 </div>
53775551 </div>
53785552 <!-- end group peeking cope structure-->
5379- <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
5553+
5380- <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5554+ <div class="scrollableInner">
5381- <div id="avatar-and-name-block">
5555+ <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
5382- <div id="name_div">
5556+ <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5383- <input id="character_name_pole" name="ch_name" class="text_pole" data-i18n="[placeholder]Name this character" placeholder="Name this character" value="" autocomplete="off">
5557+ <div id="avatar-and-name-block">
53845558 <div classid="extension_token_countername_div">
5385- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="character_name_pole" data-token-permanent="true">counting...</span>
5559+ <input id="character_name_pole" name="ch_name" class="text_pole" data-i18n="[placeholder]Name this character" placeholder="Name this character" value="" autocomplete="off">
5386- </div>
5560+ <div class="extension_token_counter">
5387- </div>
5561+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="character_name_pole" data-token-permanent="true">counting...</span>
5388- <div class="flex-container flexFlowColumn expander flexNoGap">
5562+ </div>
5389- <div id="avatar_div" class="avatar_div buttons_block alignitemsflexstart justifySpaceBetween flexnowrap">
5563+ </div>
5390- <label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character" data-i18n="[title]Click to select a new avatar for this character">
5564+ <div class="flex-container flexFlowColumn expander flexNoGap">
5391- <img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
5565+ <div id="avatar_div" class="avatar_div buttons_block alignitemsflexstart justifySpaceBetween flexnowrap">
5392- <input hidden type="file" id="add_avatar_button" name="avatar" accept="image/*">
5566+ <label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character" data-i18n="[title]Click to select a new avatar for this character">
5393- </label>
5567+ <img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
5394- <div class="flex-container" id="avatar_controls">
5568+ <input hidden type="file" id="add_avatar_button" name="avatar" accept="image/*">
5395- <div class="form_create_bottom_buttons_block buttons_block">
5569+ </label>
5396- <div id="rm_button_back" class="menu_button fa-solid fa-left-long "></div>
5570+ <div class="flex-container" id="avatar_controls">
5397- <!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
5571+ <div class="form_create_bottom_buttons_block buttons_block">
53985572 <div id="favorite_buttonrm_button_back" class="menu_button fa-solid fa-star" title="Add to Favorites" dataleft-i18n="[title]Add tolong Favorites"></div>
5399- <input type="hidden" id="fav_checkbox" name="fav" />
5573+ <!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
54005574 <div id="advanced_divfavorite_button" class="menu_button fa-solid fa-book star" title="AdvancedAdd Definitionsto Favorites" data-i18n="[title]AdvancedAdd Definitionto Favorites"></div>
5401- <div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore&#10;&#10;Click to load&#10;Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div>
5575+ <input type="hidden" id="fav_checkbox" name="fav" />
54025576 <div classid="chat_lorebook_buttonadvanced_div" class="menu_button fa-solid fa-passportbook " title="Chat Lore&#10;Alt+Click to open theAdvanced lorebookDefinitions" data-i18n="[title]Chat Lore Alt+Click to open theAdvanced lorebookDefinition"></div>
54035577 <div id="char_connections_buttonworld_button" class="menu_button fa-solid fa-face-smileglobe" title="ConnectedCharacter PersonasLore&#10;&#10;Click to load&#10;Shift-click to open 'Link to World Info' popup" data-i18n="[title]Connected Personasworld_button_title"></div>
54045578 <div id="export_button" class="chat_lorebook_button menu_button fa-solid fa-file-export passport" title="ExportChat andLore&#10;Alt+Click Downloadto open the lorebook" data-i18n="[title]ExportChat andLore DownloadAlt+Click to open the lorebook"></div>
54055579 <!-- <div id="set_chat_scenariochar_connections_button" class="menu_button fa-solid fa-scrollface-smile" title="Set aConnected chatPersonas" scenariodata-i18n="[title]Connected overridePersonas"></div> -->
54065580 <!-- <div id="set_character_worldexport_button" class="menu_button fa-solid fa-globefile-export " title="Set aExport characterand WorldDownload" Infodata-i18n="[title]Export /and LorebookDownload"></div> -->
54075581 <!-- <div id="dupe_buttonset_chat_scenario" class="menu_button fa-solid fa-clone scroll" title="DuplicateSet Character"a data-i18n="[title]Duplicatechat Characterscenario override"></div> -->
54085582 <label!-- for="create_button"<div id="create_button_labelset_character_world" class="menu_button fa-solid fa-user-checkglobe" title="CreateSet Character"a data-i18n="[title]Createcharacter CharacterWorld Info / Lorebook"></div> -->
5409- <input type="submit" id="create_button" name="create_button">
5583+ <div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character" data-i18n="[title]Duplicate Character"></div>
5410- </label>
5584+ <label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character" data-i18n="[title]Create Character">
5411- <div id="delete_button" class="menu_button fa-solid fa-skull red_button" title="Delete Character" data-i18n="[title]Delete Character"></div>
5585+ <input type="submit" id="create_button" name="create_button">
54125586 </divlabel>
5413- <label class="flex1 height100p" for="char-management-dropdown">
5587+ <div id="delete_button" class="menu_button fa-solid fa-skull red_button" title="Delete Character" data-i18n="[title]Delete Character"></div>
5414- <select id="char-management-dropdown" class="text_pole">
5588+ </div>
5415- <option value="default" disabled selected data-i18n="More...">More...</option>
5589+ <label class="flex1 height100p" for="char-management-dropdown">
54165590 <optionselect id="set_character_worldchar-management-dropdown" data-i18nclass="Link to World Infotext_pole">
5417- Link to World Info
5591+ <option value="default" disabled selected data-i18n="More...">More...</option>
5418- </option>
5592+ <option id="set_character_world" data-i18n="Link to World Info">
5419- <option id="import_character_info" data-i18n="Import Card Lore">
5593+ Link to World Info
5420- Import Card Lore
5421- </option>
5422- <option id="set_chat_scenario" data-i18n="Scenario Override">
5423- Scenario Override
5424- </option>
5425- <option id="convert_to_persona" data-i18n="Convert to Persona">
5426- Convert to Persona
5427- </option>
5428- <option id="renameCharButton" data-i18n="Rename">
5429- Rename
5430- </option>
5431- <option id="character_source" data-i18n="Link to Source">
5432- Link to Source
5433- </option>
5434- <option id="replace_update" data-i18n="Replace / Update">
5435- Replace / Update
5436- </option>
5437- <option id="import_tags" data-i18n="Import Tags">
5438- Import Tags
5439- </option>
5440- <option id="set_as_assistant" data-i18n="Set / Unset as Welcome Page Assistant">
5441- Set / Unset as Welcome Page Assistant
5442- </option>
5443- <!--<option id="dupe_button">
5444- Duplicate
54455594 </option>
54465595 <option id="export_buttonimport_character_info" data-i18n="Import Card Lore">
5447- Export
5596+ Import Card Lore
54485597 </option>
54495598 <option id="delete_buttonset_chat_scenario" data-i18n="Scenario Override">
5450- Delete
5599+ Scenario Override
54515600 </option>-->
5452- </select>
5601+ <option id="convert_to_persona" data-i18n="Convert to Persona">
5453- </label>
5602+ Convert to Persona
5603+ </option>
5604+ <option id="renameCharButton" data-i18n="Rename">
5605+ Rename
5606+ </option>
5607+ <option id="character_source" data-i18n="Link to Source">
5608+ Link to Source
5609+ </option>
5610+ <option id="replace_update" data-i18n="Replace / Update">
5611+ Replace / Update
5612+ </option>
5613+ <option id="import_tags" data-i18n="Import Tags">
5614+ Import Tags
5615+ </option>
5616+ <option id="set_as_assistant" data-i18n="Set / Unset as Welcome Page Assistant">
5617+ Set / Unset as Welcome Page Assistant
5618+ </option>
5619+ <!--<option id="dupe_button">
5620+ Duplicate
5621+ </option>
5622+ <option id="export_button">
5623+ Export
5624+ </option>
5625+ <option id="delete_button">
5626+ Delete
5627+ </option>-->
5628+ </select>
5629+ </label>
5630+ </div>
54545631 </div>
5455- </div>
5632+ <div id="tags_div">
54565633 <div idclass="tags_divtag_controls">
5457- <div class="tag_controls">
5634+ <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5458- <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5635+ <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
5459- <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
5636+ </div>
5637+ <div id="tagList" class="tags"></div>
54605638 </div>
5461- <div id="tagList" class="tags"></div>
54625639 </div>
54635640 </div>
5464- </div>
5465- <hr>
5466- <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap">
5467- <div id="creators_notes_div" class="title_restorable flexGap5">
5468- <span class="flex1" data-i18n="Creator's Notes">Creator's Notes</span>
5469- <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small>
5470- <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div>
5471- <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div>
5472- </div>
5473- <div id="creator_notes_spoiler" class="flex1"></div>
5474- <!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
5475- </div>
5476- <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
54775641 <hr>
54785642 <div id="description_divspoiler_free_desc" class="title_restorableflex-container flexFlowColumn flex1 flexNoGap">
54795643 <div id="creators_notes_div" class="flex-containertitle_restorable alignitemscenterflexGap5">
54805644 <span data-i18nclass="Character Descriptionflex1" classdata-i18n="mdhotkey_locationCreator's Notes">DescriptionCreator's Notes</span>
5481- <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
5645+ <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small>
5482- <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
5646+ <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div>
5483- <span class="fa-solid fa-circle-question note-link-span"></span>
5647+ <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div>
5484- </a>
54855648 </div>
5486- <div id="character_open_media_overrides" class="menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this character." data-i18n="[title]Click to allow/forbid the use of external media for this character.">
5649+ <div id="creator_notes_spoiler" class="flex1"></div>
5487- <i id="character_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5650+ <!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
5488- <i id="character_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5489- <span data-i18n="Ext. Media">
5490- Ext. Media
5491- </span>
5492- </div>
5493- </div>
5494- <textarea id="description_textarea" class="mdHotkeys" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea>
5495- <div class="extension_token_counter">
5496- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
54975651 </div>
5498- </div>
5652+ <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
5499- <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
5653+ <hr>
55005654 <div id="first_message_divdescription_div" class="title_restorable">
55015655 <div class="flex-container alignitemscenter flex1">
55025656 <span data-i18n="FirstCharacter messageDescription" class="mdhotkey_location">First messageDescription</span>
55035657 <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textareadescription_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
55045658 <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#firstcharacter-messagedescription" class="notes-link" target="_blank">
55055659 <span class="fa-solid fa-circle-question note-link-span"></span>
55065660 </a>
5661+ </div>
5662+ <div id="character_open_media_overrides" class="menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this character." data-i18n="[title]Click to allow/forbid the use of external media for this character.">
5663+ <i id="character_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5664+ <i id="character_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5665+ <span data-i18n="Ext. Media">
5666+ Ext. Media
5667+ </span>
5668+ </div>
55075669 </div>
5508- <div class="menu_button menu_button_icon open_alternate_greetings margin0" title="Click to set additional greeting messages" data-i18n="[title]Click to set additional greeting messages">
5670+ <textarea id="description_textarea" class="mdHotkeys" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea>
5509- <span data-i18n="Alt. Greetings">
5671+ <div class="extension_token_counter">
5510- Alt. Greetings
5672+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
5511- </span>
55125673 </div>
55135674 </div>
5514- <textarea class="mdHotkeys" id="firstmessage_textarea" data-i18n="[placeholder]This will be the first message from the character that starts every chat." placeholder="This will be the first message from the character that starts every chat." name="first_mes" placeholder=""></textarea>
5675+ <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
55155676 <div id="first_message_div" class="extension_token_countertitle_restorable">
5516- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span>
5677+ <div class="flex-container alignitemscenter flex1">
5517- </div>
5678+ <span data-i18n="First message" class="mdhotkey_location">First message</span>
5518- </div>
5679+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
5519- <!-- these divs are invisible and used for server communication purposes -->
5680+ <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank">
5520- <div id="hidden-divs">
5681+ <span class="fa-solid fa-circle-question note-link-span"></span>
5521- <input id="character_json_data" name="json_data" type="hidden">
5682+ </a>
5522- <input id="avatar_url_pole" name="avatar_url" type="hidden">
5683+ </div>
5523- <input id="selected_chat_pole" name="chat" type="hidden">
5684+ <div class="menu_button menu_button_icon open_alternate_greetings margin0" title="Click to set additional greeting messages" data-i18n="[title]Click to set additional greeting messages">
5524- <input id="create_date_pole" name="create_date" type="hidden">
5685+ <span data-i18n="Alt. Greetings">
5525- <input id="last_mes_pole" name="last_mes" type="hidden">
5686+ Alt. Greetings
5526- <input id="character_world" name="world" type="hidden">
5687+ </span>
5527- </div>
5528- <!-- now back to normal divs for display purposes-->
5529- </form>
5530- </div>
5531- <div name="Group Chat Edit Panel" id="rm_group_chats_block" class="right_menu flex-container flexNoGap">
5532- <div class="inline-drawer wide100p flexFlowColumn">
5533- <div id="groupControlsToggle" class="inline-drawer-toggle inline-drawer-header">
5534- <span>
5535- <span data-i18n="Group Controls">Group Controls</span>
5536- <a href="https://docs.sillytavern.app/usage/core-concepts/groupchats/" class="notes-link" target="_blank">
5537- <span class="fa-solid fa-circle-question note-link-span"></span>
5538- </a>
5539- </span>
5540- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5541- </div>
5542- <div class="inline-drawer-content">
5543- <div id="group-metadata-controls" class="marginTopBot5">
5544- <div class="flex-container wide100p">
5545- <input id="rm_group_chat_name" class="text_pole flex1" type="text" name="chat_name" data-i18n="[placeholder]Chat Name (Optional)" placeholder="Chat Name (Optional)" />
5546- <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div>
5547- </div>
5548- <div id="group_tags_div" class="wide100p">
5549- <div class="tag_controls">
5550- <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5551- <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
55525688 </div>
5553- <div id="groupTagList" class="tags paddingTopBot5"></div>
55545689 </div>
5555- <div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p fontsize80p">
5690+ <textarea class="mdHotkeys" id="firstmessage_textarea" data-i18n="[placeholder]This will be the first message from the character that starts every chat." placeholder="This will be the first message from the character that starts every chat." name="first_mes" placeholder=""></textarea>
5556- <div>
5691+ <div class="extension_token_counter">
5557- <label class="add_avatar avatar flex-container justifyCenter" for="group_avatar_button" title="Click to select a new avatar for this group" data-i18n="[title]Click to select a new avatar for this group">
5692+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span>
5558- <div id="group_avatar_preview">
5693+ </div>
5559- <div class="avatar">
5694+ </div>
5560- <img src="img/ai4.png" alt="avatar">
5695+ <!-- these divs are invisible and used for server communication purposes -->
5561- </div>
5696+ <div id="hidden-divs">
5562- </div>
5697+ <input id="character_json_data" name="json_data" type="hidden">
5563- <input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
5698+ <input id="avatar_url_pole" name="avatar_url" type="hidden">
5564- </label>
5699+ <input id="selected_chat_pole" name="chat" type="hidden">
5700+ <input id="create_date_pole" name="create_date" type="hidden">
5701+ <input id="last_mes_pole" name="last_mes" type="hidden">
5702+ <input id="character_world" name="world" type="hidden">
5703+ </div>
5704+ <!-- now back to normal divs for display purposes-->
5705+ </form>
5706+ </div>
5707+ <div name="Group Chat Edit Panel" id="rm_group_chats_block" class="right_menu flex-container flexNoGap">
5708+ <div class="inline-drawer wide100p flexFlowColumn">
5709+ <div id="groupControlsToggle" class="inline-drawer-toggle inline-drawer-header">
5710+ <span>
5711+ <span data-i18n="Group Controls">Group Controls</span>
5712+ <a href="https://docs.sillytavern.app/usage/core-concepts/groupchats/" class="notes-link" target="_blank">
5713+ <span class="fa-solid fa-circle-question note-link-span"></span>
5714+ </a>
5715+ </span>
5716+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5717+ </div>
5718+ <div class="inline-drawer-content">
5719+ <div id="group-metadata-controls" class="marginTopBot5">
5720+ <div class="flex-container wide100p">
5721+ <input id="rm_group_chat_name" class="text_pole flex1" type="text" name="chat_name" data-i18n="[placeholder]Chat Name (Optional)" placeholder="Chat Name (Optional)" />
5722+ <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div>
55655723 </div>
55665724 <div name="GroupStragegyAndOrder" id="rm_group_buttonsgroup_tags_div" class="flex-container paddingLeftRight5 flex2wide100p">
55675725 <div class="flex1 flexGap5tag_controls">
5568- <label for="rm_group_activation_strategy" class="flexnowrap width100p whitespacenowrap">
5726+ <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5569- <span data-i18n="Group reply strategy">Group reply strategy</span>
5727+ <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
5570- </label>
5571- <select id="rm_group_activation_strategy">
5572- <option value="2" data-i18n="Manual">Manual</option>
5573- <option value="0" data-i18n="Natural order">Natural order</option>
5574- <option value="1" data-i18n="List order">List order</option>
5575- <option value="3" data-i18n="Pooled order">Pooled order</option>
5576- </select>
55775728 </div>
55785729 <div id="groupTagList" class="flex1tags flexGap5paddingTopBot5"></div>
5579- <label for="rm_group_generation_mode" class="flexnowrap width100p whitespacenowrap">
5730+ </div>
5580- <span data-i18n="Group generation handling mode">Group generation handling mode</span>
5731+ <div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p fontsize80p">
5581- </label>
5732+ <div>
5582- <select id="rm_group_generation_mode">
5733+ <label class="add_avatar avatar flex-container justifyCenter" for="group_avatar_button" title="Click to select a new avatar for this group" data-i18n="[title]Click to select a new avatar for this group">
5583- <option value="0" data-i18n="Swap character cards">Swap character cards</option>
5734+ <div id="group_avatar_preview">
5584- <option value="1" data-i18n="Join character cards (exclude muted)">Join character cards (exclude muted)</option>
5735+ <div class="avatar">
5585- <option value="2" data-i18n="Join character cards (include muted)">Join character cards (include muted)</option>
5736+ <img src="img/ai4.png" alt="avatar">
55865737 </selectdiv>
5587- </div>
5588- <div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
5589- <label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
5590- <span data-i18n="Join Prefix">Join Prefix</span>
5591- <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5592- </div>
5593- </label>
5594- <textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5595- </div>
5596- <div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
5597- <label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
5598- <span data-i18n="Join Suffix">Join Suffix</span>
5599- <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
56005738 </div>
5739+ <input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
56015740 </label>
5602- <textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
56035741 </div>
5604- </div>
5742+ <div name="GroupStragegyAndOrder" id="rm_group_buttons" class="flex-container paddingLeftRight5 flex2">
56055743 <div id="GroupFavDelOkBack" class="flex-containerflex1 flexGap5 spaceEvenly flex1">
5606- <div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-left-long"></div>
5744+ <label for="rm_group_activation_strategy" class="flexnowrap width100p whitespacenowrap">
5607- <div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-scroll" title="Set a group chat scenario" data-i18n="[title]Set a group chat scenario"></div>
5745+ <span data-i18n="Group reply strategy">Group reply strategy</span>
5608- <div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
5746+ </label>
56095747 <inputselect id="rm_group_fav" type="hiddenrm_group_activation_strategy" />
5610- <div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
5748+ <option value="2" data-i18n="Manual">Manual</option>
5611- <i id="group_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5749+ <option value="0" data-i18n="Natural order">Natural order</option>
5612- <i id="group_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5750+ <option value="1" data-i18n="List order">List order</option>
5751+ <option value="3" data-i18n="Pooled order">Pooled order</option>
5752+ </select>
5753+ </div>
5754+ <div class="flex1 flexGap5">
5755+ <label for="rm_group_generation_mode" class="flexnowrap width100p whitespacenowrap">
5756+ <span data-i18n="Group generation handling mode">Group generation handling mode</span>
5757+ </label>
5758+ <select id="rm_group_generation_mode">
5759+ <option value="0" data-i18n="Swap character cards">Swap character cards</option>
5760+ <option value="1" data-i18n="Join character cards (exclude muted)">Join character cards (exclude muted)</option>
5761+ <option value="2" data-i18n="Join character cards (include muted)">Join character cards (include muted)</option>
5762+ </select>
5763+ </div>
5764+ <div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
5765+ <label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
5766+ <span data-i18n="Join Prefix">Join Prefix</span>
5767+ <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5768+ </div>
5769+ </label>
5770+ <textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5771+ </div>
5772+ <div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
5773+ <label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
5774+ <span data-i18n="Join Suffix">Join Suffix</span>
5775+ <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5776+ </div>
5777+ </label>
5778+ <textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5779+ </div>
56135780 </div>
5614- <div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-check" title="Create" data-i18n="[title]Create"></div>
5781+ <div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">
56155782 <div id="rm_group_restore_avatarrm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-images" title="Restore collage avatar" dataleft-i18n="[title]Restore collage avatarlong"></div>
56165783 <div id="rm_group_deleterm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-trash-canscroll" title="DeleteSet a group chat scenario" data-i18n="[title]DeleteSet a group chat scenario"></div>
5617- <div class="flex1">
5784+ <div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
56185785 <labelinput classid="checkbox_labelrm_group_fav" whitespacenowraptype="hidden" />
5619- <input id="rm_group_allow_self_responses" type="checkbox" />
5786+ <div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
5620- <span data-i18n="Allow self responses">Allow self responses</span>
5787+ <i id="group_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5621- </label>
5788+ <i id="group_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5622- <label id="rm_group_automode_label" class="checkbox_label whitespacenowrap">
5789+ </div>
5623- <input id="rm_group_automode" type="checkbox" />
5790+ <div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-check" title="Create" data-i18n="[title]Create"></div>
5624- <span data-i18n="Auto Mode">Auto Mode</span>
5791+ <div id="rm_group_restore_avatar" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-images" title="Restore collage avatar" data-i18n="[title]Restore collage avatar"></div>
56255792 <inputdiv id="rm_group_automode_delayrm_group_delete" class="text_pole textarea_compact widthUnset"heightFitContent type="number"margin0 min="1"menu_button max="999"fa-solid step="1"fa-fw value="5fa-trash-can" title="Auto Mode delayDelete" data-i18n="[title]Auto Mode delayDelete" ></div>
5626- </label>
5793+ <div class="flex1">
56275794 <label id="rm_group_hidemutedsprites_label" class="checkbox_label whitespacenowrap">
56285795 <input id="rm_group_hidemutedspritesrm_group_allow_self_responses" type="checkbox" />
56295796 <span data-i18n="Hide MutedAllow Memberself Spritesresponses">Hide MutedAllow Memberself Spritesresponses</span>
56305797 </label>
5798+ <label id="rm_group_automode_label" class="checkbox_label whitespacenowrap">
5799+ <input id="rm_group_automode" type="checkbox" />
5800+ <span data-i18n="Auto Mode">Auto Mode</span>
5801+ <input id="rm_group_automode_delay" class="text_pole textarea_compact widthUnset" type="number" min="1" max="999" step="1" value="5" title="Auto Mode delay" data-i18n="[title]Auto Mode delay" />
5802+ </label>
5803+ <label id="rm_group_hidemutedsprites_label" class="checkbox_label whitespacenowrap">
5804+ <input id="rm_group_hidemutedsprites" type="checkbox" />
5805+ <span data-i18n="Hide Muted Member Sprites">Hide Muted Member Sprites</span>
5806+ </label>
5807+ </div>
56315808 </div>
56325809 </div>
56335810 </div>
56345811 </div>
56355812 </div>
5636- </div>
5813+ <div class="inline-drawer wide100p flexFlowColumn">
56375814 <div id="groupCurrentMemberListToggle" class="inline-drawer wide100p-toggle flexFlowColumninline-drawer-header">
5638- <div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5815+ <span data-i18n="Current Members">Current Members </span><i id="groupCurrentMemberPopoutButton" class="fa-solid fa-window-restore menu_button"></i>
5639- <span data-i18n="Current Members">Current Members </span><i id="groupCurrentMemberPopoutButton" class="fa-solid fa-window-restore menu_button"></i>
5816+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5640- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5641- </div>
5642- <div class="inline-drawer-content">
5643- <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
5644- <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
5645- <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
56465817 </div>
5647- </div>
5818+ <div class="inline-drawer-content">
5648- </div>
5819+ <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
5649- <div class="inline-drawer wide100p flexFlowColumn">
5820+ <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
5650- <div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5821+ <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
5651- <span data-i18n="Add Members">Add Members</span>
5652- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5653- </div>
5654- <div class="inline-drawer-content">
5655- <div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
5656- <div id="rm_group_add_members_header">
5657- <input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
56585822 </div>
5659- <div class="rm_tag_controls">
5823+ </div>
5660- <div class="tags rm_tag_filter"></div>
5824+ </div>
5825+ <div class="inline-drawer wide100p flexFlowColumn">
5826+ <div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5827+ <span data-i18n="Add Members">Add Members</span>
5828+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5829+ </div>
5830+ <div class="inline-drawer-content">
5831+ <div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
5832+ <div id="rm_group_add_members_header">
5833+ <input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5834+ </div>
5835+ <div class="rm_tag_controls">
5836+ <div class="tags rm_tag_filter"></div>
5837+ </div>
5838+ <div id="rm_group_add_members_pagination" class="group_pagination"></div>
5839+ <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
56615840 </div>
5662- <div id="rm_group_add_members_pagination" class="group_pagination"></div>
5663- <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
56645841 </div>
56655842 </div>
56665843 </div>
5667- </div>
5844+ <div id="rm_character_import" class="right_menu" style="display: none;">
56685845 <divform id="rm_character_importform_import" classaction="right_menujavascript:void(null);" stylemethod="display:post" none;enctype="multipart/form-data">
5669- <form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5846+ <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar">
5670- <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar">
5847+ <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off">
5671- <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off">
5672- </form>
5673- <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden>
5674- </div>
5675- <div name="Character List Panel" id="rm_characters_block" class="right_menu">
5676- <div id="charListFixedTop">
5677- <form id="form_character_search_form" action="javascript:void(null);">
5678- <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
5679- <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
5680- <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
5681- <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
5682- <input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5683- <select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order">
5684- <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option>
5685- <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option>
5686- <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option>
5687- <option data-field="create_date" data-order="desc" data-i18n="Newest">Newest</option>
5688- <option data-field="create_date" data-order="asc" data-i18n="Oldest">Oldest</option>
5689- <option data-field="fav" data-order="desc" data-rule="boolean" data-i18n="Favorites">Favorites</option>
5690- <option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
5691- <option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
5692- <option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
5693- <option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
5694- <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
5695- <option data-field="name" data-order="random" data-i18n="Random">Random</option>
5696- </select>
56975848 </form>
5698- <div class="rm_tag_controls">
5849+ <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden>
5699- <div class="tags rm_tag_filter"></div>
5700- <div class="tags rm_tag_bogus_drilldown"></div>
5701- </div>
5702- <hr>
57035850 </div>
5851+ <div name="Character List Panel" id="rm_characters_block" class="right_menu">
5852+ <div id="charListFixedTop">
5853+ <form id="form_character_search_form" action="javascript:void(null);">
5854+ <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
5855+ <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
5856+ <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
5857+ <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
5858+ <input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5859+ <select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order">
5860+ <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option>
5861+ <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option>
5862+ <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option>
5863+ <option data-field="create_date" data-order="desc" data-i18n="Newest">Newest</option>
5864+ <option data-field="create_date" data-order="asc" data-i18n="Oldest">Oldest</option>
5865+ <option data-field="fav" data-order="desc" data-rule="boolean" data-i18n="Favorites">Favorites</option>
5866+ <option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
5867+ <option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
5868+ <option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
5869+ <option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
5870+ <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
5871+ <option data-field="name" data-order="random" data-i18n="Random">Random</option>
5872+ </select>
5873+ </form>
5874+ <div class="rm_tag_controls">
5875+ <div class="tags rm_tag_filter"></div>
5876+ <div class="tags rm_tag_bogus_drilldown"></div>
5877+ </div>
5878+ <hr>
5879+ </div>
57045880
57055881 <div id="rm_print_characters_pagination">
57065882 <i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view" data-i18n="[title]Toggle character grid view"></i>
57075883 <i id="bulkEditButton" class="fa-solid fa-edit menu_button bulkEditButton" title="Bulk edit characters&#13;&#13;Click to toggle characters&#13;Shift + Click to select/deselect a range of characters&#13;Right-click for actions" data-i18n="[title]Bulk_edit_characters"></i>
57085884 <div id="bulkSelectedCount" class="bulkEditOptionElement paginationjs-nav"></div>
57095885 <i id="bulkSelectAllButton" class="fa-solid fa-check-double menu_button bulkEditOptionElement bulkSelectAllButton" title="Bulk select all characters" data-i18n="[title]Bulk select all characters" style="display: none;"></i>
57105886 <i id="bulkDeleteButton" class="fa-solid fa-trash menu_button bulkEditOptionElement bulkDeleteButton" title="Bulk delete characters" data-i18n="[title]Bulk delete characters" style="display: none;"></i>
5887+ </div>
5888+ <div id="rm_print_characters_block" class="flexFlowColumn"></div>
57115889 </div>
5712- <div id="rm_print_characters_block" class="flexFlowColumn"></div>
57135890 </div>
57145891 </nav>
57155892 </div>
@@ -6086,6 +6263,298 @@
60866263 </div>
60876264 </div>
60886265 <div id="entry_edit_template" class="template_element">
6266+ <div class="world_entry_edit">
6267+ <div class="flex-container wide100p alignitemscenter">
6268+ <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
6269+ <div class="world_entry_form_control keyprimary flex1">
6270+ <small class="displayNone">
6271+ <span data-i18n="Comma separated (required)">
6272+ Comma separated (required)
6273+ </span>
6274+ </small>
6275+ <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small>
6276+ <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select>
6277+ <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea>
6278+ <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6279+ ⌨️
6280+ </button>
6281+ </div>
6282+ <div class="world_entry_form_control">
6283+ <small class="textAlignCenter" data-i18n="Logic">Logic</small>
6284+ <select name="entryLogicType" class="text_pole widthFitContent margin0">
6285+ <option value="0" data-i18n="AND ANY">AND ANY</option>
6286+ <option value="3" data-i18n="AND ALL">AND ALL</option>
6287+ <option value="1" data-i18n="NOT ALL">NOT ALL</option>
6288+ <option value="2" data-i18n="NOT ANY">NOT ANY</option>
6289+ </select>
6290+ </div>
6291+ <div class="world_entry_form_control keysecondary flex1">
6292+ <small class="displayNone">
6293+ <span data-i18n="(ignored if empty)">
6294+ (ignored if empty)
6295+ </span>
6296+ </small>
6297+ <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small>
6298+ <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select>
6299+ <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea>
6300+ <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6301+ ⌨️
6302+ </button>
6303+ </div>
6304+ </div>
6305+ <div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter">
6306+ <div class="world_entry_form_control flex1">
6307+ <small class="textAlignCenter" data-i18n="Scan Depth">Scan Depth</small>
6308+ <input class="text_pole margin0" name="scanDepth" type="number" placeholder="Use global setting" data-i18n="[placeholder]Use global setting" max="1000">
6309+ </div>
6310+ <div class="world_entry_form_control flex1">
6311+ <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
6312+ <select name="caseSensitive" class="text_pole widthNatural margin0">
6313+ <option value="null" data-i18n="Use global">Use global</option>
6314+ <option value="true" data-i18n="Yes">Yes</option>
6315+ <option value="false" data-i18n="No">No</option>
6316+ </select>
6317+ </div>
6318+ <div class="world_entry_form_control flex1">
6319+ <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
6320+ <select name="matchWholeWords" class="text_pole widthNatural margin0">
6321+ <option value="null" data-i18n="Use global">Use global</option>
6322+ <option value="true" data-i18n="Yes">Yes</option>
6323+ <option value="false" data-i18n="No">No</option>
6324+ </select>
6325+ </div>
6326+ <div class="world_entry_form_control flex1">
6327+ <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
6328+ <select name="useGroupScoring" class="text_pole widthNatural margin0">
6329+ <option value="null" data-i18n="Use global">Use global</option>
6330+ <option value="true" data-i18n="Yes">Yes</option>
6331+ <option value="false" data-i18n="No">No</option>
6332+ </select>
6333+ </div>
6334+ <div class="world_entry_form_control flex1" title="Can be used to automatically activate Quick Replies" data-i18n="[title]Can be used to automatically activate Quick Replies">
6335+ <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
6336+ <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
6337+ </div>
6338+ <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">
6339+ <small class="textAlignCenter">
6340+ <span data-i18n="Recursion Level">Recursion Level</span>
6341+ <div class="fa-solid fa-circle-info opacity50p"></div>
6342+ </small>
6343+ <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
6344+ </div>
6345+ </div>
6346+ <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
6347+ <div class="world_entry_form_control flex1">
6348+ <label for="content ">
6349+ <small>
6350+ <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
6351+ <span class="alignitemscenter flex-container">
6352+ <span data-i18n="Content" class="mdhotkey_location">
6353+ Content
6354+ </span>
6355+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
6356+ </span>
6357+ <span>
6358+ (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
6359+ <span class="world_entry_form_uid_value" data-first-run="true"></span>
6360+ </span>
6361+ <div>
6362+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6363+ <input type="checkbox" name="excludeRecursion" />
6364+ <span data-i18n="Non-recursable (will not be activated by another)">
6365+ Non-recursable (will not be activated by another)
6366+ </span>
6367+ </label>
6368+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6369+ <input type="checkbox" name="preventRecursion" />
6370+ <span data-i18n="Prevent further recursion (this entry will not activate others)">
6371+ Prevent further recursion (will not activate others)
6372+ </span>
6373+ </label>
6374+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6375+ <input type="checkbox" name="delay_until_recursion" />
6376+ <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
6377+ Delay until recursion (can only be activated on recursive checking)
6378+ </span>
6379+ </label>
6380+ </div>
6381+ </span>
6382+ </small>
6383+ <small class="displayNone">
6384+ <span data-i18n="What this keyword should mean to the AI, sent verbatim">
6385+ What this keyword should mean to the AI, sent verbatim
6386+ </span>
6387+ </small>
6388+ </label>
6389+ <textarea class="text_pole mdHotkeys" name="content" rows="8" data-i18n="[placeholder]What this keyword should mean to the AI, sent verbatim" placeholder="What this keyword should mean to the AI, sent verbatim"></textarea>
6390+ </div>
6391+ </div>
6392+ <div class="world_entry_thin_controls commentContainer">
6393+ </div>
6394+ </div>
6395+ <div class="flex-container wide100p flexGap10">
6396+ <div class="flex4 flex-container flexFlowColumn flexNoGap">
6397+ <div class="flex-container justifySpaceBetween">
6398+ <small for="group">
6399+ <span data-i18n="Inclusion Group">Inclusion Group</span>
6400+ <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
6401+ <span class="fa-solid fa-circle-question note-link-span"></span>
6402+ </a>
6403+ </small>
6404+ <label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
6405+ <input type="checkbox" name="groupOverride" />
6406+ <span>
6407+ <small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
6408+ <span data-i18n="Prioritize">Prioritize</span>
6409+ <div class="fa-solid fa-circle-info opacity50p"></div>
6410+ </small>
6411+ </span>
6412+ </label>
6413+ </div>
6414+ <div class="range-block-range">
6415+ <input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
6416+ </div>
6417+ </div>
6418+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
6419+ <div class="flex-container justifySpaceBetween marginBot5">
6420+ <small for="groupWeight" data-i18n="Group Weight">
6421+ Group Weight
6422+ </small>
6423+ </div>
6424+ <div class="range-block-range">
6425+ <input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
6426+ </div>
6427+ </div>
6428+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
6429+ <div class="flex-container justifySpaceBetween marginBot5">
6430+ <small class="flex-container alignItemsBaseline" for="sticky">
6431+ <span data-i18n="Sticky">
6432+ Sticky
6433+ </span>
6434+ <i class="fa-solid fa-comments fa-xs"></i>
6435+ </small>
6436+ </div>
6437+ <div class="range-block-range">
6438+ <input class="text_pole margin0" name="sticky" type="number" data-i18n="[placeholder]Non-sticky" placeholder="Non-sticky" min="0" max="999999">
6439+ </div>
6440+ </div>
6441+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
6442+ <div class="flex-container justifySpaceBetween marginBot5">
6443+ <small class="flex-container alignItemsBaseline" for="cooldown">
6444+ <span data-i18n="Cooldown">
6445+ Cooldown
6446+ </span>
6447+ <i class="fa-solid fa-comments fa-xs"></i>
6448+ </small>
6449+ </div>
6450+ <div class="range-block-range">
6451+ <input class="text_pole margin0" name="cooldown" type="number" data-i18n="[placeholder]No cooldown" placeholder="No cooldown" min="0" max="999999">
6452+ </div>
6453+ </div>
6454+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
6455+ <div class="flex-container justifySpaceBetween marginBot5">
6456+ <small class="flex-container alignItemsBaseline" for="delay">
6457+ <span data-i18n="Delay">
6458+ Delay
6459+ </span>
6460+ <i class="fa-solid fa-comments fa-xs"></i>
6461+ </small>
6462+ </div>
6463+ <div class="range-block-range">
6464+ <input class="text_pole margin0" name="delay" type="number" data-i18n="[placeholder]No delay" placeholder="No delay" min="0" max="999999">
6465+ </div>
6466+ </div>
6467+ </div>
6468+ <div class="flex-container wide100p flexGap10">
6469+ <div class="flex4 flex-container flexFlowColumn flexNoGap">
6470+ <div class="flex-container justifySpaceBetween">
6471+ <small for="characterFilter" data-i18n="Filter to Characters or Tags">
6472+ Filter to Characters or Tags
6473+ </small>
6474+ <label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
6475+ <input type="checkbox" name="character_exclusion" />
6476+ <span>
6477+ <small title="Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry" data-i18n="[title]Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry">
6478+ <span data-i18n="Exclude">Exclude</span>
6479+ <div class="fa-solid fa-circle-info opacity50p"></div>
6480+ </small>
6481+ </span>
6482+ </label>
6483+ </div>
6484+ <div class="range-block-range">
6485+ <select name="characterFilter" class="select2_multi_sameline" multiple>
6486+ <option value="" data-i18n="-- Characters not found --">
6487+ -- Characters not found --
6488+ </option>
6489+ </select>
6490+ </div>
6491+ </div>
6492+ </div>
6493+ <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal">
6494+ <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text">
6495+ <label class="checkbox flex-container">
6496+ <input type="checkbox" name="selective" />
6497+ <span data-i18n="Selective">Selective</span>
6498+ </label>
6499+ <label class="checkbox flex-container">
6500+ <input type="checkbox" name="useProbability" />
6501+ <span data-i18n="Use Probability">Use Probability</span>
6502+ </label>
6503+ <label class="checkbox flex-container">
6504+ <input type="checkbox" name="addMemo">
6505+ <span data-i18n="Add Memo">Add Memo</span>
6506+ </label>
6507+ </div>
6508+ </div>
6509+ <div class="inline-drawer wide100p flexFlowColumn">
6510+ <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable">
6511+ <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong>
6512+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6513+ </div>
6514+ <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px">
6515+ <small class="flex-container flex1 flexFlowColumn">
6516+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6517+ <input type="checkbox" name="matchCharacterDescription" />
6518+ <span data-i18n="Character Description">
6519+ Character Description
6520+ </span>
6521+ </label>
6522+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6523+ <input type="checkbox" name="matchCharacterPersonality" />
6524+ <span data-i18n="Character Personality">
6525+ Character Personality
6526+ </span>
6527+ </label>
6528+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6529+ <input type="checkbox" name="matchScenario" />
6530+ <span data-i18n="Scenario">
6531+ Scenario
6532+ </span>
6533+ </label>
6534+ </small>
6535+ <small class="flex-container flex1 flexFlowColumn">
6536+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6537+ <input type="checkbox" name="matchPersonaDescription" />
6538+ <span data-i18n="Persona Description">
6539+ Persona Description
6540+ </span>
6541+ </label>
6542+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6543+ <input type="checkbox" name="matchCharacterDepthPrompt" />
6544+ <span data-i18n="Character's Note">
6545+ Character's Note
6546+ </span>
6547+ </label>
6548+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6549+ <input type="checkbox" name="matchCreatorNotes" />
6550+ <span data-i18n="Creator's Notes">
6551+ Creator's Notes
6552+ </span>
6553+ </label>
6554+ </small>
6555+ </div>
6556+ </div>
6557+ </div>
60896558 <div class="world_entry">
60906559 <form class="world_entry_form wi-card-entry">
60916560 <div class="inline-drawer wide100p">
@@ -6158,297 +6627,8 @@
61586627 <i class="menu_button duplicate_entry_button fa-solid fa-paste" title="Duplicate world info entry" data-i18n="[title]Duplicate world info entry" type="submit" value=""></i>
61596628 <i class="menu_button delete_entry_button fa-solid fa-trash-can" title="Delete world info entry" data-i18n="[title]Delete world info entry" type="submit" value=""></i>
61606629 </div>
61616630 <div class="inline-drawer-content inline-drawer-outlet flex-container paddingBottom5px wide100p ">
6162- <div class="flex-container wide100p alignitemscenter">
6631+ <!-- Outlet for world_entry_edit -->
6163- <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
6164- <div class="world_entry_form_control keyprimary flex1">
6165- <small class="displayNone">
6166- <span data-i18n="Comma separated (required)">
6167- Comma separated (required)
6168- </span>
6169- </small>
6170- <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small>
6171- <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select>
6172- <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea>
6173- <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6174- ⌨️
6175- </button>
6176- </div>
6177- <div class="world_entry_form_control">
6178- <small class="textAlignCenter" data-i18n="Logic">Logic</small>
6179- <select name="entryLogicType" class="text_pole widthFitContent margin0">
6180- <option value="0" data-i18n="AND ANY">AND ANY</option>
6181- <option value="3" data-i18n="AND ALL">AND ALL</option>
6182- <option value="1" data-i18n="NOT ALL">NOT ALL</option>
6183- <option value="2" data-i18n="NOT ANY">NOT ANY</option>
6184- </select>
6185- </div>
6186- <div class="world_entry_form_control keysecondary flex1">
6187- <small class="displayNone">
6188- <span data-i18n="(ignored if empty)">
6189- (ignored if empty)
6190- </span>
6191- </small>
6192- <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small>
6193- <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select>
6194- <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea>
6195- <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6196- ⌨️
6197- </button>
6198- </div>
6199- </div>
6200- <div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter">
6201- <div class="world_entry_form_control flex1">
6202- <small class="textAlignCenter" data-i18n="Scan Depth">Scan Depth</small>
6203- <input class="text_pole margin0" name="scanDepth" type="number" placeholder="Use global setting" data-i18n="[placeholder]Use global setting" max="1000">
6204- </div>
6205- <div class="world_entry_form_control flex1">
6206- <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
6207- <select name="caseSensitive" class="text_pole widthNatural margin0">
6208- <option value="null" data-i18n="Use global">Use global</option>
6209- <option value="true" data-i18n="Yes">Yes</option>
6210- <option value="false" data-i18n="No">No</option>
6211- </select>
6212- </div>
6213- <div class="world_entry_form_control flex1">
6214- <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
6215- <select name="matchWholeWords" class="text_pole widthNatural margin0">
6216- <option value="null" data-i18n="Use global">Use global</option>
6217- <option value="true" data-i18n="Yes">Yes</option>
6218- <option value="false" data-i18n="No">No</option>
6219- </select>
6220- </div>
6221- <div class="world_entry_form_control flex1">
6222- <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
6223- <select name="useGroupScoring" class="text_pole widthNatural margin0">
6224- <option value="null" data-i18n="Use global">Use global</option>
6225- <option value="true" data-i18n="Yes">Yes</option>
6226- <option value="false" data-i18n="No">No</option>
6227- </select>
6228- </div>
6229- <div class="world_entry_form_control flex1" title="Can be used to automatically activate Quick Replies" data-i18n="[title]Can be used to automatically activate Quick Replies">
6230- <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
6231- <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
6232- </div>
6233- <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">
6234- <small class="textAlignCenter">
6235- <span data-i18n="Recursion Level">Recursion Level</span>
6236- <div class="fa-solid fa-circle-info opacity50p"></div>
6237- </small>
6238- <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
6239- </div>
6240- </div>
6241- <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
6242- <div class="world_entry_form_control flex1">
6243- <label for="content ">
6244- <small>
6245- <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
6246- <span class="alignitemscenter flex-container">
6247- <span data-i18n="Content" class="mdhotkey_location">
6248- Content
6249- </span>
6250- <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
6251- </span>
6252- <span>
6253- (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
6254- <span class="world_entry_form_uid_value" data-first-run="true"></span>
6255- </span>
6256- <div>
6257- <label class="checkbox flex-container alignitemscenter flexNoGap">
6258- <input type="checkbox" name="exclude_recursion" />
6259- <span data-i18n="Non-recursable (will not be activated by another)">
6260- Non-recursable (will not be activated by another)
6261- </span>
6262- </label>
6263- <label class="checkbox flex-container alignitemscenter flexNoGap">
6264- <input type="checkbox" name="prevent_recursion" />
6265- <span data-i18n="Prevent further recursion (this entry will not activate others)">
6266- Prevent further recursion (will not activate others)
6267- </span>
6268- </label>
6269- <label class="checkbox flex-container alignitemscenter flexNoGap">
6270- <input type="checkbox" name="delay_until_recursion" />
6271- <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
6272- Delay until recursion (can only be activated on recursive checking)
6273- </span>
6274- </label>
6275- </div>
6276- </span>
6277- </small>
6278- <small class="displayNone">
6279- <span data-i18n="What this keyword should mean to the AI, sent verbatim">
6280- What this keyword should mean to the AI, sent verbatim
6281- </span>
6282- </small>
6283- </label>
6284- <textarea class="text_pole mdHotkeys" name="content" rows="8" data-i18n="[placeholder]What this keyword should mean to the AI, sent verbatim" placeholder="What this keyword should mean to the AI, sent verbatim"></textarea>
6285- </div>
6286- </div>
6287- <div class="world_entry_thin_controls commentContainer">
6288- </div>
6289- </div>
6290- <div class="flex-container wide100p flexGap10">
6291- <div class="flex4 flex-container flexFlowColumn flexNoGap">
6292- <div class="flex-container justifySpaceBetween">
6293- <small for="group">
6294- <span data-i18n="Inclusion Group">Inclusion Group</span>
6295- <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
6296- <span class="fa-solid fa-circle-question note-link-span"></span>
6297- </a>
6298- </small>
6299- <label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
6300- <input type="checkbox" name="groupOverride" />
6301- <span>
6302- <small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
6303- <span data-i18n="Prioritize">Prioritize</span>
6304- <div class="fa-solid fa-circle-info opacity50p"></div>
6305- </small>
6306- </span>
6307- </label>
6308- </div>
6309- <div class="range-block-range">
6310- <input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
6311- </div>
6312- </div>
6313- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
6314- <div class="flex-container justifySpaceBetween marginBot5">
6315- <small for="groupWeight" data-i18n="Group Weight">
6316- Group Weight
6317- </small>
6318- </div>
6319- <div class="range-block-range">
6320- <input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
6321- </div>
6322- </div>
6323- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
6324- <div class="flex-container justifySpaceBetween marginBot5">
6325- <small class="flex-container alignItemsBaseline" for="sticky">
6326- <span data-i18n="Sticky">
6327- Sticky
6328- </span>
6329- <i class="fa-solid fa-comments fa-xs"></i>
6330- </small>
6331- </div>
6332- <div class="range-block-range">
6333- <input class="text_pole margin0" name="sticky" type="number" data-i18n="[placeholder]Non-sticky" placeholder="Non-sticky" min="0" max="999999">
6334- </div>
6335- </div>
6336- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
6337- <div class="flex-container justifySpaceBetween marginBot5">
6338- <small class="flex-container alignItemsBaseline" for="cooldown">
6339- <span data-i18n="Cooldown">
6340- Cooldown
6341- </span>
6342- <i class="fa-solid fa-comments fa-xs"></i>
6343- </small>
6344- </div>
6345- <div class="range-block-range">
6346- <input class="text_pole margin0" name="cooldown" type="number" data-i18n="[placeholder]No cooldown" placeholder="No cooldown" min="0" max="999999">
6347- </div>
6348- </div>
6349- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
6350- <div class="flex-container justifySpaceBetween marginBot5">
6351- <small class="flex-container alignItemsBaseline" for="delay">
6352- <span data-i18n="Delay">
6353- Delay
6354- </span>
6355- <i class="fa-solid fa-comments fa-xs"></i>
6356- </small>
6357- </div>
6358- <div class="range-block-range">
6359- <input class="text_pole margin0" name="delay" type="number" data-i18n="[placeholder]No delay" placeholder="No delay" min="0" max="999999">
6360- </div>
6361- </div>
6362- </div>
6363- <div class="flex-container wide100p flexGap10">
6364- <div class="flex4 flex-container flexFlowColumn flexNoGap">
6365- <div class="flex-container justifySpaceBetween">
6366- <small for="characterFilter" data-i18n="Filter to Characters or Tags">
6367- Filter to Characters or Tags
6368- </small>
6369- <label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
6370- <input type="checkbox" name="character_exclusion" />
6371- <span>
6372- <small title="Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry" data-i18n="[title]Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry">
6373- <span data-i18n="Exclude">Exclude</span>
6374- <div class="fa-solid fa-circle-info opacity50p"></div>
6375- </small>
6376- </span>
6377- </label>
6378- </div>
6379- <div class="range-block-range">
6380- <select name="characterFilter" class="select2_multi_sameline" multiple>
6381- <option value="" data-i18n="-- Characters not found --">
6382- -- Characters not found --
6383- </option>
6384- </select>
6385- </div>
6386- </div>
6387- </div>
6388- <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal">
6389- <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text ">
6390- <label class="checkbox flex-container">
6391- <input type="checkbox" name="selective" />
6392- <span data-i18n="Selective">Selective</span>
6393- </label>
6394- <label class="checkbox flex-container">
6395- <input type="checkbox" name="useProbability" />
6396- <span data-i18n="Use Probability">Use Probability</span>
6397- </label>
6398- <label class="checkbox flex-container">
6399- <input type="checkbox" name="addMemo">
6400- <span data-i18n="Add Memo">Add Memo</span>
6401- </label>
6402- </div>
6403- </div>
6404- <div class="inline-drawer wide100p flexFlowColumn">
6405- <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable">
6406- <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong>
6407- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6408- </div>
6409- <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px">
6410- <small class="flex-container flex1 flexFlowColumn">
6411- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6412- <input type="checkbox" name="matchCharacterDescription" />
6413- <span data-i18n="Character Description">
6414- Character Description
6415- </span>
6416- </label>
6417- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6418- <input type="checkbox" name="matchCharacterPersonality" />
6419- <span data-i18n="Character Personality">
6420- Character Personality
6421- </span>
6422- </label>
6423- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6424- <input type="checkbox" name="matchScenario" />
6425- <span data-i18n="Scenario">
6426- Scenario
6427- </span>
6428- </label>
6429- </small>
6430- <small class="flex-container flex1 flexFlowColumn">
6431- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6432- <input type="checkbox" name="matchPersonaDescription" />
6433- <span data-i18n="Persona Description">
6434- Persona Description
6435- </span>
6436- </label>
6437- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6438- <input type="checkbox" name="matchCharacterDepthPrompt" />
6439- <span data-i18n="Character's Note">
6440- Character's Note
6441- </span>
6442- </label>
6443- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6444- <input type="checkbox" name="matchCreatorNotes" />
6445- <span data-i18n="Creator's Notes">
6446- Creator's Notes
6447- </span>
6448- </label>
6449- </small>
6450- </div>
6451- </div>
64526632 </div>
64536633 </div>
64546634 </form>
@@ -6587,7 +6767,6 @@
65876767 <span id="completion_prompt_manager_popup_entry_source"></span>
65886768 </div>
65896769 <textarea id="completion_prompt_manager_popup_entry_form_prompt" class="text_pole" name="prompt" placeholder="The prompt to be sent." data-i18n="[placeholder]The prompt to be sent."></textarea>
6590- </textarea>
65916770 </div>
65926771 <div class="completion_prompt_manager_popup_entry_form_footer">
65936772 <a id="completion_prompt_manager_popup_entry_form_close" title="close" data-i18n="[title]close" class="fa-solid fa-close menu_button"></a>
@@ -7347,6 +7526,9 @@
73477526 </div>
73487527 </div>
73497528
7529+ <!-- Area for hidden data lists for secrets autosuggests -->
7530+ <div id="secrets_datalists" class="displayNone"></div>
7531+
73507532 <!-- Script includes -->
73517533 <script src="lib/polyfill.js"></script>
73527534 <script src="lib/jquery-3.5.1.min.js"></script>
public/locales/ar-sa.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "رابط PNG المباشر (راجع",
13801380 "char_import_7": "للمضيفين المسموح بهم)",
13811381 "char_import_8": "شخصية RisuRealm (رابط مباشر)",
1382+ "char_import_9": "شخصية Soulkyn (رابط مباشر)",
13821383 "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.",
13831384 "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.",
13841385 "Export for character": "تصدير للشخصية",
public/locales/de-de.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Direkter PNG-Link (siehe",
13801380 "char_import_7": "für erlaubte Hosts)",
13811381 "char_import_8": "RisuRealm-Charakter (Direktlink)",
1382+ "char_import_9": "Soulkyn-Charakter (Direktlink)",
13821383 "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.",
13831384 "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.",
13841385 "Export for character": "Export für Zeichen",
public/locales/es-es.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Enlace PNG directo (consulte",
13801380 "char_import_7": "para hosts permitidos)",
13811381 "char_import_8": "Personaje RisuRealm (Enlace directo)",
1382+ "char_import_9": "Personaje Soulkyn (Enlace directo)",
13821383 "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.",
13831384 "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.",
13841385 "Export for character": "Exportar para personaje",
public/locales/fr-fr.json+1 -0
@@ -1301,6 +1301,7 @@
13011301 "char_import_6": "Lien PNG direct (voir",
13021302 "char_import_7": "pour les hôtes autorisés)",
13031303 "char_import_8": "Personnage de RisuRealm (lien direct)",
1304+ "char_import_9": "Personnage de Soulkyn (lien direct)",
13041305 "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.",
13051306 "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.",
13061307 "Export for character": "Exportation pour le personnage",
public/locales/is-is.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Beinn PNG hlekkur (sjá",
13801380 "char_import_7": "fyrir leyfilega gestgjafa)",
13811381 "char_import_8": "RisuRealm karakter (beinn hlekkur)",
1382+ "char_import_9": "Soulkyn karakter (beinn hlekkur)",
13821383 "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.",
13831384 "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.",
13841385 "Export for character": "Flytja út fyrir persónu",
public/locales/it-it.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Collegamento PNG diretto (fare riferimento a",
13801380 "char_import_7": "per gli host consentiti)",
13811381 "char_import_8": "Personaggio RisuRealm (collegamento diretto)",
1382+ "char_import_9": "Personaggio Soulkyn (collegamento diretto)",
13821383 "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.",
13831384 "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.",
13841385 "Export for character": "Esporta per carattere",
public/locales/ja-jp.json+1 -0
@@ -1381,6 +1381,7 @@
13811381 "char_import_6": "直接PNGリンク(参照",
13821382 "char_import_7": "許可されたホストの場合)",
13831383 "char_import_8": "RisuRealm キャラクター (直接リンク)",
1384+ "char_import_9": "Soulkyn キャラクター (直接リンク)",
13841385 "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。",
13851386 "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",
13861387 "Export for character": "キャラクターのエクスポート",
public/locales/ko-kr.json+1 -0
@@ -1398,6 +1398,7 @@
13981398 "char_import_6": "직접 PNG 링크(참조",
13991399 "char_import_7": "허용된 호스트의 경우)",
14001400 "char_import_8": "RisuRealm 캐릭터 (직접링크)",
1401+ "char_import_9": "Soulkyn 캐릭터 (직접링크)",
14011402 "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.",
14021403 "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.",
14031404 "Export for character": "캐릭터 내보내기",
public/locales/nl-nl.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Directe PNG-link (zie",
13801380 "char_import_7": "voor toegestane hosts)",
13811381 "char_import_8": "RisuRealm-personage (directe link)",
1382+ "char_import_9": "Soulkyn-personage (directe link)",
13821383 "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.",
13831384 "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.",
13841385 "Export for character": "Exporteren voor karakter",
public/locales/pt-pt.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Link PNG direto (consulte",
13801380 "char_import_7": "para hosts permitidos)",
13811381 "char_import_8": "Personagem RisuRealm (link direto)",
1382+ "char_import_9": "Personagem Soulkyn (link direto)",
13821383 "Supports importing multiple characters.": "Suporta importação de vários caracteres.",
13831384 "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.",
13841385 "Export for character": "Exportar para personagem",
public/locales/ru-ru.json+144 -4
@@ -105,7 +105,7 @@
105105 "Adjust context size to worker capabilities": "Подстраивать размер контекста под возможности рабочих машин",
106106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
107107 "API key": "API-ключ",
108108 "Tabby API key": "Ключ от Tabby API-ключ",
109109 "Get it here:": "Получите здесь:",
110110 "Register": "Зарегистрироваться",
111111 "TogetherAI Model": "Модель TogetherAI",
@@ -275,7 +275,7 @@
275275 "Chat History": "История чатов",
276276 "Content": "Содержание",
277277 "Selective": "Выборочно",
278278 "Disable": "ОтключеноОткл.",
279279 "Back to parent chat": "Вернуться в основной чат",
280280 "Convert to group": "Сделать групповым",
281281 "Start new chat": "Начать новый чат",
@@ -509,7 +509,7 @@
509509 "Restore default prompt": "Восстановить станндартный промпт",
510510 "New preset": "Новый пресет",
511511 "Delete preset": "Удалить пресет",
512512 "API Connections": "СоединенияСоединение с API",
513513 "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.",
514514 "Clear your API key": "Стереть ключ от API",
515515 "Refresh models": "Обновить модели",
@@ -965,6 +965,7 @@
965965 "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",
966966 "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",
967967 "char_import_7": ")",
968+ "char_import_9": "Персонаж с Soulkyn (прямая ссылка)",
968969 "Grammar String": "Грамматика",
969970 "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
970971 "Account": "Аккаунт",
@@ -2418,5 +2419,144 @@
24182419 "Update enabled": "Обновить включенные",
24192420 "Could not connect to API": "Не удалось подключиться к API",
24202421 "Connected to API": "Соединение с API установлено",
24212422 "Go back": "Назад",
2423+ "Recent Chats": "Недавние чаты",
2424+ "Docs": "Документация",
2425+ "Temporary Chat": "Временный чат",
2426+ "Load": "Загрузить",
2427+ "Import from JSONL": "Импорт из формата JSONL",
2428+ "**Hint:** Set any character as your welcome page assistant from their \"More...\" menu.": "**Подсказка**: задайте персонажа, который будет встречать вас на главной странице. Для этого зайдите в меню \"Ещё...\" в интерфейсе карточки.",
2429+ "[The chat is empty]": "[Чат пуст]",
2430+ "Show more recent chats": "Больше недавних чатов",
2431+ "Show less recent chats": "Меньше недавних чатов",
2432+ "Rename chat": "Переименовать чат",
2433+ "Delete chat": "Удалить чат",
2434+ "Hide recent chats": "Скрыть недавние чаты",
2435+ "Show recent chats": "Показать недавние чаты",
2436+ "Set / Unset as Welcome Page Assistant": "Установить / снять с роли помощника на экране приветствия",
2437+ "${0} is a system assistant. Choose another character.": "Персонаж ${0} выбран в качестве системного помощника. Выберите другого.",
2438+ "Set ${0} as your assistant.": "Персонаж ${0} установлен в качестве помощника.",
2439+ "${0} is no longer your assistant.": "Персонаж ${0} снят с роли помощника.",
2440+ "Manage API keys": "Управление ключами",
2441+ "Key:": "Ключ:",
2442+ "No secrets saved.": "Ключей нет.",
2443+ "Add Secret": "Добавить ключ",
2444+ "Enter the secret value:": "Ключ:",
2445+ "Enter a label for the secret (optional):": "Название ключа (необязательно):",
2446+ "Copy ID": "Копировать ID",
2447+ "Rename Secret": "Переименовать ключ",
2448+ "Enter new label for the secret:": "Новое название ключа:",
2449+ "Delete Secret: ${0}": "Удалить ключ ${0}",
2450+ "Are you sure you want to delete this secret? This action cannot be undone.": "Вы точно хотите удалить этот ключ? Отменить будет невозможно.",
2451+ "Secret ID copied to clipboard.": "ID ключа скопирован в буфер обмена.",
2452+ "The key exposure might be disabled by the server config.": "Вероятно, в настройках сервера отключена возможность раскрытия ключей.",
2453+ "Failed to copy secret value": "Не удалось скопировать значение",
2454+ "Select": "Выделить",
2455+ "Allow compressing requests by removing messages from the middle of the prompt.": "Включает возможность сжатия запроса путём удаления сообщений из середины промпта.",
2456+ "Allow": "Разрешить",
2457+ "Forbid": "Запретить",
2458+ "Auto": "Авто",
2459+ "Max prompt cost:": "Макс. стоимость промпта:",
2460+ "Unknown": "Неизвестно",
2461+ "enable_functions_desc_4": "Не работает, если выбрана постобработка промпта с отключенными функциями!",
2462+ "This setting affects visibility only.": "Влияет только на видимость.",
2463+ "openrouter_web_search_fee": "Платная опция! Добавляет $0,02 к стоимости каждого промпта.",
2464+ "prompt_post_processing_semi_tools": "Semi-strict (чередовать роли, функции включены)",
2465+ "prompt_post_processing_strict_tools": "Strict (чередовать роли, сначала пользователь; функции включены)",
2466+ "prompt_post_processing_single": "Только одно сообщение пользователя (функции отключены)",
2467+ "prompt_post_processing_merge_tools": "Объединять идущие подряд сообщение с одной ролью (функции включены)",
2468+ "completions note prefix": "Постфикс ",
2469+ "suffix will be added automatically.": "будет добавлен автоматически.",
2470+ "Tabby Model": "Модель Tabby",
2471+ "Experimental feature. Use at your own risk.": "Экспериментальная возможность. Используйте на собственный риск.",
2472+ "Tabby model hint prefix": "Чтобы иметь возможность менять модели на лету, укажите параметр ",
2473+ "must be set in Tabby's config.yml to switch models.": " в файле config.yml на сервере Tabby.",
2474+ "Use an admin API key.": "Используйте admin-ключ.",
2475+ "Use OpenRouter website setting": "Использовать указанную на сайте OpenRouter",
2476+ "Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.": "Отводит часть ответа под рассуждения (Минимальные: 10%, Обычные: 25%, Подробные: 50%, Максимальные: 95%), но минимум 1024 токена. При выборе значение Авто рассуждения не запрашиваются.",
2477+ "Use system prompt": "Включить системный промпт",
2478+ "Send inline videos": "Отправлять inline-видео",
2479+ "video_inlining_hint_1": "Отправляет модели видео, если она поддерживает такую возможность. Чтобы прикрепить видео к чату, используйте на любом сообщении кнопку",
2480+ "video_inlining_hint_2": "либо меню",
2481+ "video_inlining_hint_3": ".",
2482+ "video_inlining_hint_4": "Видео должно весить не более 20 Мб и длиться не дольше 1 минуты.",
2483+ "Allocates a portion of the response length for thinking (Flash 2.5/Pro 2.5) (min: 0/128 tokens, low: 10%, medium: 25%, high: 50%, max: 24576/32768 tokens). Auto lets the model decide.": "Отводит часть ответа под рассуждения (Flash 2.5/Pro 2.5) (Минимальные: 0/128 токенов, Поверхностные: 10%, Обычные: 25%, Подробные: 50%, Максимальные: 24576/32768 токенов). При выборе значения Авто модель определяет объём самостоятельно.",
2484+ "Google Vertex AI Configuration": "Настройки Google Vertex AI",
2485+ "Authentication Mode": "Режим аутентификации:",
2486+ "Express Mode (API Key)": "Express Mode (ключ от API)",
2487+ "Full Version (Service Account)": "Полновесный (аккаунт на сервисе)",
2488+ "Google Vertex AI API Key": "Ключ от Google Vertex AI",
2489+ "Service Account Configuration": "Настройки аккаунта",
2490+ "Region": "Регион",
2491+ "Service Account JSON Content": "Данные аккаунта в формате JSON",
2492+ "Validate JSON": "Провалидировать JSON",
2493+ "View available regions and models": "Посмотреть список доступных регионов и моделей",
2494+ "Click to Edit": "Редактирование по клику",
2495+ "Click the message text in the chat log to edit it.": "Нажмите на текст сообщения в чате, чтобы приступить к редактированию.",
2496+ "Rounded": "Закруглённые",
2497+ "Notifications:": "Уведомления:",
2498+ "Top Left": "Слева сверху",
2499+ "Top Center": "Сверху по центру",
2500+ "Top Right": "Справа сверху",
2501+ "Bottom Left": "Снизу слева",
2502+ "Bottom Center": "Снизу по центру",
2503+ "Bottom Right": "Снизу справа",
2504+ "Additional Matching Sources": "Доп. места для поиска",
2505+ "Character Personality": "Резюме по личности персонажа",
2506+ "Cap the number of entry activation recursions": "Макс. глубина рекурсии при активации записи другой записью",
2507+ "Animated background thumbnails": "Статичное превью анимированных фонов",
2508+ "Make animated backgrounds static in the selection menu. This will reduce UI and network lag if you have a large amount. This is only a change for the selection menu.": "Отключить анимацию фонов при их предпросмотре в меню выбора. Может помочь снизить объём лагов интерфейса и/или интернет-соединения. Затрагивает только меню выбора.",
2509+ "Clean-Up": "Уборка",
2510+ "Find and delete backups, unused chats, files, images, etc.": "Найти и удалить бэкапы, неиспользуемые чаты, файлы, картинки и т.д.",
2511+ "Once deleted, the files will be gone forever!": "Восстановить удалённые файлы будет невозможно!",
2512+ "Make sure to back up your data in advance.": "Перед удалением не забывайте делать копии.",
2513+ "No results yet. Tap 'Scan' to start scanning.": "Пока ничего не найдено. Нажмите \"Начать сканирование\", чтобы запустить процесс поиска.",
2514+ "Scan": "Начать сканирование",
2515+ "Chats": "Чаты",
2516+ "Chat files associated with deleted characters.": "Чаты с удалёнными персонажами.",
2517+ "Group Chats": "Групповые чаты",
2518+ "Chat files associated with deleted groups.": "Чаты с удалёнными группами.",
2519+ "Chat Backups": "Копии чатов",
2520+ "Automatically generated chat backups.": "Автоматически создаваемые бэкапы чатов.",
2521+ "Settings Backups": "Копии настроек",
2522+ "Automatically generated settings backups.": "Автоматически создаваемые бэкапы настроек.",
2523+ "Delete all items in this category": "Удалить всё в этой категории",
2524+ "View item content": "Посмотреть содержимое",
2525+ "Download item": "Скачать содержимое",
2526+ "Delete this item": "Удалить содержимое",
2527+ "This will permanently delete the file. THIS CANNOT BE UNDONE!": "Файл будет удалён навсегда. ОТМЕНИТЬ БУДЕТ НЕВОЗМОЖНО!",
2528+ "This will permanently delete all files in this category. THIS CANNOT BE UNDONE!": "Все файлы в этой категории будут удалены. ОТМЕНИТЬ БУДЕТ НЕВОЗМОЖНО!",
2529+ "Close popup": "Закрыть окно",
2530+ "Branch or tag name (optional)": "Ветка или тэг (необязательно)",
2531+ "ext_regex_bulk_edit": "Массовое редактирование",
2532+ "Enable": "Вкл.",
2533+ "Provided free of charge by Pollinations.AI": "Предоставляется бесплатно сервисом Pollinations.AI",
2534+ "Avoid sending sensitive information. Provider's outputs may include ads.": "Не отправляйте чувствительную информацию. Тексты от провайдера могут содержать рекламу.",
2535+ "Pollinations Model": "Модель Pollinations",
2536+ "Character details are hidden.": "Информация о персонаже скрыта.",
2537+ "Allow / Forbid the use of global styles for this character.": "Разрешить/запретить использование персонажем глобальных стилей.",
2538+ "Choose how to apply CSS style tags if they are defined in Creator's Notes of this character:": "Выберите, к чему применять CSS-теги, объявленные в Заметках создателя у этого персонажа:",
2539+ "CAUTION: Malformed styles may cause issues.": "ОСТОРОЖНО: некорректно размеченные стили могут приводить к неожиданному поведению.",
2540+ "Just to Creator's Notes": "Только к Заметкам создателя",
2541+ "Apply to the entire app": "Ко всему приложению",
2542+ "Pin greeting message styles": "CSS из первого сообщения активен всегда",
2543+ "Always render style tags from greetings, even if the message is unloaded due to lazy loading.": "Всегда учитывать CSS-стили из первого сообщения, даже если оно на данный момент скрыто механизмом ленивой загрузки.",
2544+ "Secret": "Ключ",
2545+ "Save and connect": "Сохранить и подключиться",
2546+ "No scripts found": "Скриптов нет",
2547+ "Are you sure you want to delete this regex script?": "Вы точно хотите удалить этот скрипт?",
2548+ "Are you sure you want to move this regex script to scoped?": "Вы точно хотите сделать этот скрипт локальным?",
2549+ "Cannot edit scoped scripts in group chats.": "Локальные скрипты невозможно редактировать в групповом чате.",
2550+ "No character selected.": "Не выбран персонаж.",
2551+ "Are you sure you want to move this regex script to global?": "Вы точно хотите сделать этот скрипт глобальным?",
2552+ "Could not save regex script: The script name was undefined or empty!": "Не удалось сохранить regex-скрипт: Не указано либо не определено название скрипта!",
2553+ "This regex script will not work, but was saved anyway: A find regex isn't present.": "Этот regex не будет работать, но всё равно сохранён: Не указано рег. выражение для поиска",
2554+ "Ephemerality": "Иллюзорность",
2555+ "This regex script will not work, but was saved anyway: One \"Affects\" checkbox must be selected!": "Этот regex не будет работать, но всё равно сохранён: Должна быть отмечена хотя бы одна галочка в блоке \"Затрагивает\"!",
2556+ "This character has embedded regex script(s).": "У этого персонажа имеются встроенные regex скрипты.",
2557+ "Would you like to allow using them?": "Должны ли они продолжать работу?",
2558+ "If you want to do it later, select 'Regex' from the extensions menu.": "Если захотите включить их позже, выберите в меню расширений пункт \"Regex\".",
2559+ "Are you sure you want to delete the selected regex scripts?": "Вы точно хотите удалить выбранные скрипты?",
2560+ "Regex script \"${0}\" imported.": "Успешно импортирован скрипт ${0}",
2561+ "Invalid regex object.": "Некорректный объект рег. выражения."
24222562}
public/locales/uk-ua.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Пряме посилання на PNG (див",
13801380 "char_import_7": "для дозволених хостів)",
13811381 "char_import_8": "Персонаж RisuRealm (пряме посилання)",
1382+ "char_import_9": "Персонаж Soulkyn (пряме посилання)",
13821383 "Supports importing multiple characters.": "Підтримується імпорт кількох символів.",
13831384 "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.",
13841385 "Export for character": "Експорт для персонажа",
public/locales/vi-vn.json+1 -0
@@ -1379,6 +1379,7 @@
13791379 "char_import_6": "Nhập PNG trực tiếp (tham khảo",
13801380 "char_import_7": "đối với các máy chủ được phép)",
13811381 "char_import_8": "RisuRealm (URL trực tiếp)",
1382+ "char_import_9": "Soulkyn (URL trực tiếp)",
13821383 "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.",
13831384 "Write each URL or ID into a new line.": "Viết mỗi URL hoặc ID vào một dòng mới.",
13841385 "Export for character": "Xuất cho nhân vật",
public/locales/zh-cn.json+2 -1
@@ -670,7 +670,7 @@
670670 "Square": "正方形",
671671 "Rectangle": "矩形",
672672 "Chat Style:": "聊天风格:",
673673 "Flat": "扁平\n气泡\n文档",
674674 "Bubbles": "气泡",
675675 "Document": "文档",
676676 "Specify colors for your theme.": "指定您的主题的颜色。",
@@ -1893,6 +1893,7 @@
18931893 "char_import_6": "被允许的PNG直链(请参阅",
18941894 "char_import_7": ")",
18951895 "char_import_8": "RisuRealm 角色(直链)",
1896+ "char_import_9": "Soulkyn 角色(直链)",
18961897 "Supports importing multiple characters.": "支持导入多个角色。",
18971898 "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。",
18981899 "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",
public/locales/zh-tw.json+150 -28
@@ -19,7 +19,7 @@
1919 "Delete the preset": "刪除預設設定檔",
2020 "novelaipresets": "NovelAI 預設設定檔",
2121 "Default": "預設",
2222 "openaipresets": "OpenAI 預設設定檔聊天補全預設設定檔",
2323 "Text Completion presets": "文字補全預設設定檔",
2424 "AI Module": "AI 模組",
2525 "Changes the style of the generated text.": "變更生成文字的樣式。",
@@ -506,9 +506,9 @@
506506 "Global Lore First": "全域知識書優先",
507507 "Entries can activate other entries by mentioning their keywords": "條目可以透過提及其關鍵字來啟用其他條目",
508508 "Recursive Scan": "遞迴掃描",
509509 "Lookup for the entry keys in the context will respect the case": "在上下文中查詢條目鍵將區分大小寫在上下文中查詢條目關鍵字將區分大小寫",
510510 "Case Sensitive": "區分大小寫",
511511 "If the entry key consists of only one word, it would not be matched as part of other words": "如果條目鍵僅包含一個詞如果條目關鍵字僅包含一個詞,則不會作為其他詞的一部分進行配對",
512512 "Match Whole Words": "完全配對",
513513 "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "只有符合最多鍵值數量的條目將被選中進行包含群組過濾",
514514 "Use Group Scoring": "使用群組評分",
@@ -615,9 +615,9 @@
615615 "Compact Input Area (Mobile)": "緊湊的聊天訊息輸入欄(行動版)",
616616 "In the Character Management panel, show quick selection buttons for favorited characters": "在角色管理面板中,顯示加入到最愛角色的快速選擇按鈕。",
617617 "Characters Hotswap": "角色卡快捷選單",
618618 "Enable magnification for zoomed avatar display.": "啟用放大顯示頭像啟用放大功能檢視頭像。",
619619 "Avatar Hover Magnification": "頭像懸停時的放大倍數",
620620 "Enables a magnification effect on hover when you display the zoomed avatar after clicking an avatar's image in chat.": "當你在聊天中點選頭像的圖片後當您點選訊息中的角色頭像後這會啟用滑鼠懸停時的放大效果將啟用滑鼠懸停時的放大效果。",
621621 "Show tagged character folders in the character list": "在角色列表中顯示標籤角色資料夾。",
622622 "Tags as Folders": "標籤作為資料夾",
623623 "Tags_as_Folders_desc": "標籤必須在「標籤管理」選單中標記為資料夾才可適用。點選這裡開啟。",
@@ -769,7 +769,7 @@
769769 "Name": "名稱",
770770 "Enter your name": "輸入您的名字",
771771 "Click to set a new User Name": "設定新的使用者名稱",
772772 "Click to lock your selected persona to the current chat. Click again to remove the lock.": "鎖定目前所選的使用者角色至本次聊天綁定目前所選的使用者角色至本次聊天再次點選將移除鎖定再次點選將移除綁定。",
773773 "Click to set user name for all messages": "設定所有訊息的使用者名稱",
774774 "Persona Description": "使用者角色描述",
775775 "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "範例:[{{user}} 是一個 28 歲的羅馬尼亞貓娘。]",
@@ -903,8 +903,8 @@
903903 "@ Depth": "@ 深度",
904904 "Role": "角色",
905905 "Talkativeness": "健談度",
906906 "How often the character speaks in group chats!": "角色在群組聊天中說話的頻率角色在群組聊天中的發言頻率!",
907907 "How often the character speaks in": "角色說話的頻率角色發言頻率",
908908 "group chats!": "群組聊天!",
909909 "Shy": "寡言",
910910 "Normal": "正常",
@@ -970,10 +970,10 @@
970970 "Comma separated list": "逗號分隔列表",
971971 "Switch to plaintext mode": "切換到純文字模式",
972972 "Logic": "邏輯",
973973 "AND ANY": "AND 任何且「包含任一」",
974974 "AND ALL": "AND 所有且「包含全部」",
975975 "NOT ALL": "NOT 所有且「未完全包含」",
976976 "NOT ANY": "NOT 任何且「完全不含」",
977977 "(ignored if empty)": "(如果為空則忽略)",
978978 "Optional Filter": "選填過濾器",
979979 "Keywords or Regexes (ignored if empty)": "關鍵字或正規表示式(如果為空則忽略)",
@@ -993,11 +993,11 @@
993993 "Filter to Character(s)": "角色篩選",
994994 "Character Exclusion": "角色排除",
995995 "-- Characters not found --": "-- 未找到角色 --",
996996 "Inclusion Group": "包含的群組納入群組",
997997 "Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "如果觸發多個條目納入群組可確保在同一群組中包含群組可確保一次僅啟動一組中的一個條目同時被觸發的多個條目僅有一個會被啟用。\r支援多個以逗號分隔的群組。\r\r文件r詳見 SillyTavern 官方文件世界資訊——包容性集團World Info - Inclusion Group",
998998 "Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "優先考慮此條目優先此條目選取後勾選後此條目將在所有選擇中優先此條目將在所有可選條目中被優先考慮。\r如果有多個優先順序r若有多個條目同時設為優先則選擇將選擇「順序(Order)最高的一個數值最高的條目。",
999999 "Only one entry with the same label will be activated": "僅會啟用具有相同標籤的一個條目僅啟用一個具有相同標籤的條目",
10001000 "A relative likelihood of entry activation within the group": "群組內條目啟用的相對可能性此條目在群組中被選中的相對可能性",
10011001 "Group Weight": "群組權重",
10021002 "Selective": "選擇性",
10031003 "Use Probability": "使用機率",
@@ -1134,7 +1134,7 @@
11341134 "Manage chat files": "管理聊天檔案",
11351135 "Delete messages": "刪除訊息",
11361136 "Regenerate": "重新生成",
11371137 "Ask AI to write your message for you": " AI 為您撰寫訊息扮演您的角色撰寫訊息",
11381138 "Impersonate": "AI 扮演使用者",
11391139 "Continue": "繼續",
11401140 "Bind user name to that avatar": "將使用者名稱附加到該頭像",
@@ -1384,6 +1384,7 @@
13841384 "char_import_6": "直接 PNG 連結(請參閱",
13851385 "char_import_7": "對於允許的主機)",
13861386 "char_import_8": "RisuRealm 角色(直接連結)",
1387+ "char_import_9": "Soulkyn 角色(直接連結)",
13871388 "Supports importing multiple characters.": "支援匯入多個字元。",
13881389 "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。",
13891390 "Export for character": "匯出字元",
@@ -2382,7 +2383,7 @@
23822383 "Do you want to remove these fields before exporting?": "是否要在匯出前移除這些欄位?",
23832384 "Delete the preset? This action is irreversible and your current settings will be overwritten.": "確定刪除此預設設定檔?刪除後無法復原,且此設定將被覆蓋。",
23842385 "Update all": "全部更新",
23852386 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "當所選提供者無法滿足您的請求時當所選供應商無法滿足您的請求時自動選擇替代提供者自動選擇替代供應商。",
23862387 "Use extension settings": "使用擴充功能設定",
23872388 "To use instruct formatting, switch to OpenRouter under Text Completion API.": "若要使用指令格式,請在文字補全 API 下切換至 OpenRouter。",
23882389 "Automatically 'continue' a response if the model stopped before reaching a certain amount of tokens.": "如果模型在達到一定數量的符元前停止,則自動繼續生成回應。",
@@ -2456,7 +2457,7 @@
24562457 "openai_reasoning_effort_low": "低",
24572458 "openai_reasoning_effort_medium": "中",
24582459 "openai_reasoning_effort_high": "高",
24592460 "Reasoning": "推理 Reasoning",
24602461 "reasoning_auto_parse": "自動解析主要內容中推理區塊,需定義且不為空的前綴與後綴欄位。",
24612462 "Auto-Parse": "自動解析",
24622463 "reasoning_auto_expand": "自動展開推理區塊。",
@@ -2556,7 +2557,7 @@
25562557 "Swiping a message will automatically trigger a re-summarization if it has already been summarized.": "滑動訊息後若已進行摘要,將自動觸發重新摘要。",
25572558 "Re-summarize on Swipe": "滑動後重新摘要",
25582559 "Block chat input while summarizing.": "在摘要進行時暫時禁用聊天訊息輸入。",
25592560 "Block Chat": "訊息輸入鎖定鎖定訊息輸入",
25602561 "Whether to use messages and/or summaries as context for summarization. You must use {{history}} in the summary prompt.": "決定是否在摘要中使用訊息及/或過往摘要作為背景資訊。需於摘要提示詞中,使用 {{history}}。",
25612562 "Message History": "訊息歷史",
25622563 "Messages": "僅訊息",
@@ -2630,7 +2631,7 @@
26302631 "Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "分配部分回應長度用於推理功能(低:10%、中:25%、高:50%)。其他選項依模型而異。",
26312632 "Attach a file or image to a current chat.": "附加檔案或圖片至目前的聊天。",
26322633 "Change Persona Image": "變更使用者角色頭像",
26332634 "Click to lock your selected persona to the current character. Click again to remove the lock.": "點選以將所選使用者角色鎖定目前聊天的角色點選以將所選使用者角色與目前聊天的角色綁定再次點選以解除鎖定再次點選可解除綁定。",
26342635 "Click to select this as default persona for the new chats. Click again to remove it.": "點選以將此設為新聊天的預設使用者角色,再次點選可取消設定。",
26352636 "Connected Personas": "已連結的使用者角色",
26362637 "Create a new prompt": "新增新提示詞",
@@ -2639,21 +2640,21 @@
26392640 "Duplicate Persona": "複製使用者角色",
26402641 "Expand and zoom": "展開並縮放",
26412642 "Move Entry to Another Lorebook": "將條目移動到其他知識書",
26422643 "Persona is locked to the current character": "使用者角色已鎖定至目前角色使用者角色已綁定至目前角色",
26432644 "Persona is locked to the current chat": "使用者角色已鎖定至目前聊天使用者角色已綁定至目前聊天",
26442645 "Rename a prompt": "重新命名提示詞",
26452646 "Rename Persona": "重新命名使用者角色",
26462647 "Select your current Reasoning Template": "選擇你目前使用的推理範本",
26472648 "Toggles sprite scaling for character sprites.": "切換角色立繪的縮放比例",
26482649 "When multiple personas are connected to a character, a popup will appear to select which one to use.": "當多個使用者角色連接至一個角色時,將顯示選擇彈窗。",
26492650 "When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.": "使用 LLM 或 WebLLM 分類器時,僅顯示並使用已指派的表情立繪。",
26502651 "Whenever a persona is selected, it will be locked and automatically selected when the chat is opened.": "若已選擇使用者角色,將在開啟聊天時自動進行鎖定將在開啟聊天時自動進行綁定。",
26512652 "Additional Matching Sources": "額外匹配來源",
26522653 "All Classes": "所有類別",
26532654 "Allow multiple persona connections per character": "允許每個角色連接多個使用者角色",
26542655 "Allows the model to return image attachments.": "允許模型返回圖片附件。",
26552656 "Auto-Hide Sprite Settings": "自動隱藏立繪設定",
26562657 "Auto-lock a chosen persona to the chat": "自動鎖定使用者角色至聊天中自動綁定使用者角色至聊天中",
26572658 "Character": "角色",
26582659 "Character Personality": "角色個性",
26592660 "Character Tint/Share World Tint With Characters requires Prome to be enabled.": "角色著色/與角色共享世界著色功能需啟用 Prome。",
@@ -2703,7 +2704,7 @@
27032704 "Scan depth cannot be negative": "掃描深度不能為負值",
27042705 "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}",
27052706 "Reasoning Template": "推理範本",
27062707 "OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.": "OpenAI 支援程度:低、中、高。Minimum 與 Maximum 分別等同於低與高。選擇 Auto(自動)不會傳送努力等級不會傳送推理耗費等級。",
27072708 "Branch or tag name (optional)": "分支/標籤名稱(可留空)",
27082709 "Update enabled": "僅更新啟用項目",
27092710 "Sort: Loading Order": "排序:載入順序",
@@ -2742,5 +2743,126 @@
27422743 "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "角色或對話已被刪除。系統為安全考量導回此頁,請重新選擇角色。",
27432744 "Could not connect to API": "無法連線至 API",
27442745 "Connected to API": "已成功連線至 API",
27452746 "help_macros_charDepthPrompt": "角色的 @ 深度註記",
2747+ "Bind presets to API connections": "將預設綁定至 API 連線",
2748+ "Allow compressing requests by removing messages from the middle of the prompt.": "允許透過移除提示中間的訊息來壓縮請求。",
2749+ "Unknown": "未知",
2750+ "Min Keep": "最小保留",
2751+ "enable_functions_desc_4": "當使用「無工具」提示後處理時不支援!",
2752+ "Send inline videos": "傳送內嵌影片",
2753+ "video_inlining_hint_1": "若模型支援,則在提示中傳送影片。使用",
2754+ "video_inlining_hint_2": "操作於任何訊息或使用",
2755+ "video_inlining_hint_3": "選單以上傳影片檔案到聊天中。",
2756+ "video_inlining_hint_4": "影片大小必須小於 20 MB 且長度低於 1 分鐘",
2757+ "This setting affects visibility only.": "此設定僅影響可見性。",
2758+ "Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.": "為推理部分分配回應長度(最大回應長度至少需 1024 符元,低:10%、中:25%、高:50%、最高:95%)。選擇「自動」將不請求推理。",
2759+ "Allocates a portion of the response length for thinking (Flash 2.5/Pro 2.5) (min: 0/128 tokens, low: 10%, medium: 25%, high: 50%, max: 24576/32768 tokens). Auto lets the model decide.": "為推理部分分配回應長度(Flash 2.5/Pro 2.5)(最少:0/128 符元,低:10%、中:25%、高:50%、最高:24576/32768 符元)。選擇「自動」將由模型決定。",
2760+ "Save and connect": "儲存並連線",
2761+ "Manage API keys": "管理 API 金鑰",
2762+ "For privacy reasons, your API key will be hidden after you click 'Connect'.": "出於隱私考量,點選「連線」後,將隱藏您的 API 金鑰。",
2763+ "Experimental feature. Use at your own risk.": "實驗性功能。請自行斟酌使用風險。",
2764+ "suffix will be added automatically.": "後綴將自動新增。",
2765+ "Google Vertex AI Configuration": "Google Vertex AI 設定",
2766+ "Authentication Mode": "驗證模式:",
2767+ "Express Mode (API Key)": "快速模式(API 金鑰)",
2768+ "Full Version (Service Account)": "完整版(服務帳號)",
2769+ "Google Vertex AI API Key": "Google Vertex AI API 金鑰",
2770+ "(Express mode)": "(快速模式)",
2771+ "Service Account Configuration": "服務帳號設定",
2772+ "Region": "區域:",
2773+ "View available regions and models": "檢視可用區域和模型",
2774+ "Service Account JSON Content": "服務帳號 JSON 內容:",
2775+ "For privacy reasons, your Service Account JSON content will be hidden after you click 'Validate JSON'.": "出於隱私考量,點選「驗證 JSON」將隱藏您的服務帳號 JSON 內容。",
2776+ "Validate JSON": "驗證 JSON",
2777+ "Pollinations Model": "Pollinations 模型",
2778+ "Provided free of charge by Pollinations.AI": "由 Pollinations.AI 免費提供",
2779+ "Avoid sending sensitive information. Provider's outputs may include ads.": "請避免傳送敏感資訊。供應商的輸出可能包含廣告。",
2780+ "prompt_post_processing_merge_tools": "合併連續角色(含工具)",
2781+ "prompt_post_processing_semi_tools": "半嚴格(角色交替;含工具)",
2782+ "prompt_post_processing_strict_tools": "嚴格(使用者優先,角色交替;含工具)",
2783+ "prompt_post_processing_single": "單一使用者訊息(無工具)",
2784+ "Rounded": "圓角",
2785+ "Notifications:": "系統通知:",
2786+ "Top Left": "左上",
2787+ "Top Center": "上方置中",
2788+ "Top Right": "右上",
2789+ "Bottom Left": "左下",
2790+ "Bottom Center": "下方置中",
2791+ "Bottom Right": "右下",
2792+ "Click the message text in the chat log to edit it.": "點選對話訊息文字後即可編輯內容。",
2793+ "Click to Edit": "點選以編輯聊天訊息",
2794+ "Make animated backgrounds static in the selection menu. This will reduce UI and network lag if you have a large amount. This is only a change for the selection menu.": "將選單中的動畫背景靜態化,以減少背景過多所造成的介面與網路延遲。此設定僅影響選單。",
2795+ "Animated background thumbnails": "動畫背景縮圖",
2796+ "Find and delete backups, unused chats, files, images, etc.": "搜尋並刪除備份、未使用的聊天、檔案、圖片等。",
2797+ "Clean-Up": "清理",
2798+ "Always render style tags from greetings, even if the message is unloaded due to lazy loading.": "始終渲染問候訊息中的樣式標籤,即使該訊息因延遲載入而未顯示。",
2799+ "Pin greeting message styles": "固定問候訊息樣式",
2800+ "Visibility": "可見性",
2801+ "Don't show": "不顯示",
2802+ "Input length > 1": "輸入長度 > 1",
2803+ "Always show": "始終顯示",
2804+ "(DEPRECATED)": "(已淘汰)",
2805+ "When multiple personas are connected to a character, a popup will appear to select which one to use": "當多個使用者角色綁定至同一角色時,將顯示選單以擇一。",
2806+ "Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "您所選的使用者角色將綁定至目前聊天,並於開啟此聊天檔案時自動套用。",
2807+ "Set / Unset as Welcome Page Assistant": "設定/取消為歡迎頁面助理",
2808+ "Allow / Forbid the use of global styles for this character.": "允許/禁止此角色使用全域樣式。",
2809+ "This character will be used as a welcome page assistant.": "此角色將用作歡迎頁面助理。",
2810+ "prompt_manager_order": "順序",
2811+ "prompt_manager_order_note": "來自其他來源(世界資訊、作者備註等)的提示注入預設順序為 100。",
2812+ "Ordered from low/top to high/bottom, and at same order: Assistant, User, System.": "順序從低(上)至高(下);同順序時,排列為助理 > 使用者 > 系統。",
2813+ "Source:": "來源:",
2814+ "Secondary captioning endpoint URL": "次要說明文字端點 URL",
2815+ "ext_regex_bulk_edit": "批次編輯",
2816+ "Export": "匯出",
2817+ "No scripts found": "未找到腳本",
2818+ "ext_regex_flags_help": "點選此處了解更多關於正規表達式(regex)標誌的資訊。",
2819+ "ext_regex_other_options_desc": "預設情況下,正規表達式腳本會直接且不可逆地變更聊天檔案。\r\n啟用以下任一(或兩者)選項將防止更改聊天檔案,同時仍會變更指定項目。",
2820+ "This character has embedded regex script(s).": "此角色包含嵌入式正規表達式腳本。",
2821+ "Would you like to allow using them?": "您想允許使用它們嗎?",
2822+ "If you want to do it later, select \"Regex\" from the extensions menu.": "您可稍後從擴充功能選單中啟用 「正規表達式(Regex)」。",
2823+ "SillyTavern Settings": "SillyTavern 設定",
2824+ "Documentation": "文件",
2825+ "Import from JSONL": "從 JSONL 匯入",
2826+ "Load": "載入",
2827+ "Export as JSONL": "匯出為 JSONL",
2828+ "Delete all items in this category": "刪除此分類中的所有項目",
2829+ "View item content": "檢視項目內容",
2830+ "Download item": "下載項目",
2831+ "Delete this item": "刪除此項目",
2832+ "Once deleted, the files will be gone forever!": "一旦刪除,檔案將永久消失!",
2833+ "Make sure to back up your data in advance.": "請先備份您的資料。",
2834+ "Scan": "掃描",
2835+ "No results yet. Tap 'Scan' to start scanning.": "尚無結果。點選「掃描」以開始掃描。",
2836+ "Do you want to export connection data with the preset?": "您想將連線資料與預設一起匯出嗎?",
2837+ "This includes the selected source, models, and other preferences set in the API Connections panel.": "這包括在 API 連線面板中設定的來源、模型及其他偏好設定。",
2838+ "Your stored API keys are never exported.": "您的 API 金鑰將不會被匯出。",
2839+ "Export connection data": "匯出連線資料",
2840+ "Do not export connection data": "不匯出連線資料",
2841+ "Creator's Notes contain CSS style tags. Do you want to apply them just to Creator's Notes or to the entire application?": "此創作者備註內含 CSS 樣式。請選擇是否僅套用於備註區域,或應用至整體介面。",
2842+ "CAUTION: Malformed styles may cause issues.": "注意:樣式錯誤可能導致問題。",
2843+ "To change the preference later, use the": "若想稍後更改偏好設定,請使用",
2844+ "button in the Creator's Notes block.": "創作者備註區塊中的按鈕。",
2845+ "Class names will be automatically prefixed with 'custom-'.": "CSS 類別名稱將自動套用「custom-」前綴。",
2846+ "Choose how to apply CSS style tags if they are defined in Creator's Notes of this character:": "請選擇如何套用此角色創作者備註中的 CSS 樣式:",
2847+ "Just to Creator's Notes": "僅限創作者備註",
2848+ "Apply to the entire app": "應用於整個應用程式",
2849+ "API:": "API:",
2850+ "Key:": "金鑰:",
2851+ "Add Secret": "新增密鑰",
2852+ "No secrets saved.": "尚未儲存任何密鑰。",
2853+ "Copy ID": "複製 ID",
2854+ "Select": "選擇",
2855+ "Show recent chats": "顯示最近的聊天",
2856+ "Hide recent chats": "隱藏最近的聊天",
2857+ "Recent Chats": "最近的聊天",
2858+ "Docs": "文件",
2859+ "GitHub": "GitHub",
2860+ "Discord": "Discord",
2861+ "Temporary Chat": "臨時聊天",
2862+ "No recent chats": "沒有最近的聊天",
2863+ "(Message Box)": "(訊息框)",
2864+ "[title]Create a new": "新增",
2865+ "[title]Delete a ": "刪除 ",
2866+ "[title]Rename a ": "重新命名 ",
2867+ "completions note prefix": "完成提示前綴"
27462868}
public/script.js+341 -245
@@ -10,7 +10,6 @@ import {
1010 SVGInject,
1111 Popper,
1212 initLibraryShims,
13- slideToggle,
1413 default as libs,
1514} from './lib.js';
1615
@@ -209,6 +208,7 @@ import {
209208} from './scripts/tags.js';
210209import {
211210 SECRET_KEYS,
211+ initSecrets,
212212 readSecretState,
213213 secret_state,
214214 writeSecret,
@@ -250,7 +250,20 @@ import {
250250import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
251251import { hideLoader, showLoader } from './scripts/loader.js';
252252import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
253-import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js';
253+import {
254+ loadFeatherlessModels,
255+ loadMancerModels,
256+ loadOllamaModels,
257+ loadTogetherAIModels,
258+ loadInfermaticAIModels,
259+ loadOpenRouterModels,
260+ loadVllmModels,
261+ loadAphroditeModels,
262+ loadDreamGenModels,
263+ initTextGenModels,
264+ loadTabbyModels,
265+ loadGenericModels,
266+} from './scripts/textgen-models.js';
254267import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js';
255268import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
256269import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
@@ -283,6 +296,7 @@ import { getContext } from './scripts/st-context.js';
283296import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js';
284297import { accountStorage } from './scripts/util/AccountStorage.js';
285298import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js';
299+import { initDataMaid } from './scripts/data-maid.js';
286300
287301// API OBJECT FOR EXTERNAL WIRING
288302globalThis.SillyTavern = {
@@ -482,6 +496,7 @@ export const event_types = {
482496 GENERATION_STARTED: 'generation_started',
483497 GENERATION_STOPPED: 'generation_stopped',
484498 GENERATION_ENDED: 'generation_ended',
499+ SD_PROMPT_PROCESSING: 'sd_prompt_processing',
485500 EXTENSIONS_FIRST_LOAD: 'extensions_first_load',
486501 EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded',
487502 SETTINGS_LOADED: 'settings_loaded',
@@ -541,6 +556,10 @@ export const event_types = {
541556 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
542557 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
543558 CHARACTER_MANAGEMENT_DROPDOWN: 'charManagementDropdown',
559+ SECRET_WRITTEN: 'secret_written',
560+ SECRET_DELETED: 'secret_deleted',
561+ SECRET_ROTATED: 'secret_rotated',
562+ SECRET_EDITED: 'secret_edited',
544563};
545564
546565export const eventSource = new EventEmitter([event_types.APP_READY]);
@@ -672,16 +691,8 @@ export const extension_prompt_roles = {
672691
673692export const MAX_INJECTION_DEPTH = 10000;
674693
675-const SAFETY_CHAT = [
694+// Initialized in getSystemMessages()
676- {
695+const SAFETY_CHAT = [];
677- name: systemUserName,
678- force_avatar: system_avatar,
679- is_system: true,
680- is_user: false,
681- create_date: 0,
682- mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!',
683- },
684-];
685696
686697export let system_messages = {};
687698
@@ -768,22 +779,17 @@ async function getSystemMessages() {
768779 },
769780 },
770781 };
771-}
772-
773-// Register configuration migrations
774-registerPromptManagerMigration();
775782
776-$(document).ajaxError(function myErrorHandler(_, xhr) {
783+ const safetyMessage = {
777- // Cohee: CSRF doesn't error out in multiple tabs anymore, so this is unnecessary
784+ name: systemUserName,
778- /*
785+ force_avatar: system_avatar,
779- if (xhr.status == 403) {
786+ is_system: true,
780- toastr.warning(
787+ is_user: false,
781- 'doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.',
788+ create_date: 0,
782- 'Looks like you\'ve opened SillyTavern in another browser tab',
789+ mes: t`You deleted a character/chat and arrived back here for safety reasons! Pick another character!`,
783- { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
790+ };
784- );
791+ SAFETY_CHAT.splice(0, SAFETY_CHAT.length, safetyMessage);
785- } */
792+}
786-});
787793
788794async function getClientVersion() {
789795 try {
@@ -865,7 +871,8 @@ export let create_save = {
865871 creator: '',
866872 personality: '',
867873 first_message: '',
868- avatar: '',
874+ /** @type {FileList|null} */
875+ avatar: null,
869876 scenario: '',
870877 mes_example: '',
871878 world: '',
@@ -982,11 +989,14 @@ async function firstLoadInit() {
982989 }
983990
984991 showLoader();
992+ registerPromptManagerMigration();
993+ initStandaloneMode();
985994 initLibraryShims();
986995 addShowdownPatch(showdown);
987996 reloadMarkdownProcessor();
988997 applyBrowserFixes();
989998 await getClientVersion();
999+ await initSecrets();
9901000 await readSecretState();
9911001 await initLocales();
9921002 initChatUtilities();
@@ -1026,6 +1036,7 @@ async function firstLoadInit() {
10261036 initWelcomeScreen();
10271037 await initScrapers();
10281038 initCustomSelectedSamplers();
1039+ initDataMaid();
10291040 addDebugFunctions();
10301041 doDailyExtensionUpdatesCheck();
10311042 await hideLoader();
@@ -1039,6 +1050,13 @@ async function fixViewport() {
10391050 document.body.style.position = '';
10401051}
10411052
1053+function initStandaloneMode() {
1054+ const isPwaMode = window.matchMedia('(display-mode: standalone)').matches;
1055+ if (isPwaMode) {
1056+ $('body').addClass('PWA');
1057+ }
1058+}
1059+
10421060function cancelStatusCheck(reason = 'Manually cancelled status check') {
10431061 abortStatusCheck?.abort(new AbortReason(reason));
10441062 abortStatusCheck = new AbortController();
@@ -1395,7 +1413,6 @@ export function resultCheckStatus() {
13951413 stopStatusLoading();
13961414}
13971415
1398-
13991416/**
14001417 * Switches the currently selected character to the one with the given ID. (character index, not the character key!)
14011418 *
@@ -1880,6 +1897,52 @@ async function delChat(chatfile) {
18801897 }
18811898}
18821899
1900+/**
1901+ * Deletes a character chat by its name.
1902+ * @param {string} characterId Character ID to delete chat for
1903+ * @param {string} fileName Name of the chat file to delete (without .jsonl extension)
1904+ * @returns {Promise<void>} A promise that resolves when the chat is deleted.
1905+ */
1906+export async function deleteCharacterChatByName(characterId, fileName) {
1907+ // Make sure all the data is loaded.
1908+ await unshallowCharacter(characterId);
1909+
1910+ /** @type {import('./scripts/char-data.js').v1CharData} */
1911+ const character = characters[characterId];
1912+ if (!character) {
1913+ console.warn(`Character with ID ${characterId} not found.`);
1914+ return;
1915+ }
1916+
1917+ const response = await fetch('/api/chats/delete', {
1918+ method: 'POST',
1919+ headers: getRequestHeaders(),
1920+ body: JSON.stringify({
1921+ chatfile: `${fileName}.jsonl`,
1922+ avatar_url: character.avatar,
1923+ }),
1924+ });
1925+
1926+ if (!response.ok) {
1927+ console.error('Failed to delete chat for character.');
1928+ return;
1929+ }
1930+
1931+ if (fileName === character.chat) {
1932+ const chatsResponse = await fetch('/api/characters/chats', {
1933+ method: 'POST',
1934+ headers: getRequestHeaders(),
1935+ body: JSON.stringify({ avatar_url: character.avatar }),
1936+ });
1937+ const chats = Object.values(await chatsResponse.json());
1938+ chats.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
1939+ const newChatName = chats.length && typeof chats[0] === 'object' ? chats[0].file_name.replace('.jsonl', '') : `${character.name} - ${humanizedDateTime()}`;
1940+ await updateRemoteChatName(characterId, newChatName);
1941+ }
1942+
1943+ await eventSource.emit(event_types.CHAT_DELETED, fileName);
1944+}
1945+
18831946export async function replaceCurrentChat() {
18841947 await clearChat();
18851948 chat.length = 0;
@@ -2391,7 +2454,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
23912454 const image = messageElement.find('.mes_img');
23922455 const text = messageElement.find('.mes_text');
23932456 const isInline = !!mes.extra?.inline_image;
2394- image.off('load').on('load', function () {
2457+ const doAdjustScroll = () => {
23952458 if (!adjustScroll) {
23962459 return;
23972460 }
@@ -2399,6 +2462,16 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
23992462 const newChatHeight = $('#chat').prop('scrollHeight');
24002463 const diff = newChatHeight - chatHeight;
24012464 $('#chat').scrollTop(scrollPosition + diff);
2465+ };
2466+ image.off('load').on('load', function () {
2467+ image.removeAttr('alt');
2468+ image.removeClass('error');
2469+ doAdjustScroll();
2470+ });
2471+ image.off('error').on('error', function () {
2472+ image.attr('alt', '');
2473+ image.addClass('error');
2474+ doAdjustScroll();
24022475 });
24032476 image.attr('src', mes.extra?.image);
24042477 image.attr('title', mes.extra?.title || mes.title || '');
@@ -2425,6 +2498,31 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
24252498 }
24262499 }
24272500
2501+ // Add video to message
2502+ if (mes.extra?.video) {
2503+ const container = messageElement.find('.mes_block');
2504+ const chatHeight = $('#chat').prop('scrollHeight');
2505+
2506+ // Create video element if it doesn't exist
2507+ let video = messageElement.find('.mes_video');
2508+ if (video.length === 0) {
2509+ video = $('<video class="mes_video" controls preload="metadata"></video>');
2510+ container.append(video);
2511+ }
2512+
2513+ video.off('loadedmetadata').on('loadedmetadata', function () {
2514+ if (!adjustScroll) {
2515+ return;
2516+ }
2517+ const scrollPosition = $('#chat').scrollTop();
2518+ const newChatHeight = $('#chat').prop('scrollHeight');
2519+ const diff = newChatHeight - chatHeight;
2520+ $('#chat').scrollTop(scrollPosition + diff);
2521+ });
2522+
2523+ video.attr('src', mes.extra?.video);
2524+ }
2525+
24282526 // Add file to message
24292527 if (mes.extra?.file) {
24302528 messageElement.find('.mes_file_container').remove();
@@ -2646,7 +2744,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
26462744
26472745/**
26482746 * Returns the URL of the avatar for the given character Id.
26492747 * @param {number|string} characterId Character Id
26502748 * @returns {string} Avatar URL
26512749 */
26522750export function getCharacterAvatar(characterId) {
@@ -3520,7 +3618,7 @@ class StreamingProcessor {
35203618 }
35213619
35223620 if (this.image) {
35233621 await processImageAttachment(chat[messageId], { imageUrl: this.image, parsedImage: null });
35243622 appendMediaToMessage(chat[messageId], $(this.messageDom));
35253623 }
35263624
@@ -3678,6 +3776,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
36783776 if (responseLengthCustomized) {
36793777 TempResponseLength.save(api, responseLength);
36803778 }
3779+ /** @type {object|any[]} */
36813780 let generateData = {};
36823781
36833782 switch (api) {
@@ -6027,7 +6126,11 @@ export function extractMessageFromData(data, activeApi = null) {
60276126 case 'koboldhorde':
60286127 return data.text;
60296128 case 'textgenerationwebui':
60306129 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';
6130+ ?? data.choices?.[0]?.message?.content
6131+ ?? data.content
6132+ ?? data.response
6133+ ?? '';
60316134 case 'novel':
60326135 return data.output;
60336136 case 'openai':
@@ -6263,17 +6366,11 @@ export function cleanUpMessage({ getMessage, isImpersonate, isContinue, displayI
62636366 * Adds an image to the message.
62646367 * @param {object} message Message object
62656368 * @param {object} sources Image sources
6266- * @param {ParsedImage} [sources.parsedImage] Parsed image
62676369 * @param {string} [sources.imageUrl] Image URL
62686370 *
62696371 * @returns {Promise<void>}
62706372 */
62716373async function processImageAttachment(message, { parsedImage, imageUrl }) {
6272- if (parsedImage?.image) {
6273- saveImageToMessage(parsedImage, message);
6274- return;
6275- }
6276-
62776374 if (!imageUrl) {
62786375 return;
62796376 }
@@ -6332,8 +6429,6 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63326429
63336430 let oldMessage = '';
63346431 const generationFinished = new Date();
6335- const parsedImage = extractImageFromMessage(getMessage);
6336- getMessage = parsedImage.getMessage;
63376432 if (type === 'swipe') {
63386433 oldMessage = chat[chat.length - 1]['mes'];
63396434 chat[chat.length - 1]['swipes'].length++;
@@ -6347,7 +6442,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63476442 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
63486443 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
63496444 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
63506445 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });
63516446 if (power_user.message_token_count_enabled) {
63526447 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
63536448 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -6371,7 +6466,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63716466 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
63726467 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
63736468 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
63746469 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });
63756470 if (power_user.message_token_count_enabled) {
63766471 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
63776472 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -6391,7 +6486,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63916486 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
63926487 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
63936488 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
63946489 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });
63956490 // We don't know if the reasoning duration extended, so we don't update it here on purpose.
63966491 if (power_user.message_token_count_enabled) {
63976492 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
@@ -6437,7 +6532,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
64376532 chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
64386533 }
64396534
64406535 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl: imageUrl });
64416536 const chat_id = (chat.length - 1);
64426537
64436538 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
@@ -6654,15 +6749,6 @@ function getGeneratingModel(mes) {
66546749 return model;
66556750}
66566751
6657-function extractImageFromMessage(getMessage) {
6658- const regex = /<img src="(.*?)".*?alt="(.*?)".*?>/g;
6659- const results = regex.exec(getMessage);
6660- const image = results ? results[1] : '';
6661- const title = results ? results[2] : '';
6662- getMessage = getMessage.replace(regex, '');
6663- return { getMessage, image, title, inline: true };
6664-}
6665-
66666752/**
66676753 * A function mainly used to switch 'generating' state - setting it to false and activating the buttons again
66686754 */
@@ -7073,6 +7159,11 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
70737159 }
70747160}
70757161
7162+/**
7163+ * Processes the avatar image from the input element, allowing the user to crop it if necessary.
7164+ * @param {HTMLInputElement} input - The input element containing the avatar file.
7165+ * @returns {Promise<void>}
7166+ */
70767167async function read_avatar_load(input) {
70777168 if (input.files && input.files[0]) {
70787169 if (selected_button == 'create') {
@@ -7104,7 +7195,7 @@ async function read_avatar_load(input) {
71047195 await createOrEditCharacter();
71057196 await delay(DEFAULT_SAVE_EDIT_TIMEOUT);
71067197
71077198 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0)));
71087199 await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
71097200 method: 'GET',
71107201 cache: 'no-cache',
@@ -7114,22 +7205,21 @@ async function read_avatar_load(input) {
71147205 },
71157206 });
71167207
71177208 const messages = $('.mes').each(async function toArray() {;
7118- const nameMatch = $(this).attr('ch_name') == formData.get('ch_name');
7209+ for (const el of messages) {
7119- if ($(this).attr('is_system') == 'true' && !nameMatch) {
7210+ const $el = $(el);
7120- return;
7211+ const nameMatch = $el.attr('ch_name') == formData.get('ch_name');
7121- }
7212+ if ($el.attr('is_system') == 'true' && !nameMatch) continue;
71227213 if ($(this)el.attr('is_user') == 'true') {continue;
7123- return;
7214+
7124- }
71257215 if (nameMatch) {
71267216 const previewSrc = $('#avatar_load_preview').attr('src');
71277217 const avatar = $(this)el.find('.avatar img');
71287218 avatar.attr('src', default_avatar);
71297219 await delay(1);
71307220 avatar.attr('src', previewSrc);
71317221 }
71327222 });
71337223
71347224 console.log('Avatar refreshed');
71357225 }
@@ -7979,7 +8069,7 @@ export async function getChatsFromFiles(data, isGroupChat) {
79798069 * response is an object with an `error` property set to `true`.
79808070 */
79818071export async function getPastCharacterChats(characterId = null) {
79828072 characterId = characterId ?? parseInt(this_chid);
79838073 if (!characters[characterId]) return [];
79848074
79858075 const response = await fetch('/api/characters/chats', {
@@ -8046,9 +8136,7 @@ export async function displayPastChats() {
80468136 // UX convenience: Focus the search field when the Manage Chat Files view opens.
80478137 setTimeout(function () {
80488138 const textSearchElement = $('#select_chat_search');
8049- textSearchElement.click();
8139+ textSearchElement.trigger('click').trigger('focus').trigger('select');
8050- textSearchElement.focus();
8051- textSearchElement.select(); // select content (if any) for easy erasing
80528140 }, 200);
80538141}
80548142
@@ -8315,11 +8403,10 @@ function select_rm_create({ switchMenu = true } = {}) {
83158403 switchMenu && setMenuType('create');
83168404
83178405 //console.log('select_rm_Create() -- selected button: '+selected_button);
83188406 if (selected_button == 'create' && create_save.avatar) {
8319- if (create_save.avatar != '') {
8407+ const addAvatarInput = /** @type {HTMLInputElement} */ ($('#add_avatar_button').get(0));
83208408 $('#add_avatar_button').get(0) addAvatarInput.files = create_save.avatar;
8321- read_avatar_load($('#add_avatar_button').get(0));
8409+ read_avatar_load(addAvatarInput);
8322- }
83238410 }
83248411
83258412 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
@@ -8445,11 +8532,18 @@ export function updateChatMetadata(newValues, reset) {
84458532 chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues };
84468533}
84478534
8535+
8536+/**
8537+ * Updates the state of the favorite button based on the provided state.
8538+ * @param {boolean} state Whether the favorite button should be on or off.
8539+ */
84488540function updateFavButtonState(state) {
8541+ // Update global state of the flag
8542+ // TODO: This is bad and needs to be refactored.
84498543 fav_ch_checked = state;
84508544 $('#fav_checkbox').valprop(fav_ch_checked'checked', state);
84518545 $('#favorite_button').toggleClass('fav_on', fav_ch_checkedstate);
84528546 $('#favorite_button').toggleClass('fav_off', !fav_ch_checkedstate);
84538547}
84548548
84558549export async function setScenarioOverride() {
@@ -8566,7 +8660,7 @@ export function showSwipeButtons() {
85668660
85678661 //had to add this to make the swipe counter work
85688662 //(copied from the onclick functions for swipe buttons..
85698663 //don't know why the array isn't set for non-swipe messsagesmessages in Generate or addOneMessage..)
85708664 if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
85718665 chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0
85728666 chat[chat.length - 1]['swipes'] = []; // empty the array
@@ -8765,7 +8859,7 @@ function updateEditArrowClasses() {
87658859export function closeMessageEditor(what = 'all') {
87668860 if (what === 'message' || what === 'all') {
87678861 if (this_edit_mes_id) {
87688862 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).clicktrigger('click');
87698863 }
87708864 }
87718865 if (what === 'reasoning' || what === 'all') {
@@ -8854,7 +8948,8 @@ function openCharacterWorldPopup() {
88548948 }
88558949
88568950 function onExtraWorldInfoChanged() {
88578951 const selectedWorldsselectorFieldValue = $('.character_extra_world_info_selector').val();
8952+ const selectedWorlds = Array.isArray(selectorFieldValue) ? selectorFieldValue : [];
88588953 let charLore = world_info.charLore ?? [];
88598954
88608955 // TODO: Maybe make this utility function not use the window context?
@@ -8899,7 +8994,7 @@ function openCharacterWorldPopup() {
88998994 // Apped to base dropdown
89008995 world_names.forEach((item, i) => {
89018996 const option = document.createElement('option');
89028997 option.value = String(i);
89038998 option.innerText = item;
89048999 option.selected = item === worldId;
89059000 select.append(option);
@@ -8911,7 +9006,7 @@ function openCharacterWorldPopup() {
89119006 }
89129007 world_names.forEach((item, i) => {
89139008 const option = document.createElement('option');
89149009 option.value = String(i);
89159010 option.innerText = item;
89169011
89179012 const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());
@@ -9017,7 +9112,7 @@ function addAlternateGreeting(template, greeting, index, getArray, popup) {
90179112 */
90189113async function createOrEditCharacter(e) {
90199114 $('#rm_info_avatar').html('');
90209115 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0)));
90219116 formData.set('fav', String(fav_ch_checked));
90229117 const isNewChat = e instanceof CustomEvent && e.type === 'newChat';
90239118
@@ -9099,7 +9194,7 @@ async function createOrEditCharacter(e) {
90999194
91009195 $('#character_popup-button-h3').text('Create character');
91019196
91029197 create_save.avatar = ''null;
91039198
91049199 $('#add_avatar_button').replaceWith(
91059200 $('#add_avatar_button').val('').clone(true),
@@ -9348,13 +9443,13 @@ export function swipe_left(_event, { source, repeated } = {}) {
93489443
93499444/**
93509445 * Handles the swipe to the right event.
93519446 * @param {JQuery.Event} [_event] Event.
93529447 * @param {object} params Additional parameters.
93539448 * @param {string} [params.source] The source of the swipe event.
93549449 * @param {boolean} [params.repeated] Is the swipe event repeated.
93559450 */
93569451//MARK: swipe_right
93579452export function swipe_right(_event = null, { source, repeated } = {}) {
93589453 if (chat.length - 1 === Number(this_edit_mes_id)) {
93599454 closeMessageEditor();
93609455 }
@@ -9366,6 +9461,7 @@ export function swipe_right(_event, { source, repeated } = {}) {
93669461 // Make sure ad-hoc changes to extras are saved before swiping away
93679462 syncMesToSwipe();
93689463
9464+ const isPristine = !chat_metadata?.tainted;
93699465 const swipe_duration = 200;
93709466 const swipe_range = 700;
93719467 //console.log(swipe_range);
@@ -9384,14 +9480,16 @@ export function swipe_right(_event, { source, repeated } = {}) {
93849480 };
93859481 //assign swipe info array with last message from chat
93869482 }
9387- if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1) { // if swipe_right is called on the last alternate greeting, loop back around
9483+ // if swipe_right is called on the last alternate greeting in pristine chats, loop back around
9484+ if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1 && isPristine) {
93889485 chat[0]['swipe_id'] = 0;
93899486 } else {
93909487 // If the user is holding down the key and we're at the last swipe, don't do anything
93919488 if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === chat[chat.length - 1].swipes.length - 1) {
93929489 return;
93939490 }
93949491 chat[chat.length - 1]['swipe_id']++; // make new slot in array
9492+ chat[chat.length - 1]['swipe_id']++;
93959493 }
93969494 if (chat[chat.length - 1].extra) {
93979495 // if message has memory attached - remove it to allow regen
@@ -9406,8 +9504,8 @@ export function swipe_right(_event, { source, repeated } = {}) {
94069504 if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
94079505 chat[chat.length - 1]['swipe_info'] = [];
94089506 }
9409- //console.log(chat[chat.length-1]['swipes']);
9507+ //if swipe id of last message is the same as the length of the 'swipes' array and not the greeting
94109508 if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length && (chat.length !== 1) { //if swipe id of last message is the same as the length of the 'swipes' array and not|| the!isPristine)) greeting{
94119509 delete chat[chat.length - 1].gen_started;
94129510 delete chat[chat.length - 1].gen_finished;
94139511 run_generate = true;
@@ -9985,16 +10083,21 @@ async function doRenameChat(_, chatName) {
998510083}
998610084
998710085/**
998810086 * Renames thea currentlygroup selectedor character chat.
998910087 * @param {stringobject} oldFileName Oldparam nameParameters offor therenaming chat (no JSONL extension)
999010088 * @param {string} newName[param.characterId] NewCharacter nameID forto therename chat (no JSONL extension)for
10089+ * @param {string} [param.groupId] Group ID to rename chat for
10090+ * @param {string} param.oldFileName Old name of the chat (no JSONL extension)
10091+ * @param {string} param.newFileName New name for the chat (no JSONL extension)
10092+ * @param {boolean} [param.loader=true] Whether to show loader during the operation
999110093 */
999210094export async function renameChatrenameGroupOrCharacterChat({ characterId, groupId, oldFileName, newNamenewFileName, loader }) {
10095+ const currentChatId = getCurrentChatId();
999310096 const body = {
999410097 is_group: !!selected_groupgroupId,
999510098 avatar_url: characters[this_chidcharacterId]?.avatar,
999610099 original_file: `${oldFileName}.jsonl`,
999710100 renamed_file: `${newNamenewFileName.trim()}.jsonl`,
999810101 };
999910102
1000010103 if (body.original_file === body.renamed_file) {
@@ -10007,7 +10110,8 @@ export async function renameChat(oldFileName, newName) {
1000710110 }
1000810111
1000910112 try {
1001010113 loader && showLoader();
10114+
1001110115 const response = await fetch('/api/chats/rename', {
1001210116 method: 'POST',
1001310117 body: JSON.stringify(body),
@@ -10025,27 +10129,69 @@ export async function renameChat(oldFileName, newName) {
1002510129 }
1002610130
1002710131 if (data.sanitizedFileName) {
1002810132 newNamenewFileName = data.sanitizedFileName;
1002910133 }
1003010134
1003110135 if (selected_groupgroupId) {
1003210136 await renameGroupChat(selected_groupgroupId, oldFileName, newNamenewFileName);
1003310137 }
10034- else {
10138+ else if (characterId !== undefined && String(characterId) === String(this_chid) && characters[characterId]?.chat === oldFileName) {
1003510139 if (characters[this_chidcharacterId].chat == oldFileName) {newFileName;
10036- characters[this_chid].chat = newName;
10140+ $('#selected_chat_pole').val(characters[characterId].chat);
10037- $('#selected_chat_pole').val(characters[this_chid].chat);
10141+ await createOrEditCharacter();
10038- await createOrEditCharacter();
10039- }
1004010142 }
1004110143
1004210144 awaitif reloadCurrentChat(currentChatId); {
10145+ await reloadCurrentChat();
10146+ }
1004310147 } catch {
1004410148 loader && hideLoader();
1004510149 await delay(500);
1004610150 await callPopupcallGenericPopup('An error has occurred. Chat was not renamed.', 'text'POPUP_TYPE.TEXT);
1004710151 } finally {
1004810152 loader && hideLoader();
10153+ }
10154+}
10155+
10156+/**
10157+ * Renames the currently selected chat.
10158+ * @param {string} oldFileName Old name of the chat (no JSONL extension)
10159+ * @param {string} newName New name for the chat (no JSONL extension)
10160+ */
10161+export async function renameChat(oldFileName, newName) {
10162+ return await renameGroupOrCharacterChat({
10163+ characterId: this_chid,
10164+ groupId: selected_group,
10165+ oldFileName: oldFileName,
10166+ newFileName: newName,
10167+ loader: true,
10168+ });
10169+}
10170+
10171+/**
10172+ * Forces the update of the chat name for a remote character.
10173+ * @param {string|number} characterId Character ID to update chat name for
10174+ * @param {string} newName New name for the chat
10175+ * @returns {Promise<void>}
10176+ */
10177+export async function updateRemoteChatName(characterId, newName) {
10178+ const character = characters[characterId];
10179+ if (!character) {
10180+ console.warn(`Character not found for ID: ${characterId}`);
10181+ return;
10182+ }
10183+ character.chat = newName;
10184+ const mergeRequest = {
10185+ avatar: character.avatar,
10186+ chat: newName,
10187+ };
10188+ const mergeResponse = await fetch('/api/characters/merge-attributes', {
10189+ method: 'POST',
10190+ headers: getRequestHeaders(),
10191+ body: JSON.stringify(mergeRequest),
10192+ });
10193+ if (!mergeResponse.ok) {
10194+ console.error('Failed to save extension field', mergeResponse.statusText);
1004910195 }
1005010196}
1005110197
@@ -10056,9 +10202,6 @@ async function doGetChatName() {
1005610202 return getCurrentChatDetails().sessionName;
1005710203}
1005810204
10059-const isPwaMode = window.navigator.standalone;
10060-if (isPwaMode) { $('body').addClass('PWA'); }
10061-
1006210205function doCharListDisplaySwitch() {
1006310206 power_user.charListGrid = !power_user.charListGrid;
1006410207 document.body.classList.toggle('charListGrid', power_user.charListGrid);
@@ -10203,86 +10346,43 @@ function doDrawerOpenClick() {
1020310346/**
1020410347 * Event handler to open or close a navbar drawer when a navbar icon is clicked.
1020510348 * Handles click events on .drawer-toggle elements.
1020610349 * @returns {Promise<void>}
1020710350 */
1020810351export async function doNavbarIconClick() {
1020910352 const icon = $(this).find('.drawer-icon');
1021010353 const drawer = $(this).parent().find('.drawer-content');
10211- if (drawer.hasClass('resizing')) { return; }
1021210354 const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
1021310355 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
10214- const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
10356+
10215-
10357+ if (!drawerWasOpenAlready) {
10216- if (!drawerWasOpenAlready) { //to open the drawer
10358+ const $openDrawers = $('.openDrawer:not(.pinnedOpen)');
10217- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10359+ const $openIcons = $('.openIcon:not(.drawerPinnedOpen)');
10218- slideToggle(el, {
10360+ for (const iconEl of $openIcons) {
10219- ...getSlideToggleOptions(),
10361+ $(iconEl).toggleClass('closedIcon openIcon');
10220- onAnimationEnd: function (el) {
10362+ }
10221- el.closest('.drawer-content').classList.remove('resizing');
10363+ for (const el of $openDrawers) {
10222- },
10364+ $(el).toggleClass('closedDrawer openDrawer');
10223- });
10365+ }
10224- });
10366+ if ($openDrawers.length) {
10225- $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
10367+ await delay(animation_duration);
10226- $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
10368+ }
1022710369 icon.toggleClass('openIcon closedIcon');
1022810370 drawer.toggleClass('openDrawer closedDrawer');
1022910371
10230- //console.log(targetDrawerID);
1023110372 if (targetDrawerID === 'right-nav-panel') {
10232- $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
10373+ favsToHotswap();
10233- slideToggle(el, {
10374+ $('#rm_print_characters_block').trigger('scroll');
10234- ...getSlideToggleOptions(),
10235- elementDisplayStyle: 'flex',
10236- onAnimationEnd: function (el) {
10237- el.closest('.drawer-content').classList.remove('resizing');
10238- favsToHotswap();
10239- $('#rm_print_characters_block').trigger('scroll');
10240- },
10241- });
10242- });
10243- } else {
10244- $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
10245- slideToggle(el, {
10246- ...getSlideToggleOptions(),
10247- onAnimationEnd: function (el) {
10248- el.closest('.drawer-content').classList.remove('resizing');
10249- },
10250- });
10251- });
1025210375 }
1025310376
1025410377 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height
1025510378 if (!CSS.supports('field-sizing', 'content')) {
1025610379 const textareas = $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {;
10257- await resetScrollHeight($(this));
10380+ for (const textarea of textareas) {
10258- return;
10381+ await resetScrollHeight($(textarea));
1025910382 });
1026010383 }
10261-
10384+ } else if (drawerWasOpenAlready) {
10262- } else if (drawerWasOpenAlready) { //to close manually
1026310385 icon.toggleClass('closedIcon openIcon');
10264-
10265- if (pinnedDrawerClicked) {
10266- $(drawer).addClass('resizing').each((_, el) => {
10267- slideToggle(el, {
10268- ...getSlideToggleOptions(),
10269- onAnimationEnd: function (el) {
10270- el.classList.remove('resizing');
10271- },
10272- });
10273- });
10274- }
10275- else {
10276- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10277- slideToggle(el, {
10278- ...getSlideToggleOptions(),
10279- onAnimationEnd: function (el) {
10280- el.closest('.drawer-content').classList.remove('resizing');
10281- },
10282- });
10283- });
10284- }
10285-
1028610386 drawer.toggleClass('closedDrawer openDrawer');
1028710387 }
1028810388}
@@ -10686,23 +10786,23 @@ jQuery(async function () {
1068610786
1068710787 //menu buttons setup
1068810788
1068910789 $('#rm_button_settings').clickon('click', function () {
1069010790 selected_button = 'settings';
1069110791 selectRightMenuWithAnimation('rm_api_block');
1069210792 });
1069310793 $('#rm_button_characters').clickon('click', function () {
1069410794 selected_button = 'characters';
1069510795 select_rm_characters();
1069610796 });
1069710797 $('#rm_button_back').clickon('click', function () {
1069810798 selected_button = 'characters';
1069910799 select_rm_characters();
1070010800 });
1070110801 $('#rm_button_create').clickon('click', function () {
1070210802 selected_button = 'create';
1070310803 select_rm_create();
1070410804 });
1070510805 $('#rm_button_selected_ch').clickon('click', function () {
1070610806 if (selected_group) {
1070710807 select_group_chats(selected_group);
1070810808 } else {
@@ -10797,7 +10897,7 @@ jQuery(async function () {
1079710897 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
1079810898 });
1079910899
1080010900 $('#advanced_div').clickon('click', function () {
1080110901 if (!is_advanced_char_open) {
1080210902 is_advanced_char_open = true;
1080310903 $('#character_popup').css({ 'display': 'flex', 'opacity': 0.0 }).addClass('open');
@@ -10812,7 +10912,7 @@ jQuery(async function () {
1081210912 }
1081310913 });
1081410914
1081510915 $('#character_cross').clickon('click', function () {
1081610916 is_advanced_char_open = false;
1081710917 $('#character_popup').transition({
1081810918 opacity: 0,
@@ -10822,12 +10922,12 @@ jQuery(async function () {
1082210922 setTimeout(function () { $('#character_popup').css('display', 'none'); }, animation_duration);
1082310923 });
1082410924
1082510925 $('#character_popup_ok').clickon('click', function () {
1082610926 is_advanced_char_open = false;
1082710927 $('#character_popup').css('display', 'none');
1082810928 });
1082910929
1083010930 $('#dialogue_popup_ok').clickon('click', async function (e_e, customData) {
1083110931 const fromSlashCommand = customData?.fromSlashCommand || false;
1083210932 dialogueCloseStop = false;
1083310933 $('#shadow_popup').transition({
@@ -10857,7 +10957,7 @@ jQuery(async function () {
1085710957 hideLoader();
1085810958 } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat).
1085910959 setTimeout(function () {
1086010960 $('#option_select_chat').clicktrigger('click');
1086110961 $('#options').hide(); // hide option popup menu
1086210962 hideLoader();
1086310963 }, 2000);
@@ -10868,18 +10968,16 @@ jQuery(async function () {
1086810968 if (popup_type == 'input') {
1086910969 dialogueResolve($('#dialogue_popup_input').val());
1087010970 $('#dialogue_popup_input').val('');
10871-
1087210971 }
1087310972 else {
1087410973 dialogueResolve(true);
10875-
1087610974 }
1087710975
1087810976 dialogueResolve = null;
1087910977 }
1088010978 });
1088110979
1088210980 $('#dialogue_popup_cancel').clickon('click', function (e) {
1088310981 dialogueCloseStop = false;
1088410982 $('#shadow_popup').transition({
1088510983 opacity: 0,
@@ -10892,21 +10990,20 @@ jQuery(async function () {
1089210990 $('#dialogue_popup').removeClass('large_dialogue_popup');
1089310991 }, animation_duration);
1089410992
10895- //$("#shadow_popup").css("opacity:", 0.0);
1089610993 popup_type = '';
1089710994
1089810995 if (dialogueResolve) {
1089910996 dialogueResolve(false);
1090010997 dialogueResolve = null;
1090110998 }
10902-
1090310999 });
1090411000
1090511001 $('#add_avatar_button').changeon('change', function () {
10906- read_avatar_load(this);
11002+ const inputElement = /** @type {HTMLInputElement} */ (this);
11003+ read_avatar_load(inputElement);
1090711004 });
1090811005
1090911006 $('#form_create').on('submit', (e) => createOrEditCharacter(e.originalEvent));
1091011007
1091111008 $('#delete_button').on('click', async function () {
1091211009 if (this_chid === undefined || !characters[this_chid]) {
@@ -10917,7 +11014,7 @@ jQuery(async function () {
1091711014 let deleteChats = false;
1091811015
1091911016 const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), {
1092011017 onClose: () => { deleteChats = !!$('#del_char_checkbox').prop('checked'); },
1092111018 });
1092211019 if (!confirm) {
1092311020 return;
@@ -10977,9 +11074,9 @@ jQuery(async function () {
1097711074 const oldFileName = oldFileNameFull.replace('.jsonl', '');
1097811075
1097911076 const popupText = await renderTemplateAsync('chatRename');
1098011077 const newName = await callPopupcallGenericPopup(popupText, 'input'POPUP_TYPE.INPUT, oldFileName);
1098111078
1098211079 if (!newName || typeof newName !== 'string' || newName == oldFileName) {
1098311080 console.log('no new name found, aborting');
1098411081 return;
1098511082 }
@@ -11038,7 +11135,7 @@ jQuery(async function () {
1103811135
1103911136 ///////////////////////////////////////////////////////////////////////////////////
1104011137
1104111138 $('#api_button').clickon('click', function (e) {
1104211139 if ($('#api_url_text').val() != '') {
1104311140 let value = formatKoboldUrl(String($('#api_url_text').val()).trim());
1104411141
@@ -11179,7 +11276,7 @@ jQuery(async function () {
1117911276 if ((selected_group || this_chid !== undefined) && !is_send_press) {
1118011277 let deleteCurrentChat = false;
1118111278 const result = await Popup.show.confirm(t`Start new chat?`, await renderTemplateAsync('newChatConfirm'), {
1118211279 onClose: () => { deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'); },
1118311280 });
1118411281 if (!result) {
1118511282 return;
@@ -11283,7 +11380,7 @@ jQuery(async function () {
1128311380 //////////////////////////////////////////////////////////////////////////////////////////////
1128411381
1128511382 //functionality for the cancel delete messages button, reverts to normal display of input form
1128611383 $('#dialogue_del_mes_cancel').clickon('click', function () {
1128711384 $('#dialogue_del_mes').css('display', 'none');
1128811385 $('#send_form').css('display', css_send_form_display);
1128911386 $('.del_checkbox').each(function () {
@@ -11423,7 +11520,7 @@ jQuery(async function () {
1142311520
1142411521 //////////////////////////////////////////////////////////////
1142511522
1142611523 $('#select_chat_cross').clickon('click', function () {
1142711524 $('#shadow_select_chat_popup').transition({
1142811525 opacity: 0,
1142911526 duration: animation_duration,
@@ -11517,8 +11614,10 @@ jQuery(async function () {
1151711614 edit_textarea.height(0);
1151811615 edit_textarea.height(edit_textarea[0].scrollHeight);
1151911616 }
1152011617 edit_textarea.focustrigger('focus');
11521- edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text
11618+ const textAreaElement = /** @type {HTMLTextAreaElement} */ (edit_textarea[0]);
11619+ // Sets the cursor at the end of the text
11620+ textAreaElement.setSelectionRange(
1152211621 String(edit_textarea.val()).length,
1152311622 String(edit_textarea.val()).length,
1152411623 );
@@ -11770,8 +11869,8 @@ jQuery(async function () {
1177011869 //Select chat
1177111870
1177211871 //**************************CHARACTER IMPORT EXPORT*************************//
1177311872 $('#character_import_button').clickon('click', function () {
1177411873 $('#character_import_file').clicktrigger('click');
1177511874 });
1177611875
1177711876 $('#character_import_file').on('change', async function (e) {
@@ -11839,12 +11938,16 @@ jQuery(async function () {
1183911938 }
1184011939 });
1184111940 //**************************CHAT IMPORT EXPORT*************************//
1184211941 $('#chat_import_button').clickon('click', function () {
1184311942 $('#chat_import_file').clicktrigger('click');
1184411943 });
1184511944
1184611945 $('#chat_import_file').on('change', async function (e) {
11847- const file = e.target.files[0];
11946+ const targetElement = /** @type {HTMLInputElement} */ (e.target);
11947+ if (!(targetElement instanceof HTMLInputElement)) {
11948+ return;
11949+ }
11950+ const file = targetElement.files[0];
1184811951
1184911952 if (!file) {
1185011953 return;
@@ -11866,7 +11969,7 @@ jQuery(async function () {
1186611969 const format = ext[1].toLowerCase();
1186711970 $('#chat_import_file_type').val(format);
1186811971
1186911972 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_import_chat').get(0)));
1187011973 formData.append('user_name', name1);
1187111974 $('#select_chat_div').html('');
1187211975
@@ -11877,12 +11980,12 @@ jQuery(async function () {
1187711980 }
1187811981 });
1187911982
1188011983 $('#rm_button_group_chats').clickon('click', function () {
1188111984 selected_button = 'group_chats';
1188211985 select_group_chats();
1188311986 });
1188411987
1188511988 $('#rm_button_back_from_group').clickon('click', function () {
1188611989 selected_button = 'characters';
1188711990 select_rm_characters();
1188811991 });
@@ -11911,8 +12014,8 @@ jQuery(async function () {
1191112014
1191212015 $('.drawer-toggle').on('click', doNavbarIconClick);
1191312016
1191412017 $('html').on('touchstart mousedown', async function (e) {
1191512018 varconst clickTarget = $(e.target);
1191612019
1191712020 if (isExportPopupOpen
1191812021 && clickTarget.closest('#export_button').length == 0
@@ -11933,33 +12036,26 @@ jQuery(async function () {
1193312036 '#toast-container',
1193412037 '.select2-results',
1193512038 ];
12039+
1193612040 for (const id of forbiddenTargets) {
1193712041 if (clickTarget.closest(id).length > 0) {
1193812042 return;
1193912043 }
1194012044 }
1194112045
11942- var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
12046+ // This autocloses open drawers that are not pinned if a click happens inside the app which does not target them.
11943- if (clickTarget.hasClass('drawer-icon') == false && !clickTarget.hasClass('openDrawer')) {
12047+ const targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
11944- if ($('.openDrawer').length !== 0) {
12048+ if (!clickTarget.hasClass('drawer-icon') && !clickTarget.hasClass('openDrawer')) {
11945- if (targetParentHasOpenDrawer === 0) {
12049+ const $openDrawers = $('.openDrawer').not('.pinnedOpen');
11946- //console.log($('.openDrawer').not('.pinnedOpen').length);
12050+ if ($openDrawers.length && targetParentHasOpenDrawer === 0) {
11947- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
12051+ // Toggle icon and drawer classes
11948- slideToggle(el, {
12052+ $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
11949- ...getSlideToggleOptions(),
12053+ $openDrawers.toggleClass('closedDrawer openDrawer');
11950- onAnimationEnd: (el) => {
11951- el.closest('.drawer-content').classList.remove('resizing');
11952- },
11953- });
11954- });
11955- $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
11956- $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
11957- }
1195812054 }
1195912055 }
1196012056 });
1196112057
1196212058 $(document).on('click', '.inline-drawer-toggle', async function (e) {
1196312059 if ($(e.target).hasClass('text_pole')) {
1196412060 return;
1196512061 }
@@ -11968,6 +12064,7 @@ jQuery(async function () {
1196812064 const drawerContent = drawer.find('>.inline-drawer-content');
1196912065 icon.toggleClass('down up');
1197012066 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
12067+ drawer.trigger('inline-drawer-toggle');
1197112068 drawerContent.stop().slideToggle({
1197212069 complete: () => {
1197312070 $(this).css('height', '');
@@ -11976,10 +12073,10 @@ jQuery(async function () {
1197612073
1197712074 // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
1197812075 if (!CSS.supports('field-sizing', 'content')) {
1197912076 const textareas = drawerContent.find('textarea.autoSetHeight').each(async function () {;
11980- await resetScrollHeight($(this));
12077+ for (const textarea of textareas) {
11981- return;
12078+ await resetScrollHeight($(textarea));
1198212079 });
1198312080 }
1198412081 });
1198512082
@@ -12064,7 +12161,7 @@ jQuery(async function () {
1206412161 if (!(e.target instanceof HTMLElement)) return;
1206512162 if (e.target.matches('#OpenAllWIEntries')) {
1206612163 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
1206712164 delay(0).then(() => toggleDrawer(drawer, true));
1206812165 });
1206912166 } else if (e.target.matches('#CloseAllWIEntries')) {
1207012167 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
@@ -12094,7 +12191,7 @@ jQuery(async function () {
1209412191 return;
1209512192 }
1209612193 if (isEditVisible && power_user.auto_save_msg_edits === true) {
1209712194 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).clicktrigger('click');
1209812195 closeMessageEditor('reasoning');
1209912196 $('#send_textarea').focus();
1210012197 return;
@@ -12109,7 +12206,8 @@ jQuery(async function () {
1210912206 });
1211012207
1211112208 $('#char-management-dropdown').on('change', async (e) => {
12112- let target = $(e.target.selectedOptions).attr('id');
12209+ const targetElement = /** @type {HTMLSelectElement} */ (e.target);
12210+ const target = $(targetElement.selectedOptions).attr('id');
1211312211 switch (target) {
1211412212 case 'set_character_world':
1211512213 openCharacterWorldPopup();
@@ -12257,9 +12355,7 @@ jQuery(async function () {
1225712355
1225812356 $(document).on('click', '.external_import_button, #external_import_button', async () => {
1225912357 const html = await renderTemplateAsync('importCharacters');
12260-
12358+ const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '', { allowVerticalScrolling: true, wider: true, okButton: $('#popup_template').attr('popup-button-import'), rows: 4 });
12261- /** @type {string?} */
12262- const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '', { wider: true, okButton: $('#popup_template').attr('popup-button-import'), rows: 4 });
1226312359
1226412360 if (!input) {
1226512361 console.debug('Custom content import cancelled');
@@ -12267,7 +12363,7 @@ jQuery(async function () {
1226712363 }
1226812364
1226912365 // break input into one input per line
1227012366 const inputs = String(input).split('\n').map(x => x.trim()).filter(x => x.length > 0);
1227112367
1227212368 for (const url of inputs) {
1227312369 let request;
public/scripts/BulkEditOverlay.js+6 -5
@@ -2,7 +2,6 @@
22
33import {
44 characterGroupOverlay,
5- callPopup,
65 characters,
76 event_types,
87 eventSource,
@@ -17,6 +16,7 @@ import {
1716import { favsToHotswap } from './RossAscends-mods.js';
1817import { hideLoader, showLoader } from './loader.js';
1918import { convertCharacterToPersona } from './personas.js';
19+import { callGenericPopup, POPUP_TYPE } from './popup.js';
2020import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
2121
2222/**
@@ -835,12 +835,13 @@ class BulkEditOverlay {
835835 */
836836 handleContextMenuDelete = () => {
837837 const characterIds = this.selectedCharacters;
838838 const popupContent = $(BulkEditOverlay.#getDeletePopupContentHtml(characterIds));
839839 const promisecheckbox = callPopup(popupContent, null.find('#del_char_checkbox');
840+ const promise = callGenericPopup(popupContent, POPUP_TYPE.CONFIRM)
840841 .then((accept) => {
841842 if (true !== accept) return;
842843
843844 const deleteChats = documentcheckbox.getElementByIdprop('del_char_checkboxchecked').checked ?? false;
844845
845846 showLoader();
846847 const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it');
public/scripts/RossAscends-mods.js+140 -186
@@ -1,4 +1,4 @@
11import { DOMPurify, Bowser, slideToggle } from '../lib.js';
22
33import {
44 characters,
@@ -19,7 +19,7 @@ import {
1919 menu_type,
2020 substituteParams,
2121 sendTextareaMessage,
2222 getSlideToggleOptionsdoNavbarIconClick,
2323} from '../script.js';
2424
2525import {
@@ -402,7 +402,8 @@ function RA_autoconnect(PrevApi) {
402402 || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
403403 || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
404404 || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)
405405 || (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI && oai_settings.vertexai_auth_mode === 'express')
406+ || (secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI && oai_settings.vertexai_auth_mode === 'full')
406407 || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
407408 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
408409 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
@@ -411,6 +412,7 @@ function RA_autoconnect(PrevApi) {
411412 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
412413 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
413414 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
415+ || (secret_state[SECRET_KEYS.AIMLAPI] && oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI)
414416 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
415417 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
416418 ) {
@@ -472,54 +474,72 @@ const saveUserInputDebounced = debounce(saveUserInput);
472474
473475// Make the DIV element draggable:
474476
475-export function dragElement(elmnt) {
477+/**
476- var isHeaderBeingDragged = false;
478+ * Make the given element draggable. This is used for Moving UI.
477- var isMouseDown = false;
479+ * @param {JQuery} $elmnt - The element to make draggable.
480+ */
481+export function dragElement($elmnt) {
482+ let actionType = null; // "drag" or "resize"
483+ let isMouseDown = false;
478484
479485 varlet pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
480486 varlet height, width, top, left, right, bottom,
481487 maxX, maxY, winHeight, winWidth,
482488 topbar, topBarFirstX, topBarLastY;
483489
484490 varconst elmntName = $elmnt.attr('id');
485- console.debug(`dragElement called for ${elmntName}`);
486491 const elmntNameEscaped = $.escapeSelector(elmntName);
487492 const $elmntHeader = $(`#${elmntNameEscaped}header`);
488493
489- if (elmntHeader.length) {
494+ // Helper: Save position/size to state and emit events
490- elmntHeader.off('mousedown').on('mousedown', (e) => { //listener for drag handle repositioning
495+ function savePositionAndSize() {
491- isHeaderBeingDragged = true;
496+ if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
492- isMouseDown = true;
497+ power_user.movingUIState[elmntName].top = top;
493- observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
498+ power_user.movingUIState[elmntName].left = left;
494- dragMouseDown(e);
499+ power_user.movingUIState[elmntName].right = right;
495- });
500+ power_user.movingUIState[elmntName].bottom = bottom;
496- $(elmnt).off('mousedown').on('mousedown', () => { //listener for resize
501+ power_user.movingUIState[elmntName].margin = 'unset';
497- isMouseDown = true;
502+ if (actionType === 'resize') {
498- observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
503+ power_user.movingUIState[elmntName].width = width;
499- });
504+ power_user.movingUIState[elmntName].height = height;
505+ eventSource.emit('resizeUI', elmntName);
506+ }
507+ saveSettingsDebounced();
500508 }
501509
510+ // Helper: Clamp element within viewport
511+ function clampToViewport() {
512+ if (top <= 0) $elmnt.css('top', '0px');
513+ else if (maxY >= winHeight) $elmnt.css('top', winHeight - maxY + top - 1 + 'px');
514+ if (left <= 0) $elmnt.css('left', '0px');
515+ else if (maxX >= winWidth) $elmnt.css('left', winWidth - maxX + left - 1 + 'px');
516+ }
517+
518+ // Observer for style changes (position/size)
502519 const observer = new MutationObserver((mutations) => {
503520 const $target = $(mutations[0].target);
504- if (!$(target).is(':visible') //abort if element is invisible
521+ if (
505- || $(target).hasClass('resizing') //being auto-resized by other JS code
522+ !$target.is(':visible') ||
506- || Number((String(target.height).replace('px', ''))) < 50 //too short
523+ $target.hasClass('resizing') ||
507- || Number((String(target.width).replace('px', ''))) < 50 //too narrow
524+ $target.height() < 50 ||
508- || power_user.movingUI === false // if MUI is not turned on
525+ $target.width() < 50 ||
509- || isMobile() // if it's a mobile screen
526+ power_user.movingUI === false ||
527+ isMobile() ||
528+ !isMouseDown
510529 ) {
530+ observer.disconnect();
511531 return;
512532 }
513533
514534 const style = getComputedStyle($target[0]);
515535 height = parseInt(style.height);
516536 width = parseInt(style.width);
517537 top = parseInt(style.top);
518538 left = parseInt(style.left);
519539 right = parseInt(style.right);
520540 bottom = parseInt(style.bottom);
521541 maxX = parseInt(width + left);
522542 maxY = parseInt(height + top);
523543 winWidth = window.innerWidth;
524544 winHeight = window.innerHeight;
525545
@@ -528,182 +548,119 @@ export function dragElement(elmnt) {
528548 topBarFirstX = parseInt(topbarstyle.marginInline);
529549 topBarLastY = parseInt(topbarstyle.height);
530550
531- //prepare an empty poweruser object for the item being altered if we don't have one already
551+ // Prepare state object if missing
532552 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
533- console.debug(`adding config property for ${elmntName}`);
534- power_user.movingUIState[elmntName] = {};
535- }
536553
537- //handle resizing
554+ if (actionType === 'resize') {
538- if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header)
539- let imgHeight, imgWidth, imageAspectRatio;
540555 let containerAspectRatio = height / width;
541-
556+ if ($elmnt.attr('id').startsWith('zoomFor_')) {
542- //force aspect ratio for zoomed avatars
557+ const zoomedAvatarImage = $elmnt.find('.zoomed_avatar_img');
543- if ($(elmnt).attr('id').startsWith('zoomFor_')) {
558+ const imgHeight = zoomedAvatarImage.height();
544559 letconst zoomedAvatarImageimgWidth = $(elmnt)zoomedAvatarImage.findwidth('.zoomed_avatar_img');
545- imgHeight = zoomedAvatarImage.height();
560+ const imageAspectRatio = imgHeight / imgWidth;
546- imgWidth = zoomedAvatarImage.width();
547- imageAspectRatio = imgHeight / imgWidth;
548-
549- // Maintain aspect ratio
550561 if (containerAspectRatio !== imageAspectRatio) {
551562 $elmnt.css('width', $elmnt.width());
552563 $elmnt.css('height', $elmnt.width() * imageAspectRatio);
553564 }
554-
565+ if (top + $elmnt.height() >= winHeight) {
555- // Prevent resizing offscreen
566+ $elmnt.css('height', winHeight - top - 1 + 'px');
556- if (top + elmnt.height() >= winHeight) {
567+ $elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
557- elmnt.css('height', winHeight - top - 1 + 'px');
558- elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
559568 }
560-
569+ if (left + $elmnt.width() >= winWidth) {
561- if (left + elmnt.width() >= winWidth) {
570+ $elmnt.css('width', winWidth - left - 1 + 'px');
562571 $elmnt.css('widthheight', (winWidth - left - 1) * imageAspectRatio + 'px');
563- elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
564- }
565- } else { //prevent divs that are not zoomedAvatars from resizing offscreen
566-
567- if (top + elmnt.height() >= winHeight) {
568- elmnt.css('height', winHeight - top - 1 + 'px');
569- }
570-
571- if (left + elmnt.width() >= winWidth) {
572- elmnt.css('width', winWidth - left - 1 + 'px');
573572 }
573+ } else {
574+ if (top + $elmnt.height() >= winHeight) $elmnt.css('height', winHeight - top - 1 + 'px');
575+ if (left + $elmnt.width() >= winWidth) $elmnt.css('width', winWidth - left - 1 + 'px');
574576 }
575-
576- //prevent resizing from top left into the top bar
577577 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {
578578 $elmnt.css('width', width - 1 + 'px');
579579 }
580-
580+ $elmnt.css({ left, top });
581- //set css to prevent weird resize behavior (does not save)
581+ $elmnt.off('mouseup').on('mouseup', () => {
582- elmnt.css('left', left);
582+ if (
583- elmnt.css('top', top);
583+ power_user.movingUIState[elmntName].width === $elmnt.width() &&
584-
584+ power_user.movingUIState[elmntName].height === $elmnt.height()
585- //set a listener for mouseup to save new width/height
585+ ) return;
586- $(window).off('mouseup').on('mouseup', () => {
586+ savePositionAndSize();
587- console.log(`Saving ${elmntName} Height/Width`);
587+ observer.disconnect();
588- // check if the height or width actually changed
589- if (power_user.movingUIState[elmntName].width === elmnt.width() && power_user.movingUIState[elmntName].height === elmnt.height()) {
590- console.log('no change detected, aborting save');
591- return;
592- }
593-
594- power_user.movingUIState[elmntName].width = width;
595- power_user.movingUIState[elmntName].height = height;
596- eventSource.emit('resizeUI', elmntName);
597- saveSettingsDebounced();
598- imgHeight = null;
599- imgWidth = null;
600- height = null;
601- width = null;
602-
603- containerAspectRatio = null;
604- imageAspectRatio = null;
605- $(window).off('mouseup');
606588 });
589+ } else if (actionType === 'drag') {
590+ clampToViewport();
607591 }
608592
609- //only record position changes if header is being dragged
593+ // Always update position in state
610- power_user.movingUIState[elmntName].top = top;
594+ savePositionAndSize();
611- power_user.movingUIState[elmntName].left = left;
612- power_user.movingUIState[elmntName].right = right;
613- power_user.movingUIState[elmntName].bottom = bottom;
614- power_user.movingUIState[elmntName].margin = 'unset';
615-
616- //handle dragging hit detection to prevent dragging offscreen
617- if (isHeaderBeingDragged && isMouseDown) {
618-
619- if (top <= 0) {
620- elmnt.css('top', '0px');
621- } else if (maxY >= winHeight) {
622- elmnt.css('top', winHeight - maxY + top - 1 + 'px');
623- }
624-
625- if (left <= 0) {
626- elmnt.css('left', '0px');
627- } else if (maxX >= winWidth) {
628- elmnt.css('left', winWidth - maxX + left - 1 + 'px');
629- }
630- }
631-
632- // Check if the element header exists and set the reposition listener on the grabber in the header
633- if (elmntHeader.length) {
634- elmntHeader.off('mousedown').on('mousedown', (e) => {
635- dragMouseDown(e);
636- });
637- } else { //if no header, put the listener on the elmnt itself.
638- elmnt.off('mousedown').on('mousedown', dragMouseDown);
639- }
640595 });
641596
597+ // Mouse event handlers
642598 function dragMouseDown(e) {
643-
644599 if (e) {
645600 isHeaderBeingDraggedactionType = true'drag';
601+ isMouseDown = true;
646602 e.preventDefault();
647603 pos3 = e.clientX; //mouse X at click
648604 pos4 = e.clientY; //mouse Y at click
649605 }
650606 $(document).on('mouseup', closeDragElement);
651607 $(document).on('mousemove', elementDrag);
652608 }
653609
654610 function elementDrag(e) {
655611 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
656- power_user.movingUIState[elmntName] = {};
657- }
658-
659- e = e || window.event;
660612 e.preventDefault();
661-
613+ pos1 = pos3 - e.clientX;
662- pos1 = pos3 - e.clientX; //X change amt (-1 or 1)
614+ pos2 = pos4 - e.clientY;
663- pos2 = pos4 - e.clientY; //Y change amt (-1 or 1)
615+ pos3 = e.clientX;
664616 pos3pos4 = e.clientXclientY; //new mouse X
665- pos4 = e.clientY; //new mouse Y
617+ $elmnt.attr('data-dragged', 'true');
666-
618+ $elmnt.css('left', ($elmnt.offset().left - pos1) + 'px');
667- elmnt.attr('data-dragged', 'true');
619+ $elmnt.css('top', ($elmnt.offset().top - pos2) + 'px');
668-
620+ $elmnt.css('margin', 'unset');
669- //first set css to computed values to avoid CSS NaN results from 'auto', etc
621+ $elmnt.css('height', height);
670622 $elmnt.css('leftwidth', (elmnt.offset().left) + 'px'width);
671- elmnt.css('top', (elmnt.offset().top) + 'px');
672-
673- //then update element position styles to account for drag changes
674- elmnt.css('margin', 'unset');
675- elmnt.css('left', (elmnt.offset().left - pos1) + 'px');
676- elmnt.css('top', (elmnt.offset().top - pos2) + 'px');
677- /* elmnt.css('right', ((winWidth - maxX) + 'px'));
678- elmnt.css('bottom', ((winHeight - maxY) + 'px')); */
679-
680- // Height/Width here are for visuals only, and are not saved to settings.
681- // This is required because some divs do hot have a set width/height
682- // and will default to shrink to min value of 100px set in CSS file
683- elmnt.css('height', height);
684- elmnt.css('width', width);
685- return;
686623 }
687624
688625 function closeDragElement() {
689- console.debug('drag finished');
690- isHeaderBeingDragged = false;
691626 isMouseDown = false;
627+ actionType = null;
692628 $(document).off('mouseup', closeDragElement);
693629 $(document).off('mousemove', elementDrag);
694630 $('body')elmnt.cssattr('overflowdata-dragged', 'false');
695- // Clear the "data-dragged" attribute
696- elmnt.attr('data-dragged', 'false');
697631 observer.disconnect();
698- console.debug(`Saving ${elmntName} UI position`);
632+ savePositionAndSize();
699- saveSettingsDebounced();
633+ }
700- top = null;
634+
701- left = null;
635+ // Setup event listeners
702- right = null;
636+ if ($elmntHeader.length) {
703- bottom = null;
637+ $elmntHeader.off('mousedown').on('mousedown', (e) => {
704- maxX = null;
638+ if ($(e.target).hasClass('drag-grabber')) {
705639 maxY actionType = null'drag';
640+ isMouseDown = true;
641+ observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
642+ dragMouseDown(e);
643+ }
644+ });
706645 }
646+
647+ $elmnt.off('mousedown').on('mousedown', (e) => {
648+ const rect = $elmnt[0].getBoundingClientRect();
649+ const resizeMargin = 16;
650+ const isNearRight = e.clientX > rect.right - resizeMargin;
651+ const isNearBottom = e.clientY > rect.bottom - resizeMargin;
652+ if (isNearRight && isNearBottom) {
653+ actionType = 'resize';
654+ isMouseDown = true;
655+ observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
656+ }
657+ });
658+
659+ $elmnt.off('mouseup').on('mouseup', () => {
660+ isMouseDown = false;
661+ actionType = null;
662+ observer.disconnect();
663+ });
707664}
708665
709666export async function initMovingUI() {
@@ -774,9 +731,8 @@ export function initRossMods() {
774731 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
775732
776733 if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
777- slideToggle(RightNavPanel, getSlideToggleOptions());
734+ const toggle = $('#unimportantYes');
778- $(RightNavDrawerIcon).toggleClass('closedIcon openIcon');
735+ doNavbarIconClick.call(toggle);
779- $(RightNavPanel).toggleClass('openDrawer closedDrawer');
780736 }
781737 }
782738 });
@@ -792,14 +748,13 @@ export function initRossMods() {
792748 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
793749
794750 if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
795- slideToggle(LeftNavPanel, getSlideToggleOptions());
751+ const toggle = $('#ai-config-button>.drawer-toggle');
796- $(LeftNavDrawerIcon).toggleClass('closedIcon openIcon');
752+ doNavbarIconClick.call(toggle);
797- $(LeftNavPanel).toggleClass('openDrawer closedDrawer');
798753 }
799754 }
800755 });
801756
802757 $(WIPanelPin).on('click', async function () {
803758 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
804759 if ($(WIPanelPin).prop('checked') == true) {
805760 console.debug('adding pin class to WI');
@@ -812,9 +767,8 @@ export function initRossMods() {
812767
813768 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
814769 console.debug('closing WI after lock removal');
815- slideToggle(WorldInfo, getSlideToggleOptions());
770+ const toggle = $('#WI-SP-button>.drawer-toggle');
816- $(WIDrawerIcon).toggleClass('closedIcon openIcon');
771+ doNavbarIconClick.call(toggle);
817- $(WorldInfo).toggleClass('openDrawer closedDrawer');
818772 }
819773 }
820774 });
public/scripts/authors-note.js+17 -14
@@ -1,9 +1,11 @@
11import {
2+ MAX_INJECTION_DEPTH,
23 animation_duration,
34 chat_metadata,
45 eventSource,
56 event_types,
67 extension_prompt_roles,
8+ extension_prompt_types,
79 saveSettingsDebounced,
810 this_chid,
911} from '../script.js';
@@ -347,7 +349,7 @@ export function setFloatingPrompt() {
347349 }
348350
349351 if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
350352 context.setExtensionPrompt(MODULE_NAME, '', extension_prompt_types.NONE, MAX_INJECTION_DEPTH);
351353 $('#extension_floating_counter').text('(disabled)');
352354 shouldWIAddPrompt = false;
353355 return;
@@ -380,7 +382,7 @@ export function setFloatingPrompt() {
380382 }
381383 context.setExtensionPrompt(
382384 MODULE_NAME,
383385 String(prompt),
384386 chat_metadata[metadata_keys.position],
385387 chat_metadata[metadata_keys.depth],
386388 extension_settings.note.allowWIScan,
@@ -396,37 +398,38 @@ function onANMenuItemClick() {
396398 }
397399
398400 //show AN if it's hidden
399- if ($('#floatingPrompt').css('display') !== 'flex') {
401+ const $ANcontainer = $('#floatingPrompt');
400- $('#floatingPrompt').addClass('resizing');
402+ if ($ANcontainer.css('display') !== 'flex') {
401403 $('#floatingPrompt')ANcontainer.cssaddClass('display', 'flexresizing');
402404 $('#floatingPrompt')ANcontainer.css('opacitydisplay', 0.0'flex');
403- $('#floatingPrompt').transition({
405+ $ANcontainer.css('opacity', 0.0);
406+ $ANcontainer.transition({
404407 opacity: 1.0,
405408 duration: animation_duration,
406409 }, async function () {
407410 await delay(50);
408411 $('#floatingPrompt')ANcontainer.removeClass('resizing');
409412 });
410413
411414 //auto-open the main AN inline drawer
412415 if ($('#ANBlockToggle')
413416 .siblings('.inline-drawer-content')
414417 .css('display') !== 'block') {
415418 $('#floatingPrompt')ANcontainer.addClass('resizing');
416419 $('#ANBlockToggle').clicktrigger('click');
417420 }
418421 } else {
419422 //hide AN if it's already displayed
420423 $('#floatingPrompt')ANcontainer.addClass('resizing');
421424 $('#floatingPrompt')ANcontainer.transition({
422425 opacity: 0.0,
423426 duration: animation_duration,
424427 }, async function () {
425428 await delay(50);
426429 $('#floatingPrompt')ANcontainer.removeClass('resizing');
427430 });
428431 setTimeout(function () {
429432 $('#floatingPrompt')ANcontainer.hide();
430433 }, animation_duration);
431434 }
432435
public/scripts/autocomplete/AutoComplete.js+36 -30
@@ -21,6 +21,14 @@ export const AUTOCOMPLETE_SELECT_KEY = {
2121 'ENTER': 2, // 2^1
2222};
2323
24+/** @readonly */
25+/** @enum {Number} */
26+export const AUTOCOMPLETE_STATE = {
27+ DISABLED: 0,
28+ MIN_LENGTH: 1,
29+ ALWAYS: 2,
30+};
31+
2432export class AutoComplete {
2533 /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;
2634 /**@type {boolean}*/ isFloating = false;
@@ -109,20 +117,20 @@ export class AutoComplete {
109117 this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
110118 this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
111119
112120 textarea.addEventListener('input', () => {
113121 this.selectionStart = this.textarea.selectionStart;
114122 if (this.text != this.textarea.value) this.show(true, this.wasForced);
115123 });
116124 textarea.addEventListener('keydown', (evt) => this.handleKeyDown(evt));
117125 textarea.addEventListener('click', () => {
118126 this.selectionStart = this.textarea.selectionStart;
119127 if (this.isActive) this.show();
120128 });
121129 textarea.addEventListener('blur', () => this.hide());
122130 if (isFloating) {
123131 textarea.addEventListener('scroll', () => this.updateFloatingPositionDebounced());
124132 }
125133 window.addEventListener('resize', () => this.updatePositionDebounced());
126134 }
127135
128136 /**
@@ -132,9 +140,9 @@ export class AutoComplete {
132140 makeItem(option) {
133141 const li = option.renderItem();
134142 // gotta listen to pointerdown (happens before textarea-blur)
135143 li.addEventListener('pointerdown', (evt) => {
136144 evt.preventDefault();
137145 this.selectedItem = this.result.find(it => it.name == li.getAttribute('data-name'));
138146 this.select();
139147 });
140148 return li;
@@ -149,7 +157,7 @@ export class AutoComplete {
149157 const chars = Array.from(item.dom.querySelector('.name').children);
150158 switch (this.matchType) {
151159 case 'strict': {
152160 chars.forEach((it, idx) => {
153161 if (idx + item.nameOffset < item.name.length) {
154162 it.classList.add('matched');
155163 } else {
@@ -160,7 +168,7 @@ export class AutoComplete {
160168 }
161169 case 'includes': {
162170 const start = item.name.toLowerCase().search(this.name);
163171 chars.forEach((it, idx) => {
164172 if (idx + item.nameOffset < start) {
165173 it.classList.remove('matched');
166174 } else if (idx + item.nameOffset < start + item.name.length) {
@@ -172,18 +180,18 @@ export class AutoComplete {
172180 break;
173181 }
174182 case 'fuzzy': {
175183 item.name.replace(this.fuzzyRegex, (_, ...parts) => {
176184 parts.splice(-2, 2);
177185 if (parts.length == 2) {
178186 chars.forEach(c => c.classList.remove('matched'));
179187 } else {
180188 let cIdx = item.nameOffset;
181189 parts.forEach((it, idx) => {
182190 if (it === null || it.length == 0) return '';
183191 if (idx % 2 == 1) {
184192 chars.slice(cIdx, cIdx + it.length).forEach(c => c.classList.add('matched'));
185193 } else {
186194 chars.slice(cIdx, cIdx + it.length).forEach(c => c.classList.remove('matched'));
187195 }
188196 cIdx += it.length;
189197 });
@@ -230,7 +238,7 @@ export class AutoComplete {
230238 if (current.length > 0) {
231239 consecutive.push(current);
232240 }
233241 consecutive.sort((a, b) => b.length - a.length);
234242 option.score = new AutoCompleteFuzzyScore(start, consecutive[0]?.length ?? 0);
235243 return option;
236244 }
@@ -254,8 +262,7 @@ export class AutoComplete {
254262 + this.parserResult.name.length
255263 + (this.startQuote ? 1 : 0)
256264 + (this.endQuote ? 1 : 0)
257265 + 1;
258- ;
259266 }
260267
261268 /**
@@ -344,7 +351,7 @@ export class AutoComplete {
344351
345352 if (this.matchType == 'fuzzy') {
346353 // only build the fuzzy regex if match type is set to fuzzy
347354 this.fuzzyRegex = new RegExp(`^(.*?)${this.name.split('').map(char => `(${escapeRegex(char)})`).join('(.*?)')}(.*?)$`, 'i');
348355 }
349356
350357 //TODO maybe move the matchers somewhere else; a single match function? matchType is available as property
@@ -358,12 +365,12 @@ export class AutoComplete {
358365 // filter the list of options by the partial name according to the matching type
359366 .filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name)
360367 // remove aliases
361368 .filter((it, idx, list) => list.findIndex(opt => opt.value == it.value) == idx);
362369
363370 if (this.result.length == 0 && this.effectiveParserResult != this.parserResult && isForced) {
364371 // no matching secondary results and forced trigger -> show current command details
365372 this.secondaryParserResult = null;
366373 this.result = [this.effectiveParserResult.optionList.find(it => it.name == this.effectiveParserResult.name)];
367374 this.name = this.effectiveParserResult.name;
368375 this.fuzzyRegex = /(.*)(.*)(.*)/;
369376 }
@@ -387,8 +394,7 @@ export class AutoComplete {
387394 return option;
388395 })
389396 // sort by fuzzy score or alphabetical
390397 .toSorted(this.matchType == 'fuzzy' ? this.fuzzyScoreCompare : (a, b) => a.name.localeCompare(b.name));
391- ;
392398
393399
394400
@@ -628,12 +634,12 @@ export class AutoComplete {
628634 this.clone.style.position = 'fixed';
629635 this.clone.style.visibility = 'hidden';
630636 document.body.append(this.clone);
631637 const mo = new MutationObserver(muts => {
632638 if (muts.find(it => Array.from(it.removedNodes).includes(this.textarea))) {
633639 this.clone.remove();
634640 }
635641 });
636642 mo.observe(this.textarea.parentElement, { childList: true });
637643 }
638644 this.clone.style.height = `${inputRect.height}px`;
639645 this.clone.style.left = `${inputRect.left}px`;
@@ -685,7 +691,7 @@ export class AutoComplete {
685691 this.textarea.selectionDirection = selectionEnd;
686692 }
687693 this.wasForced = false;
688694 this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
689695 this.onSelect?.(this.selectedItem);
690696 }
691697
@@ -700,7 +706,7 @@ export class AutoComplete {
700706 this.selectedItem.dom.classList.add('selected');
701707 const rect = this.selectedItem.dom.children[0].getBoundingClientRect();
702708 const rectParent = this.dom.getBoundingClientRect();
703709 if (rect.top < rectParent.top || rect.bottom > rectParent.bottom ) {
704710 this.dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;
705711 }
706712 this.renderDetailsDebounced();
@@ -809,8 +815,8 @@ export class AutoComplete {
809815 }
810816 // await keyup to see if cursor position or text has changed
811817 const oldText = this.textarea.value;
812818 await new Promise(resolve => {
813819 window.addEventListener('keyup', resolve, { once: true });
814820 });
815821 if (this.selectionStart != this.textarea.selectionStart) {
816822 this.selectionStart = this.textarea.selectionStart;
public/scripts/backgrounds.js+172 -27
@@ -1,20 +1,48 @@
11import { Fuse, localforage } from '../lib.js';
2-
2+import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
3-import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
43import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';
54import { SlashCommand } from './slash-commands/SlashCommand.js';
65import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
76import { createThumbnail, flashHighlight, getBase64Async, stringFormat } from './utils.js';
87import { t } from './i18n.js';
98import { Popup } from './popup.js';
109
1110const BG_METADATA_KEY = 'custom_background';
1211const LIST_METADATA_KEY = 'chat_backgrounds';
1312
13+// A single transparent PNG pixel used as a placeholder for errored backgrounds
14+const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
15+const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' });
16+const PLACEHOLDER_IMAGE = `url('data:image/png;base64,${PNG_PIXEL}')`;
17+
18+/**
19+ * Storage for frontend-generated background thumbnails.
20+ * This is used to store thumbnails for backgrounds that cannot be generated on the server.
21+ */
22+const THUMBNAIL_STORAGE = localforage.createInstance({ name: 'SillyTavern_Thumbnails' });
23+
24+/**
25+ * Cache for thumbnail blob URLs.
26+ * @type {Map<string, string>}
27+ */
28+const THUMBNAIL_BLOBS = new Map();
29+
30+const THUMBNAIL_CONFIG = {
31+ width: 160,
32+ height: 90,
33+};
34+
35+/**
36+ * Global IntersectionObserver instance for lazy loading backgrounds
37+ * @type {IntersectionObserver|null}
38+ */
39+let lazyLoadObserver = null;
40+
1441export let background_settings = {
1542 name: '__transparent.png',
1643 url: generateUrlParameter('__transparent.png', false),
1744 fitting: 'classic',
45+ animation: false,
1846};
1947
2048export function loadBackgroundSettings(settings) {
@@ -25,16 +53,20 @@ export function loadBackgroundSettings(settings) {
2553 if (!backgroundSettings.fitting) {
2654 backgroundSettings.fitting = 'classic';
2755 }
56+ if (!Object.hasOwn(backgroundSettings, 'animation')) {
57+ backgroundSettings.animation = false;
58+ }
2859 setBackground(backgroundSettings.name, backgroundSettings.url);
2960 setFittingClass(backgroundSettings.fitting);
3061 $('#background_fitting').val(backgroundSettings.fitting);
62+ $('#background_thumbnails_animation').prop('checked', background_settings.animation);
3163}
3264
3365/**
3466 * Sets the background for the current chat and adds it to the list of custom backgrounds.
3567 * @param {{url: string, path:string}} backgroundInfo
3668 */
3769async function forceSetBackground(backgroundInfo) {
3870 saveBackgroundMetadata(backgroundInfo.url);
3971 setCustomBackground();
4072
@@ -43,7 +75,7 @@ function forceSetBackground(backgroundInfo) {
4375 list.push(bg);
4476 chat_metadata[LIST_METADATA_KEY] = list;
4577 saveMetadataDebounced();
4678 await getChatBackgroundsList();
4779 highlightNewBackground(bg);
4880 highlightLockedBackground();
4981}
@@ -56,11 +88,11 @@ async function onChatChanged() {
5688 unsetCustomBackground();
5789 }
5890
5991 await getChatBackgroundsList();
6092 highlightLockedBackground();
6193}
6294
6395async function getChatBackgroundsList() {
6496 const list = chat_metadata[LIST_METADATA_KEY];
6597 const listEmpty = !Array.isArray(list) || list.length === 0;
6698
@@ -72,9 +104,10 @@ function getChatBackgroundsList() {
72104 }
73105
74106 for (const bg of list) {
75107 const template = await getBackgroundFromTemplate(bg, true);
76108 $('#bg_custom_content').append(template);
77109 }
110+ activateLazyLoader();
78111}
79112
80113function getBackgroundPath(fileUrl) {
@@ -224,7 +257,49 @@ async function onCopyToSystemBackgroundClick(e) {
224257 const index = list.indexOf(bgNames.oldBg);
225258 list.splice(index, 1);
226259 saveMetadataDebounced();
227260 await getChatBackgroundsList();
261+}
262+
263+/**
264+ * Gets a thumbnail for the background from storage or fetches it if not available.
265+ * It caches the thumbnail in local storage and returns a blob URL for the thumbnail.
266+ * If the thumbnail cannot be fetched, it returns a transparent PNG pixel as a fallback.
267+ * @param {string} bg Background URL
268+ * @returns {Promise<string>} Blob URL of the thumbnail
269+ */
270+async function getThumbnailFromStorage(bg) {
271+ const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg);
272+ if (cachedBlobUrl) {
273+ return cachedBlobUrl;
274+ }
275+
276+ const savedBlob = await THUMBNAIL_STORAGE.getItem(bg);
277+ if (savedBlob) {
278+ const savedBlobUrl = URL.createObjectURL(savedBlob);
279+ THUMBNAIL_BLOBS.set(bg, savedBlobUrl);
280+ return savedBlobUrl;
281+ }
282+
283+ try {
284+ const response = await fetch(getBackgroundPath(bg), { cache: 'force-cache' });
285+ if (!response.ok) {
286+ throw new Error('Fetch failed with status: ' + response.status);
287+ }
288+ const imageBlob = await response.blob();
289+ const imageBase64 = await getBase64Async(imageBlob);
290+ const thumbnailBase64 = await createThumbnail(imageBase64, THUMBNAIL_CONFIG.width, THUMBNAIL_CONFIG.height);
291+ const thumbnailBlob = await fetch(thumbnailBase64).then(res => res.blob());
292+ await THUMBNAIL_STORAGE.setItem(bg, thumbnailBlob);
293+ const blobUrl = URL.createObjectURL(thumbnailBlob);
294+ THUMBNAIL_BLOBS.set(bg, blobUrl);
295+ return blobUrl;
296+ } catch (error) {
297+ console.error('Error fetching thumbnail, fallback image will be used:', error);
298+ const fallbackBlob = PNG_PIXEL_BLOB;
299+ const fallbackBlobUrl = URL.createObjectURL(fallbackBlob);
300+ THUMBNAIL_BLOBS.set(bg, fallbackBlobUrl);
301+ return fallbackBlobUrl;
302+ }
228303}
229304
230305/**
@@ -245,7 +320,7 @@ async function getNewBackgroundName(referenceElement) {
245320 const fileExtension = oldBg.split('.').pop();
246321 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
247322 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
248323 const newBgExtensionless = await callPopupPopup.show.input('<h3>' + t`Enter new background name:` + '</h3>', 'input'null, oldBgExtensionless);
249324
250325 if (!newBgExtensionless) {
251326 console.debug('no new_bg_extensionless');
@@ -327,7 +402,7 @@ async function onDeleteBackgroundClick(e) {
327402 }
328403
329404 if (isCustom) {
330405 await getChatBackgroundsList();
331406 saveMetadataDebounced();
332407 }
333408 }
@@ -372,22 +447,55 @@ export async function getBackgrounds() {
372447 const response = await fetch('/api/backgrounds/all', {
373448 method: 'POST',
374449 headers: getRequestHeaders(),
375450 body: JSON.stringify({}),
376- '': '',
377- }),
378451 });
379452 if (response.ok) {
380453 const getData{ images, config } = await response.json();
381- //background = getData;
454+ Object.assign(THUMBNAIL_CONFIG, config);
382- //console.log(getData.length);
383455 $('#bg_menu_content').children('div').remove();
384456 for (const bg of getDataimages) {
385457 const template = await getBackgroundFromTemplate(bg, false);
386458 $('#bg_menu_content').append(template);
387459 }
460+ activateLazyLoader();
388461 }
389462}
390463
464+function activateLazyLoader() {
465+ // Disconnect previous observer to prevent memory leaks
466+ if (lazyLoadObserver) {
467+ lazyLoadObserver.disconnect();
468+ lazyLoadObserver = null;
469+ }
470+
471+ const lazyLoadElements = document.querySelectorAll('.lazy-load-background');
472+
473+ const options = {
474+ root: null,
475+ rootMargin: '200px',
476+ threshold: 0.01,
477+ };
478+
479+ lazyLoadObserver = new IntersectionObserver((entries, observer) => {
480+ entries.forEach(entry => {
481+ if (entry.target instanceof HTMLElement && entry.isIntersecting) {
482+ const target = entry.target;
483+ const bg = target.getAttribute('bgfile');
484+ const isCustom = target.getAttribute('custom') === 'true';
485+ resolveImageUrl(bg, isCustom)
486+ .then(url => { target.style.backgroundImage = url; })
487+ .catch(() => { target.style.backgroundImage = PLACEHOLDER_IMAGE; });
488+ target.classList.remove('lazy-load-background');
489+ observer.unobserve(target);
490+ }
491+ });
492+ }, options);
493+
494+ lazyLoadElements.forEach(element => {
495+ lazyLoadObserver.observe(element);
496+ });
497+}
498+
391499/**
392500 * Gets the CSS URL of the background
393501 * @param {Element} block
@@ -402,22 +510,41 @@ function generateUrlParameter(bg, isCustom) {
402510}
403511
404512/**
513+ * Resolves the image URL for the background.
514+ * @param {string} bg Background file name
515+ * @param {boolean} isCustom Is a custom background
516+ * @returns {Promise<string>} CSS URL of the background
517+ */
518+async function resolveImageUrl(bg, isCustom) {
519+ const fileExtension = bg.split('.').pop().toLowerCase();
520+ const isAnimated = ['mp4', 'webp'].includes(fileExtension);
521+ const thumbnailUrl = isAnimated && !background_settings.animation
522+ ? await getThumbnailFromStorage(bg)
523+ : isCustom
524+ ? bg
525+ : getThumbnailUrl('bg', bg);
526+
527+ return `url('${thumbnailUrl}')`;
528+}
529+
530+/**
405531 * Instantiates a background template
406532 * @param {string} bg Path to background
407533 * @param {boolean} isCustom Whether the background is custom
408534 * @returns {Promise<JQuery<HTMLElement>>} Background template
409535 */
410536async function getBackgroundFromTemplate(bg, isCustom) {
411537 const template = $('#background_template .bg_example').clone();
412- const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg);
413538 const url = generateUrlParameter(bg, isCustom);
414539 const title = isCustom ? bg.split('/').pop() : bg;
415540 const friendlyTitle = title.slice(0, title.lastIndexOf('.'));
541+
416542 template.attr('title', title);
417543 template.attr('bgfile', bg);
418544 template.attr('custom', String(isCustom));
419545 template.data('url', url);
420- template.css('background-image', `url('${thumbPath}')`);
546+ template.addClass('lazy-load-background');
547+ template.css('background-image', PLACEHOLDER_IMAGE);
421548 template.find('.BGSampleTitle').text(friendlyTitle);
422549 return template;
423550}
@@ -437,6 +564,12 @@ async function delBackground(bg) {
437564 bg: bg,
438565 }),
439566 });
567+
568+ await THUMBNAIL_STORAGE.removeItem(bg);
569+ if (THUMBNAIL_BLOBS.has(bg)) {
570+ URL.revokeObjectURL(THUMBNAIL_BLOBS.get(bg));
571+ THUMBNAIL_BLOBS.delete(bg);
572+ }
440573}
441574
442575async function onBackgroundUploadSelected() {
@@ -572,17 +705,20 @@ export function initBackgrounds() {
572705 $('#auto_background').on('click', autoBackgroundCommand);
573706 $('#add_bg_button').on('change', onBackgroundUploadSelected);
574707 $('#bg-filter').on('input', onBackgroundFilterInput);
575708 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg',
709+ name: 'lockbg',
576710 callback: () => onLockBackgroundClick(new CustomEvent('click')),
577711 aliases: ['bglock'],
578712 helpString: 'Locks a background for the currently selected chat',
579713 }));
580714 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg',
715+ name: 'unlockbg',
581716 callback: () => onUnlockBackgroundClick(new CustomEvent('click')),
582717 aliases: ['bgunlock'],
583718 helpString: 'Unlocks a background for the currently selected chat',
584719 }));
585720 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'autobg',
721+ name: 'autobg',
586722 callback: autoBackgroundCommand,
587723 aliases: ['bgauto'],
588724 helpString: 'Automatically changes the background based on the chat context using the AI request prompt',
@@ -593,4 +729,13 @@ export function initBackgrounds() {
593729 setFittingClass(background_settings.fitting);
594730 saveSettingsDebounced();
595731 });
732+
733+ $('#background_thumbnails_animation').on('input', async function () {
734+ background_settings.animation = !!$(this).prop('checked');
735+ saveSettingsDebounced();
736+
737+ // Refresh background thumbnails
738+ await getBackgrounds();
739+ await onChatChanged();
740+ });
596741}
public/scripts/bookmarks.js+32 -13
@@ -14,7 +14,6 @@ import {
1414} from '../script.js';
1515import { humanizedDateTime } from './RossAscends-mods.js';
1616import {
17- getGroupPastChats,
1817 group_activation_strategy,
1918 groups,
2019 openGroupById,
@@ -40,22 +39,42 @@ import {
4039
4140const bookmarkNameToken = 'Checkpoint #';
4241
42+/**
43+ * Gets the names of existing chats for the current character or group.
44+ * @returns {Promise<string[]>} - Returns a promise that resolves to an array of existing chat names.
45+ */
4346async function getExistingChatNames() {
4447 if (selected_group) {
4548 const datagroup = await getGroupPastChatsgroups.find(x => x.id == selected_group);
46- return data.map(x => x.file_name);
49+ if (group && Array.isArray(group.chats)) {
47- } else {
50+ return [...group.chats];
48- const response = await fetch('/api/characters/chats', {
49- method: 'POST',
50- headers: getRequestHeaders(),
51- body: JSON.stringify({ avatar_url: characters[this_chid].avatar }),
52- });
53-
54- if (response.ok) {
55- const data = await response.json();
56- return Object.values(data).map(x => x.file_name.replace('.jsonl', ''));
5751 }
52+
53+ return [];
5854 }
55+
56+ if (this_chid === undefined) {
57+ return [];
58+ }
59+
60+ const character = characters[this_chid];
61+ if (!character) {
62+ return [];
63+ }
64+
65+ const response = await fetch('/api/characters/chats', {
66+ method: 'POST',
67+ headers: getRequestHeaders(),
68+ body: JSON.stringify({ avatar_url: character.avatar, simple: true }),
69+ });
70+
71+ if (response.ok) {
72+ const data = await response.json();
73+ const chats = Object.values(data).map(x => x.file_name.replace('.jsonl', ''));
74+ return [...chats];
75+ }
76+
77+ return [];
5978}
6079
6180async function getBookmarkName({ isReplace = false, forceName = null } = {}) {
public/scripts/chats.js+7 -1
@@ -211,6 +211,12 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
211211 const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
212212 message.extra.image = imageUrl;
213213 message.extra.inline_image = true;
214+ }
215+ // If file is video
216+ else if (file.type.startsWith('video/')) {
217+ const extension = file.type.split('/')[1];
218+ const videoUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
219+ message.extra.video = videoUrl;
214220 } else {
215221 const uniqueFileName = `${fileNamePrefix}.txt`;
216222
@@ -1240,7 +1246,7 @@ async function openAttachmentManager() {
12401246
12411247 return () => {
12421248 modalButtonData.forEach(p => {
12431249 const { popper, bodyListener } = p;
12441250 popper.destroy();
12451251 document.body.removeEventListener('click', bodyListener);
12461252 });
public/scripts/custom-request.js+4 -4
@@ -279,9 +279,9 @@ export class TextCompletionService {
279279 instructPreset,
280280 );
281281 } else { // e.g. "<|im_start|>assistant: Hello, my name is"
282282 const overridenInstructPresetoverriddenInstructPreset = structuredClone(instructPreset);
283283 overridenInstructPresetoverriddenInstructPreset.output_suffix = '';
284284 overridenInstructPresetoverriddenInstructPreset.wrap = false;
285285 messageContent = formatInstructModeChat(
286286 message.role,
287287 message.content,
@@ -291,7 +291,7 @@ export class TextCompletionService {
291291 undefined,
292292 undefined,
293293 undefined,
294294 overridenInstructPresetoverriddenInstructPreset,
295295 );
296296 }
297297 }
public/scripts/data-maid.js+393 -0
@@ -0,0 +1,393 @@
1+import { getRequestHeaders } from '../script.js';
2+import { t } from './i18n.js';
3+import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
4+import { renderTemplateAsync } from './templates.js';
5+import { humanFileSize, timestampToMoment } from './utils.js';
6+
7+/**
8+ * @typedef {object} DataMaidReportResult
9+ * @property {import('../../src/endpoints/data-maid.js').DataMaidSanitizedReport} report - The sanitized report of the Data Maid.
10+ * @property {string} token - The token to use for the Data Maid report.
11+ */
12+
13+/**
14+ * Data Maid Dialog class for managing the cleanup dialog interface.
15+ */
16+class DataMaidDialog {
17+ constructor() {
18+ this.token = null;
19+ this.container = null;
20+ this.isScanning = false;
21+
22+ this.DATA_MAID_CATEGORIES = {
23+ files: {
24+ name: t`Files`,
25+ description: t`Files that are not associated with chat messages or Data Bank. WILL DELETE MANUAL UPLOADS!`,
26+ },
27+ images: {
28+ name: t`Images`,
29+ description: t`Images that are not associated with chat messages. WILL DELETE MANUAL UPLOADS!`,
30+ },
31+ chats: {
32+ name: t`Chats`,
33+ description: t`Chat files associated with deleted characters.`,
34+ },
35+ groupChats: {
36+ name: t`Group Chats`,
37+ description: t`Chat files associated with deleted groups.`,
38+ },
39+ avatarThumbnails: {
40+ name: t`Avatar Thumbnails`,
41+ description: t`Thumbnails for avatars of missing or deleted characters.`,
42+ },
43+ backgroundThumbnails: {
44+ name: t`Background Thumbnails`,
45+ description: t`Thumbnails for missing or deleted backgrounds.`,
46+ },
47+ chatBackups: {
48+ name: t`Chat Backups`,
49+ description: t`Automatically generated chat backups.`,
50+ },
51+ settingsBackups: {
52+ name: t`Settings Backups`,
53+ description: t`Automatically generated settings backups.`,
54+ },
55+ };
56+ }
57+
58+ /**
59+ * Returns a promise that resolves to the Data Maid report.
60+ * @returns {Promise<DataMaidReportResult>}
61+ * @private
62+ */
63+ async getReport() {
64+ const response = await fetch('/api/data-maid/report', {
65+ method: 'POST',
66+ headers: getRequestHeaders(),
67+ });
68+
69+ if (!response.ok) {
70+ throw new Error(`Error fetching Data Maid report: ${response.statusText}`);
71+ }
72+
73+ return await response.json();
74+ }
75+
76+ /**
77+ * Finalizes the Data Maid process by sending a request to the server.
78+ * @returns {Promise<void>}
79+ * @private
80+ */
81+ async finalize() {
82+ const response = await fetch('/api/data-maid/finalize', {
83+ method: 'POST',
84+ headers: getRequestHeaders(),
85+ body: JSON.stringify({ token: this.token }),
86+ });
87+
88+ if (!response.ok) {
89+ throw new Error(`Error finalizing Data Maid: ${response.statusText}`);
90+ }
91+ }
92+
93+ /**
94+ * Sets up the dialog UI elements and event listeners.
95+ * @private
96+ */
97+ async setupDialogUI() {
98+ const template = await renderTemplateAsync('dataMaidDialog');
99+ this.container = document.createElement('div');
100+ this.container.classList.add('dataMaidDialogContainer');
101+ this.container.innerHTML = template;
102+
103+ const startButton = this.container.querySelector('.dataMaidStartButton');
104+ startButton.addEventListener('click', () => this.handleScanClick());
105+ }
106+
107+ /**
108+ * Handles the scan button click event.
109+ * @private
110+ */
111+ async handleScanClick() {
112+ if (this.isScanning) {
113+ toastr.warning(t`The scan is already running. Please wait for it to finish.`);
114+ return;
115+ }
116+
117+ try {
118+ const resultsList = this.container.querySelector('.dataMaidResultsList');
119+ resultsList.innerHTML = '';
120+ this.showSpinner();
121+ this.isScanning = true;
122+
123+ const report = await this.getReport();
124+
125+ this.hideSpinner();
126+ await this.renderReport(report, resultsList);
127+ this.token = report.token;
128+ } catch (error) {
129+ this.hideSpinner();
130+ toastr.error(t`An error has occurred. Check the console for details.`);
131+ console.error('Error generating Data Maid report:', error);
132+ } finally {
133+ this.isScanning = false;
134+ }
135+ }
136+
137+ /**
138+ * Shows the loading spinner and hides the placeholder.
139+ * @private
140+ */
141+ showSpinner() {
142+ const spinner = this.container.querySelector('.dataMaidSpinner');
143+ const placeholder = this.container.querySelector('.dataMaidPlaceholder');
144+ placeholder.classList.add('displayNone');
145+ spinner.classList.remove('displayNone');
146+ }
147+
148+ /**
149+ * Hides the loading spinner.
150+ * @private
151+ */
152+ hideSpinner() {
153+ const spinner = this.container.querySelector('.dataMaidSpinner');
154+ spinner.classList.add('displayNone');
155+ }
156+
157+ /**
158+ * Renders the Data Maid report into the results list.
159+ * @param {DataMaidReportResult} report
160+ * @param {Element} resultsList
161+ * @private
162+ */
163+ async renderReport(report, resultsList) {
164+ for (const [prop, data] of Object.entries(this.DATA_MAID_CATEGORIES)) {
165+ const category = await this.renderCategory(prop, data.name, data.description, report.report[prop]);
166+ if (!category) {
167+ continue;
168+ }
169+ resultsList.appendChild(category);
170+ }
171+ this.displayEmptyPlaceholder();
172+ }
173+
174+ /**
175+ * Displays a placeholder message if no items are found in the results list.
176+ * @private
177+ */
178+ displayEmptyPlaceholder() {
179+ const resultsList = this.container.querySelector('.dataMaidResultsList');
180+ if (resultsList.children.length === 0) {
181+ const placeholder = this.container.querySelector('.dataMaidPlaceholder');
182+ placeholder.classList.remove('displayNone');
183+ placeholder.textContent = t`No items found to clean up. Come back later!`;
184+ }
185+ }
186+
187+ /**
188+ * Renders a single Data Maid category into a DOM element.
189+ * @param {string} prop Property name for the category
190+ * @param {string} name Name of the category
191+ * @param {string} description Description of the category
192+ * @param {import('../../src/endpoints/data-maid.js').DataMaidSanitizedRecord[]} items List of items in the category
193+ * @return {Promise<Element|null>} A promise that resolves to a DOM element containing the rendered category
194+ * @private
195+ */
196+ async renderCategory(prop, name, description, items) {
197+ if (!Array.isArray(items) || items.length === 0) {
198+ return null;
199+ }
200+
201+ const viewModel = {
202+ name: name,
203+ description: description,
204+ totalSize: humanFileSize(items.reduce((sum, item) => sum + item.size, 0)),
205+ totalItems: items.length,
206+ items: items.sort((a, b) => b.mtime - a.mtime).map(item => ({
207+ ...item,
208+ size: humanFileSize(item.size),
209+ date: timestampToMoment(item.mtime).format('L LT'),
210+ })),
211+ };
212+
213+ const template = await renderTemplateAsync('dataMaidCategory', viewModel);
214+ const categoryElement = document.createElement('div');
215+ categoryElement.innerHTML = template;
216+ categoryElement.querySelectorAll('.dataMaidItemView').forEach(button => {
217+ button.addEventListener('click', async () => {
218+ const item = button.closest('.dataMaidItem');
219+ const hash = item?.getAttribute('data-hash');
220+ if (hash) {
221+ await this.view(prop, hash);
222+ }
223+ });
224+ });
225+ categoryElement.querySelectorAll('.dataMaidItemDownload').forEach(button => {
226+ button.addEventListener('click', async () => {
227+ const item = button.closest('.dataMaidItem');
228+ const hash = item?.getAttribute('data-hash');
229+ if (hash) {
230+ await this.download(items, hash);
231+ }
232+ });
233+ });
234+ categoryElement.querySelectorAll('.dataMaidDeleteAll').forEach(button => {
235+ button.addEventListener('click', async (event) => {
236+ event.stopPropagation();
237+ const confirm = await Popup.show.confirm(t`Are you sure?`, t`This will permanently delete all files in this category. THIS CANNOT BE UNDONE!`);
238+ if (!confirm) {
239+ return;
240+ }
241+
242+ const hashes = items.map(item => item.hash).filter(hash => hash);
243+ await this.delete(hashes);
244+
245+ categoryElement.remove();
246+ this.displayEmptyPlaceholder();
247+ });
248+
249+ });
250+ categoryElement.querySelectorAll('.dataMaidItemDelete').forEach(button => {
251+ button.addEventListener('click', async () => {
252+ const item = button.closest('.dataMaidItem');
253+ const hash = item?.getAttribute('data-hash');
254+ if (hash) {
255+ const confirm = await Popup.show.confirm(t`Are you sure?`, t`This will permanently delete the file. THIS CANNOT BE UNDONE!`);
256+ if (!confirm) {
257+ return;
258+ }
259+ if (await this.delete([hash])) {
260+ item.remove();
261+ items.splice(items.findIndex(i => i.hash === hash), 1);
262+ if (items.length === 0) {
263+ categoryElement.remove();
264+ this.displayEmptyPlaceholder();
265+ }
266+ }
267+ }
268+ });
269+ });
270+ return categoryElement;
271+ }
272+
273+ /**
274+ * Constructs the URL for viewing an item by its hash.
275+ * @param {string} hash Hash of the item to view
276+ * @returns {string} URL to view the item
277+ * @private
278+ */
279+ getViewUrl(hash) {
280+ return `/api/data-maid/view?hash=${encodeURIComponent(hash)}&token=${encodeURIComponent(this.token)}`;
281+ }
282+
283+ /**
284+ * Downloads an item by its hash.
285+ * @param {import('../../src/endpoints/data-maid.js').DataMaidSanitizedRecord[]} items List of items in the category
286+ * @param {string} hash Hash of the item to download
287+ * @private
288+ */
289+ async download(items, hash) {
290+ const item = items.find(i => i.hash === hash);
291+ if (!item) {
292+ return;
293+ }
294+ const url = this.getViewUrl(hash);
295+ const a = document.createElement('a');
296+ a.href = url;
297+ a.download = item?.name || hash;
298+ document.body.appendChild(a);
299+ a.click();
300+ document.body.removeChild(a);
301+ }
302+
303+ /**
304+ * Opens the item view for a specific hash.
305+ * @param {string} prop Property name for the category
306+ * @param {string} hash Item hash to view
307+ * @private
308+ */
309+ async view(prop, hash) {
310+ const url = this.getViewUrl(hash);
311+ const isImage = ['images', 'avatarThumbnails', 'backgroundThumbnails'].includes(prop);
312+ const element = isImage
313+ ? await this.getViewElement(url)
314+ : await this.getTextViewElement(url);
315+ await callGenericPopup(element, POPUP_TYPE.DISPLAY, '', { large: true, wide: true });
316+ }
317+
318+ /**
319+ * Deletes an item by its file path hash.
320+ * @param {string[]} hashes Hashes of items to delete
321+ * @return {Promise<boolean>} True if the deletion was successful, false otherwise
322+ * @private
323+ */
324+ async delete(hashes) {
325+ try {
326+ const response = await fetch('/api/data-maid/delete', {
327+ method: 'POST',
328+ headers: getRequestHeaders(),
329+ body: JSON.stringify({ hashes: hashes, token: this.token }),
330+ });
331+
332+ if (!response.ok) {
333+ throw new Error(`Error deleting item: ${response.statusText}`);
334+ }
335+
336+ return true;
337+ } catch (error) {
338+ console.error('Error deleting item:', error);
339+ return false;
340+ }
341+ }
342+
343+ /**
344+ * Gets an image element for viewing images.
345+ * @param {string} url View URL
346+ * @returns {Promise<HTMLElement>} Image element
347+ * @private
348+ */
349+ async getViewElement(url) {
350+ const img = document.createElement('img');
351+ img.src = url;
352+ img.classList.add('dataMaidImageView');
353+ return img;
354+ }
355+
356+ /**
357+ * Gets an iframe element for viewing text content.
358+ * @param {string} url View URL
359+ * @returns {Promise<HTMLTextAreaElement>} Frame element
360+ * @private
361+ */
362+ async getTextViewElement(url) {
363+ const response = await fetch(url);
364+ const text = await response.text();
365+ const element = document.createElement('textarea');
366+ element.classList.add('dataMaidTextView');
367+ element.readOnly = true;
368+ element.textContent = text;
369+ return element;
370+ }
371+
372+ /**
373+ * Opens the Data Maid dialog and handles the interaction.
374+ */
375+ async open() {
376+ await this.setupDialogUI();
377+ await callGenericPopup(this.container, POPUP_TYPE.TEXT, '', { wide: true, large: true });
378+
379+ if (this.token) {
380+ await this.finalize();
381+ }
382+ }
383+}
384+
385+export function initDataMaid() {
386+ const dataMaidButton = document.getElementById('data_maid_button');
387+ if (!dataMaidButton) {
388+ console.warn('Data Maid button not found');
389+ return;
390+ }
391+
392+ dataMaidButton.addEventListener('click', () => new DataMaidDialog().open());
393+}
public/scripts/extensions.js+99 -12
@@ -36,7 +36,13 @@ export let modules = [];
3636 * A set of active extensions.
3737 * @type {Set<string>}
3838 */
3939letconst activeExtensions = new Set();
40+
41+/**
42+ * Errors that occurred while loading extensions.
43+ * @type {Set<string>}
44+ */
45+const extensionLoadErrors = new Set();
4046
4147const getApiUrl = () => extension_settings.apiUrl;
4248const sortManifestsByOrder = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));
@@ -172,6 +178,7 @@ export const extension_settings = {
172178 llmPrompt: undefined,
173179 allowMultiple: true,
174180 rerollIfSame: false,
181+ promptType: 'raw',
175182 },
176183 connectionManager: {
177184 selectedProfile: '',
@@ -379,37 +386,90 @@ async function getManifests(names) {
379386 * @returns {Promise<void>}
380387 */
381388async function activateExtensions() {
389+ extensionLoadErrors.clear();
382390 const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));
391+ const extensionNames = extensions.map(x => x[0]);
383392 const promises = [];
384393
385394 for (let entry of extensions) {
386395 const name = entry[0];
387396 const manifest = entry[1];
397+ const extrasRequirements = manifest.requires;
398+ const extensionDependencies = manifest.dependencies;
399+ const displayName = manifest.display_name || name;
388400
389401 if (activeExtensions.has(name)) {
390402 continue;
391403 }
392404
393- const meetsModuleRequirements = !Array.isArray(manifest.requires) || isSubsetOf(modules, manifest.requires);
405+ // Module requirements: pass if 'requires' is undefined, null, or not an array; check subset if it's an array
406+ let meetsModuleRequirements = true;
407+ let missingModules = [];
408+ if (extrasRequirements !== undefined) {
409+ if (Array.isArray(extrasRequirements)) {
410+ meetsModuleRequirements = isSubsetOf(modules, extrasRequirements);
411+ missingModules = extrasRequirements.filter(req => !modules.includes(req));
412+ } else {
413+ console.warn(`Extension ${name}: manifest.json 'requires' field is not an array. Loading allowed, but any intended requirements were not verified to exist.`);
414+ }
415+ }
416+
417+ // Extension dependencies: pass if 'dependencies' is undefined or not an array; check subset and disabled status if it's an array
418+ let meetsExtensionDeps = true;
419+ let missingDependencies = [];
420+ let disabledDependencies = [];
421+ if (extensionDependencies !== undefined) {
422+ if (Array.isArray(extensionDependencies)) {
423+ // Check if all dependencies exist
424+ meetsExtensionDeps = isSubsetOf(extensionNames, extensionDependencies);
425+ missingDependencies = extensionDependencies.filter(dep => !extensionNames.includes(dep));
426+ // Check for disabled dependencies
427+ if (meetsExtensionDeps) {
428+ disabledDependencies = extensionDependencies.filter(dep => extension_settings.disabledExtensions.includes(dep));
429+ if (disabledDependencies.length > 0) {
430+ // Fail if any dependencies are disabled
431+ meetsExtensionDeps = false;
432+ }
433+ }
434+ } else {
435+ console.warn(`Extension ${name}: manifest.json 'dependencies' field is not an array. Loading allowed, but any intended requirements were not verified to exist.`);
436+ }
437+ }
438+
394439 const isDisabled = extension_settings.disabledExtensions.includes(name);
395440
396441 if (meetsModuleRequirements && meetsExtensionDeps && !isDisabled) {
397442 try {
398443 console.debug('Activating extension', name);
399444 const promise = addExtensionLocale(name, manifest).finally(() => Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]));
445+ Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]),
446+ );
400447 await promise
401448 .then(() => activeExtensions.add(name))
402- .catch(err => console.log('Could not activate extension', name, err));
449+ .catch(err => {
450+ console.log('Could not activate extension', name, err);
451+ extensionLoadErrors.add(t`Extension "${displayName}" failed to load: ${err}`);
452+ });
403453 promises.push(promise);
454+ } catch (error) {
455+ console.error('Could not activate extension', name, error);
404456 }
405- catch (error) {
457+ } else if (!meetsModuleRequirements && !isDisabled) {
406- console.error('Could not activate extension', name);
458+ console.warn(t`Extension "${name}" did not load. Missing required Extras module(s): "${missingModules.join(', ')}"`);
407- console.error(error);
459+ extensionLoadErrors.add(t`Extension "${displayName}" did not load. Missing required Extras module(s): "${missingModules.join(', ')}"`);
460+ } else if (!meetsExtensionDeps && !isDisabled) {
461+ if (disabledDependencies.length > 0) {
462+ console.warn(t`Extension "${name}" did not load. Required extensions exist but are disabled: "${disabledDependencies.join(', ')}". Enable them first, then reload.`);
463+ extensionLoadErrors.add(t`Extension "${displayName}" did not load. Required extensions exist but are disabled: "${disabledDependencies.join(', ')}". Enable them first, then reload.`);
464+ } else {
465+ console.warn(t`Extension "${name}" did not load. Missing required extensions: "${missingDependencies.join(', ')}"`);
466+ extensionLoadErrors.add(t`Extension "${displayName}" did not load. Missing required extensions: "${missingDependencies.join(', ')}"`);
408467 }
409468 }
410469 }
411470
412471 await Promise.allSettled(promises);
472+ $('#extensions_details').toggleClass('warning', extensionLoadErrors.size > 0);
413473}
414474
415475async function connectClickHandler() {
@@ -752,6 +812,27 @@ function getModuleInformation() {
752812}
753813
754814/**
815+ * Generates HTML for the extension load errors.
816+ * @returns {string} HTML string containing the errors that occurred while loading extensions.
817+ */
818+function getExtensionLoadErrorsHtml() {
819+ if (extensionLoadErrors.size === 0) {
820+ return '';
821+ }
822+
823+ const container = document.createElement('div');
824+ container.classList.add('info-block', 'error');
825+
826+ for (const error of extensionLoadErrors) {
827+ const errorElement = document.createElement('div');
828+ errorElement.textContent = error;
829+ container.appendChild(errorElement);
830+ }
831+
832+ return container.outerHTML;
833+}
834+
835+/**
755836 * Generates the HTML strings for all extensions and displays them in a popup.
756837 */
757838async function showExtensionsDetails() {
@@ -765,6 +846,7 @@ async function showExtensionsDetails() {
765846 initialScrollTop = oldPopup.content.scrollTop;
766847 await oldPopup.completeCancelled();
767848 }
849+ const htmlErrors = getExtensionLoadErrorsHtml();
768850 const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');
769851 const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');
770852 const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
@@ -787,6 +869,7 @@ async function showExtensionsDetails() {
787869
788870 const html = $('<div></div>')
789871 .addClass('extensions_info')
872+ .append(htmlErrors)
790873 .append(htmlDefault)
791874 .append(htmlExternal)
792875 .append(getModuleInformation());
@@ -893,11 +976,14 @@ async function onUpdateClick() {
893976 * Updates a third-party extension via the API.
894977 * @param {string} extensionName Extension folder name
895978 * @param {boolean} quiet If true, don't show a success message
979+ * @param {number?} timeout Timeout in milliseconds to wait for the update to complete. If null, no timeout is set.
896980 */
897981async function updateExtension(extensionName, quiet, timeout = null) {
898982 try {
983+ const signal = timeout ? AbortSignal.timeout(timeout) : undefined;
899984 const response = await fetch('/api/extensions/update', {
900985 method: 'POST',
986+ signal: signal,
901987 headers: getRequestHeaders(),
902988 body: JSON.stringify({
903989 extensionName,
@@ -926,7 +1012,7 @@ async function updateExtension(extensionName, quiet) {
9261012 toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`);
9271013 }
9281014 } catch (error) {
9291015 console.error('ErrorExtension update error:', error);
9301016 }
9311017}
9321018
@@ -1397,6 +1483,7 @@ async function autoUpdateExtensions(forceAll) {
13971483 const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 });
13981484 const isCurrentUserAdmin = isAdmin();
13991485 const promises = [];
1486+ const autoUpdateTimeout = 60 * 1000;
14001487 for (const [id, manifest] of Object.entries(manifests)) {
14011488 const isDisabled = extension_settings.disabledExtensions.includes(id);
14021489 if (!forceAll && isDisabled) {
@@ -1410,7 +1497,7 @@ async function autoUpdateExtensions(forceAll) {
14101497 }
14111498 if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) {
14121499 console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`);
14131500 promises.push(updateExtension(id.replace('third-party', ''), true, autoUpdateTimeout));
14141501 }
14151502 }
14161503 await Promise.allSettled(promises);
@@ -1453,7 +1540,7 @@ export async function runGenerationInterceptors(chat, contextSize, type) {
14531540
14541541/**
14551542 * Writes a field to the character's data extensions object.
14561543 * @param {number|string} characterId Index in the character array
14571544 * @param {string} key Field name
14581545 * @param {any} value Field value
14591546 * @returns {Promise<void>} When the field is written
public/scripts/extensions/assets/index.js+5 -5
@@ -53,11 +53,11 @@ function filterAssets() {
5353}
5454
5555const KNOWN_TYPES = {
5656 'extension': 't`Extensions'`,
5757 'character': 't`Characters'`,
5858 'ambient': 't`Ambient sounds'`,
5959 'bgm': 't`Background music'`,
6060 'blip': 't`Blip sounds'`,
6161};
6262
6363async function downloadAssetsList(url) {
public/scripts/extensions/assets/style.css+1 -1
@@ -65,7 +65,7 @@
6565.asset-download-button-text {
6666 font: bold 20px "Quicksand", san-serif;
6767 color: #ffffff;
6868 transition: all 0.2svar(--animation-duration-2x);
6969}
7070
7171.asset-download-button-loading .asset-download-button-text {
public/scripts/extensions/attachments/index.js+29 -0
@@ -216,8 +216,37 @@ function cleanUpAttachments() {
216216 }
217217}
218218
219+/**
220+ * Clean up character attachments when a character is deleted.
221+ * @param {{character: import('../../char-data.js').v1CharData}} data Event data
222+ */
223+function cleanUpCharacterAttachments(data) {
224+ const avatar = data?.character?.avatar;
225+ if (!avatar) return;
226+ if (Array.isArray(extension_settings?.character_attachments?.[avatar])) {
227+ delete extension_settings.character_attachments[avatar];
228+ saveSettingsDebounced();
229+ }
230+}
231+
232+/**
233+ * Handle character rename event to update character attachments.
234+ * @param {string} oldAvatar Old avatar name
235+ * @param {string} newAvatar New avatar name
236+ */
237+function handleCharacterRename(oldAvatar, newAvatar) {
238+ if (!oldAvatar || !newAvatar) return;
239+ if (Array.isArray(extension_settings?.character_attachments?.[oldAvatar])) {
240+ extension_settings.character_attachments[newAvatar] = extension_settings.character_attachments[oldAvatar];
241+ delete extension_settings.character_attachments[oldAvatar];
242+ saveSettingsDebounced();
243+ }
244+}
245+
219246jQuery(async () => {
220247 eventSource.on(event_types.APP_READY, cleanUpAttachments);
248+ eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments);
249+ eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename);
221250 const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
222251 const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
223252 $('#data_bank_wand_container').append(manageButton);
public/scripts/extensions/attachments/notepad.html+1 -1
@@ -3,7 +3,7 @@
33 File Name
44 </label>
55 <input type="text" class="text_pole" id="notepadFileName" name="notepadFileName" value="" />
66 <labelslabel>
77 File Content
88 </label>
99 <textarea id="notepadFileContent" name="notepadFileContent" class="text_pole textarea_compact monospace flex1" placeholder="Enter your notes here."></textarea>
public/scripts/extensions/caption/index.js+7 -5
@@ -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, 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';
@@ -9,6 +9,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
99import { SlashCommand } from '../../slash-commands/SlashCommand.js';
1010import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
1111import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
12+import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
1213export { MODULE_NAME };
1314
1415const MODULE_NAME = 'caption';
@@ -98,9 +99,9 @@ async function wrapCaptionTemplate(caption) {
9899 let messageText = substituteParamsExtended(template, { caption: caption });
99100
100101 if (extension_settings.caption.refine_mode) {
101102 messageText = await callPopupPopup.show.input(
102103 '<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.',
103- 'input',
104+ 'Press "Cancel" to abort the caption sending.',
104105 messageText,
105106 { rows: 5, okButton: 'Send' });
106107
@@ -278,7 +279,7 @@ async function captionMultimodal(base64Img, externalPrompt) {
278279 let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT;
279280
280281 if (!externalPrompt && extension_settings.caption.prompt_ask) {
281282 const customPrompt = await callPopupcallGenericPopup('<h3>Enter a comment or question:</h3>', 'input'POPUP_TYPE.INPUT, prompt, { rows: 2 });
282283 if (!customPrompt) {
283284 throw new Error('User aborted the caption sending.');
284285 }
@@ -432,6 +433,7 @@ jQuery(async function () {
432433 'zerooneai': SECRET_KEYS.ZEROONEAI,
433434 'groq': SECRET_KEYS.GROQ,
434435 'cohere': SECRET_KEYS.COHERE,
436+ 'aimlapi': SECRET_KEYS.AIMLAPI,
435437 };
436438
437439 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+27 -0
@@ -18,6 +18,7 @@
1818 <label for="caption_multimodal_api" data-i18n="API">API</label>
1919 <select id="caption_multimodal_api" class="flex1 text_pole">
2020 <option value="zerooneai">01.AI (Yi)</option>
21+ <option value="aimlapi">AI/ML API</option>
2122 <option value="anthropic">Anthropic</option>
2223 <option value="cohere">Cohere</option>
2324 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
@@ -85,11 +86,15 @@
8586 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
8687 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
8788 <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
89+ <option data-type="google" value="gemini-2.5-pro">gemini-2.5-pro</option>
90+ <option data-type="google" value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option>
8891 <option data-type="google" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
8992 <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
9093 <option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
94+ <option data-type="google" value="gemini-2.5-flash">gemini-2.5-flash</option>
9195 <option data-type="google" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
9296 <option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
97+ <option data-type="google" value="gemini-2.5-flash-lite-preview-06-17">gemini-2.5-flash-lite-preview-06-17</option>
9398 <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option>
9499 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option>
95100 <option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option>
@@ -116,15 +121,37 @@
116121 <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>
117122 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
118123 <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>
124+ <option data-type="vertexai" value="gemini-2.5-pro">gemini-2.5-pro</option>
125+ <option data-type="vertexai" value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option>
119126 <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
120127 <option data-type="vertexai" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
128+ <option data-type="vertexai" value="gemini-2.5-flash">gemini-2.5-flash</option>
121129 <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
122130 <option data-type="vertexai" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
131+ <option data-type="vertexai" value="gemini-2.5-flash-lite-preview-06-17">gemini-2.5-flash-lite-preview-06-17</option>
123132 <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
124133 <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
125134 <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
126135 <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
127136 <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>
137+ <option data-type="aimlapi" value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
138+ <option data-type="aimlapi" value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option>
139+ <option data-type="aimlapi" value="gpt-4o">gpt-4o</option>
140+ <option data-type="aimlapi" value="gpt-4o-mini">gpt-4o-mini</option>
141+ <option data-type="aimlapi" value="gpt-4o-mini-2024-07-18">gpt-4o-mini-2024-07-18</option>
142+ <option data-type="aimlapi" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
143+ <option data-type="aimlapi" value="gpt-4-turbo">gpt-4-turbo</option>
144+ <option data-type="aimlapi" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
145+ <option data-type="aimlapi" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
146+ <option data-type="aimlapi" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
147+ <option data-type="aimlapi" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
148+ <option data-type="aimlapi" value="google/gemini-1.5-flash">google/gemini-1.5-flash</option>
149+ <option data-type="aimlapi" value="google/gemini-1.5-pro">google/gemini-1.5-pro</option>
150+ <option data-type="aimlapi" value="google/gemini-2.0-flash">google/gemini-2.0-flash</option>
151+ <option data-type="aimlapi" value="google/gemini-2.0-flash-exp">google/gemini-2.0-flash-exp</option>
152+ <option data-type="aimlapi" value="google/gemini-2.5-flash-preview">google/gemini-2.5-flash-preview</option>
153+ <option data-type="aimlapi" value="meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo">meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo</option>
154+ <option data-type="aimlapi" value="meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo">meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo</option>
128155 <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
129156 <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
130157 <option data-type="openrouter" value="openai/gpt-4o-2024-05-13">openai/gpt-4o-2024-05-13</option>
public/scripts/extensions/connection-manager/index.js+15 -0
@@ -13,6 +13,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
1313import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
1414import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js';
1515import { t } from '../../i18n.js';
16+import { getSecretLabelById } from '../../secrets.js';
1617
1718const MODULE_NAME = 'connection-manager';
1819const NONE = '<None>';
@@ -40,6 +41,8 @@ const CC_COMMANDS = [
4041 'stop-strings',
4142 'start-reply-with',
4243 'reasoning-template',
44+ 'prompt-post-processing',
45+ 'secret-id',
4346];
4447
4548const TC_COMMANDS = [
@@ -56,6 +59,7 @@ const TC_COMMANDS = [
5659 'stop-strings',
5760 'start-reply-with',
5861 'reasoning-template',
62+ 'secret-id',
5963];
6064
6165const FANCY_NAMES = {
@@ -73,6 +77,8 @@ const FANCY_NAMES = {
7377 'stop-strings': 'Custom Stopping Strings',
7478 'start-reply-with': 'Start Reply With',
7579 'reasoning-template': 'Reasoning Template',
80+ 'prompt-post-processing': 'Prompt Post-Processing',
81+ 'secret-id': 'Secret',
7682};
7783
7884/**
@@ -342,6 +348,15 @@ function makeFancyProfile(profile) {
342348 return acc;
343349 }
344350
351+ // UUID is not very useful in the UI, so we replace it with a label (if available)
352+ if (key === 'secret-id') {
353+ const label = getSecretLabelById(profile[key]);
354+ if (label) {
355+ acc[value] = label;
356+ return acc;
357+ }
358+ }
359+
345360 acc[value] = profile[key];
346361 return acc;
347362 }, {});
public/scripts/extensions/expressions/index.js+39 -6
@@ -1,6 +1,6 @@
11import { Fuse } from '../../../lib.js';
22
33import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js';
44import { dragElement, isMobile } from '../../RossAscends-mods.js';
55import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
66import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
@@ -86,6 +86,12 @@ const EXPRESSION_API = {
8686 none: 99,
8787};
8888
89+/** @enum {string} */
90+const PROMPT_TYPE = {
91+ raw: 'raw',
92+ full: 'full',
93+};
94+
8995let expressionsList = null;
9096let lastCharacter = undefined;
9197let lastMessage = null;
@@ -909,10 +915,6 @@ function sampleClassifyText(text) {
909915 * @returns {Promise<string>} Prompt for the LLM API.
910916 */
911917async function getLlmPrompt(labels) {
912- if (isJsonSchemaSupported()) {
913- return '';
914- }
915-
916918 const labelsString = labels.map(x => `"${x}"`).join(', ');
917919 const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });
918920 return prompt;
@@ -976,6 +978,7 @@ function getJsonSchema(emotions) {
976978 required: [
977979 'emotion',
978980 ],
981+ additionalProperties: false,
979982 };
980983}
981984
@@ -1047,7 +1050,21 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10471050 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
10481051 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
10491052 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
1050- const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
1053+
1054+ let emotionResponse;
1055+ try {
1056+ inApiCall = true;
1057+ switch (extension_settings.expressions.promptType) {
1058+ case PROMPT_TYPE.raw:
1059+ emotionResponse = await generateRaw(text, main_api, false, false, prompt);
1060+ break;
1061+ case PROMPT_TYPE.full:
1062+ emotionResponse = await generateQuietPrompt(prompt, false, false);
1063+ break;
1064+ }
1065+ } finally {
1066+ inApiCall = false;
1067+ }
10511068 return parseLlmResponse(emotionResponse, expressionsList);
10521069 }
10531070 // Using WebLLM
@@ -1702,6 +1719,7 @@ function onExpressionApiChanged() {
17021719 if (tempApi) {
17031720 extension_settings.expressions.api = Number(tempApi);
17041721 $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
1722+ $('.expression_prompt_type_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
17051723 expressionsList = null;
17061724 spriteCache = {};
17071725 moduleWorker();
@@ -2102,6 +2120,11 @@ function migrateSettings() {
21022120 extension_settings.expressions.showDefault = false;
21032121 saveSettingsDebounced();
21042122 }
2123+
2124+ if (extension_settings.expressions.promptType === undefined) {
2125+ extension_settings.expressions.promptType = PROMPT_TYPE.raw;
2126+ saveSettingsDebounced();
2127+ }
21052128}
21062129
21072130(async function () {
@@ -2169,6 +2192,16 @@ function migrateSettings() {
21692192 extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT;
21702193 saveSettingsDebounced();
21712194 });
2195+ $('#expression_prompt_raw').on('input', function () {
2196+ extension_settings.expressions.promptType = PROMPT_TYPE.raw;
2197+ saveSettingsDebounced();
2198+ });
2199+ $('#expression_prompt_full').on('input', function () {
2200+ extension_settings.expressions.promptType = PROMPT_TYPE.full;
2201+ saveSettingsDebounced();
2202+ });
2203+ $(`input[name="expression_prompt_type"][value="${extension_settings.expressions.promptType}"]`).prop('checked', true);
2204+ $('.expression_prompt_type_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
21722205
21732206 $('#expression_custom_add').on('click', onClickExpressionAddCustom);
21742207 $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
public/scripts/extensions/expressions/settings.html+16 -3
@@ -40,8 +40,21 @@
4040 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
4141 </div>
4242 </label>
4343 <small data-i18n="Will be used if theUsed APIin doesn'taddition supportto JSON schemas orand function calling.">Will be used if theUsed APIin doesn'taddition supportto JSON schemas orand function calling.</small>
4444 <textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact autoSetHeight" rows="2" placeholder="Use &lcub;&lcub;labels&rcub;&rcub; special macro."></textarea>
45+ </div>
46+ <div class="expression_prompt_type_block flex-container flexFlowColumn">
47+ <div data-i18n="LLM Prompt Strategy" class="title_restorable">
48+ LLM Prompt Strategy
49+ </div>
50+ <label for="expression_prompt_raw" class="checkbox_label">
51+ <input id="expression_prompt_raw" type="radio" name="expression_prompt_type" value="raw">
52+ <span data-i18n="Limited Context">Limited Context</span>
53+ </label>
54+ <label for="expression_prompt_full" class="checkbox_label">
55+ <input id="expression_prompt_full" type="radio" name="expression_prompt_type" value="full">
56+ <span data-i18n="Full Context">Full Context</span>
57+ </label>
4558 </div>
4659 <div class="expression_fallback_block m-b-1 m-t-1">
4760 <label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
@@ -52,7 +65,7 @@
5265 <label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>
5366 <small><span data-i18n="Can be set manually or with an _space">Can be set manually or with an </span><tt>/emote</tt><span data-i18n="space_ slash command."> slash command.</span></small>
5467 <div class="flex-container">
5568 <select id="expression_custom" class="flex1 margin0"></select>
5669 <i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
5770 <i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
5871 </div>
public/scripts/extensions/gallery/index.js+70 -6
@@ -4,6 +4,8 @@ import {
44 characters,
55 getRequestHeaders,
66 event_types,
7+ animation_duration,
8+ animation_easing,
79} from '../../../script.js';
810import { groups, selected_group } from '../../group-chats.js';
911import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js';
@@ -15,10 +17,12 @@ import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/S
1517import { DragAndDropHandler } from '../../dragdrop.js';
1618import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
1719import { t, translate } from '../../i18n.js';
20+import { Popup } from '../../popup.js';
1821
1922const extensionName = 'gallery';
2023const extensionFolderPath = `scripts/extensions/${extensionName}/`;
2124let firstTime = true;
25+let deleteModeActive = false;
2226
2327// Exposed defaults for future tweaking
2428let thumbnailHeight = 150;
@@ -30,7 +34,15 @@ let galleryMaxRows = 3;
3034$('#movingDivs').on('click', '.dragClose', function () {
3135 const relatedId = $(this).data('related-id');
3236 if (!relatedId) return;
3337 const relatedElement = $(`#movingDivs > .draggable[id="${relatedId}"]`).remove();
38+ relatedElement.transition({
39+ opacity: 0,
40+ duration: animation_duration,
41+ easing: animation_easing,
42+ complete: () => {
43+ relatedElement.remove();
44+ },
45+ });
3446});
3547
3648const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
@@ -147,6 +159,29 @@ async function getGalleryFolders() {
147159}
148160
149161/**
162+ * Deletes a gallery item based on the provided URL.
163+ * @param {string} url - The URL of the image to be deleted.
164+ */
165+async function deleteGalleryItem(url) {
166+ try {
167+ const response = await fetch('/api/images/delete', {
168+ method: 'POST',
169+ headers: getRequestHeaders(),
170+ body: JSON.stringify({ path: url }),
171+ });
172+
173+ if (!response.ok) {
174+ throw new Error(`HTTP error. Status: ${response.status}`);
175+ }
176+
177+ toastr.success(t`Image deleted successfully.`);
178+ } catch (error) {
179+ console.error('Failed to delete the image:', error);
180+ toastr.error(t`Failed to delete the image. Check the console for details.`);
181+ }
182+}
183+
184+/**
150185 * Sets the sort order for the gallery.
151186 * @param {string} order Sort order
152187 */
@@ -260,7 +295,7 @@ async function initGallery(items, url) {
260295 *
261296 * @returns {Promise<void>} - Promise representing the completion of the gallery display process.
262297 */
263298async function showCharGallery(deleteModeState = false) {
264299 // Load necessary files if it's the first time calling the function
265300 if (firstTime) {
266301 await loadFileToDocument(
@@ -276,6 +311,7 @@ async function showCharGallery() {
276311 }
277312
278313 try {
314+ deleteModeActive = deleteModeState;
279315 let url = selected_group || this_chid;
280316 if (!selected_group && this_chid !== undefined) {
281317 url = getGalleryFolder(characters[this_chid]);
@@ -347,7 +383,7 @@ async function makeMovable(url) {
347383 const id = 'gallery';
348384 const template = $('#generic_draggable_template').html();
349385 const newElement = $(template);
350386 newElement.css({ 'background-color',: 'var(--SmartThemeBlurTintColor)', 'opacity': 0 });
351387 newElement.attr('forChar', id);
352388 newElement.attr('id', id);
353389 newElement.find('.drag-grabber').attr('id', `${id}header`);
@@ -429,6 +465,18 @@ async function makeMovable(url) {
429465 galleryFolderAccept.title = t`Change gallery folder`;
430466 galleryFolderAccept.addEventListener('click', onChangeFolder);
431467
468+ const galleryDeleteMode = document.createElement('div');
469+ galleryDeleteMode.classList.add('right_menu_button', 'fa-solid', 'fa-trash', 'fa-fw');
470+ galleryDeleteMode.classList.toggle('warning', deleteModeActive);
471+ galleryDeleteMode.title = t`Delete mode`;
472+ galleryDeleteMode.addEventListener('click', () => {
473+ deleteModeActive = !deleteModeActive;
474+ galleryDeleteMode.classList.toggle('warning', deleteModeActive);
475+ if (deleteModeActive) {
476+ toastr.info(t`Delete mode is ON. Click on images you want to delete.`);
477+ }
478+ });
479+
432480 const galleryFolderRestore = document.createElement('div');
433481 galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');
434482 galleryFolderRestore.title = t`Restore gallery folder`;
@@ -436,6 +484,7 @@ async function makeMovable(url) {
436484
437485 topBarElement.appendChild(galleryFolderInput);
438486 topBarElement.appendChild(galleryFolderAccept);
487+ topBarElement.appendChild(galleryDeleteMode);
439488 topBarElement.appendChild(galleryFolderRestore);
440489 newElement.append(topBarElement);
441490
@@ -466,6 +515,11 @@ async function makeMovable(url) {
466515 loadMovingUIState();
467516 $(`.draggable[forChar="${id}"]`).css('display', 'block');
468517 dragElement(newElement);
518+ newElement.transition({
519+ opacity: 1,
520+ duration: animation_duration,
521+ easing: animation_easing,
522+ });
469523
470524 $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {
471525 console.log('saw drag on avatar!');
@@ -635,9 +689,19 @@ function sanitizeHTMLId(id) {
635689function viewWithDragbox(items) {
636690 if (items && items.length > 0) {
637691 const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
638- // ID should just be the last part of the URL, removing the extension
692+ if (deleteModeActive) {
639- const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
693+ Popup.show.confirm(t`Are you sure you want to delete this image?`, url)
640- makeDragImg(id, url);
694+ .then(async (confirmed) => {
695+ if (!confirmed) {
696+ return;
697+ }
698+ deleteGalleryItem(url).then(() => showCharGallery(deleteModeActive));
699+ });
700+ } else {
701+ // ID should just be the last part of the URL, removing the extension
702+ const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
703+ makeDragImg(id, url);
704+ }
641705 }
642706}
643707
public/scripts/extensions/gallery/style.css+5 -0
@@ -43,3 +43,8 @@
4343#dragGallery {
4444 min-height: 25dvh;
4545}
46+
47+#gallery .right_menu_button.warning {
48+ opacity: 1;
49+ filter: unset;
50+}
public/scripts/extensions/memory/index.js+3 -1
@@ -16,6 +16,7 @@ import {
1616 getMaxContextSize,
1717 setExtensionPrompt,
1818 streamingProcessor,
19+ animation_easing,
1920} from '../../../script.js';
2021import { is_group_generating, selected_group } from '../../group-chats.js';
2122import { loadMovingUIState } from '../../power-user.js';
@@ -972,6 +973,7 @@ function doPopout(e) {
972973 </div>`;
973974 const newElement = $(template);
974975 newElement.attr('id', 'summaryExtensionPopout')
976+ .css('opacity', 0)
975977 .removeClass('zoomed_avatar')
976978 .addClass('draggable')
977979 .empty();
@@ -980,13 +982,13 @@ function doPopout(e) {
980982 originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>');
981983 newElement.append(controlBarHtml).append(originalHTMLClone);
982984 $('body').append(newElement);
985+ newElement.transition({ opacity: 1, duration: animation_duration, easing: animation_easing });
983986 $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull');
984987 setMemoryContext(prevSummaryBoxContents, false); //paste prev summary box contents into popout box
985988 setupListeners();
986989 loadSettings();
987990 loadMovingUIState();
988991
989- $('#summaryExtensionPopout').fadeIn(animation_duration);
990992 dragElement(newElement);
991993
992994 //setup listener for close button to restore extensions menu
public/scripts/extensions/quick-reply/index.js+1 -1
@@ -181,7 +181,7 @@ const init = async () => {
181181
182182 eventSource.on(event_types.APP_READY, async()=>await finalizeInit());
183183
184184 window['globalThis.quickReplyApi'] = quickReplyApi;
185185};
186186const finalizeInit = async () => {
187187 debug('executing startup');
public/scripts/extensions/quick-reply/style.css+13 -16
@@ -18,7 +18,7 @@
1818#qr--bar {
1919 outline: none;
2020 margin: 0;
21- transition: 0.3s;
21+ transition: var(--animation-duration-2x);
2222 opacity: 0.7;
2323 display: flex;
2424 align-items: center;
@@ -34,14 +34,12 @@
3434 right: 0.25em;
3535 top: 0;
3636}
37-
3837/*hide QR popout for mobile*/
3938@media screen and (max-width: 1000px) {
4039 #qr--bar > #qr--popoutTrigger {
4140 display: none;
4241 }
4342}
44-
4543#qr--bar.popoutVisible {
4644 padding-right: 2.5em;
4745}
@@ -61,9 +59,8 @@
6159 aspect-ratio: 1 / 1;
6260 font-size: 20px;
6361 opacity: 0.5;
6462 transition: all 250msvar(--animation-duration-2x);
6563}
66-
6764#qr--popout > .qr--body {
6865 overflow-y: auto;
6966}
@@ -124,7 +121,7 @@
124121 padding: 3px 5px;
125122 margin: 3px 0;
126123 cursor: pointer;
127- transition: 0.3s;
124+ transition: var(--animation-duration-2x);
128125 display: flex;
129126 align-items: center;
130127 justify-content: center;
@@ -175,7 +172,7 @@
175172 overflow: visible;
176173}
177174.ctx-menu .ctx-item .qr--hidden {
178175 display: none;
179176}
180177.list-group .list-group-item.ctx-header {
181178 font-weight: bold;
@@ -248,7 +245,7 @@
248245 display: flex;
249246 align-items: center;
250247 opacity: 0;
251248 transition: 100msvar(--animation-duration);
252249 margin: -2px 0 -11px 0;
253250 position: relative;
254251}
@@ -461,7 +458,7 @@
461458 cursor: not-allowed;
462459 opacity: 0.5;
463460 pointer-events: none;
464- transition: 200ms;
461+ transition: var(--animation-duration-2x);
465462 border-color: transparent;
466463}
467464.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
@@ -492,7 +489,7 @@
492489 width: 6px;
493490 background-color: var(--SmartThemeBorderColor);
494491 border: 2px solid var(--SmartThemeBlurTintColor);
495492 transition: border-color 200msvar(--animation-duration-2x), background-color 200msvar(--animation-duration-2x);
496493 cursor: w-resize;
497494}
498495.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {
@@ -574,7 +571,7 @@
574571 gap: 1em;
575572 text-align: left;
576573 opacity: 0.75;
577- transition: 200ms;
574+ transition: var(--animation-duration-2x);
578575 cursor: pointer;
579576}
580577.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,
@@ -736,7 +733,7 @@
736733 display: flex;
737734}
738735.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
739- transition: 200ms;
736+ transition: var(--animation-duration-2x);
740737 filter: grayscale(0);
741738}
742739.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
@@ -825,7 +822,7 @@
825822 position: absolute;
826823 inset: 0;
827824 right: calc(100% - var(--prog) * 1%);
828- transition: 200ms;
825+ transition: var(--animation-duration-2x);
829826}
830827.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
831828 animation-name: qr--progressPulse;
@@ -1053,7 +1050,7 @@
10531050 white-space: pre;
10541051 font-weight: normal;
10551052 box-shadow: 0 0 0;
1056- transition: 200ms;
1053+ transition: var(--animation-duration-2x);
10571054}
10581055.popup:has(.qr--transferModal) .popup-button-ok:after {
10591056 content: 'Transfer';
@@ -1068,7 +1065,7 @@
10681065 white-space: pre;
10691066 font-weight: normal;
10701067 box-shadow: 0 0 0;
1071- transition: 200ms;
1068+ transition: var(--animation-duration-2x);
10721069}
10731070.popup:has(.qr--transferModal) .qr--copy:after {
10741071 content: 'Copy';
public/scripts/extensions/quick-reply/style.less+363 -306
@@ -1,11 +1,16 @@
11@keyframes qr--success {
2- 0%, 100% {
2+
3+ 0%,
4+ 100% {
35 color: var(--SmartThemeBodyColor);
46 }
5- 25%, 75% {
7+
8+ 25%,
9+ 75% {
610 color: rgb(81, 163, 81);
711 }
812}
13+
914&.qr--success {
1015 animation-name: qr--success;
1116 animation-duration: 3s;
@@ -13,10 +18,11 @@
1318 animation-delay: 0s;
1419 animation-iteration-count: 1;
1520}
21+
1622#qr--bar {
1723 outline: none;
1824 margin: 0;
19- transition: 0.3s;
25+ transition: var(--animation-duration-2x);
2026 opacity: 0.7;
2127 display: flex;
2228 align-items: center;
@@ -33,6 +39,14 @@
3339 top: 0;
3440 }
3541}
42+
43+/*hide QR popout for mobile*/
44+@media screen and (max-width: 1000px) {
45+ #qr--bar>#qr--popoutTrigger {
46+ display: none;
47+ }
48+}
49+
3650#qr--bar.popoutVisible {
3751 padding-right: 2.5em;
3852}
@@ -54,7 +68,7 @@
5468 aspect-ratio: 1 / 1;
5569 font-size: 20px;
5670 opacity: 0.5;
5771 transition: all 250msvar(--animation-duration-2x);
5872 }
5973 }
6074 }
@@ -68,17 +82,21 @@
6882#qr--popout>.qr--body {
6983 >.qr--buttons {
7084 --qr--color: transparent;
85+
7186 &.qr--color {
7287 background-color: var(--qr--color);
7388 }
89+
7490 &.qr--borderColor {
7591 background-color: transparent;
7692 border-left: 5px solid var(--qr--color);
7793 border-right: 5px solid var(--qr--color);
7894 }
95+
7996 &:has(.qr--buttons.qr--color) {
8097 margin: 5px;
8198 }
99+
82100 margin: 0;
83101 padding: 0;
84102 display: flex;
@@ -89,6 +107,7 @@
89107
90108 >.qr--buttons {
91109 display: contents;
110+
92111 &.qr--color {
93112 .qr--button:before {
94113 content: '';
@@ -97,11 +116,14 @@
97116 inset: -5px;
98117 z-index: -1;
99118 }
119+
100120 &.qr--borderColor {
101121 .qr--button:before {
102122 display: none;
103123 }
104- &:before, &:after {
124+
125+ &:before,
126+ &:after {
105127 content: '';
106128 width: 5px;
107129 background-color: var(--qr--color);
@@ -118,7 +140,7 @@
118140 padding: 3px 5px;
119141 margin: 3px 0;
120142 cursor: pointer;
121- transition: 0.3s;
143+ transition: var(--animation-duration-2x);
122144 display: flex;
123145 align-items: center;
124146 justify-content: center;
@@ -132,6 +154,7 @@
132154 .qr--hidden {
133155 display: none;
134156 }
157+
135158 .qr--button-icon {
136159 margin: 0 0.5em;
137160 }
@@ -274,18 +297,22 @@
274297 display: flex;
275298 align-items: center;
276299 opacity: 0;
277300 transition: 100msvar(--animation-duration);
278301 margin: -2px 0 -11px 0;
279302 position: relative;
303+
280304 .qr--actions {
281305 display: flex;
282306 gap: 0.25em;
283307 flex: 0 0 auto;
308+
284309 .qr--action {
285310 margin: 0;
286311 }
287312 }
288- &:before, &:after {
313+
314+ &:before,
315+ &:after {
289316 content: "";
290317 display: block;
291318 flex: 1 1 auto;
@@ -293,10 +320,13 @@
293320 margin: 0 1em;
294321 height: 0;
295322 }
296- &:hover, &:focus-within {
323+
324+ &:hover,
325+ &:focus-within {
297326 opacity: 1;
298327 }
299328 }
329+
300330 >.qr--set-item .qr--content {
301331 display: flex;
302332 flex-direction: row;
@@ -336,9 +366,11 @@
336366 display: flex;
337367 align-items: center;
338368 gap: 0.5em;
369+
339370 .qr--set-itemIcon:not(.fa-solid) {
340371 display: none;
341372 }
373+
342374 .qr--set-itemLabel {
343375 min-width: 24px;
344376 }
@@ -384,15 +416,16 @@
384416 align-items: baseline;
385417 }
386418 }
387- >#qr--autoExec {
388- .checkbox_label {
389- text-wrap: nowrap;
390419
391- .fa-fw {
420+ >#qr--autoExec {
392- margin-right: 2px;
421+ .checkbox_label {
393- }
422+ text-wrap: nowrap;
423+
424+ .fa-fw {
425+ margin-right: 2px;
394426 }
395427 }
428+ }
396429}
397430
398431
@@ -433,65 +466,75 @@
433466 bottom: unset;
434467 margin: unset;
435468 padding: 0;
469+
436470 &::backdrop {
437471 backdrop-filter: unset;
438472 background-color: transparent;
439473 }
474+
440475 .popup-body {
441476 flex: 0 0 auto;
442477 height: min-content;
443478 width: min-content;
444479 }
480+
445481 .popup-content {
446482 flex: 0 0 auto;
447483 margin-top: 0;
448484
449485 > #qr--modalEditor {
450486 max-height: 50vh;
451- > #qr--main,
487+
452488 > #qr--resizeHandlemain,
453489 > #qr--qrOptions > h3resizeHandle,
454490 > #qr--qrOptions > #qr--modal-executeButtonsh3,
455491 > #qr--qrOptions > #qr--modal-executeProgressexecuteButtons,
456- {
492+ >#qr--qrOptions>#qr--modal-executeProgress {
457493 display: none;
458494 }
495+
459496 #qr--qrOptions {
460497 width: auto;
461498 }
499+
462500 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
463501 display: flex;
464502 }
503+
465504 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
466505 display: none;
467506 }
507+
468508 #qr--modal-debugState {
469509 padding-top: 0;
470510 }
471511 }
472512 }
473513 }
474- &:has(.qr--isExecuting) {
514+
475- .popup-controls {
515+ &:has(.qr--isExecuting) {
476- display: none;
516+ .popup-controls {
477- }
517+ display: none;
478-
518+ }
479- .qr--highlight {
519+
480- position: absolute;
520+ .qr--highlight {
481- z-index: 50000;
521+ position: absolute;
482522 pointer z-eventsindex: none50000;
483- background-color: rgb(47 150 180 / 0.5);
523+ pointer-events: none;
484- &.qr--unresolved {
524+ background-color: rgb(47 150 180 / 0.5);
485- background-color: rgb(255 255 0 / 0.5);
525+
486- }
526+ &.qr--unresolved {
487- }
527+ background-color: rgb(255 255 0 / 0.5);
488- .qr--highlight-secondary {
528+ }
489- position: absolute;
529+ }
490- z-index: 50000;
530+
491- pointer-events: none;
531+ .qr--highlight-secondary {
492- border: 3px solid red;
532+ position: absolute;
493- }
533+ z-index: 50000;
494- }
534+ pointer-events: none;
535+ border: 3px solid red;
536+ }
537+ }
495538
496539 .popup-content {
497540 display: flex;
@@ -504,36 +547,41 @@
504547 gap: 1em;
505548 overflow: hidden;
506549
507550 &.qr--isExecuting {
508- #qr--main > h3:first-child,
551+
509552 #qr--main > .qr-h3:first-labelschild,
510553 #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettingslabels,
511- #qr--qrOptions > h3:first-child,
554+ #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,
512555 #qr--qrOptions > #qr-h3:first-ctxEditorchild,
513556 #qr--qrOptions > .#qr--ctxEditorActionsctxEditor,
514557 #qr--qrOptions > .qr--ctxEditorActions + h3,
515558 #qr--qrOptions > .qr--ctxEditorActions + h3 + div,
516- {
559+ #qr--qrOptions>.qr--ctxEditorActions+h3+div {
517560 display: none;
518561 }
519- #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
562+
520- visibility: hidden;
563+ #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message {
521- }
564+ visibility: hidden;
522- #qr--modal-debugButtons {
565+ }
523- display: flex;
566+
567+ #qr--modal-debugButtons {
568+ display: flex;
569+
524570 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
525571 cursor: not-allowed;
526572 opacity: 0.5;
527573 pointer-events: none;
528- transition: 200ms;
574+ transition: var(--animation-duration-2x);
529575 border-color: transparent;
530576 }
531577 }
578+
532579 &.qr--isPaused #qr--modal-debugButtons {
533580 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
534581 cursor: pointer;
535582 opacity: 1;
536583 pointer-events: all;
584+
537585 &#qr--modal-resume {
538586 animation-name: qr--debugPulse;
539587 animation-duration: 1500ms;
@@ -541,73 +589,90 @@
541589 animation-delay: 0s;
542590 animation-iteration-count: infinite;
543591 }
592+
544593 &#qr--modal-resume {
545594 border-color: rgb(81, 163, 81);
546595 }
596+
547597 &#qr--modal-step {
548598 border-color: var(--SmartThemeQuoteColor);
549599 }
600+
550601 &#qr--modal-stepInto {
551602 border-color: var(--SmartThemeQuoteColor);
552603 }
604+
553605 &#qr--modal-stepOut {
554606 border-color: var(--SmartThemeQuoteColor);
555607 }
556608 }
557609 }
558- #qr--resizeHandle {
610+
559- width: 6px;
611+ #qr--resizeHandle {
560- background-color: var(--SmartThemeBorderColor);
612+ width: 6px;
561613 border background-color: 2px solid var(--SmartThemeBlurTintColorSmartThemeBorderColor);
562- transition: border-color 200ms, background-color 200ms;
614+ border: 2px solid var(--SmartThemeBlurTintColor);
563- cursor: w-resize;
615+ transition: border-color var(--animation-duration-2x), background-color var(--animation-duration-2x);
564- &:hover {
616+ cursor: w-resize;
565- background-color: var(--SmartThemeQuoteColor);
617+
566- border-color: var(--SmartThemeQuoteColor);
618+ &:hover {
567- }
619+ background-color: var(--SmartThemeQuoteColor);
568- }
620+ border-color: var(--SmartThemeQuoteColor);
569- #qr--qrOptions {
621+ }
570- width: var(--width, auto);
622+ }
571- }
623+
572- }
624+ #qr--qrOptions {
573-
625+ width: var(--width, auto);
574- > #qr--main {
626+ }
575- flex: 1 1 auto;
627+ }
576- display: flex;
628+
577- flex-direction: column;
629+ >#qr--main {
578- overflow: hidden;
630+ flex: 1 1 auto;
579- > .qr--labels {
631+ display: flex;
580- flex: 0 0 auto;
632+ flex-direction: column;
581633 display overflow: flexhidden;
582- flex-direction: row;
634+
583- gap: 0.5em;
635+ >.qr--labels {
584- padding: 1px;
636+ flex: 0 0 auto;
585- > label, > .label {
637+ display: flex;
586- flex: 1 1 1px;
638+ flex-direction: row;
587- display: flex;
639+ gap: 0.5em;
588- flex-direction: column;
640+ padding: 1px;
641+
642+ >label,
643+ >.label {
644+ flex: 1 1 1px;
645+ display: flex;
646+ flex-direction: column;
589647 position: relative;
648+
590649 &.qr--fit {
591650 flex: 0 0 auto;
592651 justify-content: center;
593652 }
653+
594654 .qr--inputGroup {
595655 display: flex;
596656 align-items: baseline;
597657 gap: 0.5em;
658+
598659 input {
599660 flex: 1 1 auto;
600661 }
601662 }
602- .qr--labelText {
663+
603- flex: 1 1 auto;
664+ .qr--labelText {
604- }
665+ flex: 1 1 auto;
605- .qr--labelHint {
666+ }
606- flex: 1 1 auto;
667+
607- }
668+ .qr--labelHint {
608- input {
669+ flex: 1 1 auto;
609- flex: 0 0 auto;
670+ }
610- }
671+
672+ input {
673+ flex: 0 0 auto;
674+ }
675+
611676 .qr--modal-switcherList {
612677 background-color: var(--stcdx--bgColor);
613678 border: 1px solid var(--SmartThemeBorderColor);
@@ -625,25 +690,32 @@
625690 list-style: none;
626691 z-index: 40000;
627692 max-width: 100%;
693+
628694 .qr--modal-switcherItem {
629695 display: flex;
630696 gap: 1em;
631697 text-align: left;
632698 opacity: 0.75;
633- transition: 200ms;
699+ transition: var(--animation-duration-2x);
634700 cursor: pointer;
701+
635702 &:hover {
636703 opacity: 1;
637704 }
705+
638706 &.qr--current {
639707 opacity: 1;
640- .qr--label, .qr--id {
708+
709+ .qr--label,
710+ .qr--id {
641711 font-weight: bold;
642712 }
643713 }
644714 }
715+
645716 .qr--label {
646717 white-space: nowrap;
718+
647719 .menu_button {
648720 display: inline-block;
649721 height: min-content;
@@ -651,11 +723,19 @@
651723 margin: 0 0.5em 0 0;
652724 }
653725 }
726+
654727 .qr--id {
655728 opacity: 0.5;
656- &:before { content: "["; }
729+
657- &:after { content: "]"; }
730+ &:before {
731+ content: "[";
732+ }
733+
734+ &:after {
735+ content: "]";
736+ }
658737 }
738+
659739 .qr--message {
660740 height: 1lh;
661741 overflow: hidden;
@@ -664,102 +744,124 @@
664744 opacity: 0.5;
665745 }
666746 }
667747 }
668748 }
669- > .qr--modal-messageContainer {
749+
670- flex: 1 1 auto;
750+ >.qr--modal-messageContainer {
671- display: flex;
751+ flex: 1 1 auto;
672- flex-direction: column;
752+ display: flex;
673- overflow: hidden;
753+ flex-direction: column;
674- > .qr--modal-editorSettings {
754+ overflow: hidden;
675- display: flex;
755+
756+ >.qr--modal-editorSettings {
757+ display: flex;
676758 flex-wrap: wrap;
677759 flex-direction: row;
678760 column-gap: 1em;
679761 color: var(--grey70);
680762 font-size: smaller;
681763 align-items: baseline;
682- > .checkbox_label {
764+
683- white-space: nowrap;
765+ >.checkbox_label {
684- > input {
766+ white-space: nowrap;
685- font-size: inherit;
767+
686- }
768+ >input {
687- }
769+ font-size: inherit;
688770 }
689- > #qr--modal-messageHolder {
771+ }
690- flex: 1 1 auto;
772+ }
691- display: grid;
773+
692- text-align: left;
774+ >#qr--modal-messageHolder {
693- overflow: hidden;
775+ flex: 1 1 auto;
694- &.qr--noSyntax {
776+ display: grid;
695- > #qr--modal-messageSyntax {
777+ text-align: left;
696778 display overflow: nonehidden;
697- }
779+
698- > #qr--modal-message {
780+ &.qr--noSyntax {
699- background-color: var(--ac-style-color-background);
781+ >#qr--modal-messageSyntax {
700- color: var(--ac-style-color-text);
782+ display: none;
701- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
783+ }
784+
785+ >#qr--modal-message {
786+ background-color: var(--ac-style-color-background);
787+ color: var(--ac-style-color-text);
788+
789+ &::-webkit-scrollbar,
790+ &::-webkit-scrollbar-thumb {
702791 visibility: visible;
703792 cursor: unset;
704793 }
705- &::selection {
794+
706- color: unset;
795+ &::selection {
707- background-color: rgba(108 171 251 / 0.25);
796+ color: unset;
708797 @supports ( background-color: rgbrgba(from white r108 g171 b251 / 0.25)) {;
709- background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
798+
710- }
799+ @supports (color: rgb(from white r g b / 0.25)) {
711- }
800+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
712801 }
713802 }
714- > #qr--modal-messageSyntax {
803+ }
715- grid-column: 1;
804+ }
716- grid-row: 1;
805+
717- padding: 0;
806+ >#qr--modal-messageSyntax {
718- margin: 0;
807+ grid-column: 1;
719- border: none;
808+ grid-row: 1;
720809 overflow padding: hidden0;
721- min-width: 100%;
810+ margin: 0;
722811 width border: 0none;
723- > #qr--modal-messageSyntaxInner {
812+ overflow: hidden;
724813 height min-width: 100%;
725- }
814+ width: 0;
726- }
815+
727816 > #qr--modal-messagemessageSyntaxInner {
728- background-color: transparent;
817+ height: 100%;
729- color: transparent;
818+ }
730- grid-column: 1;
819+ }
731- grid-row: 1;
820+
732- caret-color: var(--ac-style-color-text);
821+ >#qr--modal-message {
733- overflow: auto;
822+ background-color: transparent;
734- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
823+ color: transparent;
735- visibility: hidden;
824+ grid-column: 1;
736- cursor: default;
825+ grid-row: 1;
737- }
826+ caret-color: var(--ac-style-color-text);
738- &::selection {
827+ overflow: auto;
739- color: transparent;
828+
740- background-color: rgba(108 171 251 / 0.25);
829+ &::-webkit-scrollbar,
741- @supports (color: rgb(from white r g b / 0.25)) {
830+ &::-webkit-scrollbar-thumb {
742- background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
831+ visibility: hidden;
743- }
832+ cursor: default;
744833 }
745- }
834+
746- #qr--modal-message, #qr--modal-messageSyntaxInner {
835+ &::selection {
747- font-family: var(--monoFontFamily);
836+ color: transparent;
748- padding: 0.75em;
837+ background-color: rgba(108 171 251 / 0.25);
749- margin: 0;
838+
750- resize: none;
839+ @supports (color: rgb(from white r g b / 0.25)) {
751- line-height: 1.2;
840+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
752- border: 1px solid var(--SmartThemeBorderColor);
841+ }
753- border-radius: 5px;
842+ }
754- position: relative;
843+ }
755- }
844+
756- }
845+ #qr--modal-message,
757- }
846+ #qr--modal-messageSyntaxInner {
758- }
847+ font-family: var(--monoFontFamily);
848+ padding: 0.75em;
849+ margin: 0;
850+ resize: none;
851+ line-height: 1.2;
852+ border: 1px solid var(--SmartThemeBorderColor);
853+ border-radius: 5px;
854+ position: relative;
855+ }
856+ }
857+ }
858+ }
859+
759860 #qr--modal-icon {
760861 height: 100%;
761862 aspect-ratio: 1 / 1;
762863 }
864+
763865 #qr--modal-executeButtons {
764866 display: flex;
765867 gap: 1em;
@@ -778,7 +880,7 @@
778880 }
779881
780882 #qr--modal-execute {
781- transition: 200ms;
883+ transition: var(--animation-duration-2x);
782884 filter: grayscale(0);
783885
784886 &.qr--busy {
@@ -819,16 +921,20 @@
819921 border-color: rgb(215, 136, 114);
820922 }
821923 }
822- #qr--modal-debugButtons {
924+
823- display: none;
925+ #qr--modal-debugButtons {
824926 gap display: 1emnone;
825- .qr--modal-debugButton {
927+ gap: 1em;
826- aspect-ratio: 1.25 / 1;
928+
827- width: 2.25em;
929+ .qr--modal-debugButton {
930+ aspect-ratio: 1.25 / 1;
931+ width: 2.25em;
828932 position: relative;
933+
829934 &:not(.fa-solid) {
830935 border-width: 1px;
831936 border-style: solid;
937+
832938 &:after {
833939 content: '';
834940 position: absolute;
@@ -839,23 +945,28 @@
839945 mask-repeat: no-repeat;
840946 }
841947 }
948+
842949 &#qr--modal-resume:after {
843950 mask-image: url('/img/step-resume.svg');
844951 }
952+
845953 &#qr--modal-step:after {
846954 mask-image: url('/img/step-over.svg');
847955 }
956+
848957 &#qr--modal-stepInto:after {
849958 mask-image: url('/img/step-into.svg');
850959 }
960+
851961 &#qr--modal-stepOut:after {
852962 mask-image: url('/img/step-out.svg');
853963 }
964+
854965 &#qr--modal-maximize {
855966 display: none;
856967 }
857968 }
858969 }
859970
860971 #qr--modal-send_textarea {
861972 flex: 0 0 auto;
@@ -879,7 +990,7 @@
879990 position: absolute;
880991 inset: 0;
881992 right: calc(100% - var(--prog) * 1%);
882- transition: 200ms;
993+ transition: var(--animation-duration-2x);
883994 }
884995
885996 &.qr--paused:after {
@@ -939,135 +1050,180 @@
9391050 overflow: auto;
9401051 min-width: 100%;
9411052 width: 0;
9421053 white-space: pre-wrap;
9431054 }
944- #qr--modal-debugState {
1055+
945- display: none;
1056+ #qr--modal-debugState {
946- &.qr--active {
1057+ display: none;
947- display: block;
1058+
948- }
1059+ &.qr--active {
949- text-align: left;
1060+ display: block;
950- font-size: smaller;
1061+ }
951- font-family: var(--monoFontFamily);
1062+
952- // background-color: rgb(146, 190, 252);
1063+ text-align: left;
953- color: white;
1064+ font-size: smaller;
954- padding: 0.5em 0;
1065+ font-family: var(--monoFontFamily);
955- overflow: auto;
1066+ // background-color: rgb(146, 190, 252);
956- min-width: 100%;
1067+ color: white;
9571068 width padding: 0.5em 0;
958- white-space: pre-wrap;
1069+ overflow: auto;
959-
1070+ min-width: 100%;
960- .qr--scope {
1071+ width: 0;
961- display: grid;
1072+ white-space: pre-wrap;
962- grid-template-columns: 0fr 1fr 1fr;
1073+
963- column-gap: 0em;
1074+ .qr--scope {
964- .qr--title {
1075+ display: grid;
9651076 grid-columntemplate-columns: 10fr /1fr 41fr;
9661077 font column-weightgap: bold0em;
967- font-family: var(--mainFontFamily);
1078+
968- background-color: var(--black50a);
1079+ .qr--title {
969- padding: 0.25em;
1080+ grid-column: 1 / 4;
970- margin-top: 0.5em;
1081+ font-weight: bold;
971- }
1082+ font-family: var(--mainFontFamily);
972- .qr--var, .qr--macro, .qr--pipe {
1083+ background-color: var(--black50a);
973- display: contents;
1084+ padding: 0.25em;
974- &:nth-child(2n + 1) {
1085+ margin-top: 0.5em;
975- .qr--key, .qr--val {
1086+ }
976- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
1087+
977- }
1088+ .qr--var,
9781089 .qr--val {macro,
979- &:nth-child(2n) {
1090+ .qr--pipe {
980- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
1091+ display: contents;
981- }
1092+
982- &:hover {
1093+ &:nth-child(2n + 1) {
983- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
1094+
984- }
1095+ .qr--key,
985- }
1096+ .qr--val {
986- }
1097+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
987- &:nth-child(2n) {
1098+ }
988- .qr--val {
1099+
989- &:nth-child(2n) {
1100+ .qr--val {
990- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
991- }
992- &:hover {
993- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
994- }
995- }
996- }
997- &.qr--isHidden {
998- .qr--key, .qr--val {
999- opacity: 0.5;
1000- }
1001- }
1002- .qr--val {
1003- grid-column: 2 / 4;
1004- &.qr--singleCol {
1005- grid-column: unset;
1006- }
1007- &.qr--simple {
1008- &:before, &:after {
1009- content: '"';
1010- color: var(--SmartThemeQuoteColor);
1011- }
1012- }
1013- &.qr--unresolved {
1014- &:after {
1015- content: '-UNRESOLVED-';
1016- font-style: italic;
1017- color: var(--SmartThemeQuoteColor);
1018- }
1019- }
1020- }
1021- }
1022- .qr--key {
1023- margin-left: 0.5em;
1024- padding-right: 1em;
1025- &:after { content: ": "; }
1026- }
1027- .qr--pipe, .qr--macro {
1028- > .qr--key {
1029- &:before { content: "{{"; }
1030- &:after { content: "}}: "; }
1031- }
1032- }
1033- .qr--scope {
1034- display: contents;
1035- .qr--pipe {
1036- .qr--key, .qr--val {
1037- opacity: 0.5;
1038- }
1039- }
1040- }
1041- }
1042-
1043- .qr--stack {
public/scripts/extensions/regex/dropdown.html+0 -0
public/scripts/extensions/regex/editor.html+0 -0
public/scripts/extensions/regex/embeddedScripts.html+0 -0
public/scripts/extensions/regex/index.js+0 -0
public/scripts/extensions/regex/scriptTemplate.html+0 -0
public/scripts/extensions/regex/style.css+0 -0
public/scripts/extensions/shared.js+0 -0
public/scripts/extensions/stable-diffusion/index.js+0 -0
public/scripts/extensions/stable-diffusion/settings.html+0 -0
public/scripts/extensions/translate/index.html+0 -0
public/scripts/extensions/tts/chatterbox.js+0 -0
public/scripts/extensions/tts/google-native.js+0 -0
public/scripts/extensions/tts/index.js+0 -0
public/scripts/extensions/tts/lib/pcm-processor.js+0 -0
public/scripts/extensions/tts/openai-compatible.js+0 -0
public/scripts/extensions/tts/settings.html+0 -0
public/scripts/extensions/tts/style.css+0 -0
public/scripts/extensions/tts/tts-webui.js+0 -0
public/scripts/extensions/vectors/index.js+0 -0
public/scripts/group-chats.js+0 -0
public/scripts/horde.js+0 -0
public/scripts/i18n.js+0 -0
public/scripts/nai-settings.js+0 -0
public/scripts/openai.js+0 -0
public/scripts/personas.js+0 -0
public/scripts/popup.js+0 -0
public/scripts/power-user.js+0 -0
public/scripts/reasoning.js+0 -0
public/scripts/samplerSelect.js+0 -0
public/scripts/secrets.js+0 -0
public/scripts/slash-commands.js+0 -0
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+0 -0
public/scripts/slash-commands/SlashCommandParser.js+0 -0
public/scripts/stats.js+0 -0
public/scripts/tags.js+0 -0
public/scripts/templates/dataMaidCategory.html+0 -0
public/scripts/templates/dataMaidDialog.html+0 -0
public/scripts/templates/importCharacters.html+0 -0
public/scripts/templates/secretKeyManager.html+0 -0
public/scripts/templates/secretKeyManagerListItem.html+0 -0
public/scripts/templates/themeDelete.html+0 -0
public/scripts/templates/wandButton.html+0 -0
public/scripts/templates/welcomePanel.html+0 -0
public/scripts/textgen-models.js+0 -0
public/scripts/tool-calling.js+0 -0
public/scripts/utils.js+0 -0
public/scripts/variables.js+0 -0
public/scripts/welcome-screen.js+0 -0
public/scripts/world-info.js+0 -0
public/style.css+0 -0
src/command-line.js+0 -0
src/config-init.js+0 -0
src/constants.js+0 -0
src/endpoints/avatars.js+0 -0
src/endpoints/backends/chat-completions.js+0 -0
src/endpoints/backgrounds.js+0 -0
src/endpoints/characters.js+0 -0
src/endpoints/chats.js+0 -0
src/endpoints/content-manager.js+0 -0
src/endpoints/data-maid.js+0 -0
src/endpoints/google.js+0 -0
src/endpoints/images.js+0 -0
src/endpoints/openai.js+0 -0
src/endpoints/secrets.js+0 -0
src/endpoints/settings.js+0 -0
src/endpoints/stable-diffusion.js+0 -0
src/endpoints/thumbnails.js+0 -0
src/fetch-patch.js+0 -0
src/prompt-converters.js+0 -0
src/server-main.js+0 -0
src/server-startup.js+0 -0
src/users.js+0 -0
src/util.js+0 -0
src/vectors/makersuite-vectors.js+0 -0
Diff truncated