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
321### Unterstützte Argumente321### Unterstützte Argumente
322322
323| Option | Beschreibung | Typ |323| Option | Beschreibung | Typ |
324|-------------------------|----------------------------------------------------------------------------------------------------|----------|324|----------------------------------|--------------------------------------------------------------------------------------------------|----------|
325| `--version` | Versionsnummer anzeigen | boolean |325| `--version` | Versionsnummer anzeigen | boolean |
326| `--enableIPv6` | Aktiviert IPv6. | boolean |326| `--enableIPv6` | Aktiviert IPv6. | boolean |
327| `--enableIPv4` | Aktiviert IPv4. | boolean |327| `--enableIPv4` | Aktiviert IPv4. | boolean |
328| `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. Wenn nicht angegeben, wird auf YAML-Konfiguration „Port“ zurückgegriffen. | number |328| `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. | number |
329| „--dnsPreferIPv6“ | Bevorzugt IPv6 für DNS. Wenn nicht angegeben, wird auf YAML-Konfiguration „preferIPv6“ zurückgegriffen. | boolean |329| `--dnsPreferIPv6` | Bevorzugt IPv6 für DNS. Wenn nicht angegeben. | boolean |
330| „--autorun“ | Startet SillyTavern automatisch im Browser. Wenn nicht angegeben, wird auf YAML-Konfiguration „autorun“ zurückgegriffen.| boolean |330| `--browserLaunchEnabled` | Startet SillyTavern automatisch im Browser. | boolean |
331| „--autorunHostname“ | Der Autorun-Hostname, am besten auf „auto“ belassen. | string |331| `--browserLaunchHostname` | Der Autorun-Hostname, am besten auf „auto" belassen. | string |
332| „--autorunPortOverride“ | Überschreibt den Port für Autorun. | string |332| `--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 |
335| `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean |335| `--corsProxy` | Aktiviert CORS-Proxy. Wenn nicht angegeben. | boolean |
336| `--ssl` | Aktiviert SSL | boolean |336| `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean |
337| `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string |337| `--ssl` | Aktiviert SSL | boolean |
338| `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string |338| `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string |
339| `--whitelist` | Aktiviert den Whitelist-Modus | boolean |339| `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string |
340| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |340| `--whitelist` | Aktiviert den Whitelist-Modus | boolean |
341| `--avoidLocalhost` | Vermeidet die Verwendung von „localhost“ für Autorun im Auto-Modus. | boolean |341| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |
342| `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean |342| `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean |
343| `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean |343| `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean |
344| `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string |344| `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string |
345| `--requestProxyBypass` | Proxy-Bypass-Liste anfordern (durch Leerzeichen getrennte Liste von Hosts) | Array |345| `--requestProxyBypass` | Proxy-Bypass-Liste anfordern (durch Leerzeichen getrennte Liste von Hosts) | Array |
346346
347## Remoteverbindungen347## Remoteverbindungen
348348
.github/readme-ko_kr.md+23 -23
@@ -334,29 +334,29 @@ Start.bat --port 8000 --listen false
334334
335### 지원되는 인수335### 지원되는 인수
336336
337| 옵션 | 설명 | 타입 |337| 옵션 | 설명 | 타입 |
338|-------------------------|------------------------------------------------------------------------------------------------------|----------|338|-----------------------------------|--------------------------------------------------------------------------------------|----------|
339| `--version` | 버전 표시 | boolean |339| `--version` | 버전 표시 | boolean |
340| `--enableIPv6` | IPv6 활성화 | boolean |340| `--enableIPv6` | IPv6 활성화 | boolean |
341| `--enableIPv4` | IPv4 활성화 | boolean |341| `--enableIPv4` | IPv4 활성화 | boolean |
342| `--port` | SillyTavern이 실행될 포트를 설정합니다. 설정되지 않은 경우 yaml config 'port'를 불러옵니다. | number |342| `--port` | SillyTavern이 실행될 포트를 설정합니다. | number |
343| `--dnsPreferIPv6` | DNS에 IPv6를 우선으로 할당합니다. 설정되지 않은 경우 yaml config를 불러옵니다. | boolean |343| `--dnsPreferIPv6` | DNS에 IPv6를 우선으로 할당합니다. | boolean |
344| `--autorun` | 브라우저에서 SillyTavern을 자동으로 실행합니다. 설정되지 않은 경우 yaml config 'autorun'를 불러옵니다. | boolean |344| `--browserLaunchEnabled` | 브라우저에서 SillyTavern을 자동으로 실행합니다. | boolean |
345| `--autorunHostname` | 자동 실행 호스트 이름, 'auto'가 최적의 설정일 것입니다. | string |345| `--browserLaunchHostname` | 자동 실행 호스트 이름, 'auto'가 최적의 설정일 것입니다. | string |
346| `--autorunPortOverride` | 자동 실행 포트 덮어쓰기 | string |346| `--browserLaunchPort` | 자동 실행 포트 덮어쓰기 | string |
347| `--listen` | SillyTavern이 모든 네트워크 인터페이스에서 수신 대기합니다. 설정되지 않은 경우 yaml 구성 'listen'을 불러옵니다. | boolean |347| `--browserLaunchAvoidLocalhost` | 자동 모드에서 자동 실행 시 'localhost' 사용 방지 | boolean |
348| `--corsProxy` | CORS 프록시 활성화. 설정되지 않은 경우 yaml 구성 'enableCorsProxy'을 불러옵니다. | boolean |348| `--listen` | SillyTavern이 모든 네트워크 인터페이스에서 수신 대기합니다. | boolean |
349| `--disableCsrf` | CSRF 보호 비활성화 | boolean |349| `--corsProxy` | CORS 프록시 활성화. | boolean |
350| `--ssl` | SSL 활성화 | boolean |350| `--disableCsrf` | CSRF 보호 비활성화 | boolean |
351| `--certPath` | 인증서 파일 경로 | string |351| `--ssl` | SSL 활성화 | boolean |
352| `--keyPath` | 프라이빗 키 파일 경로 | string |352| `--certPath` | 인증서 파일 경로 | string |
353| `--whitelist` | 화이트리스트 모드 활성화 | boolean |353| `--keyPath` | 프라이빗 키 파일 경로 | string |
354| `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string |354| `--whitelist` | 화이트리스트 모드 활성화 | boolean |
355| `--avoidLocalhost` | 자동 모드에서 자동 실행 시 'localhost' 사용 방지 | boolean |355| `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string |
356| `--basicAuthMode` | 기본 인증 활성화 | boolean |356| `--basicAuthMode` | 기본 인증 활성화 | boolean |
357| `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean |357| `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean |
358| `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string |358| `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string |
359| `--requestProxyBypass` | 프록시 바이패스 리스트 리퀘스트 (공백으로 구분된 호스트 목록) | array |359| `--requestProxyBypass` | 프록시 바이패스 리스트 리퀘스트 (공백으로 구분된 호스트 목록) | array |
360360
361## 원격 연결361## 원격 연결
362362
.github/readme-ru_ru.md+23 -23
@@ -283,29 +283,29 @@ Start.bat --port 8000 --listen false
283283
284### Поддерживаемые аргументы284### Поддерживаемые аргументы
285285
286| Аргумент | Описание | Тип |286| Аргумент | Описание | Тип |
287|-------------------------|----------------------------------------------------------------------------------------------------------------|----------|287|------------------------------|-----------------------------------------------------------------------------------------------------------|---------|
288| `--version` | Показывает номер версии. | boolean |288| `--version` | Показывает номер версии. | boolean |
289| `--enableIPv6` | Включает IPv6. | boolean |289| `--enableIPv6` | Включает IPv6. | boolean |
290| `--enableIPv4` | Включает IPv4. | boolean |290| `--enableIPv4` | Включает IPv4. | boolean |
291| `--port` | Устанавливает порт, котрый будет использовать SillyTavern. Если не указан, то используется yaml-конфиг 'port'. | number |291| `--port` | Устанавливает порт, который будет использовать SillyTavern. | number |
292| `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. Если не указан, то используется yaml-конфиг 'preferIPv6'. | boolean |292| `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. | boolean |
293| `--autorun` | Автоматический запуск SillyTavern в браузере. Если не указан, то используется yaml-конфиг 'autorun'. | boolean |293| `--browserLaunchEnabled` | Автоматический запуск SillyTavern в браузере. | boolean |
294| `--autorunHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string |294| `--browserLaunchHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string |
295| `--autorunPortOverride` | Переопределяет порт для автозапуска. | string |295| `--browserLaunchPort` | Переопределяет порт для автозапуска. | string |
296| `--listen` | SillyTavern будет прослушивать все сетевые интерфейсы. Если не указан, то используется yaml-конфиг 'listen'. | boolean |296| `--browserLaunchAvoidLocalhost`| Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean |
297| `--corsProxy` | Включает CORS-прокси. Если не указан, то используется yaml-конфиг 'enableCorsProxy'. | boolean |297| `--listen` | SillyTavern будет прослушивать все сетевые интерфейсы. | boolean |
298| `--disableCsrf` | Отключает защиту от CSRF. | boolean |298| `--corsProxy` | Включает CORS-прокси. | boolean |
299| `--ssl` | Включает SSL. | boolean |299| `--disableCsrf` | Отключает защиту от CSRF. | boolean |
300| `--certPath` | Путь к файлу c сертификатом. | string |300| `--ssl` | Включает SSL. | boolean |
301| `--keyPath` | Путь к файлу с закрытым ключом. | string |301| `--certPath` | Путь к файлу c сертификатом. | string |
302| `--whitelist` | Включает режим белого списка. | boolean |302| `--keyPath` | Путь к файлу с закрытым ключом. | string |
303| `--dataRoot` | Корневой каталог для хранения данных. | string |303| `--whitelist` | Включает режим белого списка. | boolean |
304| `--avoidLocalhost` | Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean |304| `--dataRoot` | Корневой каталог для хранения данных. | string |
305| `--basicAuthMode` | Включает простую аутентификацию. | boolean |305| `--basicAuthMode` | Включает простую аутентификацию. | boolean |
306| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |306| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |
307| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |307| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |
308| `--requestProxyBypass` | Bypass список прокси (список хостов, разделенных пробелами). | array |308| `--requestProxyBypass` | Bypass список прокси (список хостов, разделенных пробелами). | array |
309309
310## Удалённое подключение310## Удалённое подключение
311311
.github/readme-zh_cn.md+269 -195
@@ -1,6 +1,8 @@
1> [!IMPORTANT] 1> [!IMPORTANT]
2> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。2> 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。
33
4<a name="readme-top"></a>
5
4![][cover]6![][cover]
57
6<div align="center">8<div align="center">
@@ -8,7 +10,7 @@
8[English](readme.md) | [German](readme-de_de.md) | 中文 | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md)10[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
10[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)12[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
11[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)13[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/forks)
12[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)14[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
13[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)15[![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
17---19---
1820
19移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。21SillyTavern 为众多 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/) 网站来回答各类问题与帮助您开始使用。27SillyTavern(简称 ST)是一个本地安装的用户界面,允许您与文本生成 LLM、图像生成引擎和 TTS 语音模型进行交互。
2628
27### SillyTavern 或 TavernAI 是什么?29SillyTavern 于 2023 年 2 月作为 TavernAI 1.2.8 的一个分支开始,如今已拥有超过 200 名贡献者和 2 年的独立开发经验,并继续作为资深 AI 爱好者领先的软件。
2830
29SillyTavern 是一个可以安装在电脑(和安卓手机)上的用户界面,让您可以与文本生成的人工智能互动,并与您或社区创建的角色聊天/玩角色扮演游戏。31## 我们的愿景
3032
31SillyTavern 是 TavernAI 1.2.8 的一个分支,正在进行更积极地开发,并添加了许多重要功能。在这一点上,它可以被视为完全独立的程序。331. 我们的目标是尽可能为用户提供 LLM Prompt 的最大效用和控制权。陡峭的学习曲线是乐趣的一部分!
342. 我们不提供任何在线或托管服务,也不会以编程方式跟踪任何用户数据。
353. SillyTavern 是一个由专注的 LLM 爱好者社区为您带来的充满激情的项目,并且将永远是免费和开源的。
3236
33### 分支37## 分支
3438
35SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。39SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。
3640
37* release -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。41- `release` -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。通常每月更新一次。
38* staging - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。42- `staging` - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。每天更新数次。
3943
40如果你不熟悉使用 Git 命令,或者不了解什么是分支,别担心!release 分支始终是您的首选。44如果你不熟悉使用 git 命令行,或者不了解什么是分支,别担心!`release` 分支始终是您的首选。
4145
42### 除了 SillyTavern,我还需要什么?46## 除了 SillyTavern,我还需要什么?
4347
44SillyTavern 本身并无用处,因为它只是一个用户聊天界面。你必须接入一个能充当角色扮演的人工智能系统。支持的人工智能系统有多种:OpenAPI API (GPT)、KoboldAI(可在本地或 Google Colab 上运行)等。您可以在 [常见问题](https://docs.sillytavern.app/usage/faq/) 中阅读更多相关信息。48由于 SillyTavern 只是一个界面,您需要接入一个 LLM 后端来进行推理。您可以使用 AI Horde 进行开箱即用的聊天。除此之外,我们还支持许多其他本地和基于云的 LLM 后端:OpenAI 兼容 API、KoboldAI、Tabby 等等。您可以在[文档](https://docs.sillytavern.app/usage/api-connections/)中阅读更多关于我们支持的 API 的信息。
4549
46### 我需要一台性能强大的电脑来运行 SillyTavern 吗?50### 我需要一台性能强大的电脑来运行 SillyTavern 吗?
4751
48由于 SillyTavern 只是一个用户聊天界面,它对硬件性能的要求很低,可以在任何电脑上运行。需要强大性能的是人工智能系统。52硬件要求很低:任何可以运行 NodeJS 18 或更高版本的设备都可以运行它。如果您打算在本地计算机上进行 LLM 推理,我们建议使用至少具有 6GB VRAM 的 3000 系列 NVIDIA 显卡。有关更多详细信息,请查看您后端的文档。
4953
50### 移动设备支持54### 建议的后端(非附属)
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***761. 发送 Pull Request。学习如何贡献:[CONTRIBUTING.md](../CONTRIBUTING.md)
772. 使用提供的模板发送功能建议和问题报告。
783. 请先阅读整个 readme 文件并查看文档网站,以避免提交重复的问题。
6779
68直接与开发人员联系:80## 屏幕截图
6981
70* Discord: cohee 或 rossascends82<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
71* Reddit:/u/RossAscends 或 /u/sillylossy83<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
74## 此版本包括85## 角色卡
7586
76* 经过大量修改的 TavernAI 1.2.8(超过 50% 的代码经过重写或优化)87SillyTavern 围绕“角色卡”的概念构建。角色卡是设定 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
90SillyTavern 支持扩展服务。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 系统优化了界面,并支持将快捷方式保存到主屏幕,在全屏模式下打开。105SillyTavern 支持扩展。
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 或其他云服务上进行全面测试。1291. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
1302. 安装 [Git for Windows](https://gitforwindows.org/)
1313. 打开 Windows 资源管理器 (`Win+E`)
1324. 浏览或创建一个不受 Windows 控制或监控的文件夹(例如:C:\MySpecialFolder\)
1335. 通过点击顶部的“地址栏”,输入 `cmd`,然后按 Enter,在该文件夹内打开命令提示符。
1346. 弹出黑框(命令提示符)后,键入以下其中一项并按 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)中。1397. 等待所有内容克隆完成后,双击 `Start.bat` 以使 NodeJS 安装其依赖项。
1408. 然后服务器将启动,SillyTavern 将在您的浏览器中弹出。
142141
143> 不要以管理员权限运行 start.bat142#### 通过 GitHub Desktop 安装
144143
145### Windows144(这**仅**允许在 GitHub Desktop 中使用 git,如果您也想在命令行上使用 `git`,则还需要安装 [Git for Windows](https://gitforwindows.org/))
146145
147通过 Git 安装(推荐使用,便于更新)1461. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
1472. 安装 [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
1483. 安装 GitHub Desktop 后,点击 `Clone a repository from the internet....`(注意:此步骤**无需**创建 GitHub 帐户)
1494. 在菜单中,点击 URL 选项卡,输入此 URL `https://github.com/SillyTavern/SillyTavern`,然后点击 Clone。您可以更改本地路径以更改 SillyTavern 的下载位置。
1505. 要打开 SillyTavern,请使用 Windows 资源管理器浏览到克隆存储库的文件夹。默认情况下,存储库将克隆到此处:`C:\Users\[您的 Windows 用户名]\Documents\GitHub\SillyTavern`
1516. 双击 `start.bat` 文件。(注意:文件名的 `.bat` 部分可能被您的操作系统隐藏,在这种情况下,它将显示为一个名为“`Start`”的文件。双击此文件以运行 SillyTavern)
1527. 双击后,应打开一个大的黑色命令控制台窗口,SillyTavern 将开始安装其运行所需的组件。
1538. 安装过程完成后,如果一切正常,命令控制台窗口应如下所示,并且您的浏览器中应打开一个 SillyTavern 选项卡:
1549. 连接到任何[支持的 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`1601. 安装 git 和 nodeJS(具体方法取决于您的操作系统)
160* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`1612. 克隆仓库
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通过压缩包下载安装(不推荐)1663. `cd SillyTavern` 导航到安装文件夹。
1674. 使用以下命令之一运行 `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### Linux172## 🐋 通过 Docker 安装
174173
175 1.运行 `start.sh` 脚本。174这些说明假定您已安装 Docker,能够访问命令行以安装容器,并熟悉其常规操作。
176 2.等待自动完成,然后开始享受
177175
178## API 密钥管理176### 使用 GitHub Container Registry
179177
180SillyTavern 会将 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
183docker-compose up
184```
185185
1861. 打开 `config.yaml` 文件,将里面的 `allowKeysExposure` 设置为 `true`。186根据您的需求自定义 `docker-compose.yml` 文件。默认端口为 8000。如果您想使用环境变量调整服务器配置,请在此处阅读文档:[链接](https://docs.sillytavern.app/administration/config-yaml/#environment-variables)。
1872. 然后重启 SillyTavern 服务。
188187
189## 远程访问188#### Docker CLI (高级)
190189
191这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。190您将需要两个强制性的目录映射和一个端口映射才能使 SillyTavern 正常运行。在命令中,替换以下位置中的选项:
192191
193不过,SillyTavern 也可以被设置为允许从任何地方进行远程访问。192#### 容器变量
194193
195**重要提示:SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。194##### 卷映射
196195
197### 1.管理白名单 IP196- `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` - 暴露流量的端口。这是强制性的,因为您将从其虚拟机容器外部访问实例。**在未实现单独的安全服务的情况下,请勿将其暴露给互联网。**
204192.168.0.1
205192.168.0.20
206```
207204
208或者205##### 附加设置
209206
210```207- `SILLYTAVERN_VERSION` - 在此 GitHub 页面的右侧,您会看到“Packages”。选择“sillytavern”包,您将看到镜像版本。镜像标签“latest”将使您与当前 release 保持同步。您还可以使用指向相应分支的每日镜像的“staging”标签。
211192.168.0.*
212```
213208
214(上述 IP 范围将允许局域网中的任何设备连接)209#### 运行容器
215210
216也接受子网掩码设置(如 10.0.0.0/24)。2111. 打开您的命令行
2122. 在您要存储配置和数据文件的文件夹中运行以下命令:
217213
218* 保存`whitelist.txt`文件。214```bash
219* 重启 SillyTavern 服务。215SILLYTAVERN_VERSION="latest"
216PUBLIC_PORT="8000"
217CONFIG_PATH="./config"
218DATA_PATH="./data"
219PLUGINS_PATH="./plugins"
220EXTENSIONS_PATH="./extensions"
220221
221然后,文件中设置的 IP 就可以访问 SillyTavern 了。222docker 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` 地址。240SillyTavern Launcher 是一个安装向导,可帮助您进行多种选项的设置,包括为本地推理安装后端。
232241
233如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。242### 对于 Windows 用户
234243
235* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。2441. 在键盘上:按 **`WINDOWS + R`** 打开“运行”对话框。然后,运行以下命令安装 git:
236245
237### 3. 使用其他设备访问 SillyTavern 服务246```shell
247cmd /c winget install -e --id Git.Git
248```
238249
239无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。2502. 在键盘上:按 **`WINDOWS + E`** 打开文件资源管理器,然后导航到要安装启动器的文件夹。进入所需文件夹后,在地址栏中键入 `cmd` 并按 Enter。然后,运行以下命令:
240251
241同一局域网中的 SillyTavern 服务的典型默认地址如下:252```shell
253git 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://2581. 打开您喜欢的终端并安装 git
2592. 使用以下命令克隆 SillyTavern-Launcher:
246260
247### 向所有 IP 开放您的 SillyTavern 服务261```shell
262git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
263```
248264
249我们不建议这样做,但您可以打开 `config.yaml` 并将里面的 `whitelistMode` 设置改为 `false`。2653. 使用以下命令启动 installer.sh:
250266
251你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。267```shell
268chmod +x install.sh && ./install.sh
269```
252270
253这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。2714. 安装后使用以下命令启动 launcher.sh:
254272
255用户名和密码在`config.yaml`文件中设置。273```shell
274chmod +x launcher.sh && ./launcher.sh
275```
256276
257重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。277### 对于 Mac 用户
258278
259### 还是无法访问?2791. 打开终端并使用以下命令安装 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### 性能问题?2852. 使用以下命令安装 git:
265286
266尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。287```shell
288brew install git
289```
267290
268## 我喜欢你的项目!我该如何贡献自己的力量?2913. 使用以下命令克隆 SillyTavern-Launcher:
269292
270### 应该293```shell
294git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
295```
271296
2721. 发送 Fork 请求2974. 使用以下命令启动 installer.sh:
2732. 使用规定的模板发送功能建议和问题报告
2743. 在提出任何问题之前,请先阅读 Readme 文件和文档
275298
276#### 不应该299```shell
300chmod +x install.sh && ./install.sh
301```
277302
2781. 提供金钱捐赠3035. 安装后使用以下命令启动 launcher.sh:
2792. 发送错误报告而不提供任何详细信息
2803. 提出已经回答过无数次的问题
281304
282## 我在哪里可以找到以前的聊天背景图片?305```shell
306chmod +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
290## 屏幕截图320## 命令行参数
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
295## 许可证和贡献324### 示例
296325
297**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。**326```shell
298327node 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# 或
300329npm run start -- --port 8000 --listen false
301* TAI Base by Humi: Unknown license330# 或 (仅限 Windows)
302* Cohee's modifications and derived code: AGPL v3331Start.bat --port 8000 --listen false
303* RossAscends' additions: AGPL v3332```
304* Portions of CncAnon's TavernAITurbo mod: Unknown license333
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 TextGen338
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 AlpinDale343| `--dataRoot` | 数据存储的根目录 | string |
315* Thanks paniphons for providing a FAQ document344| `--port` | 设置 SillyTavern 将在其下运行的端口 | number |
316* 10K Discord Users Celebratory Background by @kallmeflocc345| `--listen` | SillyTavern 将侦听所有网络接口 | boolean |
317* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc346| `--whitelist` | 启用白名单模式 | boolean |
318* Korean translation by @doloroushyeonse347| `--basicAuthMode` | 启用基本身份验证 | boolean |
319* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3348| `--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
321<!-- LINK GROUP -->392<!-- LINK GROUP -->
393
322[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44394[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
320### Supported arguments320### Supported arguments
321321
322| Option | Description | Type |322| Option | Description | Type |
323|-------------------------|------------------------------------------------------------------------------------------------------|----------|323|----------------------------------|---------------------------------------------|---------|
324| `--version` | 顯示版本序號 | boolean |324| `--version` | 顯示版本序號 | boolean |
325| `--enableIPv6` | 啟用 IPv6 | boolean |325| `--enableIPv6` | 啟用 IPv6 | boolean |
326| `--enableIPv4` | 啟用 IPv4 | boolean |326| `--enableIPv4` | 啟用 IPv4 | boolean |
327| `--port` | 設定 SillyTavern 運行的端口。若未提供,則預設使用 `config.yaml` 中的 'port' | number327| `--port` | 設定 SillyTavern 運行的端口。 | number |
328| `--dnsPreferIPv6` | 偏好使用 IPv6 解析 DNS。未提供則默認使用 `config.yaml` 中的 'preferIPv6' | boolean |328| `--dnsPreferIPv6` | 偏好使用 IPv6 解析 DNS。 | boolean |
329| `--autorun` | 自動在瀏覽器中啟動 SillyTavern。未提供則默認使用 `config.yaml` 中的 'autorun' | boolean |329| `--browserLaunchEnabled` | 自動在瀏覽器中啟動 SillyTavern。 | boolean |
330| `--autorunHostname` | 自動啟動時的主機名稱,通常建議保持為 'auto' | string |330| `--browserLaunchHostname` | 自動啟動時的主機名稱,通常建議保持為 'auto' | string |
331| `--autorunPortOverride` | 覆蓋自動啟動的端口設定 | string |331| `--browserLaunchPort` | 覆蓋自動啟動的端口設定 | string |
332| `--listen` | SillyTavern 是否可監聽所有網路接口。若未提供,則默認使用 `config.yaml` 中的 'listen' | boolean |332| `--browserLaunchAvoidLocalhost` | 在自動模式下避免使用 'localhost' | boolean |
333| `--corsProxy` | 啟用 CORS 代理。若未提供,則默認使用 `config.yaml` 中的 'enableCorsProxy' | boolean |333| `--listen` | SillyTavern 是否可監聽所有網路接口。 | boolean |
334| `--disableCsrf` | 停用 CSRF 保護 | boolean |334| `--corsProxy` | 啟用 CORS 代理。 | boolean |
335| `--ssl` | 啟用 SSL | boolean |335| `--disableCsrf` | 停用 CSRF 保護 | boolean |
336| `--certPath` | 設定您證書文件的路徑 | string |336| `--ssl` | 啟用 SSL | boolean |
337| `--keyPath` | 設定您私人金鑰文件的路徑 | string |337| `--certPath` | 設定您證書文件的路徑 | string |
338| `--whitelist` | 啟用白名單模式 | boolean |338| `--keyPath` | 設定您私人金鑰文件的路徑 | string |
339| `--dataRoot` | 設定數據儲存的根目錄 | string |339| `--whitelist` | 啟用白名單模式 | boolean |
340| `--avoidLocalhost` | 在自動模式下避免使用 'localhost' | boolean |340| `--dataRoot` | 設定數據儲存的根目錄 | string |
341| `--basicAuthMode` | 啟用基本身份驗證模式 | boolean |341| `--basicAuthMode` | 啟用基本身份驗證模式 | boolean |
342| `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean |342| `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean |
343| `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議) | string |343| `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議)| string |
344| `--requestProxyBypass` | 請求代理的例外主機清單(主機列表需以空格分隔) | array |344| `--requestProxyBypass` | 請求代理的例外主機清單(主機列表需以空格分隔)| array |
345345
346## 遠端連線346## 遠端連線
347347
.github/readme.md+48 -26
@@ -232,6 +232,28 @@ docker run \
232232
233We 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.233We 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
239Linux 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
241Two 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
246Example:
247
248```yaml
249# docker-compose.yml
250volumes:
251 ## Shared volume
252 - ./config:/home/node/app/config:z
253 ## Private volume
254 - ./data:/home/node/app/data:Z
255```
256
235## ⚡ Installing via SillyTavern Launcher257## ⚡ Installing via SillyTavern Launcher
236258
237SillyTavern Launcher is an installation wizard that will help you get setup with many options, including installing a backend for local inference.259SillyTavern 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
333> \[!TIP]355> \[!TIP]
334> None of the arguments are required. If you don't provide them, SillyTavern will use the settings in `config.yaml`.356> None of the arguments are required. If you don't provide them, SillyTavern will use the settings in `config.yaml`.
335357
336| Option | Description | Type |358| Option | Description | Type |
337|-------------------------|----------------------------------------------------------------------|----------|359|---------------------------------|----------------------------------------------------------------------|----------|
338| `--version` | Show version number | boolean |360| `--version` | Show version number | boolean |
339| `--configPath` | Override the path to the config.yaml file | string |361| `--configPath` | Override the path to the config.yaml file | string |
340| `--dataRoot` | Root directory for data storage | string |362| `--dataRoot` | Root directory for data storage | string |
341| `--port` | Sets the port under which SillyTavern will run | number |363| `--port` | Sets the port under which SillyTavern will run | number |
342| `--listen` | SillyTavern will listen on all network interfaces | boolean |364| `--listen` | SillyTavern will listen on all network interfaces | boolean |
343| `--whitelist` | Enables whitelist mode | boolean |365| `--whitelist` | Enables whitelist mode | boolean |
344| `--basicAuthMode` | Enables basic authentication | boolean |366| `--basicAuthMode` | Enables basic authentication | boolean |
345| `--enableIPv4` | Enables IPv4 protocol | boolean |367| `--enableIPv4` | Enables IPv4 protocol | boolean |
346| `--enableIPv6` | Enables IPv6 protocol | boolean |368| `--enableIPv6` | Enables IPv6 protocol | boolean |
347| `--listenAddressIPv4` | Specific IPv4 address to listen to | string |369| `--listenAddressIPv4` | Specific IPv4 address to listen to | string |
348| `--listenAddressIPv6` | Specific IPv6 address to listen to | string |370| `--listenAddressIPv6` | Specific IPv6 address to listen to | string |
349| `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean |371| `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean |
350| `--ssl` | Enables SSL | boolean |372| `--ssl` | Enables SSL | boolean |
351| `--certPath` | Path to your certificate file | string |373| `--certPath` | Path to your certificate file | string |
352| `--keyPath` | Path to your private key file | string |374| `--keyPath` | Path to your private key file | string |
353| `--autorun` | Automatically launch SillyTavern in the browser | boolean |375| `--browserLaunchEnabled` | Automatically launch SillyTavern in the browser | boolean |
354| `--autorunHostname` | Autorun hostname | string |376| `--browserLaunchHostname` | Browser launch hostname | string |
355| `--autorunPortOverride` | Overrides the port for autorun | string |377| `--browserLaunchPort` | Overrides the port for browser launch | string |
356| `--avoidLocalhost` | Avoids using 'localhost' for autorun in auto mode | boolean |378| `--browserLaunchAvoidLocalhost` | Avoids using 'localhost' for browser launch in auto mode | boolean |
357| `--corsProxy` | Enables CORS proxy | boolean |379| `--corsProxy` | Enables CORS proxy | boolean |
358| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean |380| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean |
359| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string |381| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string |
360| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array |382| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array |
361| `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean |383| `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean |
362384
363## Remote connections385## Remote connections
364386
Dockerfile+3 -3
@@ -1,4 +1,4 @@
1FROM node:lts-alpine3.191FROM node:lts-alpine3.21
22
3# Arguments3# Arguments
4ARG APP_HOME=/home/node/app4ARG APP_HOME=/home/node/app
@@ -19,7 +19,7 @@ RUN \
19 echo "*** Install npm packages ***" && \19 echo "*** Install npm packages ***" && \
20 npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force20 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 folder22# Create config directory and link config.yaml
23RUN \23RUN \
24 rm -f "config.yaml" || true && \24 rm -f "config.yaml" || true && \
25 ln -s "./config/config.yaml" "config.yaml" || true && \25 ln -s "./config/config.yaml" "config.yaml" || true && \
@@ -30,7 +30,7 @@ RUN \
30 echo "*** Run Webpack ***" && \30 echo "*** Run Webpack ***" && \
31 node "./docker/build-lib.js"31 node "./docker/build-lib.js"
3232
33# Cleanup unnecessary files33# Set the entrypoint script
34RUN \34RUN \
35 echo "*** Cleanup ***" && \35 echo "*** Cleanup ***" && \
36 mv "./docker/docker-entrypoint.sh" "./" && \36 mv "./docker/docker-entrypoint.sh" "./" && \
default/config.yaml+21 -15
@@ -16,16 +16,28 @@ protocol:
16 ipv6: false16 ipv6: false
17# Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv617# Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6
18dnsPreferIPv6: false18dnsPreferIPv6: false
19# The hostname that autorun opens.19# -- BROWSER LAUNCH CONFIGURATION --
20# - Use "auto" to let the server decide20browserLaunch:
21# - Use options like 'localhost', 'st.example.com'21 # Open the browser automatically on server startup.
22autorunHostname: "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
23# Server port39# Server port
24port: 800040port: 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.
28autorunPortOverride: -1
29# -- SSL options --41# -- SSL options --
30ssl:42ssl:
31 enabled: false43 enabled: false
@@ -70,7 +82,7 @@ enableDiscreetLogin: false
70# https://www.authelia.com/82# https://www.authelia.com/
71# This will use auto login to an account with the same username83# This will use auto login to an account with the same username
72# as that used for authlia. (Ensure the username in authlia84# as that used for authlia. (Ensure the username in authlia
73# is an exact match with that in sillytavern)85# is an exact match in lowercase with that in sillytavern)
74autheliaAuth: false86autheliaAuth: false
75# If `basicAuthMode` and this are enabled then87# If `basicAuthMode` and this are enabled then
76# the username and passwords for basic auth are the same as those88# the username and passwords for basic auth are the same as those
@@ -98,12 +110,6 @@ rateLimiting:
98 # Use X-Real-IP header instead of socket IP for rate limiting110 # Use X-Real-IP header instead of socket IP for rate limiting
99 # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy)111 # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy)
100 preferRealIpHeader: false112 preferRealIpHeader: false
101# -- ADVANCED CONFIGURATION --
102# Open the browser automatically
103autorun: true
104# Avoids using 'localhost' for autorun in auto mode.
105# use if you don't have 'localhost' in your hosts file
106avoidLocalhost: false
107113
108## BACKUP CONFIGURATION114## BACKUP CONFIGURATION
109backups:115backups:
default/content/index.json+8 -0
@@ -810,5 +810,13 @@
810 {810 {
811 "filename": "presets/reasoning/Blank.json",811 "filename": "presets/reasoning/Blank.json",
812 "type": "reasoning"812 "type": "reasoning"
813 },
814 {
815 "filename": "presets/instruct/Dots1.json",
816 "type": "instruct"
817 },
818 {
819 "filename": "presets/context/Dots1.json",
820 "type": "context"
813 }821 }
814]822]
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}
\ No newline at end of file11 \ 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 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.13.0",3 "version": "1.13.1",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.13.0",9 "version": "1.13.1",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
13 "@adobe/css-tools": "^4.4.2",13 "@adobe/css-tools": "^4.4.3",
14 "@agnai/sentencepiece-js": "^1.1.1",14 "@agnai/sentencepiece-js": "^1.1.1",
15 "@agnai/web-tokenizers": "^0.1.3",15 "@agnai/web-tokenizers": "^0.1.3",
16 "@iconfu/svg-inject": "^1.2.3",16 "@iconfu/svg-inject": "^1.2.3",
@@ -51,19 +51,19 @@
51 "cookie-session": "^2.1.0",51 "cookie-session": "^2.1.0",
52 "cors": "^2.8.5",52 "cors": "^2.8.5",
53 "crc": "^4.3.2",53 "crc": "^4.3.2",
54 "csrf-sync": "^4.0.3",54 "csrf-sync": "^4.2.1",
55 "diff-match-patch": "^1.0.5",55 "diff-match-patch": "^1.0.5",
56 "dompurify": "^3.2.4",56 "dompurify": "^3.2.6",
57 "droll": "^0.2.1",57 "droll": "^0.2.1",
58 "express": "^4.21.0",58 "express": "^4.21.0",
59 "form-data": "^4.0.2",59 "form-data": "^4.0.3",
60 "fuse.js": "^7.1.0",60 "fuse.js": "^7.1.0",
61 "google-translate-api-browser": "^3.0.1",61 "google-translate-api-browser": "^3.0.1",
62 "google-translate-api-x": "^10.7.2",62 "google-translate-api-x": "^10.7.2",
63 "handlebars": "^4.7.8",63 "handlebars": "^4.7.8",
64 "helmet": "^7.2.0",64 "helmet": "^8.1.0",
65 "highlight.js": "^11.11.1",65 "highlight.js": "^11.11.1",
66 "html-entities": "^2.5.2",66 "html-entities": "^2.6.0",
67 "iconv-lite": "^0.6.3",67 "iconv-lite": "^0.6.3",
68 "ip-matching": "^2.1.2",68 "ip-matching": "^2.1.2",
69 "ip-regex": "^5.0.0",69 "ip-regex": "^5.0.0",
@@ -71,13 +71,13 @@
71 "is-docker": "^3.0.0",71 "is-docker": "^3.0.0",
72 "localforage": "^1.10.0",72 "localforage": "^1.10.0",
73 "lodash": "^4.17.21",73 "lodash": "^4.17.21",
74 "mime-types": "^2.1.35",74 "mime-types": "^3.0.1",
75 "moment": "^2.30.1",75 "moment": "^2.30.1",
76 "morphdom": "^2.7.4",76 "morphdom": "^2.7.5",
77 "multer": "^2.0.1",77 "multer": "^2.0.1",
78 "node-fetch": "^3.3.2",78 "node-fetch": "^3.3.2",
79 "node-persist": "^4.0.4",79 "node-persist": "^4.0.4",
80 "open": "^8.4.2",80 "open": "^10.1.2",
81 "png-chunk-text": "^1.0.0",81 "png-chunk-text": "^1.0.0",
82 "png-chunks-extract": "^1.0.0",82 "png-chunks-extract": "^1.0.0",
83 "proxy-agent": "^6.5.0",83 "proxy-agent": "^6.5.0",
@@ -87,18 +87,18 @@
87 "seedrandom": "^3.0.5",87 "seedrandom": "^3.0.5",
88 "showdown": "^2.1.0",88 "showdown": "^2.1.0",
89 "sillytavern-transformers": "2.14.6",89 "sillytavern-transformers": "2.14.6",
90 "simple-git": "^3.27.0",90 "simple-git": "^3.28.0",
91 "slidetoggle": "^4.0.0",91 "slidetoggle": "^4.0.0",
92 "tiktoken": "^1.0.20",92 "tiktoken": "^1.0.21",
93 "url-join": "^5.0.0",93 "url-join": "^5.0.0",
94 "vectra": "^0.2.2",94 "vectra": "^0.2.2",
95 "wavefile": "^11.0.0",95 "wavefile": "^11.0.0",
96 "webpack": "^5.98.0",96 "webpack": "^5.99.9",
97 "write-file-atomic": "^5.0.1",97 "write-file-atomic": "^5.0.1",
98 "ws": "^8.18.1",98 "ws": "^8.18.2",
99 "yaml": "^2.7.0",99 "yaml": "^2.8.0",
100 "yargs": "^17.7.1",100 "yargs": "^17.7.1",
101 "yauzl": "^2.10.0"101 "yauzl": "^3.2.0"
102 },102 },
103 "bin": {103 "bin": {
104 "sillytavern": "server.js"104 "sillytavern": "server.js"
@@ -107,24 +107,24 @@
107 "@types/archiver": "^6.0.3",107 "@types/archiver": "^6.0.3",
108 "@types/bytes": "^3.1.5",108 "@types/bytes": "^3.1.5",
109 "@types/command-exists": "^1.2.3",109 "@types/command-exists": "^1.2.3",
110 "@types/compression": "^1.7.5",110 "@types/compression": "^1.8.1",
111 "@types/cookie-parser": "^1.4.8",111 "@types/cookie-parser": "^1.4.9",
112 "@types/cookie-session": "^2.0.49",112 "@types/cookie-session": "^2.0.49",
113 "@types/cors": "^2.8.17",113 "@types/cors": "^2.8.19",
114 "@types/deno": "^2.2.0",114 "@types/deno": "^2.3.0",
115 "@types/express": "^4.17.21",115 "@types/express": "^4.17.23",
116 "@types/jquery": "^3.5.32",116 "@types/jquery": "^3.5.32",
117 "@types/jquery-cropper": "^1.0.4",117 "@types/jquery-cropper": "^1.0.4",
118 "@types/jquery.transit": "^0.9.33",118 "@types/jquery.transit": "^0.9.33",
119 "@types/jqueryui": "^1.12.24",119 "@types/jqueryui": "^1.12.24",
120 "@types/lodash": "^4.17.16",120 "@types/lodash": "^4.17.17",
121 "@types/mime-types": "^2.1.4",121 "@types/mime-types": "^3.0.1",
122 "@types/multer": "^1.4.12",122 "@types/multer": "^1.4.13",
123 "@types/node": "^18.19.80",123 "@types/node": "^18.19.84",
124 "@types/node-persist": "^3.1.8",124 "@types/node-persist": "^3.1.8",
125 "@types/png-chunk-text": "^1.0.3",125 "@types/png-chunk-text": "^1.0.3",
126 "@types/png-chunks-extract": "^1.0.2",126 "@types/png-chunks-extract": "^1.0.2",
127 "@types/response-time": "^2.3.8",127 "@types/response-time": "^2.3.9",
128 "@types/select2": "^4.0.63",128 "@types/select2": "^4.0.63",
129 "@types/toastr": "^2.1.43",129 "@types/toastr": "^2.1.43",
130 "@types/write-file-atomic": "^4.0.3",130 "@types/write-file-atomic": "^4.0.3",
@@ -148,9 +148,9 @@
148 }148 }
149 },149 },
150 "node_modules/@adobe/css-tools": {150 "node_modules/@adobe/css-tools": {
151 "version": "4.4.2",151 "version": "4.4.3",
152 "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz",152 "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
153 "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==",153 "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
154 "license": "MIT"154 "license": "MIT"
155 },155 },
156 "node_modules/@agnai/sentencepiece-js": {156 "node_modules/@agnai/sentencepiece-js": {
@@ -350,6 +350,7 @@
350 "version": "8.0.2",350 "version": "8.0.2",
351 "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",351 "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
352 "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",352 "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
353 "license": "ISC",
353 "dependencies": {354 "dependencies": {
354 "string-width": "^5.1.2",355 "string-width": "^5.1.2",
355 "string-width-cjs": "npm:string-width@^4.2.0",356 "string-width-cjs": "npm:string-width@^4.2.0",
@@ -363,9 +364,10 @@
363 }364 }
364 },365 },
365 "node_modules/@isaacs/cliui/node_modules/ansi-regex": {366 "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
366 "version": "6.0.1",367 "version": "6.1.0",
367 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",368 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
368 "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",369 "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
370 "license": "MIT",
369 "engines": {371 "engines": {
370 "node": ">=12"372 "node": ">=12"
371 },373 },
@@ -377,6 +379,7 @@
377 "version": "6.2.1",379 "version": "6.2.1",
378 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",380 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
379 "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",381 "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
382 "license": "MIT",
380 "engines": {383 "engines": {
381 "node": ">=12"384 "node": ">=12"
382 },385 },
@@ -387,12 +390,14 @@
387 "node_modules/@isaacs/cliui/node_modules/emoji-regex": {390 "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
388 "version": "9.2.2",391 "version": "9.2.2",
389 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",392 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
390 "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="393 "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
394 "license": "MIT"
391 },395 },
392 "node_modules/@isaacs/cliui/node_modules/string-width": {396 "node_modules/@isaacs/cliui/node_modules/string-width": {
393 "version": "5.1.2",397 "version": "5.1.2",
394 "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",398 "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
395 "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",399 "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
400 "license": "MIT",
396 "dependencies": {401 "dependencies": {
397 "eastasianwidth": "^0.2.0",402 "eastasianwidth": "^0.2.0",
398 "emoji-regex": "^9.2.2",403 "emoji-regex": "^9.2.2",
@@ -409,6 +414,7 @@
409 "version": "7.1.0",414 "version": "7.1.0",
410 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",415 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
411 "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",416 "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
417 "license": "MIT",
412 "dependencies": {418 "dependencies": {
413 "ansi-regex": "^6.0.1"419 "ansi-regex": "^6.0.1"
414 },420 },
@@ -423,6 +429,7 @@
423 "version": "8.1.0",429 "version": "8.1.0",
424 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",430 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
425 "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",431 "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
432 "license": "MIT",
426 "dependencies": {433 "dependencies": {
427 "ansi-styles": "^6.1.0",434 "ansi-styles": "^6.1.0",
428 "string-width": "^5.0.1",435 "string-width": "^5.0.1",
@@ -1597,6 +1604,7 @@
1597 "version": "0.11.0",1604 "version": "0.11.0",
1598 "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",1605 "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
1599 "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",1606 "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
1607 "license": "MIT",
1600 "optional": true,1608 "optional": true,
1601 "engines": {1609 "engines": {
1602 "node": ">=14"1610 "node": ">=14"
@@ -1779,13 +1787,14 @@
1779 "license": "MIT"1787 "license": "MIT"
1780 },1788 },
1781 "node_modules/@types/compression": {1789 "node_modules/@types/compression": {
1782 "version": "1.7.5",1790 "version": "1.8.1",
1783 "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz",1791 "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
1784 "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==",1792 "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
1785 "dev": true,1793 "dev": true,
1786 "license": "MIT",1794 "license": "MIT",
1787 "dependencies": {1795 "dependencies": {
1788 "@types/express": "*"1796 "@types/express": "*",
1797 "@types/node": "*"
1789 }1798 }
1790 },1799 },
1791 "node_modules/@types/connect": {1800 "node_modules/@types/connect": {
@@ -1799,9 +1808,9 @@
1799 }1808 }
1800 },1809 },
1801 "node_modules/@types/cookie-parser": {1810 "node_modules/@types/cookie-parser": {
1802 "version": "1.4.8",1811 "version": "1.4.9",
1803 "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz",1812 "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz",
1804 "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==",1813 "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==",
1805 "dev": true,1814 "dev": true,
1806 "license": "MIT",1815 "license": "MIT",
1807 "peerDependencies": {1816 "peerDependencies": {
@@ -1820,9 +1829,9 @@
1820 }1829 }
1821 },1830 },
1822 "node_modules/@types/cors": {1831 "node_modules/@types/cors": {
1823 "version": "2.8.17",1832 "version": "2.8.19",
1824 "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",1833 "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
1825 "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",1834 "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
1826 "dev": true,1835 "dev": true,
1827 "license": "MIT",1836 "license": "MIT",
1828 "dependencies": {1837 "dependencies": {
@@ -1830,9 +1839,9 @@
1830 }1839 }
1831 },1840 },
1832 "node_modules/@types/deno": {1841 "node_modules/@types/deno": {
1833 "version": "2.2.0",1842 "version": "2.3.0",
1834 "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.2.0.tgz",1843 "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.3.0.tgz",
1835 "integrity": "sha512-4x6M/ZSyoQy6fJeMArP0dvvNT4IOolfySyukuqqKhsLmSXDV4wGanqXIZ+xFihw3TlReS6JTa4hRG9nAZInpmw==",1844 "integrity": "sha512-/4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==",
1836 "dev": true,1845 "dev": true,
1837 "license": "MIT"1846 "license": "MIT"
1838 },1847 },
@@ -1863,10 +1872,11 @@
1863 "license": "MIT"1872 "license": "MIT"
1864 },1873 },
1865 "node_modules/@types/express": {1874 "node_modules/@types/express": {
1866 "version": "4.17.21",1875 "version": "4.17.23",
1867 "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",1876 "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
1868 "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",1877 "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
1869 "dev": true,1878 "dev": true,
1879 "license": "MIT",
1870 "dependencies": {1880 "dependencies": {
1871 "@types/body-parser": "*",1881 "@types/body-parser": "*",
1872 "@types/express-serve-static-core": "^4.17.33",1882 "@types/express-serve-static-core": "^4.17.33",
@@ -1962,9 +1972,9 @@
1962 }1972 }
1963 },1973 },
1964 "node_modules/@types/lodash": {1974 "node_modules/@types/lodash": {
1965 "version": "4.17.16",1975 "version": "4.17.17",
1966 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",1976 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz",
1967 "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",1977 "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
1968 "dev": true,1978 "dev": true,
1969 "license": "MIT"1979 "license": "MIT"
1970 },1980 },
@@ -1982,16 +1992,16 @@
1982 "license": "MIT"1992 "license": "MIT"
1983 },1993 },
1984 "node_modules/@types/mime-types": {1994 "node_modules/@types/mime-types": {
1985 "version": "2.1.4",1995 "version": "3.0.1",
1986 "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",1996 "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz",
1987 "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",1997 "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==",
1988 "dev": true,1998 "dev": true,
1989 "license": "MIT"1999 "license": "MIT"
1990 },2000 },
1991 "node_modules/@types/multer": {2001 "node_modules/@types/multer": {
1992 "version": "1.4.12",2002 "version": "1.4.13",
1993 "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz",2003 "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
1994 "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==",2004 "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
1995 "dev": true,2005 "dev": true,
1996 "license": "MIT",2006 "license": "MIT",
1997 "dependencies": {2007 "dependencies": {
@@ -1999,9 +2009,9 @@
1999 }2009 }
2000 },2010 },
2001 "node_modules/@types/node": {2011 "node_modules/@types/node": {
2002 "version": "18.19.80",2012 "version": "18.19.84",
2003 "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",2013 "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.84.tgz",
2004 "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",2014 "integrity": "sha512-ACYy2HGcZPHxEeWTqowTF7dhXN+JU1o7Gr4b41klnn6pj2LD6rsiGqSZojMdk1Jh2ys3m76ap+ae1vvE4+5+vg==",
2005 "license": "MIT",2015 "license": "MIT",
2006 "dependencies": {2016 "dependencies": {
2007 "undici-types": "~5.26.4"2017 "undici-types": "~5.26.4"
@@ -2064,9 +2074,9 @@
2064 }2074 }
2065 },2075 },
2066 "node_modules/@types/response-time": {2076 "node_modules/@types/response-time": {
2067 "version": "2.3.8",2077 "version": "2.3.9",
2068 "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.8.tgz",2078 "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.9.tgz",
2069 "integrity": "sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQ/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzw==",2079 "integrity": "sha512-w5i5/y/1N3hkSBru1dat7Pf/YzdFLAANbKR78i2VIPnKw1Ub2ZNXE/n3K4v1BBMIIbAccgxpGZT8lIuLW284Dw==",
2070 "dev": true,2080 "dev": true,
2071 "license": "MIT",2081 "license": "MIT",
2072 "dependencies": {2082 "dependencies": {
@@ -2378,6 +2388,18 @@
2378 "node": ">= 0.6"2388 "node": ">= 0.6"
2379 }2389 }
2380 },2390 },
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 },
2381 "node_modules/acorn": {2403 "node_modules/acorn": {
2382 "version": "8.14.0",2404 "version": "8.14.0",
2383 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",2405 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -2557,38 +2579,39 @@
2557 }2579 }
2558 },2580 },
2559 "node_modules/archiver-utils/node_modules/brace-expansion": {2581 "node_modules/archiver-utils/node_modules/brace-expansion": {
2560 "version": "2.0.1",2582 "version": "2.0.2",
2561 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",2583 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
2562 "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",2584 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
2585 "license": "MIT",
2563 "dependencies": {2586 "dependencies": {
2564 "balanced-match": "^1.0.0"2587 "balanced-match": "^1.0.0"
2565 }2588 }
2566 },2589 },
2567 "node_modules/archiver-utils/node_modules/glob": {2590 "node_modules/archiver-utils/node_modules/glob": {
2568 "version": "10.3.12",2591 "version": "10.4.5",
2569 "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",2592 "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
2570 "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",2593 "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
2594 "license": "ISC",
2571 "dependencies": {2595 "dependencies": {
2572 "foreground-child": "^3.1.0",2596 "foreground-child": "^3.1.0",
2573 "jackspeak": "^2.3.6",2597 "jackspeak": "^3.1.2",
2574 "minimatch": "^9.0.1",2598 "minimatch": "^9.0.4",
2575 "minipass": "^7.0.4",2599 "minipass": "^7.1.2",
2576 "path-scurry": "^1.10.2"2600 "package-json-from-dist": "^1.0.0",
2601 "path-scurry": "^1.11.1"
2577 },2602 },
2578 "bin": {2603 "bin": {
2579 "glob": "dist/esm/bin.mjs"2604 "glob": "dist/esm/bin.mjs"
2580 },2605 },
2581 "engines": {
2582 "node": ">=16 || 14 >=14.17"
2583 },
2584 "funding": {2606 "funding": {
2585 "url": "https://github.com/sponsors/isaacs"2607 "url": "https://github.com/sponsors/isaacs"
2586 }2608 }
2587 },2609 },
2588 "node_modules/archiver-utils/node_modules/minimatch": {2610 "node_modules/archiver-utils/node_modules/minimatch": {
2589 "version": "9.0.4",2611 "version": "9.0.5",
2590 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",2612 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2591 "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",2613 "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2614 "license": "ISC",
2592 "dependencies": {2615 "dependencies": {
2593 "brace-expansion": "^2.0.1"2616 "brace-expansion": "^2.0.1"
2594 },2617 },
@@ -2878,9 +2901,9 @@
2878 "license": "MIT"2901 "license": "MIT"
2879 },2902 },
2880 "node_modules/brace-expansion": {2903 "node_modules/brace-expansion": {
2881 "version": "1.1.11",2904 "version": "1.1.12",
2882 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",2905 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
2883 "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",2906 "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
2884 "dev": true,2907 "dev": true,
2885 "license": "MIT",2908 "license": "MIT",
2886 "dependencies": {2909 "dependencies": {
@@ -2968,6 +2991,21 @@
2968 "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",2991 "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
2969 "license": "MIT"2992 "license": "MIT"
2970 },2993 },
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 },
2971 "node_modules/busboy": {3009 "node_modules/busboy": {
2972 "version": "1.6.0",3010 "version": "1.6.0",
2973 "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",3011 "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -3665,9 +3703,9 @@
3665 }3703 }
3666 },3704 },
3667 "node_modules/csrf-sync": {3705 "node_modules/csrf-sync": {
3668 "version": "4.0.3",3706 "version": "4.2.1",
3669 "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.0.3.tgz",3707 "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.2.1.tgz",
3670 "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==",3708 "integrity": "sha512-+q9tlUSCi/kbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==",
3671 "license": "ISC",3709 "license": "ISC",
3672 "dependencies": {3710 "dependencies": {
3673 "http-errors": "^2.0.0"3711 "http-errors": "^2.0.0"
@@ -3741,6 +3779,34 @@
3741 "dev": true,3779 "dev": true,
3742 "license": "MIT"3780 "license": "MIT"
3743 },3781 },
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 },
3744 "node_modules/defer-to-connect": {3810 "node_modules/defer-to-connect": {
3745 "version": "2.0.1",3811 "version": "2.0.1",
3746 "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",3812 "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -3768,12 +3834,15 @@
3768 }3834 }
3769 },3835 },
3770 "node_modules/define-lazy-prop": {3836 "node_modules/define-lazy-prop": {
3771 "version": "2.0.0",3837 "version": "3.0.0",
3772 "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",3838 "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
3773 "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",3839 "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
3774 "license": "MIT",3840 "license": "MIT",
3775 "engines": {3841 "engines": {
3776 "node": ">=8"3842 "node": ">=12"
3843 },
3844 "funding": {
3845 "url": "https://github.com/sponsors/sindresorhus"
3777 }3846 }
3778 },3847 },
3779 "node_modules/degenerator": {3848 "node_modules/degenerator": {
@@ -3894,9 +3963,9 @@
3894 }3963 }
3895 },3964 },
3896 "node_modules/dompurify": {3965 "node_modules/dompurify": {
3897 "version": "3.2.4",3966 "version": "3.2.6",
3898 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",3967 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
3899 "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",3968 "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
3900 "license": "(MPL-2.0 OR Apache-2.0)",3969 "license": "(MPL-2.0 OR Apache-2.0)",
3901 "optionalDependencies": {3970 "optionalDependencies": {
3902 "@types/trusted-types": "^2.0.7"3971 "@types/trusted-types": "^2.0.7"
@@ -3953,7 +4022,8 @@
3953 "node_modules/eastasianwidth": {4022 "node_modules/eastasianwidth": {
3954 "version": "0.2.0",4023 "version": "0.2.0",
3955 "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",4024 "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
3956 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="4025 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
4026 "license": "MIT"
3957 },4027 },
3958 "node_modules/ee-first": {4028 "node_modules/ee-first": {
3959 "version": "1.1.1",4029 "version": "1.1.1",
@@ -4564,15 +4634,6 @@
4564 "reusify": "^1.0.4"4634 "reusify": "^1.0.4"
4565 }4635 }
4566 },4636 },
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 },
4576 "node_modules/fetch-blob": {4637 "node_modules/fetch-blob": {
4577 "version": "3.2.0",4638 "version": "3.2.0",
4578 "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",4639 "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -4724,14 +4785,15 @@
4724 }4785 }
4725 },4786 },
4726 "node_modules/form-data": {4787 "node_modules/form-data": {
4727 "version": "4.0.2",4788 "version": "4.0.3",
4728 "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",4789 "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
4729 "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",4790 "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
4730 "license": "MIT",4791 "license": "MIT",
4731 "dependencies": {4792 "dependencies": {
4732 "asynckit": "^0.4.0",4793 "asynckit": "^0.4.0",
4733 "combined-stream": "^1.0.8",4794 "combined-stream": "^1.0.8",
4734 "es-set-tostringtag": "^2.1.0",4795 "es-set-tostringtag": "^2.1.0",
4796 "hasown": "^2.0.2",
4735 "mime-types": "^2.1.12"4797 "mime-types": "^2.1.12"
4736 },4798 },
4737 "engines": {4799 "engines": {
@@ -4744,6 +4806,18 @@
4744 "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",4806 "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
4745 "license": "MIT"4807 "license": "MIT"
4746 },4808 },
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 },
4747 "node_modules/formdata-node": {4821 "node_modules/formdata-node": {
4748 "version": "4.4.1",4822 "version": "4.4.1",
4749 "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",4823 "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
@@ -5144,12 +5218,12 @@
5144 }5218 }
5145 },5219 },
5146 "node_modules/helmet": {5220 "node_modules/helmet": {
5147 "version": "7.2.0",5221 "version": "8.1.0",
5148 "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz",5222 "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
5149 "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==",5223 "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
5150 "license": "MIT",5224 "license": "MIT",
5151 "engines": {5225 "engines": {
5152 "node": ">=16.0.0"5226 "node": ">=18.0.0"
5153 }5227 }
5154 },5228 },
5155 "node_modules/highlight.js": {5229 "node_modules/highlight.js": {
@@ -5162,9 +5236,9 @@
5162 }5236 }
5163 },5237 },
5164 "node_modules/html-entities": {5238 "node_modules/html-entities": {
5165 "version": "2.5.2",5239 "version": "2.6.0",
5166 "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",5240 "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
5167 "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",5241 "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
5168 "funding": [5242 "funding": [
5169 {5243 {
5170 "type": "github",5244 "type": "github",
@@ -5517,6 +5591,24 @@
5517 "node": ">=0.10.0"5591 "node": ">=0.10.0"
5518 }5592 }
5519 },5593 },
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 },
5520 "node_modules/is-path-inside": {5612 "node_modules/is-path-inside": {
5521 "version": "3.0.3",5613 "version": "3.0.3",
5522 "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",5614 "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -5539,27 +5631,15 @@
5539 }5631 }
5540 },5632 },
5541 "node_modules/is-wsl": {5633 "node_modules/is-wsl": {
5542 "version": "2.2.0",5634 "version": "3.1.0",
5543 "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",5635 "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
5544 "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",5636 "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
5545 "license": "MIT",5637 "license": "MIT",
5546 "dependencies": {5638 "dependencies": {
5547 "is-docker": "^2.0.0"5639 "is-inside-container": "^1.0.0"
5548 },5640 },
5549 "engines": {5641 "engines": {
5550 "node": ">=8"5642 "node": ">=16"
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"
5563 },5643 },
5564 "funding": {5644 "funding": {
5565 "url": "https://github.com/sponsors/sindresorhus"5645 "url": "https://github.com/sponsors/sindresorhus"
@@ -5608,15 +5688,13 @@
5608 }5688 }
5609 },5689 },
5610 "node_modules/jackspeak": {5690 "node_modules/jackspeak": {
5611 "version": "2.3.6",5691 "version": "3.4.3",
5612 "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",5692 "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
5613 "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",5693 "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
5694 "license": "BlueOak-1.0.0",
5614 "dependencies": {5695 "dependencies": {
5615 "@isaacs/cliui": "^8.0.2"5696 "@isaacs/cliui": "^8.0.2"
5616 },5697 },
5617 "engines": {
5618 "node": ">=14"
5619 },
5620 "funding": {5698 "funding": {
5621 "url": "https://github.com/sponsors/isaacs"5699 "url": "https://github.com/sponsors/isaacs"
5622 },5700 },
@@ -5925,12 +6003,10 @@
5925 }6003 }
5926 },6004 },
5927 "node_modules/lru-cache": {6005 "node_modules/lru-cache": {
5928 "version": "10.2.0",6006 "version": "10.4.3",
5929 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",6007 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
5930 "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",6008 "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
5931 "engines": {6009 "license": "ISC"
5932 "node": "14 || >=16.14"
5933 }
5934 },6010 },
5935 "node_modules/math-intrinsics": {6011 "node_modules/math-intrinsics": {
5936 "version": "1.1.0",6012 "version": "1.1.0",
@@ -6007,17 +6083,26 @@
6007 }6083 }
6008 },6084 },
6009 "node_modules/mime-types": {6085 "node_modules/mime-types": {
6010 "version": "2.1.35",6086 "version": "3.0.1",
6011 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",6087 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
6012 "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",6088 "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
6013 "license": "MIT",6089 "license": "MIT",
6014 "dependencies": {6090 "dependencies": {
6015 "mime-db": "1.52.0"6091 "mime-db": "^1.54.0"
6016 },6092 },
6017 "engines": {6093 "engines": {
6018 "node": ">= 0.6"6094 "node": ">= 0.6"
6019 }6095 }
6020 },6096 },
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 },
6021 "node_modules/mimic-response": {6106 "node_modules/mimic-response": {
6022 "version": "3.1.0",6107 "version": "3.1.0",
6023 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",6108 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -6061,9 +6146,10 @@
6061 }6146 }
6062 },6147 },
6063 "node_modules/minipass": {6148 "node_modules/minipass": {
6064 "version": "7.0.4",6149 "version": "7.1.2",
6065 "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",6150 "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
6066 "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",6151 "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
6152 "license": "ISC",
6067 "engines": {6153 "engines": {
6068 "node": ">=16 || 14 >=14.17"6154 "node": ">=16 || 14 >=14.17"
6069 }6155 }
@@ -6078,9 +6164,9 @@
6078 }6164 }
6079 },6165 },
6080 "node_modules/morphdom": {6166 "node_modules/morphdom": {
6081 "version": "2.7.4",6167 "version": "2.7.5",
6082 "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.4.tgz",6168 "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.5.tgz",
6083 "integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQ==",6169 "integrity": "sha512-z6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==",
6084 "license": "MIT"6170 "license": "MIT"
6085 },6171 },
6086 "node_modules/ms": {6172 "node_modules/ms": {
@@ -6333,32 +6419,18 @@
6333 }6419 }
6334 },6420 },
6335 "node_modules/open": {6421 "node_modules/open": {
6336 "version": "8.4.2",6422 "version": "10.1.2",
6337 "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",6423 "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz",
6338 "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",6424 "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==",
6339 "license": "MIT",6425 "license": "MIT",
6340 "dependencies": {6426 "dependencies": {
6341 "define-lazy-prop": "^2.0.0",6427 "default-browser": "^5.2.1",
6342 "is-docker": "^2.1.1",6428 "define-lazy-prop": "^3.0.0",
6343 "is-wsl": "^2.2.0"6429 "is-inside-container": "^1.0.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"
6359 },6431 },
6360 "engines": {6432 "engines": {
6361 "node": ">=8"6433 "node": ">=18"
6362 },6434 },
6363 "funding": {6435 "funding": {
6364 "url": "https://github.com/sponsors/sindresorhus"6436 "url": "https://github.com/sponsors/sindresorhus"
@@ -6517,6 +6589,12 @@
6517 "node": ">= 14"6589 "node": ">= 14"
6518 }6590 }
6519 },6591 },
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 },
6520 "node_modules/pako": {6598 "node_modules/pako": {
6521 "version": "1.0.11",6599 "version": "1.0.11",
6522 "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",6600 "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -6642,15 +6720,16 @@
6642 }6720 }
6643 },6721 },
6644 "node_modules/path-scurry": {6722 "node_modules/path-scurry": {
6645 "version": "1.10.2",6723 "version": "1.11.1",
6646 "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",6724 "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
6647 "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",6725 "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
6726 "license": "BlueOak-1.0.0",
6648 "dependencies": {6727 "dependencies": {
6649 "lru-cache": "^10.2.0",6728 "lru-cache": "^10.2.0",
6650 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"6729 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
6651 },6730 },
6652 "engines": {6731 "engines": {
6653 "node": ">=16 || 14 >=14.17"6732 "node": ">=16 || 14 >=14.18"
6654 },6733 },
6655 "funding": {6734 "funding": {
6656 "url": "https://github.com/sponsors/isaacs"6735 "url": "https://github.com/sponsors/isaacs"
@@ -7067,9 +7146,10 @@
7067 }7146 }
7068 },7147 },
7069 "node_modules/readdir-glob/node_modules/brace-expansion": {7148 "node_modules/readdir-glob/node_modules/brace-expansion": {
7070 "version": "2.0.1",7149 "version": "2.0.2",
7071 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",7150 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
7072 "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",7151 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
7152 "license": "MIT",
7073 "dependencies": {7153 "dependencies": {
7074 "balanced-match": "^1.0.0"7154 "balanced-match": "^1.0.0"
7075 }7155 }
@@ -7177,6 +7257,18 @@
7177 "url": "https://github.com/sponsors/isaacs"7257 "url": "https://github.com/sponsors/isaacs"
7178 }7258 }
7179 },7259 },
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 },
7180 "node_modules/run-parallel": {7272 "node_modules/run-parallel": {
7181 "version": "1.2.0",7273 "version": "1.2.0",
7182 "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",7274 "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7229,9 +7321,9 @@
7229 "license": "ISC"7321 "license": "ISC"
7230 },7322 },
7231 "node_modules/schema-utils": {7323 "node_modules/schema-utils": {
7232 "version": "4.3.0",7324 "version": "4.3.2",
7233 "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",7325 "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
7234 "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",7326 "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
7235 "license": "MIT",7327 "license": "MIT",
7236 "dependencies": {7328 "dependencies": {
7237 "@types/json-schema": "^7.0.9",7329 "@types/json-schema": "^7.0.9",
@@ -7503,14 +7595,14 @@
7503 }7595 }
7504 },7596 },
7505 "node_modules/simple-git": {7597 "node_modules/simple-git": {
7506 "version": "3.27.0",7598 "version": "3.28.0",
7507 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",7599 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz",
7508 "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==",7600 "integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==",
7509 "license": "MIT",7601 "license": "MIT",
7510 "dependencies": {7602 "dependencies": {
7511 "@kwsites/file-exists": "^1.1.1",7603 "@kwsites/file-exists": "^1.1.1",
7512 "@kwsites/promise-deferred": "^1.1.1",7604 "@kwsites/promise-deferred": "^1.1.1",
7513 "debug": "^4.3.5"7605 "debug": "^4.4.0"
7514 },7606 },
7515 "funding": {7607 "funding": {
7516 "type": "github",7608 "type": "github",
@@ -7721,6 +7813,7 @@
7721 "version": "4.2.3",7813 "version": "4.2.3",
7722 "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",7814 "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
7723 "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",7815 "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
7816 "license": "MIT",
7724 "dependencies": {7817 "dependencies": {
7725 "emoji-regex": "^8.0.0",7818 "emoji-regex": "^8.0.0",
7726 "is-fullwidth-code-point": "^3.0.0",7819 "is-fullwidth-code-point": "^3.0.0",
@@ -7747,6 +7840,7 @@
7747 "version": "6.0.1",7840 "version": "6.0.1",
7748 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",7841 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
7749 "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",7842 "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
7843 "license": "MIT",
7750 "dependencies": {7844 "dependencies": {
7751 "ansi-regex": "^5.0.1"7845 "ansi-regex": "^5.0.1"
7752 },7846 },
@@ -7893,9 +7987,9 @@
7893 "license": "MIT"7987 "license": "MIT"
7894 },7988 },
7895 "node_modules/tiktoken": {7989 "node_modules/tiktoken": {
7896 "version": "1.0.20",7990 "version": "1.0.21",
7897 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.20.tgz",7991 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.21.tgz",
7898 "integrity": "sha512-zVIpXp84kth/Ni2me1uYlJgl2RZ2EjxwDaWLeDY/s6fZiyO9n1QoTOM5P7ZSYfToPvAvwYNMbg5LETVYVKyzfQ==",7992 "integrity": "sha512-/kqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/bH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==",
7899 "license": "MIT"7993 "license": "MIT"
7900 },7994 },
7901 "node_modules/timm": {7995 "node_modules/timm": {
@@ -8010,6 +8104,18 @@
8010 "node": ">= 0.6"8104 "node": ">= 0.6"
8011 }8105 }
8012 },8106 },
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 },
8013 "node_modules/typedarray": {8119 "node_modules/typedarray": {
8014 "version": "0.0.6",8120 "version": "0.0.6",
8015 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",8121 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -8213,13 +8319,14 @@
8213 }8319 }
8214 },8320 },
8215 "node_modules/webpack": {8321 "node_modules/webpack": {
8216 "version": "5.98.0",8322 "version": "5.99.9",
8217 "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",8323 "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz",
8218 "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",8324 "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==",
8219 "license": "MIT",8325 "license": "MIT",
8220 "dependencies": {8326 "dependencies": {
8221 "@types/eslint-scope": "^3.7.7",8327 "@types/eslint-scope": "^3.7.7",
8222 "@types/estree": "^1.0.6",8328 "@types/estree": "^1.0.6",
8329 "@types/json-schema": "^7.0.15",
8223 "@webassemblyjs/ast": "^1.14.1",8330 "@webassemblyjs/ast": "^1.14.1",
8224 "@webassemblyjs/wasm-edit": "^1.14.1",8331 "@webassemblyjs/wasm-edit": "^1.14.1",
8225 "@webassemblyjs/wasm-parser": "^1.14.1",8332 "@webassemblyjs/wasm-parser": "^1.14.1",
@@ -8236,7 +8343,7 @@
8236 "loader-runner": "^4.2.0",8343 "loader-runner": "^4.2.0",
8237 "mime-types": "^2.1.27",8344 "mime-types": "^2.1.27",
8238 "neo-async": "^2.6.2",8345 "neo-async": "^2.6.2",
8239 "schema-utils": "^4.3.0",8346 "schema-utils": "^4.3.2",
8240 "tapable": "^2.1.1",8347 "tapable": "^2.1.1",
8241 "terser-webpack-plugin": "^5.3.11",8348 "terser-webpack-plugin": "^5.3.11",
8242 "watchpack": "^2.4.1",8349 "watchpack": "^2.4.1",
@@ -8289,6 +8396,18 @@
8289 "node": ">=4.0"8396 "node": ">=4.0"
8290 }8397 }
8291 },8398 },
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 },
8292 "node_modules/whatwg-fetch": {8411 "node_modules/whatwg-fetch": {
8293 "version": "3.6.20",8412 "version": "3.6.20",
8294 "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",8413 "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
@@ -8351,6 +8470,7 @@
8351 "version": "7.0.0",8470 "version": "7.0.0",
8352 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",8471 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
8353 "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",8472 "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
8473 "license": "MIT",
8354 "dependencies": {8474 "dependencies": {
8355 "ansi-styles": "^4.0.0",8475 "ansi-styles": "^4.0.0",
8356 "string-width": "^4.1.0",8476 "string-width": "^4.1.0",
@@ -8383,9 +8503,9 @@
8383 }8503 }
8384 },8504 },
8385 "node_modules/ws": {8505 "node_modules/ws": {
8386 "version": "8.18.1",8506 "version": "8.18.2",
8387 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",8507 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
8388 "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",8508 "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
8389 "license": "MIT",8509 "license": "MIT",
8390 "engines": {8510 "engines": {
8391 "node": ">=10.0.0"8511 "node": ">=10.0.0"
@@ -8462,15 +8582,15 @@
8462 }8582 }
8463 },8583 },
8464 "node_modules/yaml": {8584 "node_modules/yaml": {
8465 "version": "2.7.0",8585 "version": "2.8.0",
8466 "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",8586 "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
8467 "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",8587 "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
8468 "license": "ISC",8588 "license": "ISC",
8469 "bin": {8589 "bin": {
8470 "yaml": "bin.mjs"8590 "yaml": "bin.mjs"
8471 },8591 },
8472 "engines": {8592 "engines": {
8473 "node": ">= 14"8593 "node": ">= 14.6"
8474 }8594 }
8475 },8595 },
8476 "node_modules/yargs": {8596 "node_modules/yargs": {
@@ -8501,13 +8621,16 @@
8501 }8621 }
8502 },8622 },
8503 "node_modules/yauzl": {8623 "node_modules/yauzl": {
8504 "version": "2.10.0",8624 "version": "3.2.0",
8505 "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",8625 "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz",
8506 "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",8626 "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==",
8507 "license": "MIT",8627 "license": "MIT",
8508 "dependencies": {8628 "dependencies": {
8509 "buffer-crc32": "~0.2.3",8629 "buffer-crc32": "~0.2.3",
8510 "fd-slicer": "~1.1.0"8630 "pend": "~1.2.0"
8631 },
8632 "engines": {
8633 "node": ">=12"
8511 }8634 }
8512 },8635 },
8513 "node_modules/yocto-queue": {8636 "node_modules/yocto-queue": {
package.json+26 -26
@@ -1,6 +1,6 @@
1{1{
2 "dependencies": {2 "dependencies": {
3 "@adobe/css-tools": "^4.4.2",3 "@adobe/css-tools": "^4.4.3",
4 "@agnai/sentencepiece-js": "^1.1.1",4 "@agnai/sentencepiece-js": "^1.1.1",
5 "@agnai/web-tokenizers": "^0.1.3",5 "@agnai/web-tokenizers": "^0.1.3",
6 "@iconfu/svg-inject": "^1.2.3",6 "@iconfu/svg-inject": "^1.2.3",
@@ -41,19 +41,19 @@
41 "cookie-session": "^2.1.0",41 "cookie-session": "^2.1.0",
42 "cors": "^2.8.5",42 "cors": "^2.8.5",
43 "crc": "^4.3.2",43 "crc": "^4.3.2",
44 "csrf-sync": "^4.0.3",44 "csrf-sync": "^4.2.1",
45 "diff-match-patch": "^1.0.5",45 "diff-match-patch": "^1.0.5",
46 "dompurify": "^3.2.4",46 "dompurify": "^3.2.6",
47 "droll": "^0.2.1",47 "droll": "^0.2.1",
48 "express": "^4.21.0",48 "express": "^4.21.0",
49 "form-data": "^4.0.2",49 "form-data": "^4.0.3",
50 "fuse.js": "^7.1.0",50 "fuse.js": "^7.1.0",
51 "google-translate-api-browser": "^3.0.1",51 "google-translate-api-browser": "^3.0.1",
52 "google-translate-api-x": "^10.7.2",52 "google-translate-api-x": "^10.7.2",
53 "handlebars": "^4.7.8",53 "handlebars": "^4.7.8",
54 "helmet": "^7.2.0",54 "helmet": "^8.1.0",
55 "highlight.js": "^11.11.1",55 "highlight.js": "^11.11.1",
56 "html-entities": "^2.5.2",56 "html-entities": "^2.6.0",
57 "iconv-lite": "^0.6.3",57 "iconv-lite": "^0.6.3",
58 "ip-matching": "^2.1.2",58 "ip-matching": "^2.1.2",
59 "ip-regex": "^5.0.0",59 "ip-regex": "^5.0.0",
@@ -61,13 +61,13 @@
61 "is-docker": "^3.0.0",61 "is-docker": "^3.0.0",
62 "localforage": "^1.10.0",62 "localforage": "^1.10.0",
63 "lodash": "^4.17.21",63 "lodash": "^4.17.21",
64 "mime-types": "^2.1.35",64 "mime-types": "^3.0.1",
65 "moment": "^2.30.1",65 "moment": "^2.30.1",
66 "morphdom": "^2.7.4",66 "morphdom": "^2.7.5",
67 "multer": "^2.0.1",67 "multer": "^2.0.1",
68 "node-fetch": "^3.3.2",68 "node-fetch": "^3.3.2",
69 "node-persist": "^4.0.4",69 "node-persist": "^4.0.4",
70 "open": "^8.4.2",70 "open": "^10.1.2",
71 "png-chunk-text": "^1.0.0",71 "png-chunk-text": "^1.0.0",
72 "png-chunks-extract": "^1.0.0",72 "png-chunks-extract": "^1.0.0",
73 "proxy-agent": "^6.5.0",73 "proxy-agent": "^6.5.0",
@@ -77,18 +77,18 @@
77 "seedrandom": "^3.0.5",77 "seedrandom": "^3.0.5",
78 "showdown": "^2.1.0",78 "showdown": "^2.1.0",
79 "sillytavern-transformers": "2.14.6",79 "sillytavern-transformers": "2.14.6",
80 "simple-git": "^3.27.0",80 "simple-git": "^3.28.0",
81 "slidetoggle": "^4.0.0",81 "slidetoggle": "^4.0.0",
82 "tiktoken": "^1.0.20",82 "tiktoken": "^1.0.21",
83 "url-join": "^5.0.0",83 "url-join": "^5.0.0",
84 "vectra": "^0.2.2",84 "vectra": "^0.2.2",
85 "wavefile": "^11.0.0",85 "wavefile": "^11.0.0",
86 "webpack": "^5.98.0",86 "webpack": "^5.99.9",
87 "write-file-atomic": "^5.0.1",87 "write-file-atomic": "^5.0.1",
88 "ws": "^8.18.1",88 "ws": "^8.18.2",
89 "yaml": "^2.7.0",89 "yaml": "^2.8.0",
90 "yargs": "^17.7.1",90 "yargs": "^17.7.1",
91 "yauzl": "^2.10.0"91 "yauzl": "^3.2.0"
92 },92 },
93 "engines": {93 "engines": {
94 "node": ">= 18"94 "node": ">= 18"
@@ -111,7 +111,7 @@
111 "type": "git",111 "type": "git",
112 "url": "https://github.com/SillyTavern/SillyTavern.git"112 "url": "https://github.com/SillyTavern/SillyTavern.git"
113 },113 },
114 "version": "1.13.0",114 "version": "1.13.1",
115 "scripts": {115 "scripts": {
116 "start": "node server.js",116 "start": "node server.js",
117 "debug": "node --inspect server.js",117 "debug": "node --inspect server.js",
@@ -137,24 +137,24 @@
137 "@types/archiver": "^6.0.3",137 "@types/archiver": "^6.0.3",
138 "@types/bytes": "^3.1.5",138 "@types/bytes": "^3.1.5",
139 "@types/command-exists": "^1.2.3",139 "@types/command-exists": "^1.2.3",
140 "@types/compression": "^1.7.5",140 "@types/compression": "^1.8.1",
141 "@types/cookie-parser": "^1.4.8",141 "@types/cookie-parser": "^1.4.9",
142 "@types/cookie-session": "^2.0.49",142 "@types/cookie-session": "^2.0.49",
143 "@types/cors": "^2.8.17",143 "@types/cors": "^2.8.19",
144 "@types/deno": "^2.2.0",144 "@types/deno": "^2.3.0",
145 "@types/express": "^4.17.21",145 "@types/express": "^4.17.23",
146 "@types/jquery": "^3.5.32",146 "@types/jquery": "^3.5.32",
147 "@types/jquery-cropper": "^1.0.4",147 "@types/jquery-cropper": "^1.0.4",
148 "@types/jquery.transit": "^0.9.33",148 "@types/jquery.transit": "^0.9.33",
149 "@types/jqueryui": "^1.12.24",149 "@types/jqueryui": "^1.12.24",
150 "@types/lodash": "^4.17.16",150 "@types/lodash": "^4.17.17",
151 "@types/mime-types": "^2.1.4",151 "@types/mime-types": "^3.0.1",
152 "@types/multer": "^1.4.12",152 "@types/multer": "^1.4.13",
153 "@types/node": "^18.19.80",153 "@types/node": "^18.19.84",
154 "@types/node-persist": "^3.1.8",154 "@types/node-persist": "^3.1.8",
155 "@types/png-chunk-text": "^1.0.3",155 "@types/png-chunk-text": "^1.0.3",
156 "@types/png-chunks-extract": "^1.0.2",156 "@types/png-chunks-extract": "^1.0.2",
157 "@types/response-time": "^2.3.8",157 "@types/response-time": "^2.3.9",
158 "@types/select2": "^4.0.63",158 "@types/select2": "^4.0.63",
159 "@types/toastr": "^2.1.43",159 "@types/toastr": "^2.1.43",
160 "@types/write-file-atomic": "^4.0.3",160 "@types/write-file-atomic": "^4.0.3",
public/css/animations.css+10 -0
@@ -55,6 +55,7 @@
5555
56/* Flashing for highlighting animation */56/* Flashing for highlighting animation */
57@keyframes flash {57@keyframes flash {
58
58 0%,59 0%,
59 50%,60 50%,
60 100% {61 100% {
@@ -122,3 +123,12 @@
122 border-top-color: var(--progFlashColor);123 border-top-color: var(--progFlashColor);
123 }124 }
124}125}
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 @@
1#rm_print_characters_block.group_overlay_mode_select .character_select {1#rm_print_characters_block.group_overlay_mode_select .character_select {
2 transition: background-color 0.4s ease;2 transition: background-color var(--animation-duration-3x) ease;
3 background-color: rgba(170, 170, 170, 0.15);3 background-color: rgba(170, 170, 170, 0.15);
4}4}
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
19#load-spinner {19#load-spinner {
20 --spinner-size: 2em;20 --spinner-size: 2em;
21 transition: all 300ms ease-out;21 transition: all var(--animation-duration-2x) ease-out;
22 opacity: 1;22 opacity: 1;
23 top: calc(50% - var(--spinner-size) / 2);23 top: calc(50% - var(--spinner-size) / 2);
24 left: calc(50% - var(--spinner-size) / 2);24 left: calc(50% - var(--spinner-size) / 2);
public/css/login.css+1 -1
@@ -23,7 +23,7 @@ body.login .userSelect {
23 width: 30%;23 width: 30%;
24 cursor: pointer;24 cursor: pointer;
25 margin: 5px 0;25 margin: 5px 0;
26 transition: background-color 0.15s ease-in-out;26 transition: background-color var(--animation-duration) ease-in-out;
27 display: flex;27 display: flex;
28 align-items: center;28 align-items: center;
29 justify-content: center;29 justify-content: center;
public/css/logprobs.css+1 -1
@@ -43,7 +43,7 @@
43 height: 25px;43 height: 25px;
44 margin-left: 5px;44 margin-left: 5px;
45 opacity: 0.5;45 opacity: 0.5;
46 transition: all 250ms;46 transition: all var(--animation-duration-2x);
47 position: unset !important;47 position: unset !important;
48}48}
4949
public/css/mobile-styles.css+9 -4
@@ -25,10 +25,6 @@
25 font-size: 15px;25 font-size: 15px;
26 }26 }
2727
28 .mes_text img {
29 width: 100%;
30 }
31
32 #extensions_settings,28 #extensions_settings,
33 #extensions_settings2 {29 #extensions_settings2 {
34 width: 100% !important;30 width: 100% !important;
@@ -241,6 +237,15 @@
241 top: var(--topBarBlockSize) !important;237 top: var(--topBarBlockSize) !important;
242 left: 0 !important;238 left: 0 !important;
243 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));239 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;
244 }249 }
245250
246 /*251 /*
public/css/promptmanager.css+2 -2
@@ -79,7 +79,7 @@
79 justify-content: center;79 justify-content: center;
80 margin-left: 0.25em;80 margin-left: 0.25em;
81 cursor: pointer;81 cursor: pointer;
82 transition: 0.3s ease-in-out;82 transition: var(--animation-duration-2x) ease-in-out;
83 height: 20px;83 height: 20px;
84 width: 20px;84 width: 20px;
85 filter: drop-shadow(0px 0px 2px black);85 filter: drop-shadow(0px 0px 2px black);
@@ -239,7 +239,7 @@
239 flex-direction: column;239 flex-direction: column;
240 justify-content: center;240 justify-content: center;
241 margin-left: 0.25em;241 margin-left: 0.25em;
242 transition: 0.3s ease-in-out;242 transition: var(--animation-duration-2x) ease-in-out;
243 filter: drop-shadow(0px 0px 2px black);243 filter: drop-shadow(0px 0px 2px black);
244}244}
245245
public/css/rm-groups.css+1 -1
@@ -172,7 +172,7 @@
172#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] {172#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] {
173 opacity: 0.4;173 opacity: 0.4;
174 filter: brightness(0.5);174 filter: brightness(0.5);
175 transition: all 0.2s ease-in-out;175 transition: all var(--animation-duration-2x) ease-in-out;
176}176}
177177
178/* #rm_group_members .right_menu_button[data-action="speak"]:hover, */178/* #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 @@
125 background-color: unset;125 background-color: unset;
126 color: var(--SmartThemeBodyColor);126 color: var(--SmartThemeBodyColor);
127 opacity: 0.5;127 opacity: 0.5;
128 transition: opacity 200ms ease-in-out;128 transition: opacity var(--animation-duration-2x) ease-in-out;
129 position: relative;129 position: relative;
130}130}
131131
@@ -203,7 +203,7 @@
203/* Styling for choice remove icon */203/* Styling for choice remove icon */
204span.select2.select2-container .select2-selection__choice__remove {204span.select2.select2-container .select2-selection__choice__remove {
205 cursor: pointer;205 cursor: pointer;
206 transition: background-color 0.3s;206 transition: background-color var(--animation-duration-2x);
207 color: var(--SmartThemeBodyColor);207 color: var(--SmartThemeBodyColor);
208 background-color: var(--black50a);208 background-color: var(--black50a);
209}209}
@@ -220,7 +220,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
220220
221.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {221.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
222 cursor: pointer;222 cursor: pointer;
223 transition: background-color 0.3s;223 transition: background-color var(--animation-duration-2x);
224 color: var(--SmartThemeBodyColor);224 color: var(--SmartThemeBodyColor);
225 background-color: var(--black50a);225 background-color: var(--black50a);
226 white-space: break-spaces;226 white-space: break-spaces;
public/css/st-tailwind.css+9 -1
@@ -332,6 +332,10 @@
332 overflow: hidden;332 overflow: hidden;
333}333}
334334
335.overflowYScroll {
336 overflow-y: scroll;
337}
338
335.padding0 {339.padding0 {
336 padding: 0;340 padding: 0;
337}341}
@@ -480,7 +484,7 @@
480}484}
481485
482.hoverglow {486.hoverglow {
483 transition: opacity 200ms;487 transition: opacity var(--animation-duration-2x);
484}488}
485489
486.hoverglow:hover {490.hoverglow:hover {
@@ -556,6 +560,10 @@ textarea:disabled {
556 min-width: fit-content;560 min-width: fit-content;
557}561}
558562
563.flexGap2 {
564 gap: 2px;
565}
566
559.flexGap5 {567.flexGap5 {
560 gap: 5px;568 gap: 5px;
561}569}
public/css/tags.css+1 -1
@@ -167,7 +167,7 @@
167}167}
168168
169.rm_tag_filter .tag.actionable {169.rm_tag_filter .tag.actionable {
170 transition: opacity 200ms;170 transition: opacity var(--animation-duration-2x);
171}171}
172172
173.rm_tag_filter .tag:hover {173.rm_tag_filter .tag:hover {
public/css/toggle-dependent.css+7 -0
@@ -19,6 +19,7 @@ body:not(.tts) #ttsExtensionNarrateAll {
19}19}
2020
21body.no-hotswap .hotswap,21body.no-hotswap .hotswap,
22body.no-hotswap .hotswap~hr,
22body.no-timer .mes_timer,23body.no-timer .mes_timer,
23body.no-timestamps .timestamp,24body.no-timestamps .timestamp,
24body.no-tokenCount .tokenCounterDisplay,25body.no-tokenCount .tokenCounterDisplay,
@@ -28,6 +29,12 @@ body.hideChatAvatars .mesAvatarWrapper .avatar {
28 display: none !important;29 display: none !important;
29}30}
3031
32body.no-hotswap #CharListButtonAndHotSwaps .flex-container {
33 flex-wrap: nowrap;
34 align-items: flex-end;
35 flex-flow: row;
36}
37
31body.hideChatAvatars .last_mes:not(.smallSysMes) {38body.hideChatAvatars .last_mes:not(.smallSysMes) {
32 padding-bottom: 20px !important;39 padding-bottom: 20px !important;
33}40}
public/css/welcome.css+16 -0
@@ -142,12 +142,28 @@ body.hideChatAvatars .welcomePanel .recentChatList .recentChat .avatar {
142 justify-content: space-between;142 justify-content: space-between;
143 align-items: baseline;143 align-items: baseline;
144 font-size: calc(var(--mainFontSize) * 1);144 font-size: calc(var(--mainFontSize) * 1);
145 gap: 5px;
145}146}
146147
147.welcomeRecent .recentChatList .recentChat .chatNameContainer .chatName {148.welcomeRecent .recentChatList .recentChat .chatNameContainer .chatName {
148 white-space: nowrap;149 white-space: nowrap;
149 text-overflow: ellipsis;150 text-overflow: ellipsis;
150 overflow: hidden;151 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;
151}167}
152168
153.welcomeRecent .recentChatList .recentChat .chatMessageContainer {169.welcomeRecent .recentChatList .recentChat .chatMessageContainer {
public/css/world-info.css+6 -2
@@ -198,7 +198,11 @@
198}198}
199199
200.world_entry {200.world_entry {
201 transition: opacity 500ms;201 transition: opacity var(--animation-duration-3x);
202}
203
204.world_entry_edit {
205 display: contents;
202}206}
203207
204.disabledWIEntry {208.disabledWIEntry {
@@ -267,7 +271,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
267271
268 opacity: 0.5;272 opacity: 0.5;
269 color: var(--SmartThemeBodyColor);273 color: var(--SmartThemeBodyColor);
270 transition: opacity 0.3s;274 transition: opacity var(--animation-duration-2x);
271}275}
272276
273.switch_input_type_icon:hover {277.switch_input_type_icon:hover {
public/global.d.ts+7 -0
@@ -1,6 +1,7 @@
1import libs from './lib';1import libs from './lib';
2import getContext from './scripts/st-context';2import getContext from './scripts/st-context';
3import { power_user } from './scripts/power-user';3import { power_user } from './scripts/power-user';
4import { QuickReplyApi } from './scripts/extensions/quick-reply/api/QuickReplyApi';
45
5declare global {6declare global {
6 // Custom types7 // Custom types
@@ -13,6 +14,7 @@ declare global {
1314
14 declare var pdfjsLib;15 declare var pdfjsLib;
15 declare var ePub;16 declare var ePub;
17 declare var quickReplyApi: QuickReplyApi;
1618
17 declare var SillyTavern: {19 declare var SillyTavern: {
18 getContext(): typeof getContext;20 getContext(): typeof getContext;
@@ -30,6 +32,11 @@ declare global {
30 izoomify(options?: any): JQuery;32 izoomify(options?: any): JQuery;
31 }33 }
3234
35 // NPM package doesn't have the 'queue' property in the type definition
36 interface JQueryTransitOptions {
37 queue?: boolean;
38 }
39
33 namespace Select2 {40 namespace Select2 {
34 interface Options<Result = DataFormat | GroupedDataFormat, RemoteResult = any> {41 interface Options<Result = DataFormat | GroupedDataFormat, RemoteResult = any> {
35 /**42 /**
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 @@
140 <div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">140 <div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">
141 <i class="fa-fw fa-solid fa-file-import"></i>141 <i class="fa-fw fa-solid fa-file-import"></i>
142 </div>142 </div>
143 <div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">143 <div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
144 <i class="fa-fw fa-solid fa-file-export"></i>144 <i class="fa-fw fa-solid fa-file-export"></i>
145 </div>145 </div>
146 <div data-preset-manager-delete="novel" class="margin0 menu_button_icon menu_button" title="Delete the preset" data-i18n="[title]Delete the preset">146 <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 @@
652 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">652 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
653 </div>653 </div>
654 </div>654 </div>
655 <div class="range-block" data-source="openai,custom,xai">655 <div class="range-block" data-source="openai,custom,xai,aimlapi">
656 <div class="range-block-title" data-i18n="Multiple swipes per generation">656 <div class="range-block-title" data-i18n="Multiple swipes per generation">
657 Multiple swipes per generation657 Multiple swipes per generation
658 </div>658 </div>
@@ -661,9 +661,9 @@
661 </div>661 </div>
662 </div>662 </div>
663 <div data-source="openrouter" class="range-block">663 <div data-source="openrouter" class="range-block">
664 <div class="range-block-title" title="Allow compressing requests by removing messages from the middle of the prompt.">664 <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.">
665 <span data-i18n="Middle-out Transform">Middle-out Transform</span>665 <span data-i18n="Middle-out Transform">Middle-out Transform</span>
666 <a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>666 <a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
667 </div>667 </div>
668 <div class="wide100p">668 <div class="wide100p">
669 <select id="openrouter_middleout" class="text_pole">669 <select id="openrouter_middleout" class="text_pole">
@@ -674,7 +674,7 @@
674 </div>674 </div>
675 </div>675 </div>
676 <div data-source="openrouter">676 <div data-source="openrouter">
677 Max prompt cost: <span id="openrouter_max_prompt_cost">Unknown</span>677 <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="openrouter_max_prompt_cost" data-i18n="Unknown">Unknown</span>
678 </div>678 </div>
679 <hr>679 <hr>
680 <div class="range-block">680 <div class="range-block">
@@ -691,7 +691,7 @@
691 </span>691 </span>
692 </div>692 </div>
693 </div>693 </div>
694 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations">694 <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">
695 <div class="range-block-title" data-i18n="Temperature">695 <div class="range-block-title" data-i18n="Temperature">
696 Temperature696 Temperature
697 </div>697 </div>
@@ -704,7 +704,7 @@
704 </div>704 </div>
705 </div>705 </div>
706 </div>706 </div>
707 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">
708 <div class="range-block-title" data-i18n="Frequency Penalty">708 <div class="range-block-title" data-i18n="Frequency Penalty">
709 Frequency Penalty709 Frequency Penalty
710 </div>710 </div>
@@ -717,7 +717,7 @@
717 </div>717 </div>
718 </div>718 </div>
719 </div>719 </div>
720 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations">
721 <div class="range-block-title" data-i18n="Presence Penalty">721 <div class="range-block-title" data-i18n="Presence Penalty">
722 Presence Penalty722 Presence Penalty
723 </div>723 </div>
@@ -730,7 +730,7 @@
730 </div>730 </div>
731 </div>731 </div>
732 </div>732 </div>
733 <div class="range-block" data-source="claude,openrouter,makersuite,vertexai,cohere,perplexity">733 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity">
734 <div class="range-block-title" data-i18n="Top K">734 <div class="range-block-title" data-i18n="Top K">
735 Top K735 Top K
736 </div>736 </div>
@@ -743,7 +743,7 @@
743 </div>743 </div>
744 </div>744 </div>
745 </div>745 </div>
746 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations">746 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations">
747 <div class="range-block-title" data-i18n="Top P">747 <div class="range-block-title" data-i18n="Top P">
748 Top P748 Top P
749 </div>749 </div>
@@ -980,7 +980,7 @@
980 </div>980 </div>
981 </div>981 </div>
982 </div>982 </div>
983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations">983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations,aimlapi">
984 <div class="range-block-title justifyLeft" data-i18n="Seed">984 <div class="range-block-title justifyLeft" data-i18n="Seed">
985 Seed985 Seed
986 </div>986 </div>
@@ -1969,7 +1969,7 @@
1969 </span>1969 </span>
1970 </div>1970 </div>
1971 </div>1971 </div>
1972 <div class="range-block" data-source="makersuite,vertexai,openrouter,claude">1972 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai">
1973 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">1973 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
1974 <input id="openai_enable_web_search" type="checkbox" />1974 <input id="openai_enable_web_search" type="checkbox" />
1975 <span data-i18n="Enable web search">Enable web search</span>1975 <span data-i18n="Enable web search">Enable web search</span>
@@ -1983,7 +1983,7 @@
1983 </b>1983 </b>
1984 </div>1984 </div>
1985 </div>1985 </div>
1986 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations">1986 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations">
1987 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1987 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1988 <input id="openai_function_calling" type="checkbox" />1988 <input id="openai_function_calling" type="checkbox" />
1989 <span data-i18n="Enable function calling">Enable function calling</span>1989 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1991,10 +1991,10 @@
1991 <div class="flexBasis100p toggle-description justifyLeft">1991 <div class="flexBasis100p toggle-description justifyLeft">
1992 <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>.1992 <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>.
1993 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>1993 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
1994 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing is used!</strong>1994 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
1995 </div>1995 </div>
1996 </div>1996 </div>
1997 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations">1997 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations">
1998 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">1998 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
1999 <input id="openai_image_inlining" type="checkbox" />1999 <input id="openai_image_inlining" type="checkbox" />
2000 <span data-i18n="Send inline images">Send inline images</span>2000 <span data-i18n="Send inline images">Send inline images</span>
@@ -2020,6 +2020,20 @@
2020 </div>2020 </div>
2021 </div>2021 </div>
2022 <div class="range-block" data-source="makersuite,vertexai">2022 <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">
2023 <label for="openai_request_images" class="checkbox_label widthFreeExpand">2037 <label for="openai_request_images" class="checkbox_label widthFreeExpand">
2024 <input id="openai_request_images" type="checkbox" />2038 <input id="openai_request_images" type="checkbox" />
2025 <span>2039 <span>
@@ -2050,7 +2064,7 @@
2050 </span>2064 </span>
2051 </div>2065 </div>
2052 </div>2066 </div>
2053 <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai,makersuite">2067 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai">
2054 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2068 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2055 <input id="openai_show_thoughts" type="checkbox" />2069 <input id="openai_show_thoughts" type="checkbox" />
2056 <span data-i18n="Request model reasoning">Request model reasoning</span>2070 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2064,7 +2078,7 @@
2064 </span>2078 </span>
2065 </div>2079 </div>
2066 </div>2080 </div>
2067 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,openrouter,pollinations">2081 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations">
2068 <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.">2082 <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.">
2069 <label for="openai_reasoning_effort">2083 <label for="openai_reasoning_effort">
2070 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2084 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2072,20 +2086,20 @@
2072 </label>2086 </label>
2073 <select id="openai_reasoning_effort">2087 <select id="openai_reasoning_effort">
2074 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>2088 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>
2075 <option data-i18n="openai_reasoning_effort_minimum" value="min">Mininum</option>2089 <option data-i18n="openai_reasoning_effort_minimum" value="min">Minimum</option>
2076 <option data-i18n="openai_reasoning_effort_low" value="low">Low</option>2090 <option data-i18n="openai_reasoning_effort_low" value="low">Low</option>
2077 <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option>2091 <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option>
2078 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>2092 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>
2079 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>2093 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
2080 </select>2094 </select>
2081 <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,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.">2095 <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.">
2082 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.2096 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
2083 </div>2097 </div>
2084 <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.">2098 <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.">
2085 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.2099 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.
2086 </div>2100 </div>
2087 <div class="toggle-description justifyLeft marginBot5" data-source="makersuite,vertexai" data-i18n="Allocates a portion of the response length for thinking (min: 0 tokens, low: 10%, medium: 25%, high: 50%, max: 24576 tokens). Auto lets the model decide.">2101 <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.">
2088 Allocates a portion of the response length for thinking (min: 0 tokens, low: 10%, medium: 25%, high: 50%, max: 24576 tokens). Auto lets the model decide.2102 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.
2089 </div>2103 </div>
2090 </div>2104 </div>
2091 </div>2105 </div>
@@ -2117,7 +2131,7 @@
2117 </div>2131 </div>
2118 </div>2132 </div>
2119 </div>2133 </div>
2120 <div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">2134 <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,scale,custom">
2121 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">2135 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">
2122 Logit Bias2136 Logit Bias
2123 </div>2137 </div>
@@ -2228,10 +2242,11 @@
2228 </small>2242 </small>
2229 <div class="flex-container">2243 <div class="flex-container">
2230 <input id="horde_api_key" name="horde_api_key" class="text_pole flex1" type="text" placeholder="0000000000" autocomplete="off">2244 <input id="horde_api_key" name="horde_api_key" class="text_pole flex1" type="text" placeholder="0000000000" autocomplete="off">
2231 <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_horde"></div>2245 <div title="Save and connect" data-i18n="[title]Save and connect" class="menu_button fa-solid fa-plug fa-fw" id="horde_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>
2232 </div>2247 </div>
2233 <div data-for="horde_api_key" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2248 <div data-for="horde_api_key" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2234 For privacy reasons, your API key will be hidden after you reload the page.2249 For privacy reasons, your API key will be hidden after you click 'Connect'.
2235 </div>2250 </div>
2236 <h4 class="horde_model_title">2251 <h4 class="horde_model_title">
2237 <span data-i18n="Models">Models </span>2252 <span data-i18n="Models">Models </span>
@@ -2283,11 +2298,11 @@
2283 </span>2298 </span>
2284 <div class="flex-container">2299 <div class="flex-container">
2285 <input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" type="text" autocomplete="off">2300 <input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2286 <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_novel">2301 <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_novel">
2287 </div>2302 </div>
2288 </div>2303 </div>
2289 <div data-for="api_key_novel" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2304 <div data-for="api_key_novel" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2290 For privacy reasons, your API key will be hidden after you reload the page.2305 For privacy reasons, your API key will be hidden after you click 'Connect'.
2291 </div>2306 </div>
2292 <h4><span data-i18n="Novel AI Model">Novel AI Model</span>2307 <h4><span data-i18n="Novel AI Model">Novel AI Model</span>
2293 <a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank">2308 <a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank">
@@ -2335,10 +2350,10 @@
2335 <h4 data-i18n="TogetherAI API Key">TogetherAI API Key</h4>2350 <h4 data-i18n="TogetherAI API Key">TogetherAI API Key</h4>
2336 <div class="flex-container">2351 <div class="flex-container">
2337 <input id="api_key_togetherai" name="api_key_togetherai" class="text_pole flex1" value="" type="text" autocomplete="off">2352 <input id="api_key_togetherai" name="api_key_togetherai" class="text_pole flex1" value="" type="text" autocomplete="off">
2338 <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_togetherai"></div>2353 <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_togetherai"></div>
2339 </div>2354 </div>
2340 <div data-for="api_key_togetherai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2355 <div data-for="api_key_togetherai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2341 For privacy reasons, your API key will be hidden after you reload the page.2356 For privacy reasons, your API key will be hidden after you click 'Connect'.
2342 </div>2357 </div>
2343 <div>2358 <div>
2344 <h4 data-i18n="TogetherAI Model">TogetherAI Model</h4>2359 <h4 data-i18n="TogetherAI Model">TogetherAI Model</h4>
@@ -2359,10 +2374,10 @@
2359 </div>2374 </div>
2360 <div class="flex-container">2375 <div class="flex-container">
2361 <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">2376 <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
2362 <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_openrouter"></div>2377 <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_openrouter"></div>
2363 </div>2378 </div>
2364 <div data-for="api_key_openrouter" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2379 <div data-for="api_key_openrouter-tg" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2365 For privacy reasons, your API key will be hidden after you reload the page.2380 For privacy reasons, your API key will be hidden after you click 'Connect'.
2366 </div>2381 </div>
2367 <div>2382 <div>
2368 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>2383 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
@@ -2386,10 +2401,10 @@
2386 <h4 data-i18n="InfermaticAI API Key">InfermaticAI API Key</h4>2401 <h4 data-i18n="InfermaticAI API Key">InfermaticAI API Key</h4>
2387 <div class="flex-container">2402 <div class="flex-container">
2388 <input id="api_key_infermaticai" name="api_key_infermaticai" class="text_pole flex1" value="" type="text" autocomplete="off">2403 <input id="api_key_infermaticai" name="api_key_infermaticai" class="text_pole flex1" value="" type="text" autocomplete="off">
2389 <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_infermaticai"></div>2404 <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_infermaticai"></div>
2390 </div>2405 </div>
2391 <div data-for="api_key_infermaticai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2406 <div data-for="api_key_infermaticai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2392 For privacy reasons, your API key will be hidden after you reload the page.2407 For privacy reasons, your API key will be hidden after you click 'Connect'.
2393 </div>2408 </div>
2394 <div>2409 <div>
2395 <h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4>2410 <h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4>
@@ -2409,10 +2424,10 @@
2409 </h4>2424 </h4>
2410 <div class="flex-container">2425 <div class="flex-container">
2411 <input id="api_key_dreamgen" name="api_key_dreamgen" class="text_pole flex1" value="" type="text" autocomplete="off">2426 <input id="api_key_dreamgen" name="api_key_dreamgen" class="text_pole flex1" value="" type="text" autocomplete="off">
2412 <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_dreamgen"></div>2427 <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_dreamgen"></div>
2413 </div>2428 </div>
2414 <div data-for="api_key_dreamgen" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2429 <div data-for="api_key_dreamgen" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2415 For privacy reasons, your API key will be hidden after you reload the page.2430 For privacy reasons, your API key will be hidden after you click 'Connect'.
2416 </div>2431 </div>
2417 <div>2432 <div>
2418 <h4 data-i18n="DreamGen Model">DreamGen Model</h4>2433 <h4 data-i18n="DreamGen Model">DreamGen Model</h4>
@@ -2434,11 +2449,11 @@
2434 </h4>2449 </h4>
2435 <div class="flex-container">2450 <div class="flex-container">
2436 <input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" type="text" autocomplete="off">2451 <input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2437 <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_mancer">2452 <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_mancer">
2438 </div>2453 </div>
2439 </div>2454 </div>
2440 <div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2455 <div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2441 For privacy reasons, your API key will be hidden after you reload the page.2456 For privacy reasons, your API key will be hidden after you click 'Connect'.
2442 </div>2457 </div>
2443 <div class="flex1">2458 <div class="flex1">
2444 <h4 data-i18n="Mancer Model">Mancer Model</h4>2459 <h4 data-i18n="Mancer Model">Mancer Model</h4>
@@ -2453,11 +2468,11 @@
2453 <h4 data-i18n="API key (optional)">API key (optional)</h4>2468 <h4 data-i18n="API key (optional)">API key (optional)</h4>
2454 <div class="flex-container">2469 <div class="flex-container">
2455 <input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off">2470 <input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2456 <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_generic">2471 <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_generic">
2457 </div>2472 </div>
2458 </div>2473 </div>
2459 <div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2474 <div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2460 For privacy reasons, your API key will be hidden after you reload the page.2475 For privacy reasons, your API key will be hidden after you click 'Connect'.
2461 </div>2476 </div>
2462 <div class="flex1">2477 <div class="flex1">
2463 <h4 data-i18n="Server url">Server URL</h4>2478 <h4 data-i18n="Server url">Server URL</h4>
@@ -2482,11 +2497,11 @@
2482 <h4 data-i18n="API key (optional)">API key (optional)</h4>2497 <h4 data-i18n="API key (optional)">API key (optional)</h4>
2483 <div class="flex-container">2498 <div class="flex-container">
2484 <input id="api_key_ooba" name="api_key_ooba" class="text_pole flex1 wide100p" type="text" autocomplete="off">2499 <input id="api_key_ooba" name="api_key_ooba" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2485 <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_ooba">2500 <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_ooba">
2486 </div>2501 </div>
2487 </div>2502 </div>
2488 <div data-for="api_key_ooba" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2503 <div data-for="api_key_ooba" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2489 For privacy reasons, your API key will be hidden after you reload the page.2504 For privacy reasons, your API key will be hidden after you click 'Connect'.
2490 </div>2505 </div>
2491 <div class="flex1">2506 <div class="flex1">
2492 <h4 data-i18n="Server url">Server URL</h4>2507 <h4 data-i18n="Server url">Server URL</h4>
@@ -2504,11 +2519,11 @@
2504 <h4 data-i18n="API key (optional)">API key</h4>2519 <h4 data-i18n="API key (optional)">API key</h4>
2505 <div class="flex-container">2520 <div class="flex-container">
2506 <input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" type="text" autocomplete="off">2521 <input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2507 <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_featherless">2522 <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_featherless">
2508 </div>2523 </div>
2509 </div>2524 </div>
2510 <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2525 <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2511 For privacy reasons, your API key will be hidden after you reload the page.2526 For privacy reasons, your API key will be hidden after you click 'Connect'.
2512 </div>2527 </div>
2513 <hr>2528 <hr>
2514 <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4>2529 <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4>
@@ -2555,11 +2570,11 @@
2555 <h4 data-i18n="vLLM API key">vLLM API key</h4>2570 <h4 data-i18n="vLLM API key">vLLM API key</h4>
2556 <div class="flex-container">2571 <div class="flex-container">
2557 <input id="api_key_vllm" name="api_key_vllm" class="text_pole flex1 wide100p" type="text" autocomplete="off">2572 <input id="api_key_vllm" name="api_key_vllm" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2558 <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_vllm">2573 <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_vllm">
2559 </div>2574 </div>
2560 </div>2575 </div>
2561 <div data-for="api_key_vllm" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2576 <div data-for="api_key_vllm" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2562 For privacy reasons, your API key will be hidden after you reload the page.2577 For privacy reasons, your API key will be hidden after you click 'Connect'.
2563 </div>2578 </div>
2564 <div class="flex1">2579 <div class="flex1">
2565 <h4 data-i18n="API url">API URL</h4>2580 <h4 data-i18n="API url">API URL</h4>
@@ -2579,11 +2594,11 @@
2579 <h4 data-i18n="HuggingFace Token">HuggingFace Token</h4>2594 <h4 data-i18n="HuggingFace Token">HuggingFace Token</h4>
2580 <div class="flex-container">2595 <div class="flex-container">
2581 <input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" type="text" autocomplete="off">2596 <input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2582 <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_huggingface">2597 <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_huggingface">
2583 </div>2598 </div>
2584 </div>2599 </div>
2585 <div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2600 <div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2586 For privacy reasons, your API key will be hidden after you reload the page.2601 For privacy reasons, your API key will be hidden after you click 'Connect'.
2587 </div>2602 </div>
2588 <div class="flex1">2603 <div class="flex1">
2589 <h4 data-i18n="Endpoint URL">Endpoint URL</h4>2604 <h4 data-i18n="Endpoint URL">Endpoint URL</h4>
@@ -2601,11 +2616,11 @@
2601 <h4 data-i18n="Aphrodite API key">Aphrodite API key</h4>2616 <h4 data-i18n="Aphrodite API key">Aphrodite API key</h4>
2602 <div class="flex-container">2617 <div class="flex-container">
2603 <input id="api_key_aphrodite" name="api_key_aphrodite" class="text_pole flex1 wide100p" type="text" autocomplete="off">2618 <input id="api_key_aphrodite" name="api_key_aphrodite" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2604 <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_aphrodite">2619 <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_aphrodite">
2605 </div>2620 </div>
2606 </div>2621 </div>
2607 <div data-for="api_key_aphrodite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2622 <div data-for="api_key_aphrodite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2608 For privacy reasons, your API key will be hidden after you reload the page.2623 For privacy reasons, your API key will be hidden after you click 'Connect'.
2609 </div>2624 </div>
2610 <div class="flex1">2625 <div class="flex1">
2611 <h4 data-i18n="API url">API URL</h4>2626 <h4 data-i18n="API url">API URL</h4>
@@ -2630,11 +2645,11 @@
2630 <h4 data-i18n="API key (optional)">API key (optional)</h4>2645 <h4 data-i18n="API key (optional)">API key (optional)</h4>
2631 <div class="flex-container">2646 <div class="flex-container">
2632 <input id="api_key_llamacpp" name="api_key_llamacpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">2647 <input id="api_key_llamacpp" name="api_key_llamacpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2633 <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_llamacpp">2648 <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_llamacpp">
2634 </div>2649 </div>
2635 </div>2650 </div>
2636 <div data-for="api_key_llamacpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2651 <div data-for="api_key_llamacpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2637 For privacy reasons, your API key will be hidden after you reload the page.2652 For privacy reasons, your API key will be hidden after you click 'Connect'.
2638 </div>2653 </div>
2639 <div class="flex1">2654 <div class="flex1">
2640 <h4 data-i18n="API url">API URL</h4>2655 <h4 data-i18n="API url">API URL</h4>
@@ -2677,11 +2692,11 @@
2677 <h4 data-i18n="Tabby API key">Tabby API key</h4>2692 <h4 data-i18n="Tabby API key">Tabby API key</h4>
2678 <div class="flex-container">2693 <div class="flex-container">
2679 <input id="api_key_tabby" name="api_key_tabby" class="text_pole flex1 wide100p" type="text" autocomplete="off">2694 <input id="api_key_tabby" name="api_key_tabby" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2680 <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_tabby">2695 <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_tabby">
2681 </div>2696 </div>
2682 </div>2697 </div>
2683 <div data-for="api_key_tabby" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2698 <div data-for="api_key_tabby" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2684 For privacy reasons, your API key will be hidden after you reload the page.2699 For privacy reasons, your API key will be hidden after you click 'Connect'.
2685 </div>2700 </div>
2686 <div class="flex1">2701 <div class="flex1">
2687 <h4 data-i18n="API url">API URL</h4>2702 <h4 data-i18n="API url">API URL</h4>
@@ -2699,12 +2714,12 @@
2699 </select>2714 </select>
2700 <div class="marginTopBot5">2715 <div class="marginTopBot5">
2701 <i class="fa-solid fa-flask"></i>2716 <i class="fa-solid fa-flask"></i>
2702 <span>Experimental feature. Use at your own risk.</span>2717 <span data-i18n="Experimental feature. Use at your own risk.">Experimental feature. Use at your own risk.</span>
2703 </div>2718 </div>
2704 <div class="marginTopBot5">2719 <div class="marginTopBot5">
2705 <small>2720 <small>
2706 <i class="fa-solid fa-lightbulb"></i>2721 <i class="fa-solid fa-lightbulb"></i>
2707 &nbsp;2722 &nbsp;<span data-i18n="Tabby model hint prefix"></span>
2708 <code>inline_model_loading: True</code>2723 <code>inline_model_loading: True</code>
2709 <span data-i18n="must be set in Tabby's config.yml to switch models.">2724 <span data-i18n="must be set in Tabby's config.yml to switch models.">
2710 must be set in Tabby's config.yml to switch models.2725 must be set in Tabby's config.yml to switch models.
@@ -2729,11 +2744,11 @@
2729 <h4 data-i18n="koboldcpp API key (optional)">koboldcpp API key (optional)</h4>2744 <h4 data-i18n="koboldcpp API key (optional)">koboldcpp API key (optional)</h4>
2730 <div class="flex-container">2745 <div class="flex-container">
2731 <input id="api_key_koboldcpp" name="api_key_koboldcpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">2746 <input id="api_key_koboldcpp" name="api_key_koboldcpp" class="text_pole flex1 wide100p" type="text" autocomplete="off">
2732 <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_koboldcpp">2747 <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_koboldcpp">
2733 </div>2748 </div>
2734 </div>2749 </div>
2735 <div data-for="api_key_koboldcpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2750 <div data-for="api_key_koboldcpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2736 For privacy reasons, your API key will be hidden after you reload the page.2751 For privacy reasons, your API key will be hidden after you click 'Connect'.
2737 </div>2752 </div>
2738 <div class="flex1">2753 <div class="flex1">
2739 <h4 data-i18n="API url">API URL</h4>2754 <h4 data-i18n="API url">API URL</h4>
@@ -2774,12 +2789,13 @@
2774 <optgroup>2789 <optgroup>
2775 <option value="01ai">01.AI (Yi)</option>2790 <option value="01ai">01.AI (Yi)</option>
2776 <option value="ai21">AI21</option>2791 <option value="ai21">AI21</option>
2792 <option value="aimlapi">AI/ML API</option>
2777 <option value="claude">Claude</option>2793 <option value="claude">Claude</option>
2778 <option value="cohere">Cohere</option>2794 <option value="cohere">Cohere</option>
2779 <option value="deepseek">DeepSeek</option>2795 <option value="deepseek">DeepSeek</option>
2780 <option value="groq">Groq</option>2796 <option value="groq">Groq</option>
2781 <option value="makersuite">Google AI Studio</option>2797 <option value="makersuite">Google AI Studio</option>
2782 <option value="vertexai">Google Vertex AI (Express mode)</option>2798 <option value="vertexai">Google Vertex AI</option>
2783 <option value="mistralai">MistralAI</option>2799 <option value="mistralai">MistralAI</option>
2784 <option value="nanogpt">NanoGPT</option>2800 <option value="nanogpt">NanoGPT</option>
2785 <option value="openrouter">OpenRouter</option>2801 <option value="openrouter">OpenRouter</option>
@@ -2836,7 +2852,7 @@
2836 <input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" />2852 <input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" />
2837 <small class="reverse_proxy_warning">2853 <small class="reverse_proxy_warning">
2838 <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>2854 <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>
2839 <code>/chat/completions</code> <b data-i18n="suffix will be added automatically.">suffix will be added automatically.</b>2855 <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>
2840 </small>2856 </small>
2841 </div>2857 </div>
2842 <div class="range-block-title justifyLeft" data-i18n="Proxy Password">2858 <div class="range-block-title justifyLeft" data-i18n="Proxy Password">
@@ -2891,15 +2907,15 @@
2891 </span>2907 </span>
2892 <div class="flex-container">2908 <div class="flex-container">
2893 <input id="api_key_openai" name="api_key_openai" class="text_pole flex1" value="" type="text" autocomplete="off">2909 <input id="api_key_openai" name="api_key_openai" class="text_pole flex1" value="" type="text" autocomplete="off">
2894 <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_openai"></div>2910 <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_openai"></div>
2895 </div>2911 </div>
2896 <div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning">2912 <div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning">
2897 <b data-i18n="Use Proxy password field instead. This input will be ignored.">2913 <b data-i18n="Use Proxy password field instead. This input will be ignored.">
2898 Use "Proxy password" field instead. This input will be ignored.2914 Use "Proxy password" field instead. This input will be ignored.
2899 </b>2915 </b>
2900 </div>2916 </div>
2901 <div data-for="api_key_openai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2917 <div data-for="api_key_openai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2902 For privacy reasons, your API key will be hidden after you reload the page.2918 For privacy reasons, your API key will be hidden after you click 'Connect'.
2903 </div>2919 </div>
2904 <div>2920 <div>
2905 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>2921 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>
@@ -2985,10 +3001,10 @@
2985 </div>3001 </div>
2986 <div class="flex-container">3002 <div class="flex-container">
2987 <input id="api_key_claude" name="api_key_claude" class="text_pole flex1" value="" type="text" autocomplete="off">3003 <input id="api_key_claude" name="api_key_claude" class="text_pole flex1" value="" type="text" autocomplete="off">
2988 <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_claude"></div>3004 <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_claude"></div>
2989 </div>3005 </div>
2990 <div data-for="api_key_claude" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3006 <div data-for="api_key_claude" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
2991 For privacy reasons, your API key will be hidden after you reload the page.3007 For privacy reasons, your API key will be hidden after you click 'Connect'.
2992 </div>3008 </div>
2993 <div>3009 <div>
2994 <h4 data-i18n="Claude Model">Claude Model</h4>3010 <h4 data-i18n="Claude Model">Claude Model</h4>
@@ -3044,10 +3060,10 @@
3044 </div>3060 </div>
3045 <div class="flex-container">3061 <div class="flex-container">
3046 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">3062 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
3047 <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_openrouter"></div>3063 <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_openrouter"></div>
3048 </div>3064 </div>
3049 <div data-for="api_key_openrouter" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3065 <div data-for="api_key_openrouter" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3050 For privacy reasons, your API key will be hidden after you reload the page.3066 For privacy reasons, your API key will be hidden after you click 'Connect'.
3051 </div>3067 </div>
3052 <div>3068 <div>
3053 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>3069 <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
@@ -3108,10 +3124,10 @@
3108 <h4 data-i18n="Scale API Key">Scale API Key</h4>3124 <h4 data-i18n="Scale API Key">Scale API Key</h4>
3109 <div class="flex-container">3125 <div class="flex-container">
3110 <input id="api_key_scale" name="api_key_scale" class="text_pole flex1" value="" autocomplete="off">3126 <input id="api_key_scale" name="api_key_scale" class="text_pole flex1" value="" autocomplete="off">
3111 <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_scale"></div>3127 <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_scale"></div>
3112 </div>3128 </div>
3113 <div data-for="api_key_scale" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3129 <div data-for="api_key_scale" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3114 For privacy reasons, your API key will be hidden after you reload the page.3130 For privacy reasons, your API key will be hidden after you click 'Connect'.
3115 </div>3131 </div>
3116 <h4>Scale API URL</h4>3132 <h4>Scale API URL</h4>
3117 <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">3133 <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 @@
3120 <h4>Scale Cookie (_jwt)</h4>3136 <h4>Scale Cookie (_jwt)</h4>
3121 <div class="flex-container">3137 <div class="flex-container">
3122 <input id="scale_cookie" name="scale_cookie" class="text_pole flex1" value="" autocomplete="off">3138 <input id="scale_cookie" name="scale_cookie" class="text_pole flex1" value="" autocomplete="off">
3123 <div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="scale_cookie"></div>3139 <div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="scale_cookie"></div>
3124 </div>3140 </div>
3125 <div data-for="scale_cookie" class="neutral_warning">3141 <div data-for="scale_cookie" class="neutral_warning">
3126 For privacy reasons, your cookie will be hidden after you reload the page.3142 For privacy reasons, your cookie will be hidden after you click 'Connect'.
3127 </div>3143 </div>
3128 </div>3144 </div>
3129 <!-- Its only purpose is to trigger max context size check -->3145 <!-- Its only purpose is to trigger max context size check -->
@@ -3137,10 +3153,10 @@
3137 <h4 data-i18n="AI21 API Key">AI21 API Key</h4>3153 <h4 data-i18n="AI21 API Key">AI21 API Key</h4>
3138 <div class="flex-container">3154 <div class="flex-container">
3139 <input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" value="" type="text" autocomplete="off">3155 <input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" value="" type="text" autocomplete="off">
3140 <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_ai21"></div>3156 <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_ai21"></div>
3141 </div>3157 </div>
3142 <div data-for="api_key_ai21" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3158 <div data-for="api_key_ai21" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3143 For privacy reasons, your API key will be hidden after you reload the page.3159 For privacy reasons, your API key will be hidden after you click 'Connect'.
3144 </div>3160 </div>
3145 <div>3161 <div>
3146 <h4 data-i18n="AI21 Model">AI21 Model</h4>3162 <h4 data-i18n="AI21 Model">AI21 Model</h4>
@@ -3167,20 +3183,24 @@
3167 <h4 data-i18n="Google AI Studio API Key">Google AI Studio API Key</h4>3183 <h4 data-i18n="Google AI Studio API Key">Google AI Studio API Key</h4>
3168 <div class="flex-container">3184 <div class="flex-container">
3169 <input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" value="" type="text" autocomplete="off">3185 <input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" value="" type="text" autocomplete="off">
3170 <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_makersuite"></div>3186 <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_makersuite"></div>
3171 </div>3187 </div>
3172 <div data-for="api_key_makersuite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3188 <div data-for="api_key_makersuite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3173 For privacy reasons, your API key will be hidden after you reload the page.3189 For privacy reasons, your API key will be hidden after you click 'Connect'.
3174 </div>3190 </div>
3175 <div>3191 <div>
3176 <h4 data-i18n="Google Model">Google Model</h4>3192 <h4 data-i18n="Google Model">Google Model</h4>
3177 <select id="model_google_select">3193 <select id="model_google_select">
3178 <optgroup label="Gemini 2.5">3194 <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>
3179 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>3197 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
3180 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>3198 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
3181 <option value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>3199 <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>
3182 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>3201 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
3183 <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>3202 <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>
3184 </optgroup>3204 </optgroup>
3185 <optgroup label="Gemini 2.0">3205 <optgroup label="Gemini 2.0">
3186 <option value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option>3206 <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 @@
3212 <option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option>3232 <option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option>
3213 </optgroup>3233 </optgroup>
3214 <optgroup label="Gemma">3234 <optgroup label="Gemma">
3235 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>
3215 <option value="gemma-3-27b-it">gemma-3-27b-it</option>3236 <option value="gemma-3-27b-it">gemma-3-27b-it</option>
3216 <option value="gemma-3-12b-it">gemma-3-12b-it</option>3237 <option value="gemma-3-12b-it">gemma-3-12b-it</option>
3217 <option value="gemma-3-4b-it">gemma-3-4b-it</option>3238 <option value="gemma-3-4b-it">gemma-3-4b-it</option>
@@ -3219,37 +3240,145 @@
3219 </optgroup>3240 </optgroup>
3220 <optgroup label="LearnLM">3241 <optgroup label="LearnLM">
3221 <option value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>3242 <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>
3223 </optgroup>3243 </optgroup>
3244 <optgroup id="google_other_models" label="Other"></optgroup>
3224 </select>3245 </select>
3225 </div>3246 </div>
3226 </form>3247 </form>
3227 <div id="vertexai_form" data-source="vertexai">3248 <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 Key3251 <!-- 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>
3236 <div class="flex-container">3252 <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>
3239 </div>3325 </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>
3242 </div>3360 </div>
3361
3362 <!-- Model Selection -->
3243 <div>3363 <div>
3244 <h4 data-i18n="Google Model">Google Model</h4>3364 <h4 data-i18n="Google Model">Google Model</h4>
3245 <select id="model_vertexai_select">3365 <select id="model_vertexai_select">
3366 <!-- data-mode="full" is for models that require a service account -->
3246 <optgroup label="Gemini 2.5">3367 <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>
3247 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>3370 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
3248 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>3371 <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>
3249 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>3374 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
3250 <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>3375 <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>
3251 </optgroup>3377 </optgroup>
3252 <optgroup label="Gemini 2.0">3378 <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>
3253 <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>3382 <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
3254 <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>3383 <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
3255 </optgroup>3384 </optgroup>
@@ -3260,10 +3389,10 @@
3260 <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4>3389 <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4>
3261 <div class="flex-container">3390 <div class="flex-container">
3262 <input id="api_key_mistralai" name="api_key_mistralai" class="text_pole flex1" value="" type="text" autocomplete="off">3391 <input id="api_key_mistralai" name="api_key_mistralai" class="text_pole flex1" value="" type="text" autocomplete="off">
3263 <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_mistralai"></div>3392 <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_mistralai"></div>
3264 </div>3393 </div>
3265 <div data-for="api_key_mistralai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3394 <div data-for="api_key_mistralai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3266 For privacy reasons, your API key will be hidden after you reload the page.3395 For privacy reasons, your API key will be hidden after you click 'Connect'.
3267 </div>3396 </div>
3268 <div>3397 <div>
3269 <h4 data-i18n="MistralAI Model">MistralAI Model</h4>3398 <h4 data-i18n="MistralAI Model">MistralAI Model</h4>
@@ -3286,6 +3415,8 @@
3286 <option value="pixtral-12b-latest">pixtral-12b-latest</option>3415 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
3287 <option value="pixtral-large-latest">pixtral-large-latest</option>3416 <option value="pixtral-large-latest">pixtral-large-latest</option>
3288 <option value="devstral-small-latest">devstral-small-latest</option>3417 <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>
3289 </optgroup>3420 </optgroup>
3290 <optgroup label="Sub-versions">3421 <optgroup label="Sub-versions">
3291 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>3422 <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option>
@@ -3315,6 +3446,8 @@
3315 <option value="pixtral-12b-2409">pixtral-12b-2409</option>3446 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
3316 <option value="pixtral-large-2411">pixtral-large-2411</option>3447 <option value="pixtral-large-2411">pixtral-large-2411</option>
3317 <option value="devstral-small-2505">devstral-small-2505</option>3448 <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>
3318 </optgroup>3451 </optgroup>
3319 <optgroup id="mistralai_other_models" label="Other"></optgroup>3452 <optgroup id="mistralai_other_models" label="Other"></optgroup>
3320 </select>3453 </select>
@@ -3324,10 +3457,10 @@
3324 <h4 data-i18n="Groq API Key">Groq API Key</h4>3457 <h4 data-i18n="Groq API Key">Groq API Key</h4>
3325 <div class="flex-container">3458 <div class="flex-container">
3326 <input id="api_key_groq" name="api_key_groq" class="text_pole flex1" value="" type="text" autocomplete="off">3459 <input id="api_key_groq" name="api_key_groq" class="text_pole flex1" value="" type="text" autocomplete="off">
3327 <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_groq"></div>3460 <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_groq"></div>
3328 </div>3461 </div>
3329 <div data-for="api_key_groq" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3462 <div data-for="api_key_groq" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3330 For privacy reasons, your API key will be hidden after you reload the page.3463 For privacy reasons, your API key will be hidden after you click 'Connect'.
3331 </div>3464 </div>
3332 <h4 data-i18n="Groq Model">Groq Model</h4>3465 <h4 data-i18n="Groq Model">Groq Model</h4>
3333 <select id="model_groq_select">3466 <select id="model_groq_select">
@@ -3366,10 +3499,10 @@
3366 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>3499 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
3367 <div class="flex-container">3500 <div class="flex-container">
3368 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">3501 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">
3369 <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_nanogpt"></div>3502 <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_nanogpt"></div>
3370 </div>3503 </div>
3371 <div data-for="api_key_nanogpt" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3504 <div data-for="api_key_nanogpt" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3372 For privacy reasons, your API key will be hidden after you reload the page.3505 For privacy reasons, your API key will be hidden after you click 'Connect'.
3373 </div>3506 </div>
3374 <div>3507 <div>
3375 <h4 data-i18n="NanoGPT Model">NanoGPT Model</h4>3508 <h4 data-i18n="NanoGPT Model">NanoGPT Model</h4>
@@ -3382,10 +3515,10 @@
3382 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>3515 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
3383 <div class="flex-container">3516 <div class="flex-container">
3384 <input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off">3517 <input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off">
3385 <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_deepseek"></div>3518 <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_deepseek"></div>
3386 </div>3519 </div>
3387 <div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3520 <div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3388 For privacy reasons, your API key will be hidden after you reload the page.3521 For privacy reasons, your API key will be hidden after you click 'Connect'.
3389 </div>3522 </div>
3390 <div>3523 <div>
3391 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>3524 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
@@ -3400,10 +3533,10 @@
3400 <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>3533 <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>
3401 <div class="flex-container">3534 <div class="flex-container">
3402 <input id="api_key_perplexity" name="api_key_perplexity" class="text_pole flex1" value="" type="text" autocomplete="off">3535 <input id="api_key_perplexity" name="api_key_perplexity" class="text_pole flex1" value="" type="text" autocomplete="off">
3403 <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_perplexity"></div>3536 <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_perplexity"></div>
3404 </div>3537 </div>
3405 <div data-for="api_key_perplexity" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3538 <div data-for="api_key_perplexity" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3406 For privacy reasons, your API key will be hidden after you reload the page.3539 For privacy reasons, your API key will be hidden after you click 'Connect'.
3407 </div>3540 </div>
3408 <h4 data-i18n="Perplexity Model">Perplexity Model</h4>3541 <h4 data-i18n="Perplexity Model">Perplexity Model</h4>
3409 <select id="model_perplexity_select">3542 <select id="model_perplexity_select">
@@ -3431,10 +3564,10 @@
3431 <h4 data-i18n="Cohere API Key">Cohere API Key</h4>3564 <h4 data-i18n="Cohere API Key">Cohere API Key</h4>
3432 <div class="flex-container">3565 <div class="flex-container">
3433 <input id="api_key_cohere" name="api_key_cohere" class="text_pole flex1" value="" type="text" autocomplete="off">3566 <input id="api_key_cohere" name="api_key_cohere" class="text_pole flex1" value="" type="text" autocomplete="off">
3434 <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_cohere"></div>3567 <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_cohere"></div>
3435 </div>3568 </div>
3436 <div data-for="api_key_cohere" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3569 <div data-for="api_key_cohere" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3437 For privacy reasons, your API key will be hidden after you reload the page.3570 For privacy reasons, your API key will be hidden after you click 'Connect'.
3438 </div>3571 </div>
3439 <div>3572 <div>
3440 <h4 data-i18n="Cohere Model">Cohere Model</h4>3573 <h4 data-i18n="Cohere Model">Cohere Model</h4>
@@ -3470,7 +3603,7 @@
3470 <div>3603 <div>
3471 <small>3604 <small>
3472 <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>3605 <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>
3473 <code>/chat/completions</code> <b data-i18n="suffix will be added automatically.">suffix will be added automatically.</b>3606 <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>
3474 </small>3607 </small>
3475 </div>3608 </div>
3476 <h4>3609 <h4>
@@ -3479,10 +3612,10 @@
3479 </h4>3612 </h4>
3480 <div class="flex-container">3613 <div class="flex-container">
3481 <input id="api_key_custom" name="api_key_custom" class="text_pole flex1" value="" type="text" autocomplete="off">3614 <input id="api_key_custom" name="api_key_custom" class="text_pole flex1" value="" type="text" autocomplete="off">
3482 <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_custom"></div>3615 <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_custom"></div>
3483 </div>3616 </div>
3484 <div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3617 <div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3485 For privacy reasons, your API key will be hidden after you reload the page.3618 For privacy reasons, your API key will be hidden after you click 'Connect'.
3486 </div>3619 </div>
3487 <h4 data-i18n="Enter a Model ID">Enter a Model ID</h4>3620 <h4 data-i18n="Enter a Model ID">Enter a Model ID</h4>
3488 <div class="flex-container">3621 <div class="flex-container">
@@ -3502,10 +3635,10 @@
3502 </h4>3635 </h4>
3503 <div class="flex-container">3636 <div class="flex-container">
3504 <input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" value="" type="text" autocomplete="off">3637 <input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" value="" type="text" autocomplete="off">
3505 <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_01ai"></div>3638 <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_01ai"></div>
3506 </div>3639 </div>
3507 <div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3640 <div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3508 For privacy reasons, your API key will be hidden after you reload the page.3641 For privacy reasons, your API key will be hidden after you click 'Connect'.
3509 </div>3642 </div>
3510 <h4 data-i18n="01.AI Model">01.AI Model</h4>3643 <h4 data-i18n="01.AI Model">01.AI Model</h4>
3511 <select id="model_01ai_select">3644 <select id="model_01ai_select">
@@ -3519,10 +3652,10 @@
3519 </h4>3652 </h4>
3520 <div class="flex-container">3653 <div class="flex-container">
3521 <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off">3654 <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off">
3522 <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_xai"></div>3655 <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_xai"></div>
3523 </div>3656 </div>
3524 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">3657 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3525 For privacy reasons, your API key will be hidden after you reload the page.3658 For privacy reasons, your API key will be hidden after you click 'Connect'.
3526 </div>3659 </div>
3527 <h4 data-i18n="xAI Model">xAI Model</h4>3660 <h4 data-i18n="xAI Model">xAI Model</h4>
3528 <select id="model_xai_select">3661 <select id="model_xai_select">
@@ -3536,6 +3669,21 @@
3536 <option value="grok-beta">grok-beta</option>3669 <option value="grok-beta">grok-beta</option>
3537 </select>3670 </select>
3538 </div>3671 </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>
3539 <div id="pollinations_form" data-source="pollinations">3687 <div id="pollinations_form" data-source="pollinations">
3540 <h4 data-i18n="Pollinations Model">Pollinations Model</h4>3688 <h4 data-i18n="Pollinations Model">Pollinations Model</h4>
3541 <select id="model_pollinations_select">3689 <select id="model_pollinations_select">
@@ -3562,10 +3710,17 @@
3562 </h4>3710 </h4>
3563 <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.">3711 <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.">
3564 <option data-i18n="prompt_post_processing_none" value="">None</option>3712 <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">
3566 <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>3714 <option data-i18n="prompt_post_processing_merge_tools" value="merge_tools">Merge consecutive roles (with tools)</option>
3567 <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>3715 <option data-i18n="prompt_post_processing_semi_tools" value="semi_tools">Semi-strict (alternating roles; with tools)</option>
3568 <option data-i18n="prompt_post_processing_single" value="single">Single user message</option>3716 <option data-i18n="prompt_post_processing_strict_tools" value="strict_tools">Strict (user first, 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>
3569 </select>3724 </select>
3570 </div>3725 </div>
3571 <div class="flex-container flex">3726 <div class="flex-container flex">
@@ -3585,7 +3740,9 @@
3585 <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" />3740 <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" />
3586 <span data-i18n="Auto-connect to Last Server">Auto-connect to Last Server</span>3741 <span data-i18n="Auto-connect to Last Server">Auto-connect to Last Server</span>
3587 </label>3742 </label>
3588 <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>3743 <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">
3744 <span data-i18n="View hidden API keys">View hidden API keys</span>
3745 </a>
3589 </div>3746 </div>
3590 </div>3747 </div>
3591 </div>3748 </div>
@@ -3725,11 +3882,11 @@
3725 <input id="instruct_derived" type="checkbox" style="display:none;" />3882 <input id="instruct_derived" type="checkbox" style="display:none;" />
3726 <small><i class="fa-solid fa-bolt menu_button margin0"></i></small>3883 <small><i class="fa-solid fa-bolt menu_button margin0"></i></small>
3727 </label>3884 </label>
3728 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">3885 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10;If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">
3729 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />3886 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
3730 <small><i class="fa-solid fa-link menu_button margin0"></i></small>3887 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
3731 </label>3888 </label>
3732 <label id="instruct_enabled_label"for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">3889 <label id="instruct_enabled_label" for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
3733 <input id="instruct_enabled" type="checkbox" style="display:none;" />3890 <input id="instruct_enabled" type="checkbox" style="display:none;" />
3734 <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>3891 <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
3735 </label>3892 </label>
@@ -4042,7 +4199,7 @@
4042 </small>4199 </small>
4043 </label>4200 </label>
4044 <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">4201 <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">
4045 <input id="reasoning_max_additions" class="text_pole textarea_compact widthUnset" type="number" min="0" max="999"></textarea>4202 <input id="reasoning_max_additions" class="text_pole textarea_compact widthUnset" type="number" min="0" max="999" />
4046 <small data-i18n="Max">Max</small>4203 <small data-i18n="Max">Max</small>
4047 </div>4204 </div>
4048 </div>4205 </div>
@@ -4173,8 +4330,8 @@
4173 <small>4330 <small>
4174 <span data-i18n="Scan Depth">Scan Depth</span>4331 <span data-i18n="Scan Depth">Scan Depth</span>
4175 </small>4332 </small>
4176 <input class="neo-range-slider" type="range" id="world_info_depth" name="world_info_depth" min="0" max="100" step="1">4333 <input class="neo-range-slider" type="range" id="world_info_depth" name="world_info_depth" min="0" max="1000" step="1">
4177 <input class="neo-range-input" type="number" min="0" max="100" step="1" data-for="world_info_depth" id="world_info_depth_counter">4334 <input class="neo-range-input" type="number" min="0" max="1000" step="1" data-for="world_info_depth" id="world_info_depth_counter">
4178 </div>4335 </div>
41794336
4180 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">4337 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p">
@@ -4190,8 +4347,8 @@
4190 <span data-i18n="Budget Cap">Budget Cap</span>4347 <span data-i18n="Budget Cap">Budget Cap</span>
4191 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title](0 = disabled)" title="(0 = disabled)"></div>4348 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title](0 = disabled)" title="(0 = disabled)"></div>
4192 </small>4349 </small>
4193 <input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="8192" step="1">4350 <input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="65536" step="1">
4194 <input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter">4351 <input class="neo-range-input" type="number" min="0" max="65536" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter">
4195 </div>4352 </div>
41964353
4197 <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.">4354 <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 @@
4643 <input id="never_resize_avatars" type="checkbox" />4800 <input id="never_resize_avatars" type="checkbox" />
4644 <small data-i18n="Never resize avatars">Never resize avatars</small>4801 <small data-i18n="Never resize avatars">Never resize avatars</small>
4645 </label>4802 </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>
4646 <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">4807 <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">
4647 <input id="show_card_avatar_urls" type="checkbox" />4808 <input id="show_card_avatar_urls" type="checkbox" />
4648 <small data-i18n="Show avatar filenames">Show avatar filenames</small>4809 <small data-i18n="Show avatar filenames">Show avatar filenames</small>
@@ -4656,13 +4817,16 @@
46564817
4657 <div name="MiscellaneousToggles">4818 <div name="MiscellaneousToggles">
4658 <h4><span data-i18n="Miscellaneous">Miscellaneous</span></h4>4819 <h4><span data-i18n="Miscellaneous">Miscellaneous</span></h4>
4659 <div class="flex-container">4820 <div class="flex-container flexGap2">
4660 <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.">4821 <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.">
4661 <small data-i18n="Reload Chat">Reload Chat</small>4822 <small data-i18n="Reload Chat">Reload Chat</small>
4662 </div>4823 </div>
4663 <div id="debug_menu" class="menu_button whitespacenowrap">4824 <div id="debug_menu" class="menu_button whitespacenowrap">
4664 <small data-i18n="Debug Menu">Debug Menu</small>4825 <small data-i18n="Debug Menu">Debug Menu</small>
4665 </div>4826 </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>
4666 </div>4830 </div>
4667 <label class="checkbox_label flexWrap" for="smooth_streaming">4831 <label class="checkbox_label flexWrap" for="smooth_streaming">
4668 <input id="smooth_streaming" type="checkbox" />4832 <input id="smooth_streaming" type="checkbox" />
@@ -4942,6 +5106,14 @@
4942 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>5106 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
4943 </div>5107 </div>
4944 <div class="inline-drawer-content">5108 <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>
4945 <label class="checkbox_label" for="stscript_autocomplete_autoHide">5117 <label class="checkbox_label" for="stscript_autocomplete_autoHide">
4946 <input id="stscript_autocomplete_autoHide" type="checkbox" />5118 <input id="stscript_autocomplete_autoHide" type="checkbox" />
4947 <small data-i18n="Automatically hide details">5119 <small data-i18n="Automatically hide details">
@@ -5177,7 +5349,7 @@
5177 <div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">5349 <div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
5178 <div class="flex-container alignItemsBaseline wide100p">5350 <div class="flex-container alignItemsBaseline wide100p">
5179 <div class="flex1 flex-container alignItemsBaseline">5351 <div class="flex1 flex-container alignItemsBaseline">
5180 <h3 class="margin0" >5352 <h3 class="margin0">
5181 <span data-i18n="Persona Management">Persona Management</span>5353 <span data-i18n="Persona Management">Persona Management</span>
5182 <a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">5354 <a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
5183 <span class="fa-solid fa-circle-question note-link-span"></span>5355 <span class="fa-solid fa-circle-question note-link-span"></span>
@@ -5348,10 +5520,12 @@
5348 <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>5520 <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>
5349 </div>5521 </div>
5350 <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p">5522 <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p">
5351 <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>5523 <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">
5524 </div>
5525 <hr class="wide100p">
5352 </div>5526 </div>
5353 </div>5527 </div>
5354 <hr>5528
5355 <!-- this div structure must be preserved until group peeking can adjust -->5529 <!-- this div structure must be preserved until group peeking can adjust -->
5356 <div id="rm_PinAndTabs">5530 <div id="rm_PinAndTabs">
5357 <div id="right-nav-panel-tabs" class="">5531 <div id="right-nav-panel-tabs" class="">
@@ -5376,340 +5550,343 @@
5376 </div>5550 </div>
5377 </div>5551 </div>
5378 <!-- end group peeking cope structure-->5552 <!-- 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">
5384 <div class="extension_token_counter">5558 <div id="name_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">
5398 <div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>5572 <div id="rm_button_back" class="menu_button fa-solid fa-left-long "></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> -->
5400 <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div>5574 <div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to 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" />
5402 <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore&#10;Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>5576 <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div>
5403 <div id="char_connections_button" class="menu_button fa-solid fa-face-smile" title="Connected Personas" data-i18n="[title]Connected Personas"></div>5577 <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>
5404 <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>5578 <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore&#10;Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>
5405 <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->5579 <div id="char_connections_button" class="menu_button fa-solid fa-face-smile" title="Connected Personas" data-i18n="[title]Connected Personas"></div>
5406 <!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->5580 <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>
5407 <div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character" data-i18n="[title]Duplicate Character"></div>5581 <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
5408 <label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character" data-i18n="[title]Create Character">5582 <!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World 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">
5412 </div>5586 </label>
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">
5416 <option id="set_character_world" data-i18n="Link to World Info">5590 <select id="char-management-dropdown" class="text_pole">
5417 Link to World Info5591 <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
5445 </option>5594 </option>
5446 <option id="export_button">5595 <option id="import_character_info" data-i18n="Import Card Lore">
5447 Export5596 Import Card Lore
5448 </option>5597 </option>
5449 <option id="delete_button">5598 <option id="set_chat_scenario" data-i18n="Scenario Override">
5450 Delete5599 Scenario Override
5451 </option>-->5600 </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>
5454 </div>5631 </div>
5455 </div>5632 <div id="tags_div">
5456 <div id="tags_div">5633 <div class="tag_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>
5460 </div>5638 </div>
5461 <div id="tagList" class="tags"></div>
5462 </div>5639 </div>
5463 </div>5640 </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">
5477 <hr>5641 <hr>
5478 <div id="description_div" class="title_restorable">5642 <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap">
5479 <div class="flex-container alignitemscenter">5643 <div id="creators_notes_div" class="title_restorable flexGap5">
5480 <span data-i18n="Character Description" class="mdhotkey_location">Description</span>5644 <span class="flex1" data-i18n="Creator's Notes">Creator'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>
5485 </div>5648 </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>
5497 </div>5651 </div>
5498 </div>5652 <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
5499 <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">5653 <hr>
5500 <div id="first_message_div" class="title_restorable">5654 <div id="description_div" class="title_restorable">
5501 <div class="flex-container alignitemscenter flex1">5655 <div class="flex-container alignitemscenter">
5502 <span data-i18n="First message" class="mdhotkey_location">First message</span>5656 <span data-i18n="Character Description" class="mdhotkey_location">Description</span>
5503 <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>5657 <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>
5504 <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank">5658 <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
5505 <span class="fa-solid fa-circle-question note-link-span"></span>5659 <span class="fa-solid fa-circle-question note-link-span"></span>
5506 </a>5660 </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>
5507 </div>5669 </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. Greetings5672 <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
5511 </span>
5512 </div>5673 </div>
5513 </div>5674 </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">
5515 <div class="extension_token_counter">5676 <div id="first_message_div" class="title_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>
5552 </div>5688 </div>
5553 <div id="groupTagList" class="tags paddingTopBot5"></div>
5554 </div>5689 </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>
5565 </div>5723 </div>
5566 <div name="GroupStragegyAndOrder" id="rm_group_buttons" class="flex-container paddingLeftRight5 flex2">5724 <div id="group_tags_div" class="wide100p">
5567 <div class="flex1 flexGap5">5725 <div class="tag_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>
5577 </div>5728 </div>
5578 <div class="flex1 flexGap5">5729 <div id="groupTagList" class="tags paddingTopBot5"></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">
5586 </select>5737 </div>
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.)">
5600 </div>5738 </div>
5739 <input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
5601 </label>5740 </label>
5602 <textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5603 </div>5741 </div>
5604 </div>5742 <div name="GroupStragegyAndOrder" id="rm_group_buttons" class="flex-container paddingLeftRight5 flex2">
5605 <div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">5743 <div class="flex1 flexGap5">
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>
5609 <input id="rm_group_fav" type="hidden" />5747 <select id="rm_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>
5613 </div>5780 </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">
5615 <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>5782 <div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-left-long"></div>
5616 <div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-trash-can" title="Delete" data-i18n="[title]Delete"></div>5783 <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>
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>
5618 <label class="checkbox_label whitespacenowrap">5785 <input id="rm_group_fav" type="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>
5625 <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" />5792 <div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-trash-can" title="Delete" data-i18n="[title]Delete"></div>
5626 </label>5793 <div class="flex1">
5627 <label id="rm_group_hidemutedsprites_label" class="checkbox_label whitespacenowrap">5794 <label class="checkbox_label whitespacenowrap">
5628 <input id="rm_group_hidemutedsprites" type="checkbox" />5795 <input id="rm_group_allow_self_responses" type="checkbox" />
5629 <span data-i18n="Hide Muted Member Sprites">Hide Muted Member Sprites</span>5796 <span data-i18n="Allow self responses">Allow self responses</span>
5630 </label>5797 </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>
5631 </div>5808 </div>
5632 </div>5809 </div>
5633 </div>5810 </div>
5634 </div>5811 </div>
5635 </div>5812 </div>
5636 </div>5813 <div class="inline-drawer wide100p flexFlowColumn">
5637 <div class="inline-drawer wide100p flexFlowColumn">5814 <div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-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>
5646 </div>5817 </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..." />
5658 </div>5822 </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>
5661 </div>5840 </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>
5664 </div>5841 </div>
5665 </div>5842 </div>
5666 </div>5843 </div>
5667 </div>5844 <div id="rm_character_import" class="right_menu" style="display: none;">
5668 <div id="rm_character_import" class="right_menu" style="display: none;">5845 <form id="form_import" action="javascript:void(null);" method="post" 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>
5697 </form>5848 </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>
5703 </div>5850 </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
5705 <div id="rm_print_characters_pagination">5881 <div id="rm_print_characters_pagination">
5706 <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>5882 <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>
5707 <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>5883 <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>
5708 <div id="bulkSelectedCount" class="bulkEditOptionElement paginationjs-nav"></div>5884 <div id="bulkSelectedCount" class="bulkEditOptionElement paginationjs-nav"></div>
5709 <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>5885 <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>
5710 <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>5886 <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>
5711 </div>5889 </div>
5712 <div id="rm_print_characters_block" class="flexFlowColumn"></div>
5713 </div>5890 </div>
5714 </nav>5891 </nav>
5715 </div>5892 </div>
@@ -6086,6 +6263,298 @@
6086 </div>6263 </div>
6087 </div>6264 </div>
6088 <div id="entry_edit_template" class="template_element">6265 <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>
6089 <div class="world_entry">6558 <div class="world_entry">
6090 <form class="world_entry_form wi-card-entry">6559 <form class="world_entry_form wi-card-entry">
6091 <div class="inline-drawer wide100p">6560 <div class="inline-drawer wide100p">
@@ -6158,297 +6627,8 @@
6158 <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>6627 <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>
6159 <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>6628 <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>
6160 </div>6629 </div>
6161 <div class="inline-drawer-content flex-container paddingBottom5px wide100p">6630 <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>
6452 </div>6632 </div>
6453 </div>6633 </div>
6454 </form>6634 </form>
@@ -6587,7 +6767,6 @@
6587 <span id="completion_prompt_manager_popup_entry_source"></span>6767 <span id="completion_prompt_manager_popup_entry_source"></span>
6588 </div>6768 </div>
6589 <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>6769 <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>
6591 </div>6770 </div>
6592 <div class="completion_prompt_manager_popup_entry_form_footer">6771 <div class="completion_prompt_manager_popup_entry_form_footer">
6593 <a id="completion_prompt_manager_popup_entry_form_close" title="close" data-i18n="[title]close" class="fa-solid fa-close menu_button"></a>6772 <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 @@
7347 </div>7526 </div>
7348 </div>7527 </div>
73497528
7529 <!-- Area for hidden data lists for secrets autosuggests -->
7530 <div id="secrets_datalists" class="displayNone"></div>
7531
7350 <!-- Script includes -->7532 <!-- Script includes -->
7351 <script src="lib/polyfill.js"></script>7533 <script src="lib/polyfill.js"></script>
7352 <script src="lib/jquery-3.5.1.min.js"></script>7534 <script src="lib/jquery-3.5.1.min.js"></script>
public/locales/ar-sa.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "رابط PNG المباشر (راجع",1379 "char_import_6": "رابط PNG المباشر (راجع",
1380 "char_import_7": "للمضيفين المسموح بهم)",1380 "char_import_7": "للمضيفين المسموح بهم)",
1381 "char_import_8": "شخصية RisuRealm (رابط مباشر)",1381 "char_import_8": "شخصية RisuRealm (رابط مباشر)",
1382 "char_import_9": "شخصية Soulkyn (رابط مباشر)",
1382 "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.",1383 "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.",
1383 "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.",1384 "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.",
1384 "Export for character": "تصدير للشخصية",1385 "Export for character": "تصدير للشخصية",
public/locales/de-de.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Direkter PNG-Link (siehe",1379 "char_import_6": "Direkter PNG-Link (siehe",
1380 "char_import_7": "für erlaubte Hosts)",1380 "char_import_7": "für erlaubte Hosts)",
1381 "char_import_8": "RisuRealm-Charakter (Direktlink)",1381 "char_import_8": "RisuRealm-Charakter (Direktlink)",
1382 "char_import_9": "Soulkyn-Charakter (Direktlink)",
1382 "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.",1383 "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.",
1383 "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.",1384 "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.",
1384 "Export for character": "Export für Zeichen",1385 "Export for character": "Export für Zeichen",
public/locales/es-es.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Enlace PNG directo (consulte",1379 "char_import_6": "Enlace PNG directo (consulte",
1380 "char_import_7": "para hosts permitidos)",1380 "char_import_7": "para hosts permitidos)",
1381 "char_import_8": "Personaje RisuRealm (Enlace directo)",1381 "char_import_8": "Personaje RisuRealm (Enlace directo)",
1382 "char_import_9": "Personaje Soulkyn (Enlace directo)",
1382 "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.",1383 "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.",
1383 "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.",1384 "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.",
1384 "Export for character": "Exportar para personaje",1385 "Export for character": "Exportar para personaje",
public/locales/fr-fr.json+1 -0
@@ -1301,6 +1301,7 @@
1301 "char_import_6": "Lien PNG direct (voir",1301 "char_import_6": "Lien PNG direct (voir",
1302 "char_import_7": "pour les hôtes autorisés)",1302 "char_import_7": "pour les hôtes autorisés)",
1303 "char_import_8": "Personnage de RisuRealm (lien direct)",1303 "char_import_8": "Personnage de RisuRealm (lien direct)",
1304 "char_import_9": "Personnage de Soulkyn (lien direct)",
1304 "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.",1305 "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.",
1305 "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.",1306 "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.",
1306 "Export for character": "Exportation pour le personnage",1307 "Export for character": "Exportation pour le personnage",
public/locales/is-is.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Beinn PNG hlekkur (sjá",1379 "char_import_6": "Beinn PNG hlekkur (sjá",
1380 "char_import_7": "fyrir leyfilega gestgjafa)",1380 "char_import_7": "fyrir leyfilega gestgjafa)",
1381 "char_import_8": "RisuRealm karakter (beinn hlekkur)",1381 "char_import_8": "RisuRealm karakter (beinn hlekkur)",
1382 "char_import_9": "Soulkyn karakter (beinn hlekkur)",
1382 "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.",1383 "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.",
1383 "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.",1384 "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.",
1384 "Export for character": "Flytja út fyrir persónu",1385 "Export for character": "Flytja út fyrir persónu",
public/locales/it-it.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Collegamento PNG diretto (fare riferimento a",1379 "char_import_6": "Collegamento PNG diretto (fare riferimento a",
1380 "char_import_7": "per gli host consentiti)",1380 "char_import_7": "per gli host consentiti)",
1381 "char_import_8": "Personaggio RisuRealm (collegamento diretto)",1381 "char_import_8": "Personaggio RisuRealm (collegamento diretto)",
1382 "char_import_9": "Personaggio Soulkyn (collegamento diretto)",
1382 "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.",1383 "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.",
1383 "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.",1384 "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.",
1384 "Export for character": "Esporta per carattere",1385 "Export for character": "Esporta per carattere",
public/locales/ja-jp.json+1 -0
@@ -1381,6 +1381,7 @@
1381 "char_import_6": "直接PNGリンク(参照",1381 "char_import_6": "直接PNGリンク(参照",
1382 "char_import_7": "許可されたホストの場合)",1382 "char_import_7": "許可されたホストの場合)",
1383 "char_import_8": "RisuRealm キャラクター (直接リンク)",1383 "char_import_8": "RisuRealm キャラクター (直接リンク)",
1384 "char_import_9": "Soulkyn キャラクター (直接リンク)",
1384 "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。",1385 "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。",
1385 "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",1386 "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",
1386 "Export for character": "キャラクターのエクスポート",1387 "Export for character": "キャラクターのエクスポート",
public/locales/ko-kr.json+1 -0
@@ -1398,6 +1398,7 @@
1398 "char_import_6": "직접 PNG 링크(참조",1398 "char_import_6": "직접 PNG 링크(참조",
1399 "char_import_7": "허용된 호스트의 경우)",1399 "char_import_7": "허용된 호스트의 경우)",
1400 "char_import_8": "RisuRealm 캐릭터 (직접링크)",1400 "char_import_8": "RisuRealm 캐릭터 (직접링크)",
1401 "char_import_9": "Soulkyn 캐릭터 (직접링크)",
1401 "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.",1402 "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.",
1402 "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.",1403 "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.",
1403 "Export for character": "캐릭터 내보내기",1404 "Export for character": "캐릭터 내보내기",
public/locales/nl-nl.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Directe PNG-link (zie",1379 "char_import_6": "Directe PNG-link (zie",
1380 "char_import_7": "voor toegestane hosts)",1380 "char_import_7": "voor toegestane hosts)",
1381 "char_import_8": "RisuRealm-personage (directe link)",1381 "char_import_8": "RisuRealm-personage (directe link)",
1382 "char_import_9": "Soulkyn-personage (directe link)",
1382 "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.",1383 "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.",
1383 "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.",1384 "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.",
1384 "Export for character": "Exporteren voor karakter",1385 "Export for character": "Exporteren voor karakter",
public/locales/pt-pt.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Link PNG direto (consulte",1379 "char_import_6": "Link PNG direto (consulte",
1380 "char_import_7": "para hosts permitidos)",1380 "char_import_7": "para hosts permitidos)",
1381 "char_import_8": "Personagem RisuRealm (link direto)",1381 "char_import_8": "Personagem RisuRealm (link direto)",
1382 "char_import_9": "Personagem Soulkyn (link direto)",
1382 "Supports importing multiple characters.": "Suporta importação de vários caracteres.",1383 "Supports importing multiple characters.": "Suporta importação de vários caracteres.",
1383 "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.",1384 "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.",
1384 "Export for character": "Exportar para personagem",1385 "Export for character": "Exportar para personagem",
public/locales/ru-ru.json+144 -4
@@ -105,7 +105,7 @@
105 "Adjust context size to worker capabilities": "Подстраивать размер контекста под возможности рабочих машин",105 "Adjust context size to worker capabilities": "Подстраивать размер контекста под возможности рабочих машин",
106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
107 "API key": "API-ключ",107 "API key": "API-ключ",
108 "Tabby API key": "Tabby API-ключ",108 "Tabby API key": "Ключ от Tabby API",
109 "Get it here:": "Получите здесь:",109 "Get it here:": "Получите здесь:",
110 "Register": "Зарегистрироваться",110 "Register": "Зарегистрироваться",
111 "TogetherAI Model": "Модель TogetherAI",111 "TogetherAI Model": "Модель TogetherAI",
@@ -275,7 +275,7 @@
275 "Chat History": "История чатов",275 "Chat History": "История чатов",
276 "Content": "Содержание",276 "Content": "Содержание",
277 "Selective": "Выборочно",277 "Selective": "Выборочно",
278 "Disable": "Отключено",278 "Disable": "Откл.",
279 "Back to parent chat": "Вернуться в основной чат",279 "Back to parent chat": "Вернуться в основной чат",
280 "Convert to group": "Сделать групповым",280 "Convert to group": "Сделать групповым",
281 "Start new chat": "Начать новый чат",281 "Start new chat": "Начать новый чат",
@@ -509,7 +509,7 @@
509 "Restore default prompt": "Восстановить станндартный промпт",509 "Restore default prompt": "Восстановить станндартный промпт",
510 "New preset": "Новый пресет",510 "New preset": "Новый пресет",
511 "Delete preset": "Удалить пресет",511 "Delete preset": "Удалить пресет",
512 "API Connections": "Соединения с API",512 "API Connections": "Соединение с API",
513 "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.",513 "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.",
514 "Clear your API key": "Стереть ключ от API",514 "Clear your API key": "Стереть ключ от API",
515 "Refresh models": "Обновить модели",515 "Refresh models": "Обновить модели",
@@ -965,6 +965,7 @@
965 "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",965 "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",
966 "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",966 "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",
967 "char_import_7": ")",967 "char_import_7": ")",
968 "char_import_9": "Персонаж с Soulkyn (прямая ссылка)",
968 "Grammar String": "Грамматика",969 "Grammar String": "Грамматика",
969 "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",970 "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
970 "Account": "Аккаунт",971 "Account": "Аккаунт",
@@ -2418,5 +2419,144 @@
2418 "Update enabled": "Обновить включенные",2419 "Update enabled": "Обновить включенные",
2419 "Could not connect to API": "Не удалось подключиться к API",2420 "Could not connect to API": "Не удалось подключиться к API",
2420 "Connected to API": "Соединение с API установлено",2421 "Connected to API": "Соединение с API установлено",
2421 "Go back": "Назад"2422 "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.": "Некорректный объект рег. выражения."
2422}2562}
public/locales/uk-ua.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Пряме посилання на PNG (див",1379 "char_import_6": "Пряме посилання на PNG (див",
1380 "char_import_7": "для дозволених хостів)",1380 "char_import_7": "для дозволених хостів)",
1381 "char_import_8": "Персонаж RisuRealm (пряме посилання)",1381 "char_import_8": "Персонаж RisuRealm (пряме посилання)",
1382 "char_import_9": "Персонаж Soulkyn (пряме посилання)",
1382 "Supports importing multiple characters.": "Підтримується імпорт кількох символів.",1383 "Supports importing multiple characters.": "Підтримується імпорт кількох символів.",
1383 "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.",1384 "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.",
1384 "Export for character": "Експорт для персонажа",1385 "Export for character": "Експорт для персонажа",
public/locales/vi-vn.json+1 -0
@@ -1379,6 +1379,7 @@
1379 "char_import_6": "Nhập PNG trực tiếp (tham khảo",1379 "char_import_6": "Nhập PNG trực tiếp (tham khảo",
1380 "char_import_7": "đối với các máy chủ được phép)",1380 "char_import_7": "đối với các máy chủ được phép)",
1381 "char_import_8": "RisuRealm (URL trực tiếp)",1381 "char_import_8": "RisuRealm (URL trực tiếp)",
1382 "char_import_9": "Soulkyn (URL trực tiếp)",
1382 "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.",1383 "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.",
1383 "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.",1384 "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.",
1384 "Export for character": "Xuất cho nhân vật",1385 "Export for character": "Xuất cho nhân vật",
public/locales/zh-cn.json+2 -1
@@ -670,7 +670,7 @@
670 "Square": "正方形",670 "Square": "正方形",
671 "Rectangle": "矩形",671 "Rectangle": "矩形",
672 "Chat Style:": "聊天风格:",672 "Chat Style:": "聊天风格:",
673 "Flat": "扁平\n气泡\n文档",673 "Flat": "扁平",
674 "Bubbles": "气泡",674 "Bubbles": "气泡",
675 "Document": "文档",675 "Document": "文档",
676 "Specify colors for your theme.": "指定您的主题的颜色。",676 "Specify colors for your theme.": "指定您的主题的颜色。",
@@ -1893,6 +1893,7 @@
1893 "char_import_6": "被允许的PNG直链(请参阅",1893 "char_import_6": "被允许的PNG直链(请参阅",
1894 "char_import_7": ")",1894 "char_import_7": ")",
1895 "char_import_8": "RisuRealm 角色(直链)",1895 "char_import_8": "RisuRealm 角色(直链)",
1896 "char_import_9": "Soulkyn 角色(直链)",
1896 "Supports importing multiple characters.": "支持导入多个角色。",1897 "Supports importing multiple characters.": "支持导入多个角色。",
1897 "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。",1898 "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。",
1898 "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",1899 "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",
public/locales/zh-tw.json+150 -28
@@ -19,7 +19,7 @@
19 "Delete the preset": "刪除預設設定檔",19 "Delete the preset": "刪除預設設定檔",
20 "novelaipresets": "NovelAI 預設設定檔",20 "novelaipresets": "NovelAI 預設設定檔",
21 "Default": "預設",21 "Default": "預設",
22 "openaipresets": "OpenAI 預設設定檔",22 "openaipresets": "聊天補全預設設定檔",
23 "Text Completion presets": "文字補全預設設定檔",23 "Text Completion presets": "文字補全預設設定檔",
24 "AI Module": "AI 模組",24 "AI Module": "AI 模組",
25 "Changes the style of the generated text.": "變更生成文字的樣式。",25 "Changes the style of the generated text.": "變更生成文字的樣式。",
@@ -506,9 +506,9 @@
506 "Global Lore First": "全域知識書優先",506 "Global Lore First": "全域知識書優先",
507 "Entries can activate other entries by mentioning their keywords": "條目可以透過提及其關鍵字來啟用其他條目",507 "Entries can activate other entries by mentioning their keywords": "條目可以透過提及其關鍵字來啟用其他條目",
508 "Recursive Scan": "遞迴掃描",508 "Recursive Scan": "遞迴掃描",
509 "Lookup for the entry keys in the context will respect the case": "在上下文中查詢條目鍵將區分大小寫",509 "Lookup for the entry keys in the context will respect the case": "在上下文中查詢條目關鍵字將區分大小寫",
510 "Case Sensitive": "區分大小寫",510 "Case Sensitive": "區分大小寫",
511 "If the entry key consists of only one word, it would not be matched as part of other words": "如果條目鍵僅包含一個詞,則不會作為其他詞的一部分進行配對",511 "If the entry key consists of only one word, it would not be matched as part of other words": "如果條目關鍵字僅包含一個詞,則不會作為其他詞的一部分進行配對",
512 "Match Whole Words": "完全配對",512 "Match Whole Words": "完全配對",
513 "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "只有符合最多鍵值數量的條目將被選中進行包含群組過濾",513 "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "只有符合最多鍵值數量的條目將被選中進行包含群組過濾",
514 "Use Group Scoring": "使用群組評分",514 "Use Group Scoring": "使用群組評分",
@@ -615,9 +615,9 @@
615 "Compact Input Area (Mobile)": "緊湊的聊天訊息輸入欄(行動版)",615 "Compact Input Area (Mobile)": "緊湊的聊天訊息輸入欄(行動版)",
616 "In the Character Management panel, show quick selection buttons for favorited characters": "在角色管理面板中,顯示加入到最愛角色的快速選擇按鈕。",616 "In the Character Management panel, show quick selection buttons for favorited characters": "在角色管理面板中,顯示加入到最愛角色的快速選擇按鈕。",
617 "Characters Hotswap": "角色卡快捷選單",617 "Characters Hotswap": "角色卡快捷選單",
618 "Enable magnification for zoomed avatar display.": "啟用放大顯示頭像",618 "Enable magnification for zoomed avatar display.": "啟用放大功能檢視頭像。",
619 "Avatar Hover Magnification": "頭像懸停時的放大倍數",619 "Avatar Hover Magnification": "頭像懸停時的放大倍數",
620 "Enables a magnification effect on hover when you display the zoomed avatar after clicking an avatar's image in chat.": "當你在聊天中點選頭像的圖片後,這會啟用滑鼠懸停時的放大效果。",620 "Enables a magnification effect on hover when you display the zoomed avatar after clicking an avatar's image in chat.": "當您點選訊息中的角色頭像後,將啟用滑鼠懸停時的放大效果。",
621 "Show tagged character folders in the character list": "在角色列表中顯示標籤角色資料夾。",621 "Show tagged character folders in the character list": "在角色列表中顯示標籤角色資料夾。",
622 "Tags as Folders": "標籤作為資料夾",622 "Tags as Folders": "標籤作為資料夾",
623 "Tags_as_Folders_desc": "標籤必須在「標籤管理」選單中標記為資料夾才可適用。點選這裡開啟。",623 "Tags_as_Folders_desc": "標籤必須在「標籤管理」選單中標記為資料夾才可適用。點選這裡開啟。",
@@ -769,7 +769,7 @@
769 "Name": "名稱",769 "Name": "名稱",
770 "Enter your name": "輸入您的名字",770 "Enter your name": "輸入您的名字",
771 "Click to set a new User Name": "設定新的使用者名稱",771 "Click to set a new User Name": "設定新的使用者名稱",
772 "Click to lock your selected persona to the current chat. Click again to remove the lock.": "鎖定目前所選的使用者角色至本次聊天。再次點選將移除鎖定。",772 "Click to lock your selected persona to the current chat. Click again to remove the lock.": "綁定目前所選的使用者角色至本次聊天。再次點選將移除綁定。",
773 "Click to set user name for all messages": "設定所有訊息的使用者名稱",773 "Click to set user name for all messages": "設定所有訊息的使用者名稱",
774 "Persona Description": "使用者角色描述",774 "Persona Description": "使用者角色描述",
775 "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "範例:[{{user}} 是一個 28 歲的羅馬尼亞貓娘。]",775 "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "範例:[{{user}} 是一個 28 歲的羅馬尼亞貓娘。]",
@@ -903,8 +903,8 @@
903 "@ Depth": "@ 深度",903 "@ Depth": "@ 深度",
904 "Role": "角色",904 "Role": "角色",
905 "Talkativeness": "健談度",905 "Talkativeness": "健談度",
906 "How often the character speaks in group chats!": "角色在群組聊天中說話的頻率!",906 "How often the character speaks in group chats!": "角色在群組聊天中的發言頻率!",
907 "How often the character speaks in": "角色說話的頻率",907 "How often the character speaks in": "角色發言頻率",
908 "group chats!": "群組聊天!",908 "group chats!": "群組聊天!",
909 "Shy": "寡言",909 "Shy": "寡言",
910 "Normal": "正常",910 "Normal": "正常",
@@ -970,10 +970,10 @@
970 "Comma separated list": "逗號分隔列表",970 "Comma separated list": "逗號分隔列表",
971 "Switch to plaintext mode": "切換到純文字模式",971 "Switch to plaintext mode": "切換到純文字模式",
972 "Logic": "邏輯",972 "Logic": "邏輯",
973 "AND ANY": "AND 任何",973 "AND ANY": "且「包含任一」",
974 "AND ALL": "AND 所有",974 "AND ALL": "且「包含全部」",
975 "NOT ALL": "NOT 所有",975 "NOT ALL": "且「未完全包含」",
976 "NOT ANY": "NOT 任何",976 "NOT ANY": "且「完全不含」",
977 "(ignored if empty)": "(如果為空則忽略)",977 "(ignored if empty)": "(如果為空則忽略)",
978 "Optional Filter": "選填過濾器",978 "Optional Filter": "選填過濾器",
979 "Keywords or Regexes (ignored if empty)": "關鍵字或正規表示式(如果為空則忽略)",979 "Keywords or Regexes (ignored if empty)": "關鍵字或正規表示式(如果為空則忽略)",
@@ -993,11 +993,11 @@
993 "Filter to Character(s)": "角色篩選",993 "Filter to Character(s)": "角色篩選",
994 "Character Exclusion": "角色排除",994 "Character Exclusion": "角色排除",
995 "-- Characters not found --": "-- 未找到角色 --",995 "-- Characters not found --": "-- 未找到角色 --",
996 "Inclusion Group": "包含的群組",996 "Inclusion Group": "納入群組",
997 "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文件:世界資訊——包容性集團",997 "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詳見 SillyTavern 官方文件:World Info - Inclusion Group",
998 "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如果有多個優先順序,則選擇「順序」最高的一個。",998 "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若有多個條目同時設為優先,將選擇「順序(Order)」數值最高的條目。",
999 "Only one entry with the same label will be activated": "僅會啟用具有相同標籤的一個條目",999 "Only one entry with the same label will be activated": "僅啟用一個具有相同標籤的條目",
1000 "A relative likelihood of entry activation within the group": "群組內條目啟用的相對可能性",1000 "A relative likelihood of entry activation within the group": "此條目在群組中被選中的相對可能性",
1001 "Group Weight": "群組權重",1001 "Group Weight": "群組權重",
1002 "Selective": "選擇性",1002 "Selective": "選擇性",
1003 "Use Probability": "使用機率",1003 "Use Probability": "使用機率",
@@ -1134,7 +1134,7 @@
1134 "Manage chat files": "管理聊天檔案",1134 "Manage chat files": "管理聊天檔案",
1135 "Delete messages": "刪除訊息",1135 "Delete messages": "刪除訊息",
1136 "Regenerate": "重新生成",1136 "Regenerate": "重新生成",
1137 "Ask AI to write your message for you": "請 AI 為您撰寫訊息",1137 "Ask AI to write your message for you": "由 AI 扮演您的角色撰寫訊息",
1138 "Impersonate": "AI 扮演使用者",1138 "Impersonate": "AI 扮演使用者",
1139 "Continue": "繼續",1139 "Continue": "繼續",
1140 "Bind user name to that avatar": "將使用者名稱附加到該頭像",1140 "Bind user name to that avatar": "將使用者名稱附加到該頭像",
@@ -1384,6 +1384,7 @@
1384 "char_import_6": "直接 PNG 連結(請參閱",1384 "char_import_6": "直接 PNG 連結(請參閱",
1385 "char_import_7": "對於允許的主機)",1385 "char_import_7": "對於允許的主機)",
1386 "char_import_8": "RisuRealm 角色(直接連結)",1386 "char_import_8": "RisuRealm 角色(直接連結)",
1387 "char_import_9": "Soulkyn 角色(直接連結)",
1387 "Supports importing multiple characters.": "支援匯入多個字元。",1388 "Supports importing multiple characters.": "支援匯入多個字元。",
1388 "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。",1389 "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。",
1389 "Export for character": "匯出字元",1390 "Export for character": "匯出字元",
@@ -2382,7 +2383,7 @@
2382 "Do you want to remove these fields before exporting?": "是否要在匯出前移除這些欄位?",2383 "Do you want to remove these fields before exporting?": "是否要在匯出前移除這些欄位?",
2383 "Delete the preset? This action is irreversible and your current settings will be overwritten.": "確定刪除此預設設定檔?刪除後無法復原,且此設定將被覆蓋。",2384 "Delete the preset? This action is irreversible and your current settings will be overwritten.": "確定刪除此預設設定檔?刪除後無法復原,且此設定將被覆蓋。",
2384 "Update all": "全部更新",2385 "Update all": "全部更新",
2385 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "當所選提供者無法滿足您的請求時,自動選擇替代提供者。",2386 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "當所選供應商無法滿足您的請求時,自動選擇替代供應商。",
2386 "Use extension settings": "使用擴充功能設定",2387 "Use extension settings": "使用擴充功能設定",
2387 "To use instruct formatting, switch to OpenRouter under Text Completion API.": "若要使用指令格式,請在文字補全 API 下切換至 OpenRouter。",2388 "To use instruct formatting, switch to OpenRouter under Text Completion API.": "若要使用指令格式,請在文字補全 API 下切換至 OpenRouter。",
2388 "Automatically 'continue' a response if the model stopped before reaching a certain amount of tokens.": "如果模型在達到一定數量的符元前停止,則自動繼續生成回應。",2389 "Automatically 'continue' a response if the model stopped before reaching a certain amount of tokens.": "如果模型在達到一定數量的符元前停止,則自動繼續生成回應。",
@@ -2456,7 +2457,7 @@
2456 "openai_reasoning_effort_low": "低",2457 "openai_reasoning_effort_low": "低",
2457 "openai_reasoning_effort_medium": "中",2458 "openai_reasoning_effort_medium": "中",
2458 "openai_reasoning_effort_high": "高",2459 "openai_reasoning_effort_high": "高",
2459 "Reasoning": "推理 Reasoning",2460 "Reasoning": "推理",
2460 "reasoning_auto_parse": "自動解析主要內容中推理區塊,需定義且不為空的前綴與後綴欄位。",2461 "reasoning_auto_parse": "自動解析主要內容中推理區塊,需定義且不為空的前綴與後綴欄位。",
2461 "Auto-Parse": "自動解析",2462 "Auto-Parse": "自動解析",
2462 "reasoning_auto_expand": "自動展開推理區塊。",2463 "reasoning_auto_expand": "自動展開推理區塊。",
@@ -2556,7 +2557,7 @@
2556 "Swiping a message will automatically trigger a re-summarization if it has already been summarized.": "滑動訊息後若已進行摘要,將自動觸發重新摘要。",2557 "Swiping a message will automatically trigger a re-summarization if it has already been summarized.": "滑動訊息後若已進行摘要,將自動觸發重新摘要。",
2557 "Re-summarize on Swipe": "滑動後重新摘要",2558 "Re-summarize on Swipe": "滑動後重新摘要",
2558 "Block chat input while summarizing.": "在摘要進行時暫時禁用聊天訊息輸入。",2559 "Block chat input while summarizing.": "在摘要進行時暫時禁用聊天訊息輸入。",
2559 "Block Chat": "訊息輸入鎖定",2560 "Block Chat": "鎖定訊息輸入",
2560 "Whether to use messages and/or summaries as context for summarization. You must use {{history}} in the summary prompt.": "決定是否在摘要中使用訊息及/或過往摘要作為背景資訊。需於摘要提示詞中,使用 {{history}}。",2561 "Whether to use messages and/or summaries as context for summarization. You must use {{history}} in the summary prompt.": "決定是否在摘要中使用訊息及/或過往摘要作為背景資訊。需於摘要提示詞中,使用 {{history}}。",
2561 "Message History": "訊息歷史",2562 "Message History": "訊息歷史",
2562 "Messages": "僅訊息",2563 "Messages": "僅訊息",
@@ -2630,7 +2631,7 @@
2630 "Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "分配部分回應長度用於推理功能(低:10%、中:25%、高:50%)。其他選項依模型而異。",2631 "Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "分配部分回應長度用於推理功能(低:10%、中:25%、高:50%)。其他選項依模型而異。",
2631 "Attach a file or image to a current chat.": "附加檔案或圖片至目前的聊天。",2632 "Attach a file or image to a current chat.": "附加檔案或圖片至目前的聊天。",
2632 "Change Persona Image": "變更使用者角色頭像",2633 "Change Persona Image": "變更使用者角色頭像",
2633 "Click to lock your selected persona to the current character. Click again to remove the lock.": "點選以將所選使用者角色鎖定目前聊天的角色。再次點選以解除鎖定。",2634 "Click to lock your selected persona to the current character. Click again to remove the lock.": "點選以將所選使用者角色與目前聊天的角色綁定。再次點選可解除綁定。",
2634 "Click to select this as default persona for the new chats. Click again to remove it.": "點選以將此設為新聊天的預設使用者角色,再次點選可取消設定。",2635 "Click to select this as default persona for the new chats. Click again to remove it.": "點選以將此設為新聊天的預設使用者角色,再次點選可取消設定。",
2635 "Connected Personas": "已連結的使用者角色",2636 "Connected Personas": "已連結的使用者角色",
2636 "Create a new prompt": "新增新提示詞",2637 "Create a new prompt": "新增新提示詞",
@@ -2639,21 +2640,21 @@
2639 "Duplicate Persona": "複製使用者角色",2640 "Duplicate Persona": "複製使用者角色",
2640 "Expand and zoom": "展開並縮放",2641 "Expand and zoom": "展開並縮放",
2641 "Move Entry to Another Lorebook": "將條目移動到其他知識書",2642 "Move Entry to Another Lorebook": "將條目移動到其他知識書",
2642 "Persona is locked to the current character": "使用者角色已鎖定至目前角色",2643 "Persona is locked to the current character": "使用者角色已綁定至目前角色",
2643 "Persona is locked to the current chat": "使用者角色已鎖定至目前聊天",2644 "Persona is locked to the current chat": "使用者角色已綁定至目前聊天",
2644 "Rename a prompt": "重新命名提示詞",2645 "Rename a prompt": "重新命名提示詞",
2645 "Rename Persona": "重新命名使用者角色",2646 "Rename Persona": "重新命名使用者角色",
2646 "Select your current Reasoning Template": "選擇你目前使用的推理範本",2647 "Select your current Reasoning Template": "選擇你目前使用的推理範本",
2647 "Toggles sprite scaling for character sprites.": "切換角色立繪的縮放比例",2648 "Toggles sprite scaling for character sprites.": "切換角色立繪的縮放比例",
2648 "When multiple personas are connected to a character, a popup will appear to select which one to use.": "當多個使用者角色連接至一個角色時,將顯示選擇彈窗。",2649 "When multiple personas are connected to a character, a popup will appear to select which one to use.": "當多個使用者角色連接至一個角色時,將顯示選擇彈窗。",
2649 "When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.": "使用 LLM 或 WebLLM 分類器時,僅顯示並使用已指派的表情立繪。",2650 "When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.": "使用 LLM 或 WebLLM 分類器時,僅顯示並使用已指派的表情立繪。",
2650 "Whenever a persona is selected, it will be locked and automatically selected when the chat is opened.": "若已選擇使用者角色,將在開啟聊天時自動進行鎖定。",2651 "Whenever a persona is selected, it will be locked and automatically selected when the chat is opened.": "若已選擇使用者角色,將在開啟聊天時自動進行綁定。",
2651 "Additional Matching Sources": "額外匹配來源",2652 "Additional Matching Sources": "額外匹配來源",
2652 "All Classes": "所有類別",2653 "All Classes": "所有類別",
2653 "Allow multiple persona connections per character": "允許每個角色連接多個使用者角色",2654 "Allow multiple persona connections per character": "允許每個角色連接多個使用者角色",
2654 "Allows the model to return image attachments.": "允許模型返回圖片附件。",2655 "Allows the model to return image attachments.": "允許模型返回圖片附件。",
2655 "Auto-Hide Sprite Settings": "自動隱藏立繪設定",2656 "Auto-Hide Sprite Settings": "自動隱藏立繪設定",
2656 "Auto-lock a chosen persona to the chat": "自動鎖定使用者角色至聊天中",2657 "Auto-lock a chosen persona to the chat": "自動綁定使用者角色至聊天中",
2657 "Character": "角色",2658 "Character": "角色",
2658 "Character Personality": "角色個性",2659 "Character Personality": "角色個性",
2659 "Character Tint/Share World Tint With Characters requires Prome to be enabled.": "角色著色/與角色共享世界著色功能需啟用 Prome。",2660 "Character Tint/Share World Tint With Characters requires Prome to be enabled.": "角色著色/與角色共享世界著色功能需啟用 Prome。",
@@ -2703,7 +2704,7 @@
2703 "Scan depth cannot be negative": "掃描深度不能為負值",2704 "Scan depth cannot be negative": "掃描深度不能為負值",
2704 "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}",2705 "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}",
2705 "Reasoning Template": "推理範本",2706 "Reasoning Template": "推理範本",
2706 "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(自動)不會傳送努力等級。",2707 "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(自動)不會傳送推理耗費等級。",
2707 "Branch or tag name (optional)": "分支/標籤名稱(可留空)",2708 "Branch or tag name (optional)": "分支/標籤名稱(可留空)",
2708 "Update enabled": "僅更新啟用項目",2709 "Update enabled": "僅更新啟用項目",
2709 "Sort: Loading Order": "排序:載入順序",2710 "Sort: Loading Order": "排序:載入順序",
@@ -2742,5 +2743,126 @@
2742 "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "角色或對話已被刪除。系統為安全考量導回此頁,請重新選擇角色。",2743 "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "角色或對話已被刪除。系統為安全考量導回此頁,請重新選擇角色。",
2743 "Could not connect to API": "無法連線至 API",2744 "Could not connect to API": "無法連線至 API",
2744 "Connected to API": "已成功連線至 API",2745 "Connected to API": "已成功連線至 API",
2745 "help_macros_charDepthPrompt": "角色的 @ 深度註記"2746 "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": "完成提示前綴"
2746}2868}
public/script.js+341 -245
@@ -10,7 +10,6 @@ import {
10 SVGInject,10 SVGInject,
11 Popper,11 Popper,
12 initLibraryShims,12 initLibraryShims,
13 slideToggle,
14 default as libs,13 default as libs,
15} from './lib.js';14} from './lib.js';
1615
@@ -209,6 +208,7 @@ import {
209} from './scripts/tags.js';208} from './scripts/tags.js';
210import {209import {
211 SECRET_KEYS,210 SECRET_KEYS,
211 initSecrets,
212 readSecretState,212 readSecretState,
213 secret_state,213 secret_state,
214 writeSecret,214 writeSecret,
@@ -250,7 +250,20 @@ import {
250import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';250import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
251import { hideLoader, showLoader } from './scripts/loader.js';251import { hideLoader, showLoader } from './scripts/loader.js';
252import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';252import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
253import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js';253import {
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';
254import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js';267import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js';
255import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';268import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
256import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';269import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
@@ -283,6 +296,7 @@ import { getContext } from './scripts/st-context.js';
283import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js';296import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js';
284import { accountStorage } from './scripts/util/AccountStorage.js';297import { accountStorage } from './scripts/util/AccountStorage.js';
285import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js';298import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js';
299import { initDataMaid } from './scripts/data-maid.js';
286300
287// API OBJECT FOR EXTERNAL WIRING301// API OBJECT FOR EXTERNAL WIRING
288globalThis.SillyTavern = {302globalThis.SillyTavern = {
@@ -482,6 +496,7 @@ export const event_types = {
482 GENERATION_STARTED: 'generation_started',496 GENERATION_STARTED: 'generation_started',
483 GENERATION_STOPPED: 'generation_stopped',497 GENERATION_STOPPED: 'generation_stopped',
484 GENERATION_ENDED: 'generation_ended',498 GENERATION_ENDED: 'generation_ended',
499 SD_PROMPT_PROCESSING: 'sd_prompt_processing',
485 EXTENSIONS_FIRST_LOAD: 'extensions_first_load',500 EXTENSIONS_FIRST_LOAD: 'extensions_first_load',
486 EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded',501 EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded',
487 SETTINGS_LOADED: 'settings_loaded',502 SETTINGS_LOADED: 'settings_loaded',
@@ -541,6 +556,10 @@ export const event_types = {
541 TOOL_CALLS_PERFORMED: 'tool_calls_performed',556 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
542 TOOL_CALLS_RENDERED: 'tool_calls_rendered',557 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
543 CHARACTER_MANAGEMENT_DROPDOWN: 'charManagementDropdown',558 CHARACTER_MANAGEMENT_DROPDOWN: 'charManagementDropdown',
559 SECRET_WRITTEN: 'secret_written',
560 SECRET_DELETED: 'secret_deleted',
561 SECRET_ROTATED: 'secret_rotated',
562 SECRET_EDITED: 'secret_edited',
544};563};
545564
546export const eventSource = new EventEmitter([event_types.APP_READY]);565export const eventSource = new EventEmitter([event_types.APP_READY]);
@@ -672,16 +691,8 @@ export const extension_prompt_roles = {
672691
673export const MAX_INJECTION_DEPTH = 10000;692export const MAX_INJECTION_DEPTH = 10000;
674693
675const SAFETY_CHAT = [694// Initialized in getSystemMessages()
676 {695const 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
686export let system_messages = {};697export let system_messages = {};
687698
@@ -768,22 +779,17 @@ async function getSystemMessages() {
768 },779 },
769 },780 },
770 };781 };
771}
772
773// Register configuration migrations
774registerPromptManagerMigration();
775782
776$(document).ajaxError(function myErrorHandler(_, xhr) {783 const safetyMessage = {
777 // Cohee: CSRF doesn't error out in multiple tabs anymore, so this is unnecessary784 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
788async function getClientVersion() {794async function getClientVersion() {
789 try {795 try {
@@ -865,7 +871,8 @@ export let create_save = {
865 creator: '',871 creator: '',
866 personality: '',872 personality: '',
867 first_message: '',873 first_message: '',
868 avatar: '',874 /** @type {FileList|null} */
875 avatar: null,
869 scenario: '',876 scenario: '',
870 mes_example: '',877 mes_example: '',
871 world: '',878 world: '',
@@ -982,11 +989,14 @@ async function firstLoadInit() {
982 }989 }
983990
984 showLoader();991 showLoader();
992 registerPromptManagerMigration();
993 initStandaloneMode();
985 initLibraryShims();994 initLibraryShims();
986 addShowdownPatch(showdown);995 addShowdownPatch(showdown);
987 reloadMarkdownProcessor();996 reloadMarkdownProcessor();
988 applyBrowserFixes();997 applyBrowserFixes();
989 await getClientVersion();998 await getClientVersion();
999 await initSecrets();
990 await readSecretState();1000 await readSecretState();
991 await initLocales();1001 await initLocales();
992 initChatUtilities();1002 initChatUtilities();
@@ -1026,6 +1036,7 @@ async function firstLoadInit() {
1026 initWelcomeScreen();1036 initWelcomeScreen();
1027 await initScrapers();1037 await initScrapers();
1028 initCustomSelectedSamplers();1038 initCustomSelectedSamplers();
1039 initDataMaid();
1029 addDebugFunctions();1040 addDebugFunctions();
1030 doDailyExtensionUpdatesCheck();1041 doDailyExtensionUpdatesCheck();
1031 await hideLoader();1042 await hideLoader();
@@ -1039,6 +1050,13 @@ async function fixViewport() {
1039 document.body.style.position = '';1050 document.body.style.position = '';
1040}1051}
10411052
1053function initStandaloneMode() {
1054 const isPwaMode = window.matchMedia('(display-mode: standalone)').matches;
1055 if (isPwaMode) {
1056 $('body').addClass('PWA');
1057 }
1058}
1059
1042function cancelStatusCheck(reason = 'Manually cancelled status check') {1060function cancelStatusCheck(reason = 'Manually cancelled status check') {
1043 abortStatusCheck?.abort(new AbortReason(reason));1061 abortStatusCheck?.abort(new AbortReason(reason));
1044 abortStatusCheck = new AbortController();1062 abortStatusCheck = new AbortController();
@@ -1395,7 +1413,6 @@ export function resultCheckStatus() {
1395 stopStatusLoading();1413 stopStatusLoading();
1396}1414}
13971415
1398
1399/**1416/**
1400 * Switches the currently selected character to the one with the given ID. (character index, not the character key!)1417 * Switches the currently selected character to the one with the given ID. (character index, not the character key!)
1401 *1418 *
@@ -1880,6 +1897,52 @@ async function delChat(chatfile) {
1880 }1897 }
1881}1898}
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 */
1906export 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
1883export async function replaceCurrentChat() {1946export async function replaceCurrentChat() {
1884 await clearChat();1947 await clearChat();
1885 chat.length = 0;1948 chat.length = 0;
@@ -2391,7 +2454,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
2391 const image = messageElement.find('.mes_img');2454 const image = messageElement.find('.mes_img');
2392 const text = messageElement.find('.mes_text');2455 const text = messageElement.find('.mes_text');
2393 const isInline = !!mes.extra?.inline_image;2456 const isInline = !!mes.extra?.inline_image;
2394 image.off('load').on('load', function () {2457 const doAdjustScroll = () => {
2395 if (!adjustScroll) {2458 if (!adjustScroll) {
2396 return;2459 return;
2397 }2460 }
@@ -2399,6 +2462,16 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
2399 const newChatHeight = $('#chat').prop('scrollHeight');2462 const newChatHeight = $('#chat').prop('scrollHeight');
2400 const diff = newChatHeight - chatHeight;2463 const diff = newChatHeight - chatHeight;
2401 $('#chat').scrollTop(scrollPosition + diff);2464 $('#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();
2402 });2475 });
2403 image.attr('src', mes.extra?.image);2476 image.attr('src', mes.extra?.image);
2404 image.attr('title', mes.extra?.title || mes.title || '');2477 image.attr('title', mes.extra?.title || mes.title || '');
@@ -2425,6 +2498,31 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
2425 }2498 }
2426 }2499 }
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
2428 // Add file to message2526 // Add file to message
2429 if (mes.extra?.file) {2527 if (mes.extra?.file) {
2430 messageElement.find('.mes_file_container').remove();2528 messageElement.find('.mes_file_container').remove();
@@ -2646,7 +2744,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
26462744
2647/**2745/**
2648 * Returns the URL of the avatar for the given character Id.2746 * Returns the URL of the avatar for the given character Id.
2649 * @param {number} characterId Character Id2747 * @param {number|string} characterId Character Id
2650 * @returns {string} Avatar URL2748 * @returns {string} Avatar URL
2651 */2749 */
2652export function getCharacterAvatar(characterId) {2750export function getCharacterAvatar(characterId) {
@@ -3520,7 +3618,7 @@ class StreamingProcessor {
3520 }3618 }
35213619
3522 if (this.image) {3620 if (this.image) {
3523 await processImageAttachment(chat[messageId], { imageUrl: this.image, parsedImage: null });3621 await processImageAttachment(chat[messageId], { imageUrl: this.image });
3524 appendMediaToMessage(chat[messageId], $(this.messageDom));3622 appendMediaToMessage(chat[messageId], $(this.messageDom));
3525 }3623 }
35263624
@@ -3678,6 +3776,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
3678 if (responseLengthCustomized) {3776 if (responseLengthCustomized) {
3679 TempResponseLength.save(api, responseLength);3777 TempResponseLength.save(api, responseLength);
3680 }3778 }
3779 /** @type {object|any[]} */
3681 let generateData = {};3780 let generateData = {};
36823781
3683 switch (api) {3782 switch (api) {
@@ -6027,7 +6126,11 @@ export function extractMessageFromData(data, activeApi = null) {
6027 case 'koboldhorde':6126 case 'koboldhorde':
6028 return data.text;6127 return data.text;
6029 case 'textgenerationwebui':6128 case 'textgenerationwebui':
6030 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';6129 return data.choices?.[0]?.text
6130 ?? data.choices?.[0]?.message?.content
6131 ?? data.content
6132 ?? data.response
6133 ?? '';
6031 case 'novel':6134 case 'novel':
6032 return data.output;6135 return data.output;
6033 case 'openai':6136 case 'openai':
@@ -6263,17 +6366,11 @@ export function cleanUpMessage({ getMessage, isImpersonate, isContinue, displayI
6263 * Adds an image to the message.6366 * Adds an image to the message.
6264 * @param {object} message Message object6367 * @param {object} message Message object
6265 * @param {object} sources Image sources6368 * @param {object} sources Image sources
6266 * @param {ParsedImage} [sources.parsedImage] Parsed image
6267 * @param {string} [sources.imageUrl] Image URL6369 * @param {string} [sources.imageUrl] Image URL
6268 *6370 *
6269 * @returns {Promise<void>}6371 * @returns {Promise<void>}
6270 */6372 */
6271async function processImageAttachment(message, { parsedImage, imageUrl }) {6373async function processImageAttachment(message, { imageUrl }) {
6272 if (parsedImage?.image) {
6273 saveImageToMessage(parsedImage, message);
6274 return;
6275 }
6276
6277 if (!imageUrl) {6374 if (!imageUrl) {
6278 return;6375 return;
6279 }6376 }
@@ -6332,8 +6429,6 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63326429
6333 let oldMessage = '';6430 let oldMessage = '';
6334 const generationFinished = new Date();6431 const generationFinished = new Date();
6335 const parsedImage = extractImageFromMessage(getMessage);
6336 getMessage = parsedImage.getMessage;
6337 if (type === 'swipe') {6432 if (type === 'swipe') {
6338 oldMessage = chat[chat.length - 1]['mes'];6433 oldMessage = chat[chat.length - 1]['mes'];
6339 chat[chat.length - 1]['swipes'].length++;6434 chat[chat.length - 1]['swipes'].length++;
@@ -6347,7 +6442,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
6347 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();6442 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
6348 chat[chat.length - 1]['extra']['reasoning'] = reasoning;6443 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
6349 chat[chat.length - 1]['extra']['reasoning_duration'] = null;6444 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
6350 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });6445 await processImageAttachment(chat[chat.length - 1], { imageUrl });
6351 if (power_user.message_token_count_enabled) {6446 if (power_user.message_token_count_enabled) {
6352 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];6447 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
6353 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);6448 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -6371,7 +6466,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
6371 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();6466 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
6372 chat[chat.length - 1]['extra']['reasoning'] = reasoning;6467 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
6373 chat[chat.length - 1]['extra']['reasoning_duration'] = null;6468 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
6374 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });6469 await processImageAttachment(chat[chat.length - 1], { imageUrl });
6375 if (power_user.message_token_count_enabled) {6470 if (power_user.message_token_count_enabled) {
6376 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];6471 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
6377 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);6472 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -6391,7 +6486,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
6391 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();6486 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
6392 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();6487 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
6393 chat[chat.length - 1]['extra']['reasoning'] += reasoning;6488 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
6394 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl });6489 await processImageAttachment(chat[chat.length - 1], { imageUrl });
6395 // We don't know if the reasoning duration extended, so we don't update it here on purpose.6490 // We don't know if the reasoning duration extended, so we don't update it here on purpose.
6396 if (power_user.message_token_count_enabled) {6491 if (power_user.message_token_count_enabled) {
6397 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];6492 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
@@ -6437,7 +6532,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
6437 chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;6532 chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
6438 }6533 }
64396534
6440 await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl: imageUrl });6535 await processImageAttachment(chat[chat.length - 1], { imageUrl });
6441 const chat_id = (chat.length - 1);6536 const chat_id = (chat.length - 1);
64426537
6443 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);6538 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
@@ -6654,15 +6749,6 @@ function getGeneratingModel(mes) {
6654 return model;6749 return model;
6655}6750}
66566751
6657function 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
6666/**6752/**
6667 * A function mainly used to switch 'generating' state - setting it to false and activating the buttons again6753 * A function mainly used to switch 'generating' state - setting it to false and activating the buttons again
6668 */6754 */
@@ -7073,6 +7159,11 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
7073 }7159 }
7074}7160}
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 */
7076async function read_avatar_load(input) {7167async function read_avatar_load(input) {
7077 if (input.files && input.files[0]) {7168 if (input.files && input.files[0]) {
7078 if (selected_button == 'create') {7169 if (selected_button == 'create') {
@@ -7104,7 +7195,7 @@ async function read_avatar_load(input) {
7104 await createOrEditCharacter();7195 await createOrEditCharacter();
7105 await delay(DEFAULT_SAVE_EDIT_TIMEOUT);7196 await delay(DEFAULT_SAVE_EDIT_TIMEOUT);
71067197
7107 const formData = new FormData($('#form_create').get(0));7198 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0)));
7108 await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {7199 await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
7109 method: 'GET',7200 method: 'GET',
7110 cache: 'no-cache',7201 cache: 'no-cache',
@@ -7114,22 +7205,21 @@ async function read_avatar_load(input) {
7114 },7205 },
7115 });7206 });
71167207
7117 $('.mes').each(async function () {7208 const messages = $('.mes').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;
7122 if ($(this).attr('is_user') == 'true') {7213 if ($el.attr('is_user') == 'true') continue;
7123 return;7214
7124 }
7125 if (nameMatch) {7215 if (nameMatch) {
7126 const previewSrc = $('#avatar_load_preview').attr('src');7216 const previewSrc = $('#avatar_load_preview').attr('src');
7127 const avatar = $(this).find('.avatar img');7217 const avatar = $el.find('.avatar img');
7128 avatar.attr('src', default_avatar);7218 avatar.attr('src', default_avatar);
7129 await delay(1);7219 await delay(1);
7130 avatar.attr('src', previewSrc);7220 avatar.attr('src', previewSrc);
7131 }7221 }
7132 });7222 }
71337223
7134 console.log('Avatar refreshed');7224 console.log('Avatar refreshed');
7135 }7225 }
@@ -7979,7 +8069,7 @@ export async function getChatsFromFiles(data, isGroupChat) {
7979 * response is an object with an `error` property set to `true`.8069 * response is an object with an `error` property set to `true`.
7980 */8070 */
7981export async function getPastCharacterChats(characterId = null) {8071export async function getPastCharacterChats(characterId = null) {
7982 characterId = characterId ?? this_chid;8072 characterId = characterId ?? parseInt(this_chid);
7983 if (!characters[characterId]) return [];8073 if (!characters[characterId]) return [];
79848074
7985 const response = await fetch('/api/characters/chats', {8075 const response = await fetch('/api/characters/chats', {
@@ -8046,9 +8136,7 @@ export async function displayPastChats() {
8046 // UX convenience: Focus the search field when the Manage Chat Files view opens.8136 // UX convenience: Focus the search field when the Manage Chat Files view opens.
8047 setTimeout(function () {8137 setTimeout(function () {
8048 const textSearchElement = $('#select_chat_search');8138 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
8052 }, 200);8140 }, 200);
8053}8141}
80548142
@@ -8315,11 +8403,10 @@ function select_rm_create({ switchMenu = true } = {}) {
8315 switchMenu && setMenuType('create');8403 switchMenu && setMenuType('create');
83168404
8317 //console.log('select_rm_Create() -- selected button: '+selected_button);8405 //console.log('select_rm_Create() -- selected button: '+selected_button);
8318 if (selected_button == 'create') {8406 if (selected_button == 'create' && create_save.avatar) {
8319 if (create_save.avatar != '') {8407 const addAvatarInput = /** @type {HTMLInputElement} */ ($('#add_avatar_button').get(0));
8320 $('#add_avatar_button').get(0).files = create_save.avatar;8408 addAvatarInput.files = create_save.avatar;
8321 read_avatar_load($('#add_avatar_button').get(0));8409 read_avatar_load(addAvatarInput);
8322 }
8323 }8410 }
83248411
8325 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');8412 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
@@ -8445,11 +8532,18 @@ export function updateChatMetadata(newValues, reset) {
8445 chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues };8532 chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues };
8446}8533}
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 */
8448function updateFavButtonState(state) {8540function updateFavButtonState(state) {
8541 // Update global state of the flag
8542 // TODO: This is bad and needs to be refactored.
8449 fav_ch_checked = state;8543 fav_ch_checked = state;
8450 $('#fav_checkbox').val(fav_ch_checked);8544 $('#fav_checkbox').prop('checked', state);
8451 $('#favorite_button').toggleClass('fav_on', fav_ch_checked);8545 $('#favorite_button').toggleClass('fav_on', state);
8452 $('#favorite_button').toggleClass('fav_off', !fav_ch_checked);8546 $('#favorite_button').toggleClass('fav_off', !state);
8453}8547}
84548548
8455export async function setScenarioOverride() {8549export async function setScenarioOverride() {
@@ -8566,7 +8660,7 @@ export function showSwipeButtons() {
85668660
8567 //had to add this to make the swipe counter work8661 //had to add this to make the swipe counter work
8568 //(copied from the onclick functions for swipe buttons..8662 //(copied from the onclick functions for swipe buttons..
8569 //don't know why the array isn't set for non-swipe messsages in Generate or addOneMessage..)8663 //don't know why the array isn't set for non-swipe messages in Generate or addOneMessage..)
8570 if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array8664 if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
8571 chat[chat.length - 1]['swipe_id'] = 0; // set it to id 08665 chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0
8572 chat[chat.length - 1]['swipes'] = []; // empty the array8666 chat[chat.length - 1]['swipes'] = []; // empty the array
@@ -8765,7 +8859,7 @@ function updateEditArrowClasses() {
8765export function closeMessageEditor(what = 'all') {8859export function closeMessageEditor(what = 'all') {
8766 if (what === 'message' || what === 'all') {8860 if (what === 'message' || what === 'all') {
8767 if (this_edit_mes_id) {8861 if (this_edit_mes_id) {
8768 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click();8862 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).trigger('click');
8769 }8863 }
8770 }8864 }
8771 if (what === 'reasoning' || what === 'all') {8865 if (what === 'reasoning' || what === 'all') {
@@ -8854,7 +8948,8 @@ function openCharacterWorldPopup() {
8854 }8948 }
88558949
8856 function onExtraWorldInfoChanged() {8950 function onExtraWorldInfoChanged() {
8857 const selectedWorlds = $('.character_extra_world_info_selector').val();8951 const selectorFieldValue = $('.character_extra_world_info_selector').val();
8952 const selectedWorlds = Array.isArray(selectorFieldValue) ? selectorFieldValue : [];
8858 let charLore = world_info.charLore ?? [];8953 let charLore = world_info.charLore ?? [];
88598954
8860 // TODO: Maybe make this utility function not use the window context?8955 // TODO: Maybe make this utility function not use the window context?
@@ -8899,7 +8994,7 @@ function openCharacterWorldPopup() {
8899 // Apped to base dropdown8994 // Apped to base dropdown
8900 world_names.forEach((item, i) => {8995 world_names.forEach((item, i) => {
8901 const option = document.createElement('option');8996 const option = document.createElement('option');
8902 option.value = i;8997 option.value = String(i);
8903 option.innerText = item;8998 option.innerText = item;
8904 option.selected = item === worldId;8999 option.selected = item === worldId;
8905 select.append(option);9000 select.append(option);
@@ -8911,7 +9006,7 @@ function openCharacterWorldPopup() {
8911 }9006 }
8912 world_names.forEach((item, i) => {9007 world_names.forEach((item, i) => {
8913 const option = document.createElement('option');9008 const option = document.createElement('option');
8914 option.value = i;9009 option.value = String(i);
8915 option.innerText = item;9010 option.innerText = item;
89169011
8917 const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());9012 const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());
@@ -9017,7 +9112,7 @@ function addAlternateGreeting(template, greeting, index, getArray, popup) {
9017 */9112 */
9018async function createOrEditCharacter(e) {9113async function createOrEditCharacter(e) {
9019 $('#rm_info_avatar').html('');9114 $('#rm_info_avatar').html('');
9020 const formData = new FormData($('#form_create').get(0));9115 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0)));
9021 formData.set('fav', String(fav_ch_checked));9116 formData.set('fav', String(fav_ch_checked));
9022 const isNewChat = e instanceof CustomEvent && e.type === 'newChat';9117 const isNewChat = e instanceof CustomEvent && e.type === 'newChat';
90239118
@@ -9099,7 +9194,7 @@ async function createOrEditCharacter(e) {
90999194
9100 $('#character_popup-button-h3').text('Create character');9195 $('#character_popup-button-h3').text('Create character');
91019196
9102 create_save.avatar = '';9197 create_save.avatar = null;
91039198
9104 $('#add_avatar_button').replaceWith(9199 $('#add_avatar_button').replaceWith(
9105 $('#add_avatar_button').val('').clone(true),9200 $('#add_avatar_button').val('').clone(true),
@@ -9348,13 +9443,13 @@ export function swipe_left(_event, { source, repeated } = {}) {
93489443
9349/**9444/**
9350 * Handles the swipe to the right event.9445 * Handles the swipe to the right event.
9351 * @param {JQuery.Event} _event Event.9446 * @param {JQuery.Event} [_event] Event.
9352 * @param {object} params Additional parameters.9447 * @param {object} params Additional parameters.
9353 * @param {string} [params.source] The source of the swipe event.9448 * @param {string} [params.source] The source of the swipe event.
9354 * @param {boolean} [params.repeated] Is the swipe event repeated.9449 * @param {boolean} [params.repeated] Is the swipe event repeated.
9355 */9450 */
9356//MARK: swipe_right9451//MARK: swipe_right
9357export function swipe_right(_event, { source, repeated } = {}) {9452export function swipe_right(_event = null, { source, repeated } = {}) {
9358 if (chat.length - 1 === Number(this_edit_mes_id)) {9453 if (chat.length - 1 === Number(this_edit_mes_id)) {
9359 closeMessageEditor();9454 closeMessageEditor();
9360 }9455 }
@@ -9366,6 +9461,7 @@ export function swipe_right(_event, { source, repeated } = {}) {
9366 // Make sure ad-hoc changes to extras are saved before swiping away9461 // Make sure ad-hoc changes to extras are saved before swiping away
9367 syncMesToSwipe();9462 syncMesToSwipe();
93689463
9464 const isPristine = !chat_metadata?.tainted;
9369 const swipe_duration = 200;9465 const swipe_duration = 200;
9370 const swipe_range = 700;9466 const swipe_range = 700;
9371 //console.log(swipe_range);9467 //console.log(swipe_range);
@@ -9384,14 +9480,16 @@ export function swipe_right(_event, { source, repeated } = {}) {
9384 };9480 };
9385 //assign swipe info array with last message from chat9481 //assign swipe info array with last message from chat
9386 }9482 }
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 around9483 // 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) {
9388 chat[0]['swipe_id'] = 0;9485 chat[0]['swipe_id'] = 0;
9389 } else {9486 } else {
9390 // If the user is holding down the key and we're at the last swipe, don't do anything9487 // If the user is holding down the key and we're at the last swipe, don't do anything
9391 if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === chat[chat.length - 1].swipes.length - 1) {9488 if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === chat[chat.length - 1].swipes.length - 1) {
9392 return;9489 return;
9393 }9490 }
9394 chat[chat.length - 1]['swipe_id']++; // make new slot in array9491 // make new slot in array
9492 chat[chat.length - 1]['swipe_id']++;
9395 }9493 }
9396 if (chat[chat.length - 1].extra) {9494 if (chat[chat.length - 1].extra) {
9397 // if message has memory attached - remove it to allow regen9495 // if message has memory attached - remove it to allow regen
@@ -9406,8 +9504,8 @@ export function swipe_right(_event, { source, repeated } = {}) {
9406 if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {9504 if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
9407 chat[chat.length - 1]['swipe_info'] = [];9505 chat[chat.length - 1]['swipe_info'] = [];
9408 }9506 }
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
9410 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 greeting9508 if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length && (chat.length !== 1 || !isPristine)) {
9411 delete chat[chat.length - 1].gen_started;9509 delete chat[chat.length - 1].gen_started;
9412 delete chat[chat.length - 1].gen_finished;9510 delete chat[chat.length - 1].gen_finished;
9413 run_generate = true;9511 run_generate = true;
@@ -9985,16 +10083,21 @@ async function doRenameChat(_, chatName) {
9985}10083}
998610084
9987/**10085/**
9988 * Renames the currently selected chat.10086 * Renames a group or character chat.
9989 * @param {string} oldFileName Old name of the chat (no JSONL extension)10087 * @param {object} param Parameters for renaming chat
9990 * @param {string} newName New name for the chat (no JSONL extension)10088 * @param {string} [param.characterId] Character ID to rename chat 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
9991 */10093 */
9992export async function renameChat(oldFileName, newName) {10094export async function renameGroupOrCharacterChat({ characterId, groupId, oldFileName, newFileName, loader }) {
10095 const currentChatId = getCurrentChatId();
9993 const body = {10096 const body = {
9994 is_group: !!selected_group,10097 is_group: !!groupId,
9995 avatar_url: characters[this_chid]?.avatar,10098 avatar_url: characters[characterId]?.avatar,
9996 original_file: `${oldFileName}.jsonl`,10099 original_file: `${oldFileName}.jsonl`,
9997 renamed_file: `${newName.trim()}.jsonl`,10100 renamed_file: `${newFileName.trim()}.jsonl`,
9998 };10101 };
999910102
10000 if (body.original_file === body.renamed_file) {10103 if (body.original_file === body.renamed_file) {
@@ -10007,7 +10110,8 @@ export async function renameChat(oldFileName, newName) {
10007 }10110 }
1000810111
10009 try {10112 try {
10010 showLoader();10113 loader && showLoader();
10114
10011 const response = await fetch('/api/chats/rename', {10115 const response = await fetch('/api/chats/rename', {
10012 method: 'POST',10116 method: 'POST',
10013 body: JSON.stringify(body),10117 body: JSON.stringify(body),
@@ -10025,27 +10129,69 @@ export async function renameChat(oldFileName, newName) {
10025 }10129 }
1002610130
10027 if (data.sanitizedFileName) {10131 if (data.sanitizedFileName) {
10028 newName = data.sanitizedFileName;10132 newFileName = data.sanitizedFileName;
10029 }10133 }
1003010134
10031 if (selected_group) {10135 if (groupId) {
10032 await renameGroupChat(selected_group, oldFileName, newName);10136 await renameGroupChat(groupId, oldFileName, newFileName);
10033 }10137 }
10034 else {10138 else if (characterId !== undefined && String(characterId) === String(this_chid) && characters[characterId]?.chat === oldFileName) {
10035 if (characters[this_chid].chat == oldFileName) {10139 characters[characterId].chat = 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 }
10040 }10142 }
1004110143
10042 await reloadCurrentChat();10144 if (currentChatId) {
10145 await reloadCurrentChat();
10146 }
10043 } catch {10147 } catch {
10044 hideLoader();10148 loader && hideLoader();
10045 await delay(500);10149 await delay(500);
10046 await callPopup('An error has occurred. Chat was not renamed.', 'text');10150 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);
10047 } finally {10151 } finally {
10048 hideLoader();10152 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 */
10161export 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 */
10177export 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);
10049 }10195 }
10050}10196}
1005110197
@@ -10056,9 +10202,6 @@ async function doGetChatName() {
10056 return getCurrentChatDetails().sessionName;10202 return getCurrentChatDetails().sessionName;
10057}10203}
1005810204
10059const isPwaMode = window.navigator.standalone;
10060if (isPwaMode) { $('body').addClass('PWA'); }
10061
10062function doCharListDisplaySwitch() {10205function doCharListDisplaySwitch() {
10063 power_user.charListGrid = !power_user.charListGrid;10206 power_user.charListGrid = !power_user.charListGrid;
10064 document.body.classList.toggle('charListGrid', power_user.charListGrid);10207 document.body.classList.toggle('charListGrid', power_user.charListGrid);
@@ -10203,86 +10346,43 @@ function doDrawerOpenClick() {
10203/**10346/**
10204 * Event handler to open or close a navbar drawer when a navbar icon is clicked.10347 * Event handler to open or close a navbar drawer when a navbar icon is clicked.
10205 * Handles click events on .drawer-toggle elements.10348 * Handles click events on .drawer-toggle elements.
10206 * @returns {void}10349 * @returns {Promise<void>}
10207 */10350 */
10208function doNavbarIconClick() {10351export async function doNavbarIconClick() {
10209 const icon = $(this).find('.drawer-icon');10352 const icon = $(this).find('.drawer-icon');
10210 const drawer = $(this).parent().find('.drawer-content');10353 const drawer = $(this).parent().find('.drawer-content');
10211 if (drawer.hasClass('resizing')) { return; }
10212 const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');10354 const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
10213 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');10355 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
10214 const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');10356
1021510357 if (!drawerWasOpenAlready) {
10216 if (!drawerWasOpenAlready) { //to open the drawer10358 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 }
10227 icon.toggleClass('openIcon closedIcon');10369 icon.toggleClass('openIcon closedIcon');
10228 drawer.toggleClass('openDrawer closedDrawer');10370 drawer.toggleClass('openDrawer closedDrawer');
1022910371
10230 //console.log(targetDrawerID);
10231 if (targetDrawerID === 'right-nav-panel') {10372 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 });
10252 }10375 }
1025310376
10254 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height10377 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height
10255 if (!CSS.supports('field-sizing', 'content')) {10378 if (!CSS.supports('field-sizing', 'content')) {
10256 $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {10379 const textareas = $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight');
10257 await resetScrollHeight($(this));10380 for (const textarea of textareas) {
10258 return;10381 await resetScrollHeight($(textarea));
10259 });10382 }
10260 }10383 }
1026110384 } else if (drawerWasOpenAlready) {
10262 } else if (drawerWasOpenAlready) { //to close manually
10263 icon.toggleClass('closedIcon openIcon');10385 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
10286 drawer.toggleClass('closedDrawer openDrawer');10386 drawer.toggleClass('closedDrawer openDrawer');
10287 }10387 }
10288}10388}
@@ -10686,23 +10786,23 @@ jQuery(async function () {
1068610786
10687 //menu buttons setup10787 //menu buttons setup
1068810788
10689 $('#rm_button_settings').click(function () {10789 $('#rm_button_settings').on('click', function () {
10690 selected_button = 'settings';10790 selected_button = 'settings';
10691 selectRightMenuWithAnimation('rm_api_block');10791 selectRightMenuWithAnimation('rm_api_block');
10692 });10792 });
10693 $('#rm_button_characters').click(function () {10793 $('#rm_button_characters').on('click', function () {
10694 selected_button = 'characters';10794 selected_button = 'characters';
10695 select_rm_characters();10795 select_rm_characters();
10696 });10796 });
10697 $('#rm_button_back').click(function () {10797 $('#rm_button_back').on('click', function () {
10698 selected_button = 'characters';10798 selected_button = 'characters';
10699 select_rm_characters();10799 select_rm_characters();
10700 });10800 });
10701 $('#rm_button_create').click(function () {10801 $('#rm_button_create').on('click', function () {
10702 selected_button = 'create';10802 selected_button = 'create';
10703 select_rm_create();10803 select_rm_create();
10704 });10804 });
10705 $('#rm_button_selected_ch').click(function () {10805 $('#rm_button_selected_ch').on('click', function () {
10706 if (selected_group) {10806 if (selected_group) {
10707 select_group_chats(selected_group);10807 select_group_chats(selected_group);
10708 } else {10808 } else {
@@ -10797,7 +10897,7 @@ jQuery(async function () {
10797 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');10897 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
10798 });10898 });
1079910899
10800 $('#advanced_div').click(function () {10900 $('#advanced_div').on('click', function () {
10801 if (!is_advanced_char_open) {10901 if (!is_advanced_char_open) {
10802 is_advanced_char_open = true;10902 is_advanced_char_open = true;
10803 $('#character_popup').css({ 'display': 'flex', 'opacity': 0.0 }).addClass('open');10903 $('#character_popup').css({ 'display': 'flex', 'opacity': 0.0 }).addClass('open');
@@ -10812,7 +10912,7 @@ jQuery(async function () {
10812 }10912 }
10813 });10913 });
1081410914
10815 $('#character_cross').click(function () {10915 $('#character_cross').on('click', function () {
10816 is_advanced_char_open = false;10916 is_advanced_char_open = false;
10817 $('#character_popup').transition({10917 $('#character_popup').transition({
10818 opacity: 0,10918 opacity: 0,
@@ -10822,12 +10922,12 @@ jQuery(async function () {
10822 setTimeout(function () { $('#character_popup').css('display', 'none'); }, animation_duration);10922 setTimeout(function () { $('#character_popup').css('display', 'none'); }, animation_duration);
10823 });10923 });
1082410924
10825 $('#character_popup_ok').click(function () {10925 $('#character_popup_ok').on('click', function () {
10826 is_advanced_char_open = false;10926 is_advanced_char_open = false;
10827 $('#character_popup').css('display', 'none');10927 $('#character_popup').css('display', 'none');
10828 });10928 });
1082910929
10830 $('#dialogue_popup_ok').click(async function (e, customData) {10930 $('#dialogue_popup_ok').on('click', async function (_e, customData) {
10831 const fromSlashCommand = customData?.fromSlashCommand || false;10931 const fromSlashCommand = customData?.fromSlashCommand || false;
10832 dialogueCloseStop = false;10932 dialogueCloseStop = false;
10833 $('#shadow_popup').transition({10933 $('#shadow_popup').transition({
@@ -10857,7 +10957,7 @@ jQuery(async function () {
10857 hideLoader();10957 hideLoader();
10858 } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat).10958 } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat).
10859 setTimeout(function () {10959 setTimeout(function () {
10860 $('#option_select_chat').click();10960 $('#option_select_chat').trigger('click');
10861 $('#options').hide(); // hide option popup menu10961 $('#options').hide(); // hide option popup menu
10862 hideLoader();10962 hideLoader();
10863 }, 2000);10963 }, 2000);
@@ -10868,18 +10968,16 @@ jQuery(async function () {
10868 if (popup_type == 'input') {10968 if (popup_type == 'input') {
10869 dialogueResolve($('#dialogue_popup_input').val());10969 dialogueResolve($('#dialogue_popup_input').val());
10870 $('#dialogue_popup_input').val('');10970 $('#dialogue_popup_input').val('');
10871
10872 }10971 }
10873 else {10972 else {
10874 dialogueResolve(true);10973 dialogueResolve(true);
10875
10876 }10974 }
1087710975
10878 dialogueResolve = null;10976 dialogueResolve = null;
10879 }10977 }
10880 });10978 });
1088110979
10882 $('#dialogue_popup_cancel').click(function (e) {10980 $('#dialogue_popup_cancel').on('click', function (e) {
10883 dialogueCloseStop = false;10981 dialogueCloseStop = false;
10884 $('#shadow_popup').transition({10982 $('#shadow_popup').transition({
10885 opacity: 0,10983 opacity: 0,
@@ -10892,21 +10990,20 @@ jQuery(async function () {
10892 $('#dialogue_popup').removeClass('large_dialogue_popup');10990 $('#dialogue_popup').removeClass('large_dialogue_popup');
10893 }, animation_duration);10991 }, animation_duration);
1089410992
10895 //$("#shadow_popup").css("opacity:", 0.0);
10896 popup_type = '';10993 popup_type = '';
1089710994
10898 if (dialogueResolve) {10995 if (dialogueResolve) {
10899 dialogueResolve(false);10996 dialogueResolve(false);
10900 dialogueResolve = null;10997 dialogueResolve = null;
10901 }10998 }
10902
10903 });10999 });
1090411000
10905 $('#add_avatar_button').change(function () {11001 $('#add_avatar_button').on('change', function () {
10906 read_avatar_load(this);11002 const inputElement = /** @type {HTMLInputElement} */ (this);
11003 read_avatar_load(inputElement);
10907 });11004 });
1090811005
10909 $('#form_create').submit(createOrEditCharacter);11006 $('#form_create').on('submit', (e) => createOrEditCharacter(e.originalEvent));
1091011007
10911 $('#delete_button').on('click', async function () {11008 $('#delete_button').on('click', async function () {
10912 if (this_chid === undefined || !characters[this_chid]) {11009 if (this_chid === undefined || !characters[this_chid]) {
@@ -10917,7 +11014,7 @@ jQuery(async function () {
10917 let deleteChats = false;11014 let deleteChats = false;
1091811015
10919 const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), {11016 const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), {
10920 onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),11017 onClose: () => { deleteChats = !!$('#del_char_checkbox').prop('checked'); },
10921 });11018 });
10922 if (!confirm) {11019 if (!confirm) {
10923 return;11020 return;
@@ -10977,9 +11074,9 @@ jQuery(async function () {
10977 const oldFileName = oldFileNameFull.replace('.jsonl', '');11074 const oldFileName = oldFileNameFull.replace('.jsonl', '');
1097811075
10979 const popupText = await renderTemplateAsync('chatRename');11076 const popupText = await renderTemplateAsync('chatRename');
10980 const newName = await callPopup(popupText, 'input', oldFileName);11077 const newName = await callGenericPopup(popupText, POPUP_TYPE.INPUT, oldFileName);
1098111078
10982 if (!newName || newName == oldFileName) {11079 if (!newName || typeof newName !== 'string' || newName == oldFileName) {
10983 console.log('no new name found, aborting');11080 console.log('no new name found, aborting');
10984 return;11081 return;
10985 }11082 }
@@ -11038,7 +11135,7 @@ jQuery(async function () {
1103811135
11039 ///////////////////////////////////////////////////////////////////////////////////11136 ///////////////////////////////////////////////////////////////////////////////////
1104011137
11041 $('#api_button').click(function (e) {11138 $('#api_button').on('click', function (e) {
11042 if ($('#api_url_text').val() != '') {11139 if ($('#api_url_text').val() != '') {
11043 let value = formatKoboldUrl(String($('#api_url_text').val()).trim());11140 let value = formatKoboldUrl(String($('#api_url_text').val()).trim());
1104411141
@@ -11179,7 +11276,7 @@ jQuery(async function () {
11179 if ((selected_group || this_chid !== undefined) && !is_send_press) {11276 if ((selected_group || this_chid !== undefined) && !is_send_press) {
11180 let deleteCurrentChat = false;11277 let deleteCurrentChat = false;
11181 const result = await Popup.show.confirm(t`Start new chat?`, await renderTemplateAsync('newChatConfirm'), {11278 const result = await Popup.show.confirm(t`Start new chat?`, await renderTemplateAsync('newChatConfirm'), {
11182 onClose: () => deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'),11279 onClose: () => { deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'); },
11183 });11280 });
11184 if (!result) {11281 if (!result) {
11185 return;11282 return;
@@ -11283,7 +11380,7 @@ jQuery(async function () {
11283 //////////////////////////////////////////////////////////////////////////////////////////////11380 //////////////////////////////////////////////////////////////////////////////////////////////
1128411381
11285 //functionality for the cancel delete messages button, reverts to normal display of input form11382 //functionality for the cancel delete messages button, reverts to normal display of input form
11286 $('#dialogue_del_mes_cancel').click(function () {11383 $('#dialogue_del_mes_cancel').on('click', function () {
11287 $('#dialogue_del_mes').css('display', 'none');11384 $('#dialogue_del_mes').css('display', 'none');
11288 $('#send_form').css('display', css_send_form_display);11385 $('#send_form').css('display', css_send_form_display);
11289 $('.del_checkbox').each(function () {11386 $('.del_checkbox').each(function () {
@@ -11423,7 +11520,7 @@ jQuery(async function () {
1142311520
11424 //////////////////////////////////////////////////////////////11521 //////////////////////////////////////////////////////////////
1142511522
11426 $('#select_chat_cross').click(function () {11523 $('#select_chat_cross').on('click', function () {
11427 $('#shadow_select_chat_popup').transition({11524 $('#shadow_select_chat_popup').transition({
11428 opacity: 0,11525 opacity: 0,
11429 duration: animation_duration,11526 duration: animation_duration,
@@ -11517,8 +11614,10 @@ jQuery(async function () {
11517 edit_textarea.height(0);11614 edit_textarea.height(0);
11518 edit_textarea.height(edit_textarea[0].scrollHeight);11615 edit_textarea.height(edit_textarea[0].scrollHeight);
11519 }11616 }
11520 edit_textarea.focus();11617 edit_textarea.trigger('focus');
11521 edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text11618 const textAreaElement = /** @type {HTMLTextAreaElement} */ (edit_textarea[0]);
11619 // Sets the cursor at the end of the text
11620 textAreaElement.setSelectionRange(
11522 String(edit_textarea.val()).length,11621 String(edit_textarea.val()).length,
11523 String(edit_textarea.val()).length,11622 String(edit_textarea.val()).length,
11524 );11623 );
@@ -11770,8 +11869,8 @@ jQuery(async function () {
11770 //Select chat11869 //Select chat
1177111870
11772 //**************************CHARACTER IMPORT EXPORT*************************//11871 //**************************CHARACTER IMPORT EXPORT*************************//
11773 $('#character_import_button').click(function () {11872 $('#character_import_button').on('click', function () {
11774 $('#character_import_file').click();11873 $('#character_import_file').trigger('click');
11775 });11874 });
1177611875
11777 $('#character_import_file').on('change', async function (e) {11876 $('#character_import_file').on('change', async function (e) {
@@ -11839,12 +11938,16 @@ jQuery(async function () {
11839 }11938 }
11840 });11939 });
11841 //**************************CHAT IMPORT EXPORT*************************//11940 //**************************CHAT IMPORT EXPORT*************************//
11842 $('#chat_import_button').click(function () {11941 $('#chat_import_button').on('click', function () {
11843 $('#chat_import_file').click();11942 $('#chat_import_file').trigger('click');
11844 });11943 });
1184511944
11846 $('#chat_import_file').on('change', async function (e) {11945 $('#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
11849 if (!file) {11952 if (!file) {
11850 return;11953 return;
@@ -11866,7 +11969,7 @@ jQuery(async function () {
11866 const format = ext[1].toLowerCase();11969 const format = ext[1].toLowerCase();
11867 $('#chat_import_file_type').val(format);11970 $('#chat_import_file_type').val(format);
1186811971
11869 const formData = new FormData($('#form_import_chat').get(0));11972 const formData = new FormData(/** @type {HTMLFormElement} */($('#form_import_chat').get(0)));
11870 formData.append('user_name', name1);11973 formData.append('user_name', name1);
11871 $('#select_chat_div').html('');11974 $('#select_chat_div').html('');
1187211975
@@ -11877,12 +11980,12 @@ jQuery(async function () {
11877 }11980 }
11878 });11981 });
1187911982
11880 $('#rm_button_group_chats').click(function () {11983 $('#rm_button_group_chats').on('click', function () {
11881 selected_button = 'group_chats';11984 selected_button = 'group_chats';
11882 select_group_chats();11985 select_group_chats();
11883 });11986 });
1188411987
11885 $('#rm_button_back_from_group').click(function () {11988 $('#rm_button_back_from_group').on('click', function () {
11886 selected_button = 'characters';11989 selected_button = 'characters';
11887 select_rm_characters();11990 select_rm_characters();
11888 });11991 });
@@ -11911,8 +12014,8 @@ jQuery(async function () {
1191112014
11912 $('.drawer-toggle').on('click', doNavbarIconClick);12015 $('.drawer-toggle').on('click', doNavbarIconClick);
1191312016
11914 $('html').on('touchstart mousedown', function (e) {12017 $('html').on('touchstart mousedown', async function (e) {
11915 var clickTarget = $(e.target);12018 const clickTarget = $(e.target);
1191612019
11917 if (isExportPopupOpen12020 if (isExportPopupOpen
11918 && clickTarget.closest('#export_button').length == 012021 && clickTarget.closest('#export_button').length == 0
@@ -11933,33 +12036,26 @@ jQuery(async function () {
11933 '#toast-container',12036 '#toast-container',
11934 '.select2-results',12037 '.select2-results',
11935 ];12038 ];
12039
11936 for (const id of forbiddenTargets) {12040 for (const id of forbiddenTargets) {
11937 if (clickTarget.closest(id).length > 0) {12041 if (clickTarget.closest(id).length > 0) {
11938 return;12042 return;
11939 }12043 }
11940 }12044 }
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 }
11958 }12054 }
11959 }12055 }
11960 });12056 });
1196112057
11962 $(document).on('click', '.inline-drawer-toggle', function (e) {12058 $(document).on('click', '.inline-drawer-toggle', async function (e) {
11963 if ($(e.target).hasClass('text_pole')) {12059 if ($(e.target).hasClass('text_pole')) {
11964 return;12060 return;
11965 }12061 }
@@ -11968,6 +12064,7 @@ jQuery(async function () {
11968 const drawerContent = drawer.find('>.inline-drawer-content');12064 const drawerContent = drawer.find('>.inline-drawer-content');
11969 icon.toggleClass('down up');12065 icon.toggleClass('down up');
11970 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');12066 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
12067 drawer.trigger('inline-drawer-toggle');
11971 drawerContent.stop().slideToggle({12068 drawerContent.stop().slideToggle({
11972 complete: () => {12069 complete: () => {
11973 $(this).css('height', '');12070 $(this).css('height', '');
@@ -11976,10 +12073,10 @@ jQuery(async function () {
1197612073
11977 // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height12074 // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
11978 if (!CSS.supports('field-sizing', 'content')) {12075 if (!CSS.supports('field-sizing', 'content')) {
11979 drawerContent.find('textarea.autoSetHeight').each(async function () {12076 const textareas = drawerContent.find('textarea.autoSetHeight');
11980 await resetScrollHeight($(this));12077 for (const textarea of textareas) {
11981 return;12078 await resetScrollHeight($(textarea));
11982 });12079 }
11983 }12080 }
11984 });12081 });
1198512082
@@ -12064,7 +12161,7 @@ jQuery(async function () {
12064 if (!(e.target instanceof HTMLElement)) return;12161 if (!(e.target instanceof HTMLElement)) return;
12065 if (e.target.matches('#OpenAllWIEntries')) {12162 if (e.target.matches('#OpenAllWIEntries')) {
12066 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {12163 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
12067 toggleDrawer(drawer, true);12164 delay(0).then(() => toggleDrawer(drawer, true));
12068 });12165 });
12069 } else if (e.target.matches('#CloseAllWIEntries')) {12166 } else if (e.target.matches('#CloseAllWIEntries')) {
12070 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {12167 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
@@ -12094,7 +12191,7 @@ jQuery(async function () {
12094 return;12191 return;
12095 }12192 }
12096 if (isEditVisible && power_user.auto_save_msg_edits === true) {12193 if (isEditVisible && power_user.auto_save_msg_edits === true) {
12097 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click();12194 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).trigger('click');
12098 closeMessageEditor('reasoning');12195 closeMessageEditor('reasoning');
12099 $('#send_textarea').focus();12196 $('#send_textarea').focus();
12100 return;12197 return;
@@ -12109,7 +12206,8 @@ jQuery(async function () {
12109 });12206 });
1211012207
12111 $('#char-management-dropdown').on('change', async (e) => {12208 $('#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');
12113 switch (target) {12211 switch (target) {
12114 case 'set_character_world':12212 case 'set_character_world':
12115 openCharacterWorldPopup();12213 openCharacterWorldPopup();
@@ -12257,9 +12355,7 @@ jQuery(async function () {
1225712355
12258 $(document).on('click', '.external_import_button, #external_import_button', async () => {12356 $(document).on('click', '.external_import_button, #external_import_button', async () => {
12259 const html = await renderTemplateAsync('importCharacters');12357 const html = await renderTemplateAsync('importCharacters');
1226012358 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
12264 if (!input) {12360 if (!input) {
12265 console.debug('Custom content import cancelled');12361 console.debug('Custom content import cancelled');
@@ -12267,7 +12363,7 @@ jQuery(async function () {
12267 }12363 }
1226812364
12269 // break input into one input per line12365 // break input into one input per line
12270 const inputs = input.split('\n').map(x => x.trim()).filter(x => x.length > 0);12366 const inputs = String(input).split('\n').map(x => x.trim()).filter(x => x.length > 0);
1227112367
12272 for (const url of inputs) {12368 for (const url of inputs) {
12273 let request;12369 let request;
public/scripts/BulkEditOverlay.js+6 -5
@@ -2,7 +2,6 @@
22
3import {3import {
4 characterGroupOverlay,4 characterGroupOverlay,
5 callPopup,
6 characters,5 characters,
7 event_types,6 event_types,
8 eventSource,7 eventSource,
@@ -17,6 +16,7 @@ import {
17import { favsToHotswap } from './RossAscends-mods.js';16import { favsToHotswap } from './RossAscends-mods.js';
18import { hideLoader, showLoader } from './loader.js';17import { hideLoader, showLoader } from './loader.js';
19import { convertCharacterToPersona } from './personas.js';18import { convertCharacterToPersona } from './personas.js';
19import { callGenericPopup, POPUP_TYPE } from './popup.js';
20import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';20import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
2121
22/**22/**
@@ -835,12 +835,13 @@ class BulkEditOverlay {
835 */835 */
836 handleContextMenuDelete = () => {836 handleContextMenuDelete = () => {
837 const characterIds = this.selectedCharacters;837 const characterIds = this.selectedCharacters;
838 const popupContent = BulkEditOverlay.#getDeletePopupContentHtml(characterIds);838 const popupContent = $(BulkEditOverlay.#getDeletePopupContentHtml(characterIds));
839 const promise = callPopup(popupContent, null)839 const checkbox = popupContent.find('#del_char_checkbox');
840 const promise = callGenericPopup(popupContent, POPUP_TYPE.CONFIRM)
840 .then((accept) => {841 .then((accept) => {
841 if (true !== accept) return;842 if (!accept) return;
842843
843 const deleteChats = document.getElementById('del_char_checkbox').checked ?? false;844 const deleteChats = checkbox.prop('checked') ?? false;
844845
845 showLoader();846 showLoader();
846 const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it');847 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 @@
1import { DOMPurify, Bowser, slideToggle } from '../lib.js';1import { DOMPurify, Bowser } from '../lib.js';
22
3import {3import {
4 characters,4 characters,
@@ -19,7 +19,7 @@ import {
19 menu_type,19 menu_type,
20 substituteParams,20 substituteParams,
21 sendTextareaMessage,21 sendTextareaMessage,
22 getSlideToggleOptions,22 doNavbarIconClick,
23} from '../script.js';23} from '../script.js';
2424
25import {25import {
@@ -402,7 +402,8 @@ function RA_autoconnect(PrevApi) {
402 || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)402 || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
403 || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)403 || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
404 || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)404 || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)
405 || (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI)405 || (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')
406 || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)407 || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
407 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)408 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
408 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)409 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
@@ -411,6 +412,7 @@ function RA_autoconnect(PrevApi) {
411 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)412 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
412 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)413 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
413 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)414 || (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)
414 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)416 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
415 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)417 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
416 ) {418 ) {
@@ -472,54 +474,72 @@ const saveUserInputDebounced = debounce(saveUserInput);
472474
473// Make the DIV element draggable:475// Make the DIV element draggable:
474476
475export 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 */
481export function dragElement($elmnt) {
482 let actionType = null; // "drag" or "resize"
483 let isMouseDown = false;
478484
479 var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;485 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
480 var height, width, top, left, right, bottom,486 let height, width, top, left, right, bottom,
481 maxX, maxY, winHeight, winWidth,487 maxX, maxY, winHeight, winWidth,
482 topbar, topBarFirstX, topBarLastY;488 topbar, topBarFirstX, topBarLastY;
483489
484 var elmntName = elmnt.attr('id');490 const elmntName = $elmnt.attr('id');
485 console.debug(`dragElement called for ${elmntName}`);
486 const elmntNameEscaped = $.escapeSelector(elmntName);491 const elmntNameEscaped = $.escapeSelector(elmntName);
487 const elmntHeader = $(`#${elmntNameEscaped}header`);492 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 repositioning495 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 resize501 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();
500 }508 }
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)
502 const observer = new MutationObserver((mutations) => {519 const observer = new MutationObserver((mutations) => {
503 const target = mutations[0].target;520 const $target = $(mutations[0].target);
504 if (!$(target).is(':visible') //abort if element is invisible521 if (
505 || $(target).hasClass('resizing') //being auto-resized by other JS code522 !$target.is(':visible') ||
506 || Number((String(target.height).replace('px', ''))) < 50 //too short523 $target.hasClass('resizing') ||
507 || Number((String(target.width).replace('px', ''))) < 50 //too narrow524 $target.height() < 50 ||
508 || power_user.movingUI === false // if MUI is not turned on525 $target.width() < 50 ||
509 || isMobile() // if it's a mobile screen526 power_user.movingUI === false ||
527 isMobile() ||
528 !isMouseDown
510 ) {529 ) {
530 observer.disconnect();
511 return;531 return;
512 }532 }
513533
514 const style = getComputedStyle(target);534 const style = getComputedStyle($target[0]);
515 height = parseInt(style.height);535 height = parseInt(style.height);
516 width = parseInt(style.width);536 width = parseInt(style.width);
517 top = parseInt(style.top);537 top = parseInt(style.top);
518 left = parseInt(style.left);538 left = parseInt(style.left);
519 right = parseInt(style.right);539 right = parseInt(style.right);
520 bottom = parseInt(style.bottom);540 bottom = parseInt(style.bottom);
521 maxX = parseInt(width + left);541 maxX = width + left;
522 maxY = parseInt(height + top);542 maxY = height + top;
523 winWidth = window.innerWidth;543 winWidth = window.innerWidth;
524 winHeight = window.innerHeight;544 winHeight = window.innerHeight;
525545
@@ -528,182 +548,119 @@ export function dragElement(elmnt) {
528 topBarFirstX = parseInt(topbarstyle.marginInline);548 topBarFirstX = parseInt(topbarstyle.marginInline);
529 topBarLastY = parseInt(topbarstyle.height);549 topBarLastY = parseInt(topbarstyle.height);
530550
531 //prepare an empty poweruser object for the item being altered if we don't have one already551 // Prepare state object if missing
532 if (!power_user.movingUIState[elmntName]) {552 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 resizing554 if (actionType === 'resize') {
538 if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header)
539 let imgHeight, imgWidth, imageAspectRatio;
540 let containerAspectRatio = height / width;555 let containerAspectRatio = height / width;
541556 if ($elmnt.attr('id').startsWith('zoomFor_')) {
542 //force aspect ratio for zoomed avatars557 const zoomedAvatarImage = $elmnt.find('.zoomed_avatar_img');
543 if ($(elmnt).attr('id').startsWith('zoomFor_')) {558 const imgHeight = zoomedAvatarImage.height();
544 let zoomedAvatarImage = $(elmnt).find('.zoomed_avatar_img');559 const imgWidth = zoomedAvatarImage.width();
545 imgHeight = zoomedAvatarImage.height();560 const imageAspectRatio = imgHeight / imgWidth;
546 imgWidth = zoomedAvatarImage.width();
547 imageAspectRatio = imgHeight / imgWidth;
548
549 // Maintain aspect ratio
550 if (containerAspectRatio !== imageAspectRatio) {561 if (containerAspectRatio !== imageAspectRatio) {
551 elmnt.css('width', elmnt.width());562 $elmnt.css('width', $elmnt.width());
552 elmnt.css('height', elmnt.width() * imageAspectRatio);563 $elmnt.css('height', $elmnt.width() * imageAspectRatio);
553 }564 }
554565 if (top + $elmnt.height() >= winHeight) {
555 // Prevent resizing offscreen566 $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');
559 }568 }
560569 if (left + $elmnt.width() >= winWidth) {
561 if (left + elmnt.width() >= winWidth) {570 $elmnt.css('width', winWidth - left - 1 + 'px');
562 elmnt.css('width', winWidth - left - 1 + 'px');571 $elmnt.css('height', (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');
573 }572 }
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');
574 }576 }
575
576 //prevent resizing from top left into the top bar
577 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {577 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {
578 elmnt.css('width', width - 1 + 'px');578 $elmnt.css('width', width - 1 + 'px');
579 }579 }
580580 $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() &&
584584 power_user.movingUIState[elmntName].height === $elmnt.height()
585 //set a listener for mouseup to save new width/height585 ) 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');
606 });588 });
589 } else if (actionType === 'drag') {
590 clampToViewport();
607 }591 }
608592
609 //only record position changes if header is being dragged593 // 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 }
640 });595 });
641596
597 // Mouse event handlers
642 function dragMouseDown(e) {598 function dragMouseDown(e) {
643
644 if (e) {599 if (e) {
645 isHeaderBeingDragged = true;600 actionType = 'drag';
601 isMouseDown = true;
646 e.preventDefault();602 e.preventDefault();
647 pos3 = e.clientX; //mouse X at click603 pos3 = e.clientX;
648 pos4 = e.clientY; //mouse Y at click604 pos4 = e.clientY;
649 }605 }
650 $(document).on('mouseup', closeDragElement);606 $(document).on('mouseup', closeDragElement);
651 $(document).on('mousemove', elementDrag);607 $(document).on('mousemove', elementDrag);
652 }608 }
653609
654 function elementDrag(e) {610 function elementDrag(e) {
655 if (!power_user.movingUIState[elmntName]) {611 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
656 power_user.movingUIState[elmntName] = {};
657 }
658
659 e = e || window.event;
660 e.preventDefault();612 e.preventDefault();
661613 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;
664 pos3 = e.clientX; //new mouse X616 pos4 = e.clientY;
665 pos4 = e.clientY; //new mouse Y617 $elmnt.attr('data-dragged', 'true');
666618 $elmnt.css('left', ($elmnt.offset().left - pos1) + 'px');
667 elmnt.attr('data-dragged', 'true');619 $elmnt.css('top', ($elmnt.offset().top - pos2) + 'px');
668620 $elmnt.css('margin', 'unset');
669 //first set css to computed values to avoid CSS NaN results from 'auto', etc621 $elmnt.css('height', height);
670 elmnt.css('left', (elmnt.offset().left) + 'px');622 $elmnt.css('width', 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;
686 }623 }
687624
688 function closeDragElement() {625 function closeDragElement() {
689 console.debug('drag finished');
690 isHeaderBeingDragged = false;
691 isMouseDown = false;626 isMouseDown = false;
627 actionType = null;
692 $(document).off('mouseup', closeDragElement);628 $(document).off('mouseup', closeDragElement);
693 $(document).off('mousemove', elementDrag);629 $(document).off('mousemove', elementDrag);
694 $('body').css('overflow', '');630 $elmnt.attr('data-dragged', 'false');
695 // Clear the "data-dragged" attribute
696 elmnt.attr('data-dragged', 'false');
697 observer.disconnect();631 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')) {
705 maxY = null;639 actionType = 'drag';
640 isMouseDown = true;
641 observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
642 dragMouseDown(e);
643 }
644 });
706 }645 }
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 });
707}664}
708665
709export async function initMovingUI() {666export async function initMovingUI() {
@@ -774,9 +731,8 @@ export function initRossMods() {
774 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');731 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
775732
776 if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {733 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');
780 }736 }
781 }737 }
782 });738 });
@@ -792,14 +748,13 @@ export function initRossMods() {
792 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');748 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
793749
794 if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {750 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');
798 }753 }
799 }754 }
800 });755 });
801756
802 $(WIPanelPin).on('click', function () {757 $(WIPanelPin).on('click', async function () {
803 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));758 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
804 if ($(WIPanelPin).prop('checked') == true) {759 if ($(WIPanelPin).prop('checked') == true) {
805 console.debug('adding pin class to WI');760 console.debug('adding pin class to WI');
@@ -812,9 +767,8 @@ export function initRossMods() {
812767
813 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {768 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
814 console.debug('closing WI after lock removal');769 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');
818 }772 }
819 }773 }
820 });774 });
public/scripts/authors-note.js+17 -14
@@ -1,9 +1,11 @@
1import {1import {
2 MAX_INJECTION_DEPTH,
2 animation_duration,3 animation_duration,
3 chat_metadata,4 chat_metadata,
4 eventSource,5 eventSource,
5 event_types,6 event_types,
6 extension_prompt_roles,7 extension_prompt_roles,
8 extension_prompt_types,
7 saveSettingsDebounced,9 saveSettingsDebounced,
8 this_chid,10 this_chid,
9} from '../script.js';11} from '../script.js';
@@ -347,7 +349,7 @@ export function setFloatingPrompt() {
347 }349 }
348350
349 if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {351 if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
350 context.setExtensionPrompt(MODULE_NAME, '');352 context.setExtensionPrompt(MODULE_NAME, '', extension_prompt_types.NONE, MAX_INJECTION_DEPTH);
351 $('#extension_floating_counter').text('(disabled)');353 $('#extension_floating_counter').text('(disabled)');
352 shouldWIAddPrompt = false;354 shouldWIAddPrompt = false;
353 return;355 return;
@@ -380,7 +382,7 @@ export function setFloatingPrompt() {
380 }382 }
381 context.setExtensionPrompt(383 context.setExtensionPrompt(
382 MODULE_NAME,384 MODULE_NAME,
383 prompt,385 String(prompt),
384 chat_metadata[metadata_keys.position],386 chat_metadata[metadata_keys.position],
385 chat_metadata[metadata_keys.depth],387 chat_metadata[metadata_keys.depth],
386 extension_settings.note.allowWIScan,388 extension_settings.note.allowWIScan,
@@ -396,37 +398,38 @@ function onANMenuItemClick() {
396 }398 }
397399
398 //show AN if it's hidden400 //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') {
401 $('#floatingPrompt').css('display', 'flex');403 $ANcontainer.addClass('resizing');
402 $('#floatingPrompt').css('opacity', 0.0);404 $ANcontainer.css('display', 'flex');
403 $('#floatingPrompt').transition({405 $ANcontainer.css('opacity', 0.0);
406 $ANcontainer.transition({
404 opacity: 1.0,407 opacity: 1.0,
405 duration: animation_duration,408 duration: animation_duration,
406 }, async function () {409 }, async function () {
407 await delay(50);410 await delay(50);
408 $('#floatingPrompt').removeClass('resizing');411 $ANcontainer.removeClass('resizing');
409 });412 });
410413
411 //auto-open the main AN inline drawer414 //auto-open the main AN inline drawer
412 if ($('#ANBlockToggle')415 if ($('#ANBlockToggle')
413 .siblings('.inline-drawer-content')416 .siblings('.inline-drawer-content')
414 .css('display') !== 'block') {417 .css('display') !== 'block') {
415 $('#floatingPrompt').addClass('resizing');418 $ANcontainer.addClass('resizing');
416 $('#ANBlockToggle').click();419 $('#ANBlockToggle').trigger('click');
417 }420 }
418 } else {421 } else {
419 //hide AN if it's already displayed422 //hide AN if it's already displayed
420 $('#floatingPrompt').addClass('resizing');423 $ANcontainer.addClass('resizing');
421 $('#floatingPrompt').transition({424 $ANcontainer.transition({
422 opacity: 0.0,425 opacity: 0.0,
423 duration: animation_duration,426 duration: animation_duration,
424 }, async function () {427 }, async function () {
425 await delay(50);428 await delay(50);
426 $('#floatingPrompt').removeClass('resizing');429 $ANcontainer.removeClass('resizing');
427 });430 });
428 setTimeout(function () {431 setTimeout(function () {
429 $('#floatingPrompt').hide();432 $ANcontainer.hide();
430 }, animation_duration);433 }, animation_duration);
431 }434 }
432435
public/scripts/autocomplete/AutoComplete.js+36 -30
@@ -21,6 +21,14 @@ export const AUTOCOMPLETE_SELECT_KEY = {
21 'ENTER': 2, // 2^121 'ENTER': 2, // 2^1
22};22};
2323
24/** @readonly */
25/** @enum {Number} */
26export const AUTOCOMPLETE_STATE = {
27 DISABLED: 0,
28 MIN_LENGTH: 1,
29 ALWAYS: 2,
30};
31
24export class AutoComplete {32export class AutoComplete {
25 /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;33 /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;
26 /**@type {boolean}*/ isFloating = false;34 /**@type {boolean}*/ isFloating = false;
@@ -109,20 +117,20 @@ export class AutoComplete {
109 this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);117 this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
110 this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);118 this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
111119
112 textarea.addEventListener('input', ()=>{120 textarea.addEventListener('input', () => {
113 this.selectionStart = this.textarea.selectionStart;121 this.selectionStart = this.textarea.selectionStart;
114 if (this.text != this.textarea.value) this.show(true, this.wasForced);122 if (this.text != this.textarea.value) this.show(true, this.wasForced);
115 });123 });
116 textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));124 textarea.addEventListener('keydown', (evt) => this.handleKeyDown(evt));
117 textarea.addEventListener('click', ()=>{125 textarea.addEventListener('click', () => {
118 this.selectionStart = this.textarea.selectionStart;126 this.selectionStart = this.textarea.selectionStart;
119 if (this.isActive) this.show();127 if (this.isActive) this.show();
120 });128 });
121 textarea.addEventListener('blur', ()=>this.hide());129 textarea.addEventListener('blur', () => this.hide());
122 if (isFloating) {130 if (isFloating) {
123 textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());131 textarea.addEventListener('scroll', () => this.updateFloatingPositionDebounced());
124 }132 }
125 window.addEventListener('resize', ()=>this.updatePositionDebounced());133 window.addEventListener('resize', () => this.updatePositionDebounced());
126 }134 }
127135
128 /**136 /**
@@ -132,9 +140,9 @@ export class AutoComplete {
132 makeItem(option) {140 makeItem(option) {
133 const li = option.renderItem();141 const li = option.renderItem();
134 // gotta listen to pointerdown (happens before textarea-blur)142 // gotta listen to pointerdown (happens before textarea-blur)
135 li.addEventListener('pointerdown', (evt)=>{143 li.addEventListener('pointerdown', (evt) => {
136 evt.preventDefault();144 evt.preventDefault();
137 this.selectedItem = this.result.find(it=>it.name == li.getAttribute('data-name'));145 this.selectedItem = this.result.find(it => it.name == li.getAttribute('data-name'));
138 this.select();146 this.select();
139 });147 });
140 return li;148 return li;
@@ -149,7 +157,7 @@ export class AutoComplete {
149 const chars = Array.from(item.dom.querySelector('.name').children);157 const chars = Array.from(item.dom.querySelector('.name').children);
150 switch (this.matchType) {158 switch (this.matchType) {
151 case 'strict': {159 case 'strict': {
152 chars.forEach((it, idx)=>{160 chars.forEach((it, idx) => {
153 if (idx + item.nameOffset < item.name.length) {161 if (idx + item.nameOffset < item.name.length) {
154 it.classList.add('matched');162 it.classList.add('matched');
155 } else {163 } else {
@@ -160,7 +168,7 @@ export class AutoComplete {
160 }168 }
161 case 'includes': {169 case 'includes': {
162 const start = item.name.toLowerCase().search(this.name);170 const start = item.name.toLowerCase().search(this.name);
163 chars.forEach((it, idx)=>{171 chars.forEach((it, idx) => {
164 if (idx + item.nameOffset < start) {172 if (idx + item.nameOffset < start) {
165 it.classList.remove('matched');173 it.classList.remove('matched');
166 } else if (idx + item.nameOffset < start + item.name.length) {174 } else if (idx + item.nameOffset < start + item.name.length) {
@@ -172,18 +180,18 @@ export class AutoComplete {
172 break;180 break;
173 }181 }
174 case 'fuzzy': {182 case 'fuzzy': {
175 item.name.replace(this.fuzzyRegex, (_, ...parts)=>{183 item.name.replace(this.fuzzyRegex, (_, ...parts) => {
176 parts.splice(-2, 2);184 parts.splice(-2, 2);
177 if (parts.length == 2) {185 if (parts.length == 2) {
178 chars.forEach(c=>c.classList.remove('matched'));186 chars.forEach(c => c.classList.remove('matched'));
179 } else {187 } else {
180 let cIdx = item.nameOffset;188 let cIdx = item.nameOffset;
181 parts.forEach((it, idx)=>{189 parts.forEach((it, idx) => {
182 if (it === null || it.length == 0) return '';190 if (it === null || it.length == 0) return '';
183 if (idx % 2 == 1) {191 if (idx % 2 == 1) {
184 chars.slice(cIdx, cIdx + it.length).forEach(c=>c.classList.add('matched'));192 chars.slice(cIdx, cIdx + it.length).forEach(c => c.classList.add('matched'));
185 } else {193 } else {
186 chars.slice(cIdx, cIdx + it.length).forEach(c=>c.classList.remove('matched'));194 chars.slice(cIdx, cIdx + it.length).forEach(c => c.classList.remove('matched'));
187 }195 }
188 cIdx += it.length;196 cIdx += it.length;
189 });197 });
@@ -230,7 +238,7 @@ export class AutoComplete {
230 if (current.length > 0) {238 if (current.length > 0) {
231 consecutive.push(current);239 consecutive.push(current);
232 }240 }
233 consecutive.sort((a,b)=>b.length - a.length);241 consecutive.sort((a, b) => b.length - a.length);
234 option.score = new AutoCompleteFuzzyScore(start, consecutive[0]?.length ?? 0);242 option.score = new AutoCompleteFuzzyScore(start, consecutive[0]?.length ?? 0);
235 return option;243 return option;
236 }244 }
@@ -254,8 +262,7 @@ export class AutoComplete {
254 + this.parserResult.name.length262 + this.parserResult.name.length
255 + (this.startQuote ? 1 : 0)263 + (this.startQuote ? 1 : 0)
256 + (this.endQuote ? 1 : 0)264 + (this.endQuote ? 1 : 0)
257 + 1265 + 1;
258 ;
259 }266 }
260267
261 /**268 /**
@@ -344,7 +351,7 @@ export class AutoComplete {
344351
345 if (this.matchType == 'fuzzy') {352 if (this.matchType == 'fuzzy') {
346 // only build the fuzzy regex if match type is set to fuzzy353 // only build the fuzzy regex if match type is set to fuzzy
347 this.fuzzyRegex = new RegExp(`^(.*?)${this.name.split('').map(char=>`(${escapeRegex(char)})`).join('(.*?)')}(.*?)$`, 'i');354 this.fuzzyRegex = new RegExp(`^(.*?)${this.name.split('').map(char => `(${escapeRegex(char)})`).join('(.*?)')}(.*?)$`, 'i');
348 }355 }
349356
350 //TODO maybe move the matchers somewhere else; a single match function? matchType is available as property357 //TODO maybe move the matchers somewhere else; a single match function? matchType is available as property
@@ -358,12 +365,12 @@ export class AutoComplete {
358 // filter the list of options by the partial name according to the matching type365 // filter the list of options by the partial name according to the matching type
359 .filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name)366 .filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name)
360 // remove aliases367 // remove aliases
361 .filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);368 .filter((it, idx, list) => list.findIndex(opt => opt.value == it.value) == idx);
362369
363 if (this.result.length == 0 && this.effectiveParserResult != this.parserResult && isForced) {370 if (this.result.length == 0 && this.effectiveParserResult != this.parserResult && isForced) {
364 // no matching secondary results and forced trigger -> show current command details371 // no matching secondary results and forced trigger -> show current command details
365 this.secondaryParserResult = null;372 this.secondaryParserResult = null;
366 this.result = [this.effectiveParserResult.optionList.find(it=>it.name == this.effectiveParserResult.name)];373 this.result = [this.effectiveParserResult.optionList.find(it => it.name == this.effectiveParserResult.name)];
367 this.name = this.effectiveParserResult.name;374 this.name = this.effectiveParserResult.name;
368 this.fuzzyRegex = /(.*)(.*)(.*)/;375 this.fuzzyRegex = /(.*)(.*)(.*)/;
369 }376 }
@@ -387,8 +394,7 @@ export class AutoComplete {
387 return option;394 return option;
388 })395 })
389 // sort by fuzzy score or alphabetical396 // sort by fuzzy score or alphabetical
390 .toSorted(this.matchType == 'fuzzy' ? this.fuzzyScoreCompare : (a, b) => a.name.localeCompare(b.name))397 .toSorted(this.matchType == 'fuzzy' ? this.fuzzyScoreCompare : (a, b) => a.name.localeCompare(b.name));
391 ;
392398
393399
394400
@@ -628,12 +634,12 @@ export class AutoComplete {
628 this.clone.style.position = 'fixed';634 this.clone.style.position = 'fixed';
629 this.clone.style.visibility = 'hidden';635 this.clone.style.visibility = 'hidden';
630 document.body.append(this.clone);636 document.body.append(this.clone);
631 const mo = new MutationObserver(muts=>{637 const mo = new MutationObserver(muts => {
632 if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {638 if (muts.find(it => Array.from(it.removedNodes).includes(this.textarea))) {
633 this.clone.remove();639 this.clone.remove();
634 }640 }
635 });641 });
636 mo.observe(this.textarea.parentElement, { childList:true });642 mo.observe(this.textarea.parentElement, { childList: true });
637 }643 }
638 this.clone.style.height = `${inputRect.height}px`;644 this.clone.style.height = `${inputRect.height}px`;
639 this.clone.style.left = `${inputRect.left}px`;645 this.clone.style.left = `${inputRect.left}px`;
@@ -685,7 +691,7 @@ export class AutoComplete {
685 this.textarea.selectionDirection = selectionEnd;691 this.textarea.selectionDirection = selectionEnd;
686 }692 }
687 this.wasForced = false;693 this.wasForced = false;
688 this.textarea.dispatchEvent(new Event('input', { bubbles:true }));694 this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
689 this.onSelect?.(this.selectedItem);695 this.onSelect?.(this.selectedItem);
690 }696 }
691697
@@ -700,7 +706,7 @@ export class AutoComplete {
700 this.selectedItem.dom.classList.add('selected');706 this.selectedItem.dom.classList.add('selected');
701 const rect = this.selectedItem.dom.children[0].getBoundingClientRect();707 const rect = this.selectedItem.dom.children[0].getBoundingClientRect();
702 const rectParent = this.dom.getBoundingClientRect();708 const rectParent = this.dom.getBoundingClientRect();
703 if (rect.top < rectParent.top || rect.bottom > rectParent.bottom ) {709 if (rect.top < rectParent.top || rect.bottom > rectParent.bottom) {
704 this.dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;710 this.dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;
705 }711 }
706 this.renderDetailsDebounced();712 this.renderDetailsDebounced();
@@ -809,8 +815,8 @@ export class AutoComplete {
809 }815 }
810 // await keyup to see if cursor position or text has changed816 // await keyup to see if cursor position or text has changed
811 const oldText = this.textarea.value;817 const oldText = this.textarea.value;
812 await new Promise(resolve=>{818 await new Promise(resolve => {
813 window.addEventListener('keyup', resolve, { once:true });819 window.addEventListener('keyup', resolve, { once: true });
814 });820 });
815 if (this.selectionStart != this.textarea.selectionStart) {821 if (this.selectionStart != this.textarea.selectionStart) {
816 this.selectionStart = this.textarea.selectionStart;822 this.selectionStart = this.textarea.selectionStart;
public/scripts/backgrounds.js+172 -27
@@ -1,20 +1,48 @@
1import { Fuse } from '../lib.js';1import { Fuse, localforage } from '../lib.js';
22import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
3import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
4import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';3import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';
5import { SlashCommand } from './slash-commands/SlashCommand.js';4import { SlashCommand } from './slash-commands/SlashCommand.js';
6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';5import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
7import { flashHighlight, stringFormat } from './utils.js';6import { createThumbnail, flashHighlight, getBase64Async, stringFormat } from './utils.js';
8import { t } from './i18n.js';7import { t } from './i18n.js';
9import { Popup } from './popup.js';8import { Popup } from './popup.js';
109
11const BG_METADATA_KEY = 'custom_background';10const BG_METADATA_KEY = 'custom_background';
12const LIST_METADATA_KEY = 'chat_backgrounds';11const LIST_METADATA_KEY = 'chat_backgrounds';
1312
13// A single transparent PNG pixel used as a placeholder for errored backgrounds
14const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
15const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' });
16const 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 */
22const THUMBNAIL_STORAGE = localforage.createInstance({ name: 'SillyTavern_Thumbnails' });
23
24/**
25 * Cache for thumbnail blob URLs.
26 * @type {Map<string, string>}
27 */
28const THUMBNAIL_BLOBS = new Map();
29
30const THUMBNAIL_CONFIG = {
31 width: 160,
32 height: 90,
33};
34
35/**
36 * Global IntersectionObserver instance for lazy loading backgrounds
37 * @type {IntersectionObserver|null}
38 */
39let lazyLoadObserver = null;
40
14export let background_settings = {41export let background_settings = {
15 name: '__transparent.png',42 name: '__transparent.png',
16 url: generateUrlParameter('__transparent.png', false),43 url: generateUrlParameter('__transparent.png', false),
17 fitting: 'classic',44 fitting: 'classic',
45 animation: false,
18};46};
1947
20export function loadBackgroundSettings(settings) {48export function loadBackgroundSettings(settings) {
@@ -25,16 +53,20 @@ export function loadBackgroundSettings(settings) {
25 if (!backgroundSettings.fitting) {53 if (!backgroundSettings.fitting) {
26 backgroundSettings.fitting = 'classic';54 backgroundSettings.fitting = 'classic';
27 }55 }
56 if (!Object.hasOwn(backgroundSettings, 'animation')) {
57 backgroundSettings.animation = false;
58 }
28 setBackground(backgroundSettings.name, backgroundSettings.url);59 setBackground(backgroundSettings.name, backgroundSettings.url);
29 setFittingClass(backgroundSettings.fitting);60 setFittingClass(backgroundSettings.fitting);
30 $('#background_fitting').val(backgroundSettings.fitting);61 $('#background_fitting').val(backgroundSettings.fitting);
62 $('#background_thumbnails_animation').prop('checked', background_settings.animation);
31}63}
3264
33/**65/**
34 * Sets the background for the current chat and adds it to the list of custom backgrounds.66 * Sets the background for the current chat and adds it to the list of custom backgrounds.
35 * @param {{url: string, path:string}} backgroundInfo67 * @param {{url: string, path:string}} backgroundInfo
36 */68 */
37function forceSetBackground(backgroundInfo) {69async function forceSetBackground(backgroundInfo) {
38 saveBackgroundMetadata(backgroundInfo.url);70 saveBackgroundMetadata(backgroundInfo.url);
39 setCustomBackground();71 setCustomBackground();
4072
@@ -43,7 +75,7 @@ function forceSetBackground(backgroundInfo) {
43 list.push(bg);75 list.push(bg);
44 chat_metadata[LIST_METADATA_KEY] = list;76 chat_metadata[LIST_METADATA_KEY] = list;
45 saveMetadataDebounced();77 saveMetadataDebounced();
46 getChatBackgroundsList();78 await getChatBackgroundsList();
47 highlightNewBackground(bg);79 highlightNewBackground(bg);
48 highlightLockedBackground();80 highlightLockedBackground();
49}81}
@@ -56,11 +88,11 @@ async function onChatChanged() {
56 unsetCustomBackground();88 unsetCustomBackground();
57 }89 }
5890
59 getChatBackgroundsList();91 await getChatBackgroundsList();
60 highlightLockedBackground();92 highlightLockedBackground();
61}93}
6294
63function getChatBackgroundsList() {95async function getChatBackgroundsList() {
64 const list = chat_metadata[LIST_METADATA_KEY];96 const list = chat_metadata[LIST_METADATA_KEY];
65 const listEmpty = !Array.isArray(list) || list.length === 0;97 const listEmpty = !Array.isArray(list) || list.length === 0;
6698
@@ -72,9 +104,10 @@ function getChatBackgroundsList() {
72 }104 }
73105
74 for (const bg of list) {106 for (const bg of list) {
75 const template = getBackgroundFromTemplate(bg, true);107 const template = await getBackgroundFromTemplate(bg, true);
76 $('#bg_custom_content').append(template);108 $('#bg_custom_content').append(template);
77 }109 }
110 activateLazyLoader();
78}111}
79112
80function getBackgroundPath(fileUrl) {113function getBackgroundPath(fileUrl) {
@@ -224,7 +257,49 @@ async function onCopyToSystemBackgroundClick(e) {
224 const index = list.indexOf(bgNames.oldBg);257 const index = list.indexOf(bgNames.oldBg);
225 list.splice(index, 1);258 list.splice(index, 1);
226 saveMetadataDebounced();259 saveMetadataDebounced();
227 getChatBackgroundsList();260 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 */
270async 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 }
228}303}
229304
230/**305/**
@@ -245,7 +320,7 @@ async function getNewBackgroundName(referenceElement) {
245 const fileExtension = oldBg.split('.').pop();320 const fileExtension = oldBg.split('.').pop();
246 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;321 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
247 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');322 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
248 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);323 const newBgExtensionless = await Popup.show.input(t`Enter new background name:`, null, oldBgExtensionless);
249324
250 if (!newBgExtensionless) {325 if (!newBgExtensionless) {
251 console.debug('no new_bg_extensionless');326 console.debug('no new_bg_extensionless');
@@ -327,7 +402,7 @@ async function onDeleteBackgroundClick(e) {
327 }402 }
328403
329 if (isCustom) {404 if (isCustom) {
330 getChatBackgroundsList();405 await getChatBackgroundsList();
331 saveMetadataDebounced();406 saveMetadataDebounced();
332 }407 }
333 }408 }
@@ -372,22 +447,55 @@ export async function getBackgrounds() {
372 const response = await fetch('/api/backgrounds/all', {447 const response = await fetch('/api/backgrounds/all', {
373 method: 'POST',448 method: 'POST',
374 headers: getRequestHeaders(),449 headers: getRequestHeaders(),
375 body: JSON.stringify({450 body: JSON.stringify({}),
376 '': '',
377 }),
378 });451 });
379 if (response.ok) {452 if (response.ok) {
380 const getData = await response.json();453 const { images, config } = await response.json();
381 //background = getData;454 Object.assign(THUMBNAIL_CONFIG, config);
382 //console.log(getData.length);
383 $('#bg_menu_content').children('div').remove();455 $('#bg_menu_content').children('div').remove();
384 for (const bg of getData) {456 for (const bg of images) {
385 const template = getBackgroundFromTemplate(bg, false);457 const template = await getBackgroundFromTemplate(bg, false);
386 $('#bg_menu_content').append(template);458 $('#bg_menu_content').append(template);
387 }459 }
460 activateLazyLoader();
388 }461 }
389}462}
390463
464function 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
391/**499/**
392 * Gets the CSS URL of the background500 * Gets the CSS URL of the background
393 * @param {Element} block501 * @param {Element} block
@@ -402,22 +510,41 @@ function generateUrlParameter(bg, isCustom) {
402}510}
403511
404/**512/**
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 */
518async 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/**
405 * Instantiates a background template531 * Instantiates a background template
406 * @param {string} bg Path to background532 * @param {string} bg Path to background
407 * @param {boolean} isCustom Whether the background is custom533 * @param {boolean} isCustom Whether the background is custom
408 * @returns {JQuery<HTMLElement>} Background template534 * @returns {Promise<JQuery<HTMLElement>>} Background template
409 */535 */
410function getBackgroundFromTemplate(bg, isCustom) {536async function getBackgroundFromTemplate(bg, isCustom) {
411 const template = $('#background_template .bg_example').clone();537 const template = $('#background_template .bg_example').clone();
412 const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg);
413 const url = generateUrlParameter(bg, isCustom);538 const url = generateUrlParameter(bg, isCustom);
414 const title = isCustom ? bg.split('/').pop() : bg;539 const title = isCustom ? bg.split('/').pop() : bg;
415 const friendlyTitle = title.slice(0, title.lastIndexOf('.'));540 const friendlyTitle = title.slice(0, title.lastIndexOf('.'));
541
416 template.attr('title', title);542 template.attr('title', title);
417 template.attr('bgfile', bg);543 template.attr('bgfile', bg);
418 template.attr('custom', String(isCustom));544 template.attr('custom', String(isCustom));
419 template.data('url', url);545 template.data('url', url);
420 template.css('background-image', `url('${thumbPath}')`);546 template.addClass('lazy-load-background');
547 template.css('background-image', PLACEHOLDER_IMAGE);
421 template.find('.BGSampleTitle').text(friendlyTitle);548 template.find('.BGSampleTitle').text(friendlyTitle);
422 return template;549 return template;
423}550}
@@ -437,6 +564,12 @@ async function delBackground(bg) {
437 bg: bg,564 bg: bg,
438 }),565 }),
439 });566 });
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 }
440}573}
441574
442async function onBackgroundUploadSelected() {575async function onBackgroundUploadSelected() {
@@ -572,17 +705,20 @@ export function initBackgrounds() {
572 $('#auto_background').on('click', autoBackgroundCommand);705 $('#auto_background').on('click', autoBackgroundCommand);
573 $('#add_bg_button').on('change', onBackgroundUploadSelected);706 $('#add_bg_button').on('change', onBackgroundUploadSelected);
574 $('#bg-filter').on('input', onBackgroundFilterInput);707 $('#bg-filter').on('input', onBackgroundFilterInput);
575 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg',708 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
709 name: 'lockbg',
576 callback: () => onLockBackgroundClick(new CustomEvent('click')),710 callback: () => onLockBackgroundClick(new CustomEvent('click')),
577 aliases: ['bglock'],711 aliases: ['bglock'],
578 helpString: 'Locks a background for the currently selected chat',712 helpString: 'Locks a background for the currently selected chat',
579 }));713 }));
580 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg',714 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
715 name: 'unlockbg',
581 callback: () => onUnlockBackgroundClick(new CustomEvent('click')),716 callback: () => onUnlockBackgroundClick(new CustomEvent('click')),
582 aliases: ['bgunlock'],717 aliases: ['bgunlock'],
583 helpString: 'Unlocks a background for the currently selected chat',718 helpString: 'Unlocks a background for the currently selected chat',
584 }));719 }));
585 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'autobg',720 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
721 name: 'autobg',
586 callback: autoBackgroundCommand,722 callback: autoBackgroundCommand,
587 aliases: ['bgauto'],723 aliases: ['bgauto'],
588 helpString: 'Automatically changes the background based on the chat context using the AI request prompt',724 helpString: 'Automatically changes the background based on the chat context using the AI request prompt',
@@ -593,4 +729,13 @@ export function initBackgrounds() {
593 setFittingClass(background_settings.fitting);729 setFittingClass(background_settings.fitting);
594 saveSettingsDebounced();730 saveSettingsDebounced();
595 });731 });
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 });
596}741}
public/scripts/bookmarks.js+32 -13
@@ -14,7 +14,6 @@ import {
14} from '../script.js';14} from '../script.js';
15import { humanizedDateTime } from './RossAscends-mods.js';15import { humanizedDateTime } from './RossAscends-mods.js';
16import {16import {
17 getGroupPastChats,
18 group_activation_strategy,17 group_activation_strategy,
19 groups,18 groups,
20 openGroupById,19 openGroupById,
@@ -40,22 +39,42 @@ import {
4039
41const bookmarkNameToken = 'Checkpoint #';40const 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 */
43async function getExistingChatNames() {46async function getExistingChatNames() {
44 if (selected_group) {47 if (selected_group) {
45 const data = await getGroupPastChats(selected_group);48 const group = groups.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', ''));
57 }51 }
52
53 return [];
58 }54 }
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 [];
59}78}
6079
61async function getBookmarkName({ isReplace = false, forceName = null } = {}) {80async 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
211 const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);211 const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
212 message.extra.image = imageUrl;212 message.extra.image = imageUrl;
213 message.extra.inline_image = true;213 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;
214 } else {220 } else {
215 const uniqueFileName = `${fileNamePrefix}.txt`;221 const uniqueFileName = `${fileNamePrefix}.txt`;
216222
@@ -1240,7 +1246,7 @@ async function openAttachmentManager() {
12401246
1241 return () => {1247 return () => {
1242 modalButtonData.forEach(p => {1248 modalButtonData.forEach(p => {
1243 const { popper,bodyListener } = p;1249 const { popper, bodyListener } = p;
1244 popper.destroy();1250 popper.destroy();
1245 document.body.removeEventListener('click', bodyListener);1251 document.body.removeEventListener('click', bodyListener);
1246 });1252 });
public/scripts/custom-request.js+4 -4
@@ -279,9 +279,9 @@ export class TextCompletionService {
279 instructPreset,279 instructPreset,
280 );280 );
281 } else { // e.g. "<|im_start|>assistant: Hello, my name is"281 } else { // e.g. "<|im_start|>assistant: Hello, my name is"
282 const overridenInstructPreset = structuredClone(instructPreset);282 const overriddenInstructPreset = structuredClone(instructPreset);
283 overridenInstructPreset.output_suffix = '';283 overriddenInstructPreset.output_suffix = '';
284 overridenInstructPreset.wrap = false;284 overriddenInstructPreset.wrap = false;
285 messageContent = formatInstructModeChat(285 messageContent = formatInstructModeChat(
286 message.role,286 message.role,
287 message.content,287 message.content,
@@ -291,7 +291,7 @@ export class TextCompletionService {
291 undefined,291 undefined,
292 undefined,292 undefined,
293 undefined,293 undefined,
294 overridenInstructPreset,294 overriddenInstructPreset,
295 );295 );
296 }296 }
297 }297 }
public/scripts/data-maid.js+393 -0
@@ -0,0 +1,393 @@
1import { getRequestHeaders } from '../script.js';
2import { t } from './i18n.js';
3import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
4import { renderTemplateAsync } from './templates.js';
5import { 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 */
16class 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
385export 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 = [];
36 * A set of active extensions.36 * A set of active extensions.
37 * @type {Set<string>}37 * @type {Set<string>}
38 */38 */
39let activeExtensions = new Set();39const activeExtensions = new Set();
40
41/**
42 * Errors that occurred while loading extensions.
43 * @type {Set<string>}
44 */
45const extensionLoadErrors = new Set();
4046
41const getApiUrl = () => extension_settings.apiUrl;47const getApiUrl = () => extension_settings.apiUrl;
42const sortManifestsByOrder = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));48const 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 = {
172 llmPrompt: undefined,178 llmPrompt: undefined,
173 allowMultiple: true,179 allowMultiple: true,
174 rerollIfSame: false,180 rerollIfSame: false,
181 promptType: 'raw',
175 },182 },
176 connectionManager: {183 connectionManager: {
177 selectedProfile: '',184 selectedProfile: '',
@@ -379,37 +386,90 @@ async function getManifests(names) {
379 * @returns {Promise<void>}386 * @returns {Promise<void>}
380 */387 */
381async function activateExtensions() {388async function activateExtensions() {
389 extensionLoadErrors.clear();
382 const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));390 const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));
391 const extensionNames = extensions.map(x => x[0]);
383 const promises = [];392 const promises = [];
384393
385 for (let entry of extensions) {394 for (let entry of extensions) {
386 const name = entry[0];395 const name = entry[0];
387 const manifest = entry[1];396 const manifest = entry[1];
397 const extrasRequirements = manifest.requires;
398 const extensionDependencies = manifest.dependencies;
399 const displayName = manifest.display_name || name;
388400
389 if (activeExtensions.has(name)) {401 if (activeExtensions.has(name)) {
390 continue;402 continue;
391 }403 }
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
394 const isDisabled = extension_settings.disabledExtensions.includes(name);439 const isDisabled = extension_settings.disabledExtensions.includes(name);
395440
396 if (meetsModuleRequirements && !isDisabled) {441 if (meetsModuleRequirements && meetsExtensionDeps && !isDisabled) {
397 try {442 try {
398 console.debug('Activating extension', name);443 console.debug('Activating extension', name);
399 const promise = addExtensionLocale(name, manifest).finally(() => Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]));444 const promise = addExtensionLocale(name, manifest).finally(() =>
445 Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]),
446 );
400 await promise447 await promise
401 .then(() => activeExtensions.add(name))448 .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 });
403 promises.push(promise);453 promises.push(promise);
454 } catch (error) {
455 console.error('Could not activate extension', name, error);
404 }456 }
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(', ')}"`);
408 }467 }
409 }468 }
410 }469 }
411470
412 await Promise.allSettled(promises);471 await Promise.allSettled(promises);
472 $('#extensions_details').toggleClass('warning', extensionLoadErrors.size > 0);
413}473}
414474
415async function connectClickHandler() {475async function connectClickHandler() {
@@ -752,6 +812,27 @@ function getModuleInformation() {
752}812}
753813
754/**814/**
815 * Generates HTML for the extension load errors.
816 * @returns {string} HTML string containing the errors that occurred while loading extensions.
817 */
818function 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/**
755 * Generates the HTML strings for all extensions and displays them in a popup.836 * Generates the HTML strings for all extensions and displays them in a popup.
756 */837 */
757async function showExtensionsDetails() {838async function showExtensionsDetails() {
@@ -765,6 +846,7 @@ async function showExtensionsDetails() {
765 initialScrollTop = oldPopup.content.scrollTop;846 initialScrollTop = oldPopup.content.scrollTop;
766 await oldPopup.completeCancelled();847 await oldPopup.completeCancelled();
767 }848 }
849 const htmlErrors = getExtensionLoadErrorsHtml();
768 const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');850 const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');
769 const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');851 const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');
770 const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">852 const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
@@ -787,6 +869,7 @@ async function showExtensionsDetails() {
787869
788 const html = $('<div></div>')870 const html = $('<div></div>')
789 .addClass('extensions_info')871 .addClass('extensions_info')
872 .append(htmlErrors)
790 .append(htmlDefault)873 .append(htmlDefault)
791 .append(htmlExternal)874 .append(htmlExternal)
792 .append(getModuleInformation());875 .append(getModuleInformation());
@@ -893,11 +976,14 @@ async function onUpdateClick() {
893 * Updates a third-party extension via the API.976 * Updates a third-party extension via the API.
894 * @param {string} extensionName Extension folder name977 * @param {string} extensionName Extension folder name
895 * @param {boolean} quiet If true, don't show a success message978 * @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.
896 */980 */
897async function updateExtension(extensionName, quiet) {981async function updateExtension(extensionName, quiet, timeout = null) {
898 try {982 try {
983 const signal = timeout ? AbortSignal.timeout(timeout) : undefined;
899 const response = await fetch('/api/extensions/update', {984 const response = await fetch('/api/extensions/update', {
900 method: 'POST',985 method: 'POST',
986 signal: signal,
901 headers: getRequestHeaders(),987 headers: getRequestHeaders(),
902 body: JSON.stringify({988 body: JSON.stringify({
903 extensionName,989 extensionName,
@@ -926,7 +1012,7 @@ async function updateExtension(extensionName, quiet) {
926 toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`);1012 toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`);
927 }1013 }
928 } catch (error) {1014 } catch (error) {
929 console.error('Error:', error);1015 console.error('Extension update error:', error);
930 }1016 }
931}1017}
9321018
@@ -1397,6 +1483,7 @@ async function autoUpdateExtensions(forceAll) {
1397 const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 });1483 const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 });
1398 const isCurrentUserAdmin = isAdmin();1484 const isCurrentUserAdmin = isAdmin();
1399 const promises = [];1485 const promises = [];
1486 const autoUpdateTimeout = 60 * 1000;
1400 for (const [id, manifest] of Object.entries(manifests)) {1487 for (const [id, manifest] of Object.entries(manifests)) {
1401 const isDisabled = extension_settings.disabledExtensions.includes(id);1488 const isDisabled = extension_settings.disabledExtensions.includes(id);
1402 if (!forceAll && isDisabled) {1489 if (!forceAll && isDisabled) {
@@ -1410,7 +1497,7 @@ async function autoUpdateExtensions(forceAll) {
1410 }1497 }
1411 if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) {1498 if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) {
1412 console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`);1499 console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`);
1413 promises.push(updateExtension(id.replace('third-party', ''), true));1500 promises.push(updateExtension(id.replace('third-party', ''), true, autoUpdateTimeout));
1414 }1501 }
1415 }1502 }
1416 await Promise.allSettled(promises);1503 await Promise.allSettled(promises);
@@ -1453,7 +1540,7 @@ export async function runGenerationInterceptors(chat, contextSize, type) {
14531540
1454/**1541/**
1455 * Writes a field to the character's data extensions object.1542 * Writes a field to the character's data extensions object.
1456 * @param {number} characterId Index in the character array1543 * @param {number|string} characterId Index in the character array
1457 * @param {string} key Field name1544 * @param {string} key Field name
1458 * @param {any} value Field value1545 * @param {any} value Field value
1459 * @returns {Promise<void>} When the field is written1546 * @returns {Promise<void>} When the field is written
public/scripts/extensions/assets/index.js+5 -5
@@ -53,11 +53,11 @@ function filterAssets() {
53}53}
5454
55const KNOWN_TYPES = {55const KNOWN_TYPES = {
56 'extension': 'Extensions',56 'extension': t`Extensions`,
57 'character': 'Characters',57 'character': t`Characters`,
58 'ambient': 'Ambient sounds',58 'ambient': t`Ambient sounds`,
59 'bgm': 'Background music',59 'bgm': t`Background music`,
60 'blip': 'Blip sounds',60 'blip': t`Blip sounds`,
61};61};
6262
63async function downloadAssetsList(url) {63async function downloadAssetsList(url) {
public/scripts/extensions/assets/style.css+1 -1
@@ -65,7 +65,7 @@
65.asset-download-button-text {65.asset-download-button-text {
66 font: bold 20px "Quicksand", san-serif;66 font: bold 20px "Quicksand", san-serif;
67 color: #ffffff;67 color: #ffffff;
68 transition: all 0.2s;68 transition: all var(--animation-duration-2x);
69}69}
7070
71.asset-download-button-loading .asset-download-button-text {71.asset-download-button-loading .asset-download-button-text {
public/scripts/extensions/attachments/index.js+29 -0
@@ -216,8 +216,37 @@ function cleanUpAttachments() {
216 }216 }
217}217}
218218
219/**
220 * Clean up character attachments when a character is deleted.
221 * @param {{character: import('../../char-data.js').v1CharData}} data Event data
222 */
223function 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 */
237function 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
219jQuery(async () => {246jQuery(async () => {
220 eventSource.on(event_types.APP_READY, cleanUpAttachments);247 eventSource.on(event_types.APP_READY, cleanUpAttachments);
248 eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments);
249 eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename);
221 const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});250 const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
222 const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});251 const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
223 $('#data_bank_wand_container').append(manageButton);252 $('#data_bank_wand_container').append(manageButton);
public/scripts/extensions/attachments/notepad.html+1 -1
@@ -3,7 +3,7 @@
3 File Name3 File Name
4 </label>4 </label>
5 <input type="text" class="text_pole" id="notepadFileName" name="notepadFileName" value="" />5 <input type="text" class="text_pole" id="notepadFileName" name="notepadFileName" value="" />
6 <labels>6 <label>
7 File Content7 File Content
8 </label>8 </label>
9 <textarea id="notepadFileContent" name="notepadFileContent" class="text_pole textarea_compact monospace flex1" placeholder="Enter your notes here."></textarea>9 <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 @@
1import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';1import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
3import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';3import { appendMediaToMessage, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
4import { getMessageTimeStamp } from '../../RossAscends-mods.js';4import { getMessageTimeStamp } from '../../RossAscends-mods.js';
5import { SECRET_KEYS, secret_state } from '../../secrets.js';5import { SECRET_KEYS, secret_state } from '../../secrets.js';
6import { getMultimodalCaption } from '../shared.js';6import { getMultimodalCaption } from '../shared.js';
@@ -9,6 +9,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
9import { SlashCommand } from '../../slash-commands/SlashCommand.js';9import { SlashCommand } from '../../slash-commands/SlashCommand.js';
10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
12import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
12export { MODULE_NAME };13export { MODULE_NAME };
1314
14const MODULE_NAME = 'caption';15const MODULE_NAME = 'caption';
@@ -98,9 +99,9 @@ async function wrapCaptionTemplate(caption) {
98 let messageText = substituteParamsExtended(template, { caption: caption });99 let messageText = substituteParamsExtended(template, { caption: caption });
99100
100 if (extension_settings.caption.refine_mode) {101 if (extension_settings.caption.refine_mode) {
101 messageText = await callPopup(102 messageText = await Popup.show.input(
102 '<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.',103 'Review and edit the generated caption:',
103 'input',104 'Press "Cancel" to abort the caption sending.',
104 messageText,105 messageText,
105 { rows: 5, okButton: 'Send' });106 { rows: 5, okButton: 'Send' });
106107
@@ -278,7 +279,7 @@ async function captionMultimodal(base64Img, externalPrompt) {
278 let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT;279 let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT;
279280
280 if (!externalPrompt && extension_settings.caption.prompt_ask) {281 if (!externalPrompt && extension_settings.caption.prompt_ask) {
281 const customPrompt = await callPopup('<h3>Enter a comment or question:</h3>', 'input', prompt, { rows: 2 });282 const customPrompt = await callGenericPopup('Enter a comment or question:', POPUP_TYPE.INPUT, prompt, { rows: 2 });
282 if (!customPrompt) {283 if (!customPrompt) {
283 throw new Error('User aborted the caption sending.');284 throw new Error('User aborted the caption sending.');
284 }285 }
@@ -432,6 +433,7 @@ jQuery(async function () {
432 'zerooneai': SECRET_KEYS.ZEROONEAI,433 'zerooneai': SECRET_KEYS.ZEROONEAI,
433 'groq': SECRET_KEYS.GROQ,434 'groq': SECRET_KEYS.GROQ,
434 'cohere': SECRET_KEYS.COHERE,435 'cohere': SECRET_KEYS.COHERE,
436 'aimlapi': SECRET_KEYS.AIMLAPI,
435 };437 };
436438
437 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {439 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+27 -0
@@ -18,6 +18,7 @@
18 <label for="caption_multimodal_api" data-i18n="API">API</label>18 <label for="caption_multimodal_api" data-i18n="API">API</label>
19 <select id="caption_multimodal_api" class="flex1 text_pole">19 <select id="caption_multimodal_api" class="flex1 text_pole">
20 <option value="zerooneai">01.AI (Yi)</option>20 <option value="zerooneai">01.AI (Yi)</option>
21 <option value="aimlapi">AI/ML API</option>
21 <option value="anthropic">Anthropic</option>22 <option value="anthropic">Anthropic</option>
22 <option value="cohere">Cohere</option>23 <option value="cohere">Cohere</option>
23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>24 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
@@ -85,11 +86,15 @@
85 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>86 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
86 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>87 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
87 <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>88 <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>
88 <option data-type="google" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>91 <option data-type="google" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
89 <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>92 <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
90 <option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>93 <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>
91 <option data-type="google" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>95 <option data-type="google" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
92 <option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>96 <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>
93 <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>98 <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>
94 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option>99 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option>
95 <option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option>100 <option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option>
@@ -116,15 +121,37 @@
116 <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>121 <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>
117 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>122 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
118 <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>123 <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>
119 <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>126 <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
120 <option data-type="vertexai" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>127 <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>
121 <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>129 <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
122 <option data-type="vertexai" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>130 <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>
123 <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>132 <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
124 <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>133 <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
125 <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>134 <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
126 <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>135 <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
127 <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>136 <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>
128 <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>155 <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
129 <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>156 <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
130 <option data-type="openrouter" value="openai/gpt-4o-2024-05-13">openai/gpt-4o-2024-05-13</option>157 <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';
13import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';13import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
14import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js';14import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js';
15import { t } from '../../i18n.js';15import { t } from '../../i18n.js';
16import { getSecretLabelById } from '../../secrets.js';
1617
17const MODULE_NAME = 'connection-manager';18const MODULE_NAME = 'connection-manager';
18const NONE = '<None>';19const NONE = '<None>';
@@ -40,6 +41,8 @@ const CC_COMMANDS = [
40 'stop-strings',41 'stop-strings',
41 'start-reply-with',42 'start-reply-with',
42 'reasoning-template',43 'reasoning-template',
44 'prompt-post-processing',
45 'secret-id',
43];46];
4447
45const TC_COMMANDS = [48const TC_COMMANDS = [
@@ -56,6 +59,7 @@ const TC_COMMANDS = [
56 'stop-strings',59 'stop-strings',
57 'start-reply-with',60 'start-reply-with',
58 'reasoning-template',61 'reasoning-template',
62 'secret-id',
59];63];
6064
61const FANCY_NAMES = {65const FANCY_NAMES = {
@@ -73,6 +77,8 @@ const FANCY_NAMES = {
73 'stop-strings': 'Custom Stopping Strings',77 'stop-strings': 'Custom Stopping Strings',
74 'start-reply-with': 'Start Reply With',78 'start-reply-with': 'Start Reply With',
75 'reasoning-template': 'Reasoning Template',79 'reasoning-template': 'Reasoning Template',
80 'prompt-post-processing': 'Prompt Post-Processing',
81 'secret-id': 'Secret',
76};82};
7783
78/**84/**
@@ -342,6 +348,15 @@ function makeFancyProfile(profile) {
342 return acc;348 return acc;
343 }349 }
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
345 acc[value] = profile[key];360 acc[value] = profile[key];
346 return acc;361 return acc;
347 }, {});362 }, {});
public/scripts/extensions/expressions/index.js+39 -6
@@ -1,6 +1,6 @@
1import { Fuse } from '../../../lib.js';1import { Fuse } from '../../../lib.js';
22
3import { characters, eventSource, event_types, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js';3import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js';
4import { dragElement, isMobile } from '../../RossAscends-mods.js';4import { dragElement, isMobile } from '../../RossAscends-mods.js';
5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
@@ -86,6 +86,12 @@ const EXPRESSION_API = {
86 none: 99,86 none: 99,
87};87};
8888
89/** @enum {string} */
90const PROMPT_TYPE = {
91 raw: 'raw',
92 full: 'full',
93};
94
89let expressionsList = null;95let expressionsList = null;
90let lastCharacter = undefined;96let lastCharacter = undefined;
91let lastMessage = null;97let lastMessage = null;
@@ -909,10 +915,6 @@ function sampleClassifyText(text) {
909 * @returns {Promise<string>} Prompt for the LLM API.915 * @returns {Promise<string>} Prompt for the LLM API.
910 */916 */
911async function getLlmPrompt(labels) {917async function getLlmPrompt(labels) {
912 if (isJsonSchemaSupported()) {
913 return '';
914 }
915
916 const labelsString = labels.map(x => `"${x}"`).join(', ');918 const labelsString = labels.map(x => `"${x}"`).join(', ');
917 const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });919 const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });
918 return prompt;920 return prompt;
@@ -976,6 +978,7 @@ function getJsonSchema(emotions) {
976 required: [978 required: [
977 'emotion',979 'emotion',
978 ],980 ],
981 additionalProperties: false,
979 };982 };
980}983}
981984
@@ -1047,7 +1050,21 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1047 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });1050 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1048 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1051 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1049 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);1052 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 }
1051 return parseLlmResponse(emotionResponse, expressionsList);1068 return parseLlmResponse(emotionResponse, expressionsList);
1052 }1069 }
1053 // Using WebLLM1070 // Using WebLLM
@@ -1702,6 +1719,7 @@ function onExpressionApiChanged() {
1702 if (tempApi) {1719 if (tempApi) {
1703 extension_settings.expressions.api = Number(tempApi);1720 extension_settings.expressions.api = Number(tempApi);
1704 $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));1721 $('.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);
1705 expressionsList = null;1723 expressionsList = null;
1706 spriteCache = {};1724 spriteCache = {};
1707 moduleWorker();1725 moduleWorker();
@@ -2102,6 +2120,11 @@ function migrateSettings() {
2102 extension_settings.expressions.showDefault = false;2120 extension_settings.expressions.showDefault = false;
2103 saveSettingsDebounced();2121 saveSettingsDebounced();
2104 }2122 }
2123
2124 if (extension_settings.expressions.promptType === undefined) {
2125 extension_settings.expressions.promptType = PROMPT_TYPE.raw;
2126 saveSettingsDebounced();
2127 }
2105}2128}
21062129
2107(async function () {2130(async function () {
@@ -2169,6 +2192,16 @@ function migrateSettings() {
2169 extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT;2192 extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT;
2170 saveSettingsDebounced();2193 saveSettingsDebounced();
2171 });2194 });
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
2173 $('#expression_custom_add').on('click', onClickExpressionAddCustom);2206 $('#expression_custom_add').on('click', onClickExpressionAddCustom);
2174 $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);2207 $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
public/scripts/extensions/expressions/settings.html+16 -3
@@ -40,8 +40,21 @@
40 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>40 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
41 </div>41 </div>
42 </label>42 </label>
43 <small data-i18n="Will be used if the API doesn't support JSON schemas or function calling.">Will be used if the API doesn't support JSON schemas or function calling.</small>43 <small data-i18n="Used in addition to JSON schemas and function calling.">Used in addition to JSON schemas and function calling.</small>
44 <textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact" rows="2" placeholder="Use &lcub;&lcub;labels&rcub;&rcub; special macro."></textarea>44 <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>
45 </div>58 </div>
46 <div class="expression_fallback_block m-b-1 m-t-1">59 <div class="expression_fallback_block m-b-1 m-t-1">
47 <label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>60 <label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
@@ -52,7 +65,7 @@
52 <label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>65 <label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>
53 <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>66 <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>
54 <div class="flex-container">67 <div class="flex-container">
55 <select id="expression_custom" class="flex1 margin0"><select>68 <select id="expression_custom" class="flex1 margin0"></select>
56 <i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>69 <i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
57 <i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>70 <i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
58 </div>71 </div>
public/scripts/extensions/gallery/index.js+70 -6
@@ -4,6 +4,8 @@ import {
4 characters,4 characters,
5 getRequestHeaders,5 getRequestHeaders,
6 event_types,6 event_types,
7 animation_duration,
8 animation_easing,
7} from '../../../script.js';9} from '../../../script.js';
8import { groups, selected_group } from '../../group-chats.js';10import { groups, selected_group } from '../../group-chats.js';
9import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js';11import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js';
@@ -15,10 +17,12 @@ import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/S
15import { DragAndDropHandler } from '../../dragdrop.js';17import { DragAndDropHandler } from '../../dragdrop.js';
16import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';18import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
17import { t, translate } from '../../i18n.js';19import { t, translate } from '../../i18n.js';
20import { Popup } from '../../popup.js';
1821
19const extensionName = 'gallery';22const extensionName = 'gallery';
20const extensionFolderPath = `scripts/extensions/${extensionName}/`;23const extensionFolderPath = `scripts/extensions/${extensionName}/`;
21let firstTime = true;24let firstTime = true;
25let deleteModeActive = false;
2226
23// Exposed defaults for future tweaking27// Exposed defaults for future tweaking
24let thumbnailHeight = 150;28let thumbnailHeight = 150;
@@ -30,7 +34,15 @@ let galleryMaxRows = 3;
30$('#movingDivs').on('click', '.dragClose', function () {34$('#movingDivs').on('click', '.dragClose', function () {
31 const relatedId = $(this).data('related-id');35 const relatedId = $(this).data('related-id');
32 if (!relatedId) return;36 if (!relatedId) return;
33 $(`#movingDivs > .draggable[id="${relatedId}"]`).remove();37 const relatedElement = $(`#movingDivs > .draggable[id="${relatedId}"]`);
38 relatedElement.transition({
39 opacity: 0,
40 duration: animation_duration,
41 easing: animation_easing,
42 complete: () => {
43 relatedElement.remove();
44 },
45 });
34});46});
3547
36const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';48const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
@@ -147,6 +159,29 @@ async function getGalleryFolders() {
147}159}
148160
149/**161/**
162 * Deletes a gallery item based on the provided URL.
163 * @param {string} url - The URL of the image to be deleted.
164 */
165async 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/**
150 * Sets the sort order for the gallery.185 * Sets the sort order for the gallery.
151 * @param {string} order Sort order186 * @param {string} order Sort order
152 */187 */
@@ -260,7 +295,7 @@ async function initGallery(items, url) {
260 *295 *
261 * @returns {Promise<void>} - Promise representing the completion of the gallery display process.296 * @returns {Promise<void>} - Promise representing the completion of the gallery display process.
262 */297 */
263async function showCharGallery() {298async function showCharGallery(deleteModeState = false) {
264 // Load necessary files if it's the first time calling the function299 // Load necessary files if it's the first time calling the function
265 if (firstTime) {300 if (firstTime) {
266 await loadFileToDocument(301 await loadFileToDocument(
@@ -276,6 +311,7 @@ async function showCharGallery() {
276 }311 }
277312
278 try {313 try {
314 deleteModeActive = deleteModeState;
279 let url = selected_group || this_chid;315 let url = selected_group || this_chid;
280 if (!selected_group && this_chid !== undefined) {316 if (!selected_group && this_chid !== undefined) {
281 url = getGalleryFolder(characters[this_chid]);317 url = getGalleryFolder(characters[this_chid]);
@@ -347,7 +383,7 @@ async function makeMovable(url) {
347 const id = 'gallery';383 const id = 'gallery';
348 const template = $('#generic_draggable_template').html();384 const template = $('#generic_draggable_template').html();
349 const newElement = $(template);385 const newElement = $(template);
350 newElement.css('background-color', 'var(--SmartThemeBlurTintColor)');386 newElement.css({ 'background-color': 'var(--SmartThemeBlurTintColor)', 'opacity': 0 });
351 newElement.attr('forChar', id);387 newElement.attr('forChar', id);
352 newElement.attr('id', id);388 newElement.attr('id', id);
353 newElement.find('.drag-grabber').attr('id', `${id}header`);389 newElement.find('.drag-grabber').attr('id', `${id}header`);
@@ -429,6 +465,18 @@ async function makeMovable(url) {
429 galleryFolderAccept.title = t`Change gallery folder`;465 galleryFolderAccept.title = t`Change gallery folder`;
430 galleryFolderAccept.addEventListener('click', onChangeFolder);466 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
432 const galleryFolderRestore = document.createElement('div');480 const galleryFolderRestore = document.createElement('div');
433 galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');481 galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');
434 galleryFolderRestore.title = t`Restore gallery folder`;482 galleryFolderRestore.title = t`Restore gallery folder`;
@@ -436,6 +484,7 @@ async function makeMovable(url) {
436484
437 topBarElement.appendChild(galleryFolderInput);485 topBarElement.appendChild(galleryFolderInput);
438 topBarElement.appendChild(galleryFolderAccept);486 topBarElement.appendChild(galleryFolderAccept);
487 topBarElement.appendChild(galleryDeleteMode);
439 topBarElement.appendChild(galleryFolderRestore);488 topBarElement.appendChild(galleryFolderRestore);
440 newElement.append(topBarElement);489 newElement.append(topBarElement);
441490
@@ -466,6 +515,11 @@ async function makeMovable(url) {
466 loadMovingUIState();515 loadMovingUIState();
467 $(`.draggable[forChar="${id}"]`).css('display', 'block');516 $(`.draggable[forChar="${id}"]`).css('display', 'block');
468 dragElement(newElement);517 dragElement(newElement);
518 newElement.transition({
519 opacity: 1,
520 duration: animation_duration,
521 easing: animation_easing,
522 });
469523
470 $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {524 $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {
471 console.log('saw drag on avatar!');525 console.log('saw drag on avatar!');
@@ -635,9 +689,19 @@ function sanitizeHTMLId(id) {
635function viewWithDragbox(items) {689function viewWithDragbox(items) {
636 if (items && items.length > 0) {690 if (items && items.length > 0) {
637 const url = items[0].responsiveURL(); // Get the URL of the clicked image/video691 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 extension692 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 }
641 }705 }
642}706}
643707
public/scripts/extensions/gallery/style.css+5 -0
@@ -43,3 +43,8 @@
43#dragGallery {43#dragGallery {
44 min-height: 25dvh;44 min-height: 25dvh;
45}45}
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 {
16 getMaxContextSize,16 getMaxContextSize,
17 setExtensionPrompt,17 setExtensionPrompt,
18 streamingProcessor,18 streamingProcessor,
19 animation_easing,
19} from '../../../script.js';20} from '../../../script.js';
20import { is_group_generating, selected_group } from '../../group-chats.js';21import { is_group_generating, selected_group } from '../../group-chats.js';
21import { loadMovingUIState } from '../../power-user.js';22import { loadMovingUIState } from '../../power-user.js';
@@ -972,6 +973,7 @@ function doPopout(e) {
972 </div>`;973 </div>`;
973 const newElement = $(template);974 const newElement = $(template);
974 newElement.attr('id', 'summaryExtensionPopout')975 newElement.attr('id', 'summaryExtensionPopout')
976 .css('opacity', 0)
975 .removeClass('zoomed_avatar')977 .removeClass('zoomed_avatar')
976 .addClass('draggable')978 .addClass('draggable')
977 .empty();979 .empty();
@@ -980,13 +982,13 @@ function doPopout(e) {
980 originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>');982 originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>');
981 newElement.append(controlBarHtml).append(originalHTMLClone);983 newElement.append(controlBarHtml).append(originalHTMLClone);
982 $('body').append(newElement);984 $('body').append(newElement);
985 newElement.transition({ opacity: 1, duration: animation_duration, easing: animation_easing });
983 $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull');986 $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull');
984 setMemoryContext(prevSummaryBoxContents, false); //paste prev summary box contents into popout box987 setMemoryContext(prevSummaryBoxContents, false); //paste prev summary box contents into popout box
985 setupListeners();988 setupListeners();
986 loadSettings();989 loadSettings();
987 loadMovingUIState();990 loadMovingUIState();
988991
989 $('#summaryExtensionPopout').fadeIn(animation_duration);
990 dragElement(newElement);992 dragElement(newElement);
991993
992 //setup listener for close button to restore extensions menu994 //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
182 eventSource.on(event_types.APP_READY, async()=>await finalizeInit());182 eventSource.on(event_types.APP_READY, async()=>await finalizeInit());
183183
184 window['quickReplyApi'] = quickReplyApi;184 globalThis.quickReplyApi = quickReplyApi;
185};185};
186const finalizeInit = async () => {186const finalizeInit = async () => {
187 debug('executing startup');187 debug('executing startup');
public/scripts/extensions/quick-reply/style.css+13 -16
@@ -18,7 +18,7 @@
18#qr--bar {18#qr--bar {
19 outline: none;19 outline: none;
20 margin: 0;20 margin: 0;
21 transition: 0.3s;21 transition: var(--animation-duration-2x);
22 opacity: 0.7;22 opacity: 0.7;
23 display: flex;23 display: flex;
24 align-items: center;24 align-items: center;
@@ -34,14 +34,12 @@
34 right: 0.25em;34 right: 0.25em;
35 top: 0;35 top: 0;
36}36}
37
38/*hide QR popout for mobile*/37/*hide QR popout for mobile*/
39@media screen and (max-width: 1000px) {38@media screen and (max-width: 1000px) {
40 #qr--bar > #qr--popoutTrigger {39 #qr--bar > #qr--popoutTrigger {
41 display:none;40 display: none;
42 }41 }
43}42}
44
45#qr--bar.popoutVisible {43#qr--bar.popoutVisible {
46 padding-right: 2.5em;44 padding-right: 2.5em;
47}45}
@@ -61,9 +59,8 @@
61 aspect-ratio: 1 / 1;59 aspect-ratio: 1 / 1;
62 font-size: 20px;60 font-size: 20px;
63 opacity: 0.5;61 opacity: 0.5;
64 transition: all 250ms;62 transition: all var(--animation-duration-2x);
65}63}
66
67#qr--popout > .qr--body {64#qr--popout > .qr--body {
68 overflow-y: auto;65 overflow-y: auto;
69}66}
@@ -124,7 +121,7 @@
124 padding: 3px 5px;121 padding: 3px 5px;
125 margin: 3px 0;122 margin: 3px 0;
126 cursor: pointer;123 cursor: pointer;
127 transition: 0.3s;124 transition: var(--animation-duration-2x);
128 display: flex;125 display: flex;
129 align-items: center;126 align-items: center;
130 justify-content: center;127 justify-content: center;
@@ -175,7 +172,7 @@
175 overflow: visible;172 overflow: visible;
176}173}
177.ctx-menu .ctx-item .qr--hidden {174.ctx-menu .ctx-item .qr--hidden {
178 display: none;175 display: none;
179}176}
180.list-group .list-group-item.ctx-header {177.list-group .list-group-item.ctx-header {
181 font-weight: bold;178 font-weight: bold;
@@ -248,7 +245,7 @@
248 display: flex;245 display: flex;
249 align-items: center;246 align-items: center;
250 opacity: 0;247 opacity: 0;
251 transition: 100ms;248 transition: var(--animation-duration);
252 margin: -2px 0 -11px 0;249 margin: -2px 0 -11px 0;
253 position: relative;250 position: relative;
254}251}
@@ -461,7 +458,7 @@
461 cursor: not-allowed;458 cursor: not-allowed;
462 opacity: 0.5;459 opacity: 0.5;
463 pointer-events: none;460 pointer-events: none;
464 transition: 200ms;461 transition: var(--animation-duration-2x);
465 border-color: transparent;462 border-color: transparent;
466}463}
467.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {464.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 @@
492 width: 6px;489 width: 6px;
493 background-color: var(--SmartThemeBorderColor);490 background-color: var(--SmartThemeBorderColor);
494 border: 2px solid var(--SmartThemeBlurTintColor);491 border: 2px solid var(--SmartThemeBlurTintColor);
495 transition: border-color 200ms, background-color 200ms;492 transition: border-color var(--animation-duration-2x), background-color var(--animation-duration-2x);
496 cursor: w-resize;493 cursor: w-resize;
497}494}
498.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {495.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {
@@ -574,7 +571,7 @@
574 gap: 1em;571 gap: 1em;
575 text-align: left;572 text-align: left;
576 opacity: 0.75;573 opacity: 0.75;
577 transition: 200ms;574 transition: var(--animation-duration-2x);
578 cursor: pointer;575 cursor: pointer;
579}576}
580.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,577.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,
@@ -736,7 +733,7 @@
736 display: flex;733 display: flex;
737}734}
738.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {735.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
739 transition: 200ms;736 transition: var(--animation-duration-2x);
740 filter: grayscale(0);737 filter: grayscale(0);
741}738}
742.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {739.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
@@ -825,7 +822,7 @@
825 position: absolute;822 position: absolute;
826 inset: 0;823 inset: 0;
827 right: calc(100% - var(--prog) * 1%);824 right: calc(100% - var(--prog) * 1%);
828 transition: 200ms;825 transition: var(--animation-duration-2x);
829}826}
830.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {827.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
831 animation-name: qr--progressPulse;828 animation-name: qr--progressPulse;
@@ -1053,7 +1050,7 @@
1053 white-space: pre;1050 white-space: pre;
1054 font-weight: normal;1051 font-weight: normal;
1055 box-shadow: 0 0 0;1052 box-shadow: 0 0 0;
1056 transition: 200ms;1053 transition: var(--animation-duration-2x);
1057}1054}
1058.popup:has(.qr--transferModal) .popup-button-ok:after {1055.popup:has(.qr--transferModal) .popup-button-ok:after {
1059 content: 'Transfer';1056 content: 'Transfer';
@@ -1068,7 +1065,7 @@
1068 white-space: pre;1065 white-space: pre;
1069 font-weight: normal;1066 font-weight: normal;
1070 box-shadow: 0 0 0;1067 box-shadow: 0 0 0;
1071 transition: 200ms;1068 transition: var(--animation-duration-2x);
1072}1069}
1073.popup:has(.qr--transferModal) .qr--copy:after {1070.popup:has(.qr--transferModal) .qr--copy:after {
1074 content: 'Copy';1071 content: 'Copy';
public/scripts/extensions/quick-reply/style.less+363 -306
@@ -1,11 +1,16 @@
1@keyframes qr--success {1@keyframes qr--success {
2 0%, 100% {2
3 0%,
4 100% {
3 color: var(--SmartThemeBodyColor);5 color: var(--SmartThemeBodyColor);
4 }6 }
5 25%, 75% {7
8 25%,
9 75% {
6 color: rgb(81, 163, 81);10 color: rgb(81, 163, 81);
7 }11 }
8}12}
13
9&.qr--success {14&.qr--success {
10 animation-name: qr--success;15 animation-name: qr--success;
11 animation-duration: 3s;16 animation-duration: 3s;
@@ -13,10 +18,11 @@
13 animation-delay: 0s;18 animation-delay: 0s;
14 animation-iteration-count: 1;19 animation-iteration-count: 1;
15}20}
21
16#qr--bar {22#qr--bar {
17 outline: none;23 outline: none;
18 margin: 0;24 margin: 0;
19 transition: 0.3s;25 transition: var(--animation-duration-2x);
20 opacity: 0.7;26 opacity: 0.7;
21 display: flex;27 display: flex;
22 align-items: center;28 align-items: center;
@@ -33,6 +39,14 @@
33 top: 0;39 top: 0;
34 }40 }
35}41}
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
36#qr--bar.popoutVisible {50#qr--bar.popoutVisible {
37 padding-right: 2.5em;51 padding-right: 2.5em;
38}52}
@@ -54,7 +68,7 @@
54 aspect-ratio: 1 / 1;68 aspect-ratio: 1 / 1;
55 font-size: 20px;69 font-size: 20px;
56 opacity: 0.5;70 opacity: 0.5;
57 transition: all 250ms;71 transition: all var(--animation-duration-2x);
58 }72 }
59 }73 }
60 }74 }
@@ -68,17 +82,21 @@
68#qr--popout>.qr--body {82#qr--popout>.qr--body {
69 >.qr--buttons {83 >.qr--buttons {
70 --qr--color: transparent;84 --qr--color: transparent;
85
71 &.qr--color {86 &.qr--color {
72 background-color: var(--qr--color);87 background-color: var(--qr--color);
73 }88 }
89
74 &.qr--borderColor {90 &.qr--borderColor {
75 background-color: transparent;91 background-color: transparent;
76 border-left: 5px solid var(--qr--color);92 border-left: 5px solid var(--qr--color);
77 border-right: 5px solid var(--qr--color);93 border-right: 5px solid var(--qr--color);
78 }94 }
95
79 &:has(.qr--buttons.qr--color) {96 &:has(.qr--buttons.qr--color) {
80 margin: 5px;97 margin: 5px;
81 }98 }
99
82 margin: 0;100 margin: 0;
83 padding: 0;101 padding: 0;
84 display: flex;102 display: flex;
@@ -89,6 +107,7 @@
89107
90 >.qr--buttons {108 >.qr--buttons {
91 display: contents;109 display: contents;
110
92 &.qr--color {111 &.qr--color {
93 .qr--button:before {112 .qr--button:before {
94 content: '';113 content: '';
@@ -97,11 +116,14 @@
97 inset: -5px;116 inset: -5px;
98 z-index: -1;117 z-index: -1;
99 }118 }
119
100 &.qr--borderColor {120 &.qr--borderColor {
101 .qr--button:before {121 .qr--button:before {
102 display: none;122 display: none;
103 }123 }
104 &:before, &:after {124
125 &:before,
126 &:after {
105 content: '';127 content: '';
106 width: 5px;128 width: 5px;
107 background-color: var(--qr--color);129 background-color: var(--qr--color);
@@ -118,7 +140,7 @@
118 padding: 3px 5px;140 padding: 3px 5px;
119 margin: 3px 0;141 margin: 3px 0;
120 cursor: pointer;142 cursor: pointer;
121 transition: 0.3s;143 transition: var(--animation-duration-2x);
122 display: flex;144 display: flex;
123 align-items: center;145 align-items: center;
124 justify-content: center;146 justify-content: center;
@@ -132,6 +154,7 @@
132 .qr--hidden {154 .qr--hidden {
133 display: none;155 display: none;
134 }156 }
157
135 .qr--button-icon {158 .qr--button-icon {
136 margin: 0 0.5em;159 margin: 0 0.5em;
137 }160 }
@@ -274,18 +297,22 @@
274 display: flex;297 display: flex;
275 align-items: center;298 align-items: center;
276 opacity: 0;299 opacity: 0;
277 transition: 100ms;300 transition: var(--animation-duration);
278 margin: -2px 0 -11px 0;301 margin: -2px 0 -11px 0;
279 position: relative;302 position: relative;
303
280 .qr--actions {304 .qr--actions {
281 display: flex;305 display: flex;
282 gap: 0.25em;306 gap: 0.25em;
283 flex: 0 0 auto;307 flex: 0 0 auto;
308
284 .qr--action {309 .qr--action {
285 margin: 0;310 margin: 0;
286 }311 }
287 }312 }
288 &:before, &:after {313
314 &:before,
315 &:after {
289 content: "";316 content: "";
290 display: block;317 display: block;
291 flex: 1 1 auto;318 flex: 1 1 auto;
@@ -293,10 +320,13 @@
293 margin: 0 1em;320 margin: 0 1em;
294 height: 0;321 height: 0;
295 }322 }
296 &:hover, &:focus-within {323
324 &:hover,
325 &:focus-within {
297 opacity: 1;326 opacity: 1;
298 }327 }
299 }328 }
329
300 >.qr--set-item .qr--content {330 >.qr--set-item .qr--content {
301 display: flex;331 display: flex;
302 flex-direction: row;332 flex-direction: row;
@@ -336,9 +366,11 @@
336 display: flex;366 display: flex;
337 align-items: center;367 align-items: center;
338 gap: 0.5em;368 gap: 0.5em;
369
339 .qr--set-itemIcon:not(.fa-solid) {370 .qr--set-itemIcon:not(.fa-solid) {
340 display: none;371 display: none;
341 }372 }
373
342 .qr--set-itemLabel {374 .qr--set-itemLabel {
343 min-width: 24px;375 min-width: 24px;
344 }376 }
@@ -384,15 +416,16 @@
384 align-items: baseline;416 align-items: baseline;
385 }417 }
386 }418 }
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;
394 }426 }
395 }427 }
428 }
396}429}
397430
398431
@@ -433,65 +466,75 @@
433 bottom: unset;466 bottom: unset;
434 margin: unset;467 margin: unset;
435 padding: 0;468 padding: 0;
469
436 &::backdrop {470 &::backdrop {
437 backdrop-filter: unset;471 backdrop-filter: unset;
438 background-color: transparent;472 background-color: transparent;
439 }473 }
474
440 .popup-body {475 .popup-body {
441 flex: 0 0 auto;476 flex: 0 0 auto;
442 height: min-content;477 height: min-content;
443 width: min-content;478 width: min-content;
444 }479 }
480
445 .popup-content {481 .popup-content {
446 flex: 0 0 auto;482 flex: 0 0 auto;
447 margin-top: 0;483 margin-top: 0;
448484
449 > #qr--modalEditor {485 >#qr--modalEditor {
450 max-height: 50vh;486 max-height: 50vh;
451 > #qr--main,487
452 > #qr--resizeHandle,488 >#qr--main,
453 > #qr--qrOptions > h3,489 >#qr--resizeHandle,
454 > #qr--qrOptions > #qr--modal-executeButtons,490 >#qr--qrOptions>h3,
455 > #qr--qrOptions > #qr--modal-executeProgress491 >#qr--qrOptions>#qr--modal-executeButtons,
456 {492 >#qr--qrOptions>#qr--modal-executeProgress {
457 display: none;493 display: none;
458 }494 }
495
459 #qr--qrOptions {496 #qr--qrOptions {
460 width: auto;497 width: auto;
461 }498 }
499
462 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {500 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
463 display: flex;501 display: flex;
464 }502 }
503
465 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {504 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
466 display: none;505 display: none;
467 }506 }
507
468 #qr--modal-debugState {508 #qr--modal-debugState {
469 padding-top: 0;509 padding-top: 0;
470 }510 }
471 }511 }
472 }512 }
473 }513 }
474 &:has(.qr--isExecuting) {514
475 .popup-controls {515 &:has(.qr--isExecuting) {
476 display: none;516 .popup-controls {
477 }517 display: none;
478518 }
479 .qr--highlight {519
480 position: absolute;520 .qr--highlight {
481 z-index: 50000;521 position: absolute;
482 pointer-events: none;522 z-index: 50000;
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
496 .popup-content {539 .popup-content {
497 display: flex;540 display: flex;
@@ -504,36 +547,41 @@
504 gap: 1em;547 gap: 1em;
505 overflow: hidden;548 overflow: hidden;
506549
507 &.qr--isExecuting {550 &.qr--isExecuting {
508 #qr--main > h3:first-child,551
509 #qr--main > .qr--labels,552 #qr--main>h3:first-child,
510 #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings,553 #qr--main>.qr--labels,
511 #qr--qrOptions > h3:first-child,554 #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,
512 #qr--qrOptions > #qr--ctxEditor,555 #qr--qrOptions>h3:first-child,
513 #qr--qrOptions > .qr--ctxEditorActions,556 #qr--qrOptions>#qr--ctxEditor,
514 #qr--qrOptions > .qr--ctxEditorActions + h3,557 #qr--qrOptions>.qr--ctxEditorActions,
515 #qr--qrOptions > .qr--ctxEditorActions + h3 + div558 #qr--qrOptions>.qr--ctxEditorActions+h3,
516 {559 #qr--qrOptions>.qr--ctxEditorActions+h3+div {
517 display: none;560 display: none;
518 }561 }
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
524 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {570 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
525 cursor: not-allowed;571 cursor: not-allowed;
526 opacity: 0.5;572 opacity: 0.5;
527 pointer-events: none;573 pointer-events: none;
528 transition: 200ms;574 transition: var(--animation-duration-2x);
529 border-color: transparent;575 border-color: transparent;
530 }576 }
531 }577 }
578
532 &.qr--isPaused #qr--modal-debugButtons {579 &.qr--isPaused #qr--modal-debugButtons {
533 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {580 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
534 cursor: pointer;581 cursor: pointer;
535 opacity: 1;582 opacity: 1;
536 pointer-events: all;583 pointer-events: all;
584
537 &#qr--modal-resume {585 &#qr--modal-resume {
538 animation-name: qr--debugPulse;586 animation-name: qr--debugPulse;
539 animation-duration: 1500ms;587 animation-duration: 1500ms;
@@ -541,73 +589,90 @@
541 animation-delay: 0s;589 animation-delay: 0s;
542 animation-iteration-count: infinite;590 animation-iteration-count: infinite;
543 }591 }
592
544 &#qr--modal-resume {593 &#qr--modal-resume {
545 border-color: rgb(81, 163, 81);594 border-color: rgb(81, 163, 81);
546 }595 }
596
547 &#qr--modal-step {597 &#qr--modal-step {
548 border-color: var(--SmartThemeQuoteColor);598 border-color: var(--SmartThemeQuoteColor);
549 }599 }
600
550 &#qr--modal-stepInto {601 &#qr--modal-stepInto {
551 border-color: var(--SmartThemeQuoteColor);602 border-color: var(--SmartThemeQuoteColor);
552 }603 }
604
553 &#qr--modal-stepOut {605 &#qr--modal-stepOut {
554 border-color: var(--SmartThemeQuoteColor);606 border-color: var(--SmartThemeQuoteColor);
555 }607 }
556 }608 }
557 }609 }
558 #qr--resizeHandle {610
559 width: 6px;611 #qr--resizeHandle {
560 background-color: var(--SmartThemeBorderColor);612 width: 6px;
561 border: 2px solid var(--SmartThemeBlurTintColor);613 background-color: var(--SmartThemeBorderColor);
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 {
573625 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;
581 display: flex;633 overflow: hidden;
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;
589 position: relative;647 position: relative;
648
590 &.qr--fit {649 &.qr--fit {
591 flex: 0 0 auto;650 flex: 0 0 auto;
592 justify-content: center;651 justify-content: center;
593 }652 }
653
594 .qr--inputGroup {654 .qr--inputGroup {
595 display: flex;655 display: flex;
596 align-items: baseline;656 align-items: baseline;
597 gap: 0.5em;657 gap: 0.5em;
658
598 input {659 input {
599 flex: 1 1 auto;660 flex: 1 1 auto;
600 }661 }
601 }662 }
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
611 .qr--modal-switcherList {676 .qr--modal-switcherList {
612 background-color: var(--stcdx--bgColor);677 background-color: var(--stcdx--bgColor);
613 border: 1px solid var(--SmartThemeBorderColor);678 border: 1px solid var(--SmartThemeBorderColor);
@@ -625,25 +690,32 @@
625 list-style: none;690 list-style: none;
626 z-index: 40000;691 z-index: 40000;
627 max-width: 100%;692 max-width: 100%;
693
628 .qr--modal-switcherItem {694 .qr--modal-switcherItem {
629 display: flex;695 display: flex;
630 gap: 1em;696 gap: 1em;
631 text-align: left;697 text-align: left;
632 opacity: 0.75;698 opacity: 0.75;
633 transition: 200ms;699 transition: var(--animation-duration-2x);
634 cursor: pointer;700 cursor: pointer;
701
635 &:hover {702 &:hover {
636 opacity: 1;703 opacity: 1;
637 }704 }
705
638 &.qr--current {706 &.qr--current {
639 opacity: 1;707 opacity: 1;
640 .qr--label, .qr--id {708
709 .qr--label,
710 .qr--id {
641 font-weight: bold;711 font-weight: bold;
642 }712 }
643 }713 }
644 }714 }
715
645 .qr--label {716 .qr--label {
646 white-space: nowrap;717 white-space: nowrap;
718
647 .menu_button {719 .menu_button {
648 display: inline-block;720 display: inline-block;
649 height: min-content;721 height: min-content;
@@ -651,11 +723,19 @@
651 margin: 0 0.5em 0 0;723 margin: 0 0.5em 0 0;
652 }724 }
653 }725 }
726
654 .qr--id {727 .qr--id {
655 opacity: 0.5;728 opacity: 0.5;
656 &:before { content: "["; }729
657 &:after { content: "]"; }730 &:before {
731 content: "[";
732 }
733
734 &:after {
735 content: "]";
736 }
658 }737 }
738
659 .qr--message {739 .qr--message {
660 height: 1lh;740 height: 1lh;
661 overflow: hidden;741 overflow: hidden;
@@ -664,102 +744,124 @@
664 opacity: 0.5;744 opacity: 0.5;
665 }745 }
666 }746 }
667 }747 }
668 }748 }
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;
676 flex-wrap: wrap;758 flex-wrap: wrap;
677 flex-direction: row;759 flex-direction: row;
678 column-gap: 1em;760 column-gap: 1em;
679 color: var(--grey70);761 color: var(--grey70);
680 font-size: smaller;762 font-size: smaller;
681 align-items: baseline;763 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;
688 }770 }
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;
696 display: none;778 overflow: hidden;
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 {
702 visibility: visible;791 visibility: visible;
703 cursor: unset;792 cursor: unset;
704 }793 }
705 &::selection {794
706 color: unset;795 &::selection {
707 background-color: rgba(108 171 251 / 0.25);796 color: unset;
708 @supports (color: rgb(from white r g b / 0.25)) {797 background-color: rgba(108 171 251 / 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);
712 }801 }
713 }802 }
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;
720 overflow: hidden;809 padding: 0;
721 min-width: 100%;810 margin: 0;
722 width: 0;811 border: none;
723 > #qr--modal-messageSyntaxInner {812 overflow: hidden;
724 height: 100%;813 min-width: 100%;
725 }814 width: 0;
726 }815
727 > #qr--modal-message {816 >#qr--modal-messageSyntaxInner {
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;
744 }833 }
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
759 #qr--modal-icon {860 #qr--modal-icon {
760 height: 100%;861 height: 100%;
761 aspect-ratio: 1 / 1;862 aspect-ratio: 1 / 1;
762 }863 }
864
763 #qr--modal-executeButtons {865 #qr--modal-executeButtons {
764 display: flex;866 display: flex;
765 gap: 1em;867 gap: 1em;
@@ -778,7 +880,7 @@
778 }880 }
779881
780 #qr--modal-execute {882 #qr--modal-execute {
781 transition: 200ms;883 transition: var(--animation-duration-2x);
782 filter: grayscale(0);884 filter: grayscale(0);
783885
784 &.qr--busy {886 &.qr--busy {
@@ -819,16 +921,20 @@
819 border-color: rgb(215, 136, 114);921 border-color: rgb(215, 136, 114);
820 }922 }
821 }923 }
822 #qr--modal-debugButtons {924
823 display: none;925 #qr--modal-debugButtons {
824 gap: 1em;926 display: none;
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;
828 position: relative;932 position: relative;
933
829 &:not(.fa-solid) {934 &:not(.fa-solid) {
830 border-width: 1px;935 border-width: 1px;
831 border-style: solid;936 border-style: solid;
937
832 &:after {938 &:after {
833 content: '';939 content: '';
834 position: absolute;940 position: absolute;
@@ -839,23 +945,28 @@
839 mask-repeat: no-repeat;945 mask-repeat: no-repeat;
840 }946 }
841 }947 }
948
842 &#qr--modal-resume:after {949 &#qr--modal-resume:after {
843 mask-image: url('/img/step-resume.svg');950 mask-image: url('/img/step-resume.svg');
844 }951 }
952
845 &#qr--modal-step:after {953 &#qr--modal-step:after {
846 mask-image: url('/img/step-over.svg');954 mask-image: url('/img/step-over.svg');
847 }955 }
956
848 &#qr--modal-stepInto:after {957 &#qr--modal-stepInto:after {
849 mask-image: url('/img/step-into.svg');958 mask-image: url('/img/step-into.svg');
850 }959 }
960
851 &#qr--modal-stepOut:after {961 &#qr--modal-stepOut:after {
852 mask-image: url('/img/step-out.svg');962 mask-image: url('/img/step-out.svg');
853 }963 }
964
854 &#qr--modal-maximize {965 &#qr--modal-maximize {
855 display: none;966 display: none;
856 }967 }
857 }968 }
858 }969 }
859970
860 #qr--modal-send_textarea {971 #qr--modal-send_textarea {
861 flex: 0 0 auto;972 flex: 0 0 auto;
@@ -879,7 +990,7 @@
879 position: absolute;990 position: absolute;
880 inset: 0;991 inset: 0;
881 right: calc(100% - var(--prog) * 1%);992 right: calc(100% - var(--prog) * 1%);
882 transition: 200ms;993 transition: var(--animation-duration-2x);
883 }994 }
884995
885 &.qr--paused:after {996 &.qr--paused:after {
@@ -939,135 +1050,180 @@
939 overflow: auto;1050 overflow: auto;
940 min-width: 100%;1051 min-width: 100%;
941 width: 0;1052 width: 0;
942 white-space: pre-wrap;1053 white-space: pre-wrap;
943 }1054 }
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;
957 width: 0;1068 padding: 0.5em 0;
958 white-space: pre-wrap;1069 overflow: auto;
9591070 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;
965 grid-column: 1 / 4;1076 grid-template-columns: 0fr 1fr 1fr;
966 font-weight: bold;1077 column-gap: 0em;
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,
978 .qr--val {1089 .qr--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