Merge pull request #4189 from SillyTavern/staging Staging

a2efd8792c1cd0791c63bf6fa39ab03dada5d4bd

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

Signed
148 files changed, +4402 -1428Showing whitespace changes
.github/readme-de_de.md+9 -9
@@ -321,24 +321,24 @@ Start.bat --port 8000 --listen false
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| `--corsProxy` | Aktiviert CORS-Proxy. Wenn nicht angegeben. | boolean |
335| `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean |336| `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean |
336| `--ssl` | Aktiviert SSL | boolean |337| `--ssl` | Aktiviert SSL | boolean |
337| `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string |338| `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string |
338| `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string |339| `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string |
339| `--whitelist` | Aktiviert den Whitelist-Modus | boolean |340| `--whitelist` | Aktiviert den Whitelist-Modus | boolean |
340| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |341| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |
341| `--avoidLocalhost` | Vermeidet die Verwendung von „localhost“ für Autorun im Auto-Modus. | boolean |
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 |
.github/readme-ko_kr.md+9 -9
@@ -335,24 +335,24 @@ Start.bat --port 8000 --listen false
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| `--corsProxy` | CORS 프록시 활성화. | boolean |
349| `--disableCsrf` | CSRF 보호 비활성화 | boolean |350| `--disableCsrf` | CSRF 보호 비활성화 | boolean |
350| `--ssl` | SSL 활성화 | boolean |351| `--ssl` | SSL 활성화 | boolean |
351| `--certPath` | 인증서 파일 경로 | string |352| `--certPath` | 인증서 파일 경로 | string |
352| `--keyPath` | 프라이빗 키 파일 경로 | string |353| `--keyPath` | 프라이빗 키 파일 경로 | string |
353| `--whitelist` | 화이트리스트 모드 활성화 | boolean |354| `--whitelist` | 화이트리스트 모드 활성화 | boolean |
354| `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string |355| `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string |
355| `--avoidLocalhost` | 자동 모드에서 자동 실행 시 'localhost' 사용 방지 | boolean |
356| `--basicAuthMode` | 기본 인증 활성화 | boolean |356| `--basicAuthMode` | 기본 인증 활성화 | boolean |
357| `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean |357| `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean |
358| `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string |358| `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string |
.github/readme-ru_ru.md+9 -9
@@ -284,24 +284,24 @@ Start.bat --port 8000 --listen false
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| `--corsProxy` | Включает CORS-прокси. | boolean |
298| `--disableCsrf` | Отключает защиту от CSRF. | boolean |299| `--disableCsrf` | Отключает защиту от CSRF. | boolean |
299| `--ssl` | Включает SSL. | boolean |300| `--ssl` | Включает SSL. | boolean |
300| `--certPath` | Путь к файлу c сертификатом. | string |301| `--certPath` | Путь к файлу c сертификатом. | string |
301| `--keyPath` | Путь к файлу с закрытым ключом. | string |302| `--keyPath` | Путь к файлу с закрытым ключом. | string |
302| `--whitelist` | Включает режим белого списка. | boolean |303| `--whitelist` | Включает режим белого списка. | boolean |
303| `--dataRoot` | Корневой каталог для хранения данных. | string |304| `--dataRoot` | Корневой каталог для хранения данных. | string |
304| `--avoidLocalhost` | Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean |
305| `--basicAuthMode` | Включает простую аутентификацию. | boolean |305| `--basicAuthMode` | Включает простую аутентификацию. | boolean |
306| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |306| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |
307| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |307| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |
.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 显卡。有关更多详细信息,请查看您后端的文档。
49
50### 移动设备支持
51
52> 注意
5353
54> **此分叉可使用 Termux 在安卓手机上原生运行。请参考 ArroganceComplex#2659 编写的指南:**54### 建议的后端(非附属)
5555
56<https://rentry.org/STAI-Termux>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。
5760
58## 有问题或建议?61## 有问题或建议?
5962
60### 我们现在有了 Discord 社区63### Discord 服务器
6164
62获取支持,或分享喜爱的角色和 Prompt:65| [![][discord-shield-badge]][discord-link] | [加入我们的 Discord 社区!](https://discord.gg/sillytavern) 获取支持,分享喜爱的角色和 Prompt。 |
66| :---------------------------------------- | :---------------------------------------------------------------------------------------------- |
6367
64### [加入 Discord 社区](https://discord.gg/sillytavern)68或者直接与开发人员联系:
6569
66***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)
6773
68直接与开发人员联系:74### 我喜欢你的项目!我该如何贡献自己的力量?
6975
70* Discord: cohee 或 rossascends761. 发送 Pull Request。学习如何贡献:[CONTRIBUTING.md](../CONTRIBUTING.md)
71* Reddit:/u/RossAscends 或 /u/sillylossy772. 使用提供的模板发送功能建议和问题报告。
72* [发布 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues)783. 请先阅读整个 readme 文件并查看文档网站,以避免提交重复的问题。
7379
74## 此版本包括80## 屏幕截图
7581
76* 经过大量修改的 TavernAI 1.2.8(超过 50% 的代码经过重写或优化)82<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
77* 根据自定义规则自动重新生成消息83<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
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 仍是内部格式)
8784
88## 扩展85## 角色卡
8986
90SillyTavern 支持扩展服务。87SillyTavern 围绕“角色卡”的概念构建。角色卡是设定 LLM 行为的 Prompt 集合,是在 SillyTavern 中进行持久对话所必需的。它们的功能类似于 ChatGPT 的 GPTs 或 Poe 的 bots。角色卡的内容可以是任何东西:一个抽象的场景、一个为特定任务量身定制的助手、一个著名人物或一个虚构角色。
9188
92* 角色情绪识别89要在不选择角色卡的情况下进行快速对话或仅测试 LLM 连接,只需在打开 SillyTavern 后在欢迎屏幕的输入栏中键入您的 Prompt 输入。这将创建一个空的“助手”角色卡,您可以稍后自定义。
93* 聊天记录自动摘要
94* 在聊天窗口发送图片,并由人工智能解释图片内容
95* 文本图像生成(5 预设,以及 "自由模式")
96* 聊天信息的文字转语音(通过 ElevenLabs、Silero 或操作系统的语音生成)
9790
98扩展服务的完整功能介绍和使用教程,请参阅 [Docs](https://docs.sillytavern.app/)。91要大致了解如何定义角色卡,请参阅默认角色(Seraphina)或从“下载扩展和资源”菜单中下载选定的社区制作卡片。
9992
100## 界面/CSS/性能,由 RossAscends 调整并优化93## 主要功能
10194
102* 针对 iOS 系统优化了界面,并支持将快捷方式保存到主屏幕,在全屏模式下打开。95- 高级文本生成设置,包含许多社区制作的预设
103* 热键96- 世界书支持:创建丰富的传说或节省角色卡上的 Token
104 * 上 = 编辑聊天中的最后一条信息97- 群聊:多机器人房间,供角色与您或彼此交谈
105 * Ctrl+P = 编辑聊天中最后一条用户信息98- 丰富的 UI 自定义选项:主题颜色、背景图片、自定义 CSS 等
106 * 左 = 向左滑动99- 用户角色:让 AI 了解一些关于您的信息,以获得更强的沉浸感
107 * 右 = 向右滑动(注意:当聊天窗口输入内容时,轻扫快捷键将被禁用)100- 内置 RAG 支持:将文档添加到您的聊天中供 AI 参考
108 * Ctrl+左 = 查看本地存储的变量(在浏览器控制台窗口中)101- 广泛的聊天命令子系统和自己的[脚本引擎](https://docs.sillytavern.app/usage/st-script/)
109 * 回车(选择聊天栏)= 向人工智能发送信息
110 * Ctrl+Enter = 重新生成人工智能最后的回复
111102
112* 用户名更改和角色删除不再强制重新刷新页面。103## 扩展
113104
114* 增加在页面加载时自动连接 API 的选项。105SillyTavern 支持扩展。
115* 增加选项,在页面加载时自动加载最近的聊天信息。
116* 更好的 Tokens 计算器 - 适用于未保存的文字,并显示永久和临时 Tokens 数量
117106
118* 更好的聊天历史查询窗口107- 角色情绪表达
119 * 聊天的文件名以"(角色卡名称)+(创建时间)"的可读格式保存108- 聊天记录自动摘要
120 * 聊天历史预览从 40 个字符增加到 300 个字符。109- 自动 UI 和聊天翻译
121 * 聊天历史排序有多种选择(按名称、创建日期、聊天记录大小)。110- Stable Diffusion/FLUX/DALL-E 图像生成
111- AI 回复消息的文本转语音(通过 ElevenLabs、Silero 或操作系统的 TTS)
112- 网络搜索功能,为您的 Prompt 添加额外的现实世界背景信息
113- 更多扩展可从“下载扩展和资源”菜单中下载。
122114
123* 默认情况下,左侧和右侧弹出的设置面板会在点击其他区域时自动关闭。115有关如何使用它们的使用教程,请参阅[文档](https://docs.sillytavern.app/)。
124* 点击导航面板上的 "锁按钮" 将保持弹出面板打开,并在不同聊天中记住此设置。
125* 导航面板的打开或关闭状态也会跨聊天保存。
126116
127* 自定义聊天界面:117## ⌛ 安装
128 * 收到新消息时播放提示音
129 * 切换圆形或长方形头像样式
130 * 在台式电脑上拥有更宽的聊天窗口
131 * 可选的半透明玻璃效果聊天窗口
132 * 可定制 "主文本"、"引用文本 "和 "斜体文本 "的字体颜色。
133 * 可定制聊天界面的背景颜色和透明模糊程度
134118
135## 安装119### 🪟 Windows
136120
137*注意:SillyTavern 用于本地安装,尚未在 Colab 或其他云服务上进行全面测试。121> \[!WARNING]
122>
123> - 请勿安装到任何受 Windows 控制的文件夹(Program Files、System32 等)中。
124> - 请勿以管理员权限运行 Start.bat
125> - 无法在 Windows 7 上安装,因为它无法运行 NodeJS 18.16
138126
139> **警告**127#### 通过 Git 安装(推荐)
140128
141> 切勿安装到任何受 Windows 控制的系统文件夹(Program Files, System32, etc)中。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:
142135
143> 不要以管理员权限运行 start.bat136- Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
137- Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
144138
145### Windows1397. 等待所有内容克隆完成后,双击 `Start.bat` 以使 NodeJS 安装其依赖项。
1408. 然后服务器将启动,SillyTavern 将在您的浏览器中弹出。
146141
147通过 Git 安装(推荐使用,便于更新)142#### 通过 GitHub Desktop 安装
148143
149附有精美图片示例的简易指南:144(这**仅**允许在 GitHub Desktop 中使用 git,如果您也想在命令行上使用 `git`,则还需要安装 [Git for Windows](https://gitforwindows.org/))
150<https://docs.sillytavern.app/installation/windows/>
151145
1521. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)1461. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
153 2. 安装 [GitHub 客户端](https://central.github.com/deployments/desktop/desktop/latest/win32)1472. 安装 [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
154 3. 打开 Windows 资源管理器 (`Win+E`)1483. 安装 GitHub Desktop 后,点击 `Clone a repository from the internet....`(注意:此步骤**无需**创建 GitHub 帐户)
155 4. 浏览或创建一个不受 Windows 控制或监控的文件夹。(例如:C:\MySpecialFolder\)1494. 在菜单中,点击 URL 选项卡,输入此 URL `https://github.com/SillyTavern/SillyTavern`,然后点击 Clone。您可以更改本地路径以更改 SillyTavern 的下载位置。
156 5. 点击顶部的 "地址栏",在该文件夹内打开命令提示符,输入 `cmd`,然后按回车。1505. 要打开 SillyTavern,请使用 Windows 资源管理器浏览到克隆存储库的文件夹。默认情况下,存储库将克隆到此处:`C:\Users\[您的 Windows 用户名]\Documents\GitHub\SillyTavern`
157 6. 弹出黑框(CMD 命令提示符)后,键入以下其中一项并按 Enter:1516. 双击 `start.bat` 文件。(注意:文件名的 `.bat` 部分可能被您的操作系统隐藏,在这种情况下,它将显示为一个名为“`Start`”的文件。双击此文件以运行 SillyTavern)
1527. 双击后,应打开一个大的黑色命令控制台窗口,SillyTavern 将开始安装其运行所需的组件。
1538. 安装过程完成后,如果一切正常,命令控制台窗口应如下所示,并且您的浏览器中应打开一个 SillyTavern 选项卡:
1549. 连接到任何[支持的 API](https://docs.sillytavern.app/usage/api-connections/) 并开始聊天!
158155
159* 稳定分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`156### 🐧 Linux & 🍎 MacOS
160* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
161157
162 7. 等待 Git 克隆完成后,双击文件夹中的 `Start.bat` 将启动 NodeJS 并开始自动安装需要的软件包。158对于 MacOS / Linux,所有这些都将在终端中完成。
163 8. 然后 SillyTavern 服务就会自动启动,同时在浏览器新标签页中自动打开。
164159
165通过压缩包下载安装(不推荐)1601. 安装 git 和 nodeJS(具体方法取决于您的操作系统)
1612. 克隆仓库
166162
167 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)163- Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
168 2. 从该 GitHub 仓库下载压缩包。(从 [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest) 获取 "Source code(zip)")。164- Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
169 3. 将压缩包解压到您选择的文件夹中
170 4. 双击或在命令行中运行 `Start.bat`。
171 5. SillyTavern 服务自动为你准备好一切后,会在你的浏览器中打开一个新标签页。
172165
173### Linux1663. `cd SillyTavern` 导航到安装文件夹。
1674. 使用以下命令之一运行 `start.sh` 脚本:
174168
175 1.运行 `start.sh` 脚本。169- `./start.sh`
176 2.等待自动完成,然后开始享受170- `bash start.sh`
177171
178## API 密钥管理172## 🐋 通过 Docker 安装
179173
180SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。174这些说明假定您已安装 Docker,能够访问命令行以安装容器,并熟悉其常规操作。
181175
182默认情况下,输入密钥并重新加载页面后,密钥会自动隐藏以保证安全。176### 使用 GitHub Container Registry
183177
184如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置:178#### Docker Compose (最简单)
185179
1861. 打开 `config.yaml` 文件,将里面的 `allowKeysExposure` 设置为 `true`。180从 [GitHub 仓库](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) 获取 `docker-compose.yml` 文件,并在文件所在目录中运行以下命令。这将从 GitHub Container Registry 中拉取最新的 release 镜像并启动容器,自动创建必要的卷。
1872. 然后重启 SillyTavern 服务。
188181
189## 远程访问182```shell
183docker-compose up
184```
190185
191这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。186根据您的需求自定义 `docker-compose.yml` 文件。默认端口为 8000。如果您想使用环境变量调整服务器配置,请在此处阅读文档:[链接](https://docs.sillytavern.app/administration/config-yaml/#environment-variables)。
192187
193不过,SillyTavern 也可以被设置为允许从任何地方进行远程访问。188#### Docker CLI (高级)
194189
195**重要提示:SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。190您将需要两个强制性的目录映射和一个端口映射才能使 SillyTavern 正常运行。在命令中,替换以下位置中的选项:
196191
197### 1.管理白名单 IP192#### 容器变量
198193
199* 在你的 SillyTavern 文件夹中新建一个文本文件,名为 `whitelist.txt`。194##### 卷映射
200* 用文本编辑器打开该文件,添加你希望允许连接的 IP 地址列表。
201* 接受单个 IP 地址和 IP 范围,示例:
202195
203```196- `CONFIG_PATH` - SillyTavern 配置文件将存储在主机上的目录
204192.168.0.1197- `DATA_PATH` - SillyTavern 用户数据(包括角色)将存储在主机上的目录
205192.168.0.20198- `PLUGINS_PATH` - (可选) SillyTavern 服务器插件将存储在主机上的目录
206```199- `EXTENSIONS_PATH` - (可选) 全局 UI 扩展将存储在主机上的目录
207200
208或者201##### 端口映射
209202
210```203- `PUBLIC_PORT` - 暴露流量的端口。这是强制性的,因为您将从其虚拟机容器外部访问实例。**在未实现单独的安全服务的情况下,请勿将其暴露给互联网。**
211192.168.0.*
212```
213204
214(上述 IP 范围将允许局域网中的任何设备连接)205##### 附加设置
215206
216也接受子网掩码设置(如 10.0.0.0/24)。207- `SILLYTAVERN_VERSION` - 在此 GitHub 页面的右侧,您会看到“Packages”。选择“sillytavern”包,您将看到镜像版本。镜像标签“latest”将使您与当前 release 保持同步。您还可以使用指向相应分支的每日镜像的“staging”标签。
217208
218* 保存`whitelist.txt`文件。209#### 运行容器
219* 重启 SillyTavern 服务。
220210
221然后,文件中设置的 IP 就可以访问 SillyTavern 了。2111. 打开您的命令行
2122. 在您要存储配置和数据文件的文件夹中运行以下命令:
222213
223*注意:"config.yaml" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。214```bash
215SILLYTAVERN_VERSION="latest"
216PUBLIC_PORT="8000"
217CONFIG_PATH="./config"
218DATA_PATH="./data"
219PLUGINS_PATH="./plugins"
220EXTENSIONS_PATH="./extensions"
224221
225### 2.获取 SillyTavern 服务的 IP 地址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```
226231
227白名单设置完成后,您需要 SillyTavern 服务的 IP 地址。232> 默认情况下,容器将在前台运行。如果要在后台运行它,请将 `-d` 标志添加到 `docker run` 命令中。
228233
229如果 SillyTavern 服务设备在同一个局域网上,则使用安装 SillyTavern 服务的电脑的局域网 IP 地址:234### 自己构建镜像
230235
231* Windows:Windows 按钮 > 在搜索栏中输入 `cmd.exe` > 在打开的控制台中输入 `ipconfig`,回车 > 然后在输出中查找 `IPv4` 地址。236我们有一个关于在 Docker 中使用 SillyTavern 的综合指南[在此处](http://docs.sillytavern.app/installation/docker/),涵盖了 Windows、macOS 和 Linux 上的安装!如果您希望自己构建镜像,请阅读它。
232237
233如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。238## ⚡ 通过 SillyTavern Launcher 安装
234239
235* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。240SillyTavern Launcher 是一个安装向导,可帮助您进行多种选项的设置,包括为本地推理安装后端。
236241
237### 3. 使用其他设备访问 SillyTavern 服务242### 对于 Windows 用户
238243
239无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。2441. 在键盘上:按 **`WINDOWS + R`** 打开“运行”对话框。然后,运行以下命令安装 git:
240245
241同一局域网中的 SillyTavern 服务的典型默认地址如下:246```shell
247cmd /c winget install -e --id Git.Git
248```
242249
243`http://192.168.0.5:8000`2502. 在键盘上:按 **`WINDOWS + E`** 打开文件资源管理器,然后导航到要安装启动器的文件夹。进入所需文件夹后,在地址栏中键入 `cmd` 并按 Enter。然后,运行以下命令:
244251
245使用 http:// 而不是 https://252```shell
253git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
254```
246255
247### 向所有 IP 开放您的 SillyTavern 服务256### 对于 Linux 用户
248257
249我们不建议这样做,但您可以打开 `config.yaml` 并将里面的 `whitelistMode` 设置改为 `false`。2581. 打开您喜欢的终端并安装 git
2592. 使用以下命令克隆 SillyTavern-Launcher:
250260
251你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。261```shell
262git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
263```
252264
253这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。2653. 使用以下命令启动 installer.sh:
254266
255用户名和密码在`config.yaml`文件中设置。267```shell
268chmod +x install.sh && ./install.sh
269```
256270
257重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。2714. 安装后使用以下命令启动 launcher.sh:
272
273```shell
274chmod +x launcher.sh && ./launcher.sh
275```
276
277### 对于 Mac 用户
278
2791. 打开终端并使用以下命令安装 brew:
280
281```shell
282/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
283```
258284
259### 还是无法访问?2852. 使用以下命令安装 git:
260286
261* 为 `config.yaml` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。287```shell
262* 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。288brew install git
289```
263290
264### 性能问题?2913. 使用以下命令克隆 SillyTavern-Launcher:
265292
266尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。293```shell
294git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
295```
267296
268## 我喜欢你的项目!我该如何贡献自己的力量?2974. 使用以下命令启动 installer.sh:
269298
270### 应该299```shell
300chmod +x install.sh && ./install.sh
301```
271302
2721. 发送 Fork 请求3035. 安装后使用以下命令启动 launcher.sh:
2732. 使用规定的模板发送功能建议和问题报告
2743. 在提出任何问题之前,请先阅读 Readme 文件和文档
275304
276#### 不应该305```shell
306chmod +x launcher.sh && ./launcher.sh
307```
277308
2781. 提供金钱捐赠309## 📱 通过 Termux 在 Android OS 上安装
2792. 发送错误报告而不提供任何详细信息
2803. 提出已经回答过无数次的问题
281310
282## 我在哪里可以找到以前的聊天背景图片?311> \[!NOTE]
312> **SillyTavern 可以在 Android 设备上使用 Termux 原生运行,但我们不为此用例提供官方支持。**
313>
314> **请参阅 ArroganceComplex#2659 编写的本指南:**
315>
316> - <https://rentry.org/STAI-Termux>
283317
284我们正在实行 100% 原创内容的政策,因此旧的背景图片已从该资源库中删除。318**不支持的平台:android arm LEtime-web。** 32 位 Android 需要一个无法通过 npm 安装的外部依赖项。使用以下命令安装它:`pkg install esbuild`。然后运行常规安装步骤。
285319
286不过你可以在这里找到它们的存档:320## 命令行参数
287321
288<https://files.catbox.moe/1xevnc.zip>322您可以将命令行参数传递给 SillyTavern 服务器启动脚本,以覆盖 `config.yaml` 中的某些设置。
289323
290## 屏幕截图324### 示例
291325
292<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">326```shell
293<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">327node server.js --port 8000 --listen false
328# 或
329npm run start -- --port 8000 --listen false
330# 或 (仅限 Windows)
331Start.bat --port 8000 --listen false
332```
294333
295## 许可证和贡献334### 支持的参数
296335
297**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。**336> \[!TIP]
298337> 所有参数都不是必需的。如果您不提供它们,SillyTavern 将使用 `config.yaml` 中的设置。
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.**338
300339| 选项 | 描述 | 类型 |
301* TAI Base by Humi: Unknown license340| -------------------------------- | ---------------------------------------------- | ------- |
302* Cohee's modifications and derived code: AGPL v3341| `--version` | 显示版本号 | boolean |
303* RossAscends' additions: AGPL v3342| `--configPath` | 覆盖 config.yaml 文件的路径 | string |
304* Portions of CncAnon's TavernAITurbo mod: Unknown license343| `--dataRoot` | 数据存储的根目录 | string |
305* kingbri's various commits and suggestions (<https://github.com/bdashore3>)344| `--port` | 设置 SillyTavern 将在其下运行的端口 | number |
306* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)345| `--listen` | SillyTavern 将侦听所有网络接口 | boolean |
307* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)346| `--whitelist` | 启用白名单模式 | boolean |
308* Thanks Pygmalion University for being awesome testers and suggesting cool features!347| `--basicAuthMode` | 启用基本身份验证 | boolean |
309* Thanks oobabooga for compiling presets for TextGen348| `--enableIPv4` | 启用 IPv4 协议 | boolean |
310* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>349| `--enableIPv6` | 启用 IPv6 协议 | boolean |
311* Noto Sans font by Google (OFL license)350| `--listenAddressIPv4` | 要侦听的特定 IPv4 地址 | string |
312* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)351| `--listenAddressIPv6` | 要侦听的特定 IPv6 地址 | string |
313* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>352| `--dnsPreferIPv6` | DNS 首选 IPv6 | boolean |
314* Linux startup script by AlpinDale353| `--ssl` | 启用 SSL | boolean |
315* Thanks paniphons for providing a FAQ document354| `--certPath` | 您的证书文件路径 | string |
316* 10K Discord Users Celebratory Background by @kallmeflocc355| `--keyPath` | 您的私钥文件路径 | string |
317* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc356| `--browserLaunchEnabled` | 自动在浏览器中启动 SillyTavern | boolean |
318* Korean translation by @doloroushyeonse357| `--browserLaunchHostname` | 自动运行主机名 | string |
319* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3358| `--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+9 -9
@@ -320,24 +320,24 @@ Start.bat --port 8000 --listen false
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| `--corsProxy` | 啟用 CORS 代理。 | boolean |
334| `--disableCsrf` | 停用 CSRF 保護 | boolean |335| `--disableCsrf` | 停用 CSRF 保護 | boolean |
335| `--ssl` | 啟用 SSL | boolean |336| `--ssl` | 啟用 SSL | boolean |
336| `--certPath` | 設定您證書文件的路徑 | string |337| `--certPath` | 設定您證書文件的路徑 | string |
337| `--keyPath` | 設定您私人金鑰文件的路徑 | string |338| `--keyPath` | 設定您私人金鑰文件的路徑 | string |
338| `--whitelist` | 啟用白名單模式 | boolean |339| `--whitelist` | 啟用白名單模式 | boolean |
339| `--dataRoot` | 設定數據儲存的根目錄 | string |340| `--dataRoot` | 設定數據儲存的根目錄 | string |
340| `--avoidLocalhost` | 在自動模式下避免使用 'localhost' | boolean |
341| `--basicAuthMode` | 啟用基本身份驗證模式 | boolean |341| `--basicAuthMode` | 啟用基本身份驗證模式 | boolean |
342| `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean |342| `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean |
343| `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議)| string |343| `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議)| string |
.github/readme.md+27 -5
@@ -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.
@@ -334,7 +356,7 @@ Start.bat --port 8000 --listen false
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 |
@@ -350,10 +372,10 @@ Start.bat --port 8000 --listen false
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 |
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+19 -13
@@ -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 --
20browserLaunch:
21 # Open the browser automatically on server startup.
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.
20 # - Use "auto" to let the server decide29 # - Use "auto" to let the server decide
21 # - Use options like 'localhost', 'st.example.com'30 # - Use options like 'localhost', 'st.example.com'
22autorunHostname: "auto"31 hostname: 'auto'
23# Server port32 # Overrides the port for run in the browser.
24port: 8000
25# Overrides the port for autorun in browser.
26 # - Use -1 to use the server port.33 # - Use -1 to use the server port.
27 # - Specify a port to override the default.34 # - Specify a port to override the default.
28autorunPortOverride: -135 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
39# Server port
40port: 8000
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+400 -218
@@ -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,7 +661,7 @@
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>
@@ -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">
3249 <h4 data-i18n="Google Vertex AI Configuration">Google Vertex AI Configuration</h4>
3250
3251 <!-- Authentication Mode Selection -->
3252 <div class="flex-container">
3253 <label for="vertexai_auth_mode" data-i18n="Authentication Mode">Authentication Mode:</label>
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">
3228 <h4>3262 <h4>
3229 <span data-i18n="Google Vertex AI API Key">3263 <span data-i18n="Google Vertex AI Configuration">
3230 Google Vertex AI API Key3264 Google Vertex AI Configuration
3231 </span>3265 </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">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">
3233 (Express mode keys only)3267 (Express mode)
3234 </a>3268 </a>
3235 </h4>3269 </h4>
3270 <!-- API Key -->
3271 <div class="flex-container flexFlowColumn">
3272 <label for="api_key_vertexai" data-i18n="API Key">API Key:</label>
3236 <div class="flex-container">3273 <div class="flex-container">
3237 <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off">3274 <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off">
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>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>
3239 </div>3324 </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.">
3241 For privacy reasons, your API key will be hidden after you reload the page.
3242 </div>3325 </div>
3326
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>
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,7 +3882,7 @@
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>
@@ -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">
@@ -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,6 +5550,8 @@
5376 </div>5550 </div>
5377 </div>5551 </div>
5378 <!-- end group peeking cope structure-->5552 <!-- end group peeking cope structure-->
5553
5554 <div class="scrollableInner">
5379 <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">5555 <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
5380 <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">5556 <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5381 <div id="avatar-and-name-block">5557 <div id="avatar-and-name-block">
@@ -5711,6 +5887,7 @@
5711 </div>5887 </div>
5712 <div id="rm_print_characters_block" class="flexFlowColumn"></div>5888 <div id="rm_print_characters_block" class="flexFlowColumn"></div>
5713 </div>5889 </div>
5890 </div>
5714 </nav>5891 </nav>
5715 </div>5892 </div>
5716 </div>5893 </div>
@@ -6086,79 +6263,7 @@
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">
6089 <div class="world_entry">6266 <div class="world_entry_edit">
6090 <form class="world_entry_form wi-card-entry">
6091 <div class="inline-drawer wide100p">
6092 <div class="inline-drawer-header gap5px padding0">
6093 <span class="drag-handle">&#9776;</span>
6094 <div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
6095 <div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6096 <div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" data-i18n="[title]Toggle entry's active state." title="Toggle entry's active state."></div>
6097 <div class="flex-container alignitemscenter wide100p">
6098 <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
6099 <div class="flex-container flex1">
6100 <textarea class="text_pole" rows="1" name="comment" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
6101 </div>
6102 <!-- <span class="world_entry_form_position_value"></span> -->
6103 <select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized" title="WI Entry Status:&#13;🔵 Constant&#13;🟢 Normal&#13;🔗 Vectorized" name="entryStateSelector" class="text_pole widthNatural margin0">
6104 <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
6105 <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
6106 <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
6107 </select>
6108 </div>
6109 <div class="WIEnteryHeaderControls flex-container">
6110 <div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text">
6111 <label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label>
6112 <select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑EM: Before Example Messages&#13;↓EM: After Example Messages&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)">
6113 <option value="0" data-role="" data-i18n="Before Char Defs">
6114 ↑Char
6115 </option>
6116 <option value="1" data-role="" data-i18n="After Char Defs">
6117 ↓Char
6118 </option>
6119 <option value="5" data-role="" data-i18n="Before EM">
6120 ↑EM
6121 </option>
6122 <option value="6" data-role="" data-i18n="After EM">
6123 ↓EM
6124 </option>
6125 <option value="2" data-role="" data-i18n="Before AN">
6126 ↑AN
6127 </option>
6128 <option value="3" data-role="" data-i18n="After AN">
6129 ↓AN
6130 </option>
6131 <option value="4" data-role="0" data-i18n="at Depth System">
6132 @D ⚙️
6133 </option>
6134 <option value="4" data-role="1" data-i18n="at Depth User">
6135 @D 👤
6136 </option>
6137 <option value="4" data-role="2" data-i18n="at Depth AI">
6138 @D 🤖
6139 </option>
6140 </select>
6141 </div>
6142 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6143 <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
6144 <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
6145 </div>
6146 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6147 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
6148 <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
6149 </div>
6150 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
6151 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
6152 <input title="Probability" data-i18n="[title]Probability" class="text_pole wideMax100px margin0" type="number" name="probability" placeholder="" min="0" max="100" />
6153 </div>
6154 </div>
6155 </div>
6156 </div>
6157 <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i>
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>
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>
6160 </div>
6161 <div class="inline-drawer-content flex-container paddingBottom5px wide100p">
6162 <div class="flex-container wide100p alignitemscenter">6267 <div class="flex-container wide100p alignitemscenter">
6163 <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">6268 <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
6164 <div class="world_entry_form_control keyprimary flex1">6269 <div class="world_entry_form_control keyprimary flex1">
@@ -6255,13 +6360,13 @@
6255 </span>6360 </span>
6256 <div>6361 <div>
6257 <label class="checkbox flex-container alignitemscenter flexNoGap">6362 <label class="checkbox flex-container alignitemscenter flexNoGap">
6258 <input type="checkbox" name="exclude_recursion" />6363 <input type="checkbox" name="excludeRecursion" />
6259 <span data-i18n="Non-recursable (will not be activated by another)">6364 <span data-i18n="Non-recursable (will not be activated by another)">
6260 Non-recursable (will not be activated by another)6365 Non-recursable (will not be activated by another)
6261 </span>6366 </span>
6262 </label>6367 </label>
6263 <label class="checkbox flex-container alignitemscenter flexNoGap">6368 <label class="checkbox flex-container alignitemscenter flexNoGap">
6264 <input type="checkbox" name="prevent_recursion" />6369 <input type="checkbox" name="preventRecursion" />
6265 <span data-i18n="Prevent further recursion (this entry will not activate others)">6370 <span data-i18n="Prevent further recursion (this entry will not activate others)">
6266 Prevent further recursion (will not activate others)6371 Prevent further recursion (will not activate others)
6267 </span>6372 </span>
@@ -6450,6 +6555,81 @@
6450 </div>6555 </div>
6451 </div>6556 </div>
6452 </div>6557 </div>
6558 <div class="world_entry">
6559 <form class="world_entry_form wi-card-entry">
6560 <div class="inline-drawer wide100p">
6561 <div class="inline-drawer-header gap5px padding0">
6562 <span class="drag-handle">&#9776;</span>
6563 <div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
6564 <div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6565 <div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" data-i18n="[title]Toggle entry's active state." title="Toggle entry's active state."></div>
6566 <div class="flex-container alignitemscenter wide100p">
6567 <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
6568 <div class="flex-container flex1">
6569 <textarea class="text_pole" rows="1" name="comment" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
6570 </div>
6571 <!-- <span class="world_entry_form_position_value"></span> -->
6572 <select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized" title="WI Entry Status:&#13;🔵 Constant&#13;🟢 Normal&#13;🔗 Vectorized" name="entryStateSelector" class="text_pole widthNatural margin0">
6573 <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
6574 <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
6575 <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
6576 </select>
6577 </div>
6578 <div class="WIEnteryHeaderControls flex-container">
6579 <div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text">
6580 <label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label>
6581 <select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑EM: Before Example Messages&#13;↓EM: After Example Messages&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)">
6582 <option value="0" data-role="" data-i18n="Before Char Defs">
6583 ↑Char
6584 </option>
6585 <option value="1" data-role="" data-i18n="After Char Defs">
6586 ↓Char
6587 </option>
6588 <option value="5" data-role="" data-i18n="Before EM">
6589 ↑EM
6590 </option>
6591 <option value="6" data-role="" data-i18n="After EM">
6592 ↓EM
6593 </option>
6594 <option value="2" data-role="" data-i18n="Before AN">
6595 ↑AN
6596 </option>
6597 <option value="3" data-role="" data-i18n="After AN">
6598 ↓AN
6599 </option>
6600 <option value="4" data-role="0" data-i18n="at Depth System">
6601 @D ⚙️
6602 </option>
6603 <option value="4" data-role="1" data-i18n="at Depth User">
6604 @D 👤
6605 </option>
6606 <option value="4" data-role="2" data-i18n="at Depth AI">
6607 @D 🤖
6608 </option>
6609 </select>
6610 </div>
6611 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6612 <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
6613 <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
6614 </div>
6615 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6616 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
6617 <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
6618 </div>
6619 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
6620 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
6621 <input title="Probability" data-i18n="[title]Probability" class="text_pole wideMax100px margin0" type="number" name="probability" placeholder="" min="0" max="100" />
6622 </div>
6623 </div>
6624 </div>
6625 </div>
6626 <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></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>
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>
6629 </div>
6630 <div class="inline-drawer-content inline-drawer-outlet flex-container paddingBottom5px wide100p ">
6631 <!-- Outlet for world_entry_edit -->
6632 </div>
6453 </div>6633 </div>
6454 </form>6634 </form>
6455 </div>6635 </div>
@@ -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+336 -240
@@ -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);
10038 await createOrEditCharacter();10141 await createOrEditCharacter();
10039 }10142 }
10040 }
1004110143
10144 if (currentChatId) {
10042 await reloadCurrentChat();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) => {
10233 slideToggle(el, {
10234 ...getSlideToggleOptions(),
10235 elementDisplayStyle: 'flex',
10236 onAnimationEnd: function (el) {
10237 el.closest('.drawer-content').classList.remove('resizing');
10238 favsToHotswap();10373 favsToHotswap();
10239 $('#rm_print_characters_block').trigger('scroll');10374 $('#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 });
10260 }
10261
10262 } else if (drawerWasOpenAlready) { //to close manually
10263 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 }10382 }
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 }10383 }
1028510384 } else if (drawerWasOpenAlready) {
10385 icon.toggleClass('closedIcon openIcon');
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, {
11949 ...getSlideToggleOptions(),
11950 onAnimationEnd: (el) => {
11951 el.closest('.drawer-content').classList.remove('resizing');
11952 },
11953 });
11954 });
11955 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');12052 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
11956 $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');12053 $openDrawers.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 }
554
555 // Prevent resizing offscreen
556 if (top + elmnt.height() >= winHeight) {
557 elmnt.css('height', winHeight - top - 1 + 'px');
558 elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
559 }564 }
560565 if (top + $elmnt.height() >= winHeight) {
561 if (left + elmnt.width() >= winWidth) {566 $elmnt.css('height', winHeight - top - 1 + 'px');
562 elmnt.css('width', winWidth - left - 1 + 'px');567 $elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
563 elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
564 }568 }
565 } else { //prevent divs that are not zoomedAvatars from resizing offscreen569 if (left + $elmnt.width() >= winWidth) {
566570 $elmnt.css('width', winWidth - left - 1 + 'px');
567 if (top + elmnt.height() >= winHeight) {571 $elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
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 }
580
581 //set css to prevent weird resize behavior (does not save)
582 elmnt.css('left', left);
583 elmnt.css('top', top);
584
585 //set a listener for mouseup to save new width/height
586 $(window).off('mouseup').on('mouseup', () => {
587 console.log(`Saving ${elmntName} Height/Width`);
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 }579 }
593580 $elmnt.css({ left, top });
594 power_user.movingUIState[elmntName].width = width;581 $elmnt.off('mouseup').on('mouseup', () => {
595 power_user.movingUIState[elmntName].height = height;582 if (
596 eventSource.emit('resizeUI', elmntName);583 power_user.movingUIState[elmntName].width === $elmnt.width() &&
597 saveSettingsDebounced();584 power_user.movingUIState[elmntName].height === $elmnt.height()
598 imgHeight = null;585 ) return;
599 imgWidth = null;586 savePositionAndSize();
600 height = null;587 observer.disconnect();
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();
700 top = null;
701 left = null;
702 right = null;
703 bottom = null;
704 maxX = null;
705 maxY = null;
706 }633 }
634
635 // Setup event listeners
636 if ($elmntHeader.length) {
637 $elmntHeader.off('mousedown').on('mousedown', (e) => {
638 if ($(e.target).hasClass('drag-grabber')) {
639 actionType = 'drag';
640 isMouseDown = true;
641 observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
642 dragMouseDown(e);
643 }
644 });
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+10 -4
@@ -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;
@@ -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 /**
@@ -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
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+26 -7
@@ -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];
51 }
52
53 return [];
54 }
55
56 if (this_chid === undefined) {
57 return [];
58 }
59
60 const character = characters[this_chid];
61 if (!character) {
62 return [];
63 }
64
48 const response = await fetch('/api/characters/chats', {65 const response = await fetch('/api/characters/chats', {
49 method: 'POST',66 method: 'POST',
50 headers: getRequestHeaders(),67 headers: getRequestHeaders(),
51 body: JSON.stringify({ avatar_url: characters[this_chid].avatar }),68 body: JSON.stringify({ avatar_url: character.avatar, simple: true }),
52 });69 });
5370
54 if (response.ok) {71 if (response.ok) {
55 const data = await response.json();72 const data = await response.json();
56 return Object.values(data).map(x => x.file_name.replace('.jsonl', ''));73 const chats = Object.values(data).map(x => x.file_name.replace('.jsonl', ''));
57 }74 return [...chats];
58 }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+6 -0
@@ -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
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+100 -13
@@ -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);
404 }454 } catch (error) {
405 catch (error) {455 console.error('Could not activate extension', name, error);
406 console.error('Could not activate extension', name);456 }
407 console.error(error);457 } else if (!meetsModuleRequirements && !isDisabled) {
458 console.warn(t`Extension "${name}" did not load. Missing required Extras module(s): "${missingModules.join(', ')}"`);
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+67 -3
@@ -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,11 +689,21 @@ 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
692 if (deleteModeActive) {
693 Popup.show.confirm(t`Are you sure you want to delete this image?`, url)
694 .then(async (confirmed) => {
695 if (!confirmed) {
696 return;
697 }
698 deleteGalleryItem(url).then(() => showCharGallery(deleteModeActive));
699 });
700 } else {
638 // ID should just be the last part of the URL, removing the extension701 // ID should just be the last part of the URL, removing the extension
639 const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));702 const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
640 makeDragImg(id, url);703 makeDragImg(id, url);
641 }704 }
642 }705 }
706}
643707
644708
645// Registers a simple command for opening the char gallery.709// Registers a simple command for opening the char gallery.
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+11 -14
@@ -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;
@@ -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+201 -37
@@ -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,6 +416,7 @@
384 align-items: baseline;416 align-items: baseline;
385 }417 }
386 }418 }
419
387 >#qr--autoExec {420 >#qr--autoExec {
388 .checkbox_label {421 .checkbox_label {
389 text-wrap: nowrap;422 text-wrap: nowrap;
@@ -433,44 +466,52 @@
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;
487
451 >#qr--main,488 >#qr--main,
452 >#qr--resizeHandle,489 >#qr--resizeHandle,
453 >#qr--qrOptions>h3,490 >#qr--qrOptions>h3,
454 >#qr--qrOptions>#qr--modal-executeButtons,491 >#qr--qrOptions>#qr--modal-executeButtons,
455 > #qr--qrOptions > #qr--modal-executeProgress492 >#qr--qrOptions>#qr--modal-executeProgress {
456 {
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 }
514
474 &:has(.qr--isExecuting) {515 &:has(.qr--isExecuting) {
475 .popup-controls {516 .popup-controls {
476 display: none;517 display: none;
@@ -481,10 +522,12 @@
481 z-index: 50000;522 z-index: 50000;
482 pointer-events: none;523 pointer-events: none;
483 background-color: rgb(47 150 180 / 0.5);524 background-color: rgb(47 150 180 / 0.5);
525
484 &.qr--unresolved {526 &.qr--unresolved {
485 background-color: rgb(255 255 0 / 0.5);527 background-color: rgb(255 255 0 / 0.5);
486 }528 }
487 }529 }
530
488 .qr--highlight-secondary {531 .qr--highlight-secondary {
489 position: absolute;532 position: absolute;
490 z-index: 50000;533 z-index: 50000;
@@ -505,6 +548,7 @@
505 overflow: hidden;548 overflow: hidden;
506549
507 &.qr--isExecuting {550 &.qr--isExecuting {
551
508 #qr--main>h3:first-child,552 #qr--main>h3:first-child,
509 #qr--main>.qr--labels,553 #qr--main>.qr--labels,
510 #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,554 #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,
@@ -512,28 +556,32 @@
512 #qr--qrOptions>#qr--ctxEditor,556 #qr--qrOptions>#qr--ctxEditor,
513 #qr--qrOptions>.qr--ctxEditorActions,557 #qr--qrOptions>.qr--ctxEditorActions,
514 #qr--qrOptions>.qr--ctxEditorActions+h3,558 #qr--qrOptions>.qr--ctxEditorActions+h3,
515 #qr--qrOptions > .qr--ctxEditorActions + h3 + div559 #qr--qrOptions>.qr--ctxEditorActions+h3+div {
516 {
517 display: none;560 display: none;
518 }561 }
562
519 #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message {563 #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message {
520 visibility: hidden;564 visibility: hidden;
521 }565 }
566
522 #qr--modal-debugButtons {567 #qr--modal-debugButtons {
523 display: flex;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,31 +589,38 @@
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 }
610
558 #qr--resizeHandle {611 #qr--resizeHandle {
559 width: 6px;612 width: 6px;
560 background-color: var(--SmartThemeBorderColor);613 background-color: var(--SmartThemeBorderColor);
561 border: 2px solid var(--SmartThemeBlurTintColor);614 border: 2px solid var(--SmartThemeBlurTintColor);
562 transition: border-color 200ms, background-color 200ms;615 transition: border-color var(--animation-duration-2x), background-color var(--animation-duration-2x);
563 cursor: w-resize;616 cursor: w-resize;
617
564 &:hover {618 &:hover {
565 background-color: var(--SmartThemeQuoteColor);619 background-color: var(--SmartThemeQuoteColor);
566 border-color: var(--SmartThemeQuoteColor);620 border-color: var(--SmartThemeQuoteColor);
567 }621 }
568 }622 }
623
569 #qr--qrOptions {624 #qr--qrOptions {
570 width: var(--width, auto);625 width: var(--width, auto);
571 }626 }
@@ -576,38 +631,48 @@
576 display: flex;631 display: flex;
577 flex-direction: column;632 flex-direction: column;
578 overflow: hidden;633 overflow: hidden;
634
579 >.qr--labels {635 >.qr--labels {
580 flex: 0 0 auto;636 flex: 0 0 auto;
581 display: flex;637 display: flex;
582 flex-direction: row;638 flex-direction: row;
583 gap: 0.5em;639 gap: 0.5em;
584 padding: 1px;640 padding: 1px;
585 > label, > .label {641
642 >label,
643 >.label {
586 flex: 1 1 1px;644 flex: 1 1 1px;
587 display: flex;645 display: flex;
588 flex-direction: column;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 }
663
602 .qr--labelText {664 .qr--labelText {
603 flex: 1 1 auto;665 flex: 1 1 auto;
604 }666 }
667
605 .qr--labelHint {668 .qr--labelHint {
606 flex: 1 1 auto;669 flex: 1 1 auto;
607 }670 }
671
608 input {672 input {
609 flex: 0 0 auto;673 flex: 0 0 auto;
610 }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: "[";
658 }732 }
733
734 &:after {
735 content: "]";
736 }
737 }
738
659 .qr--message {739 .qr--message {
660 height: 1lh;740 height: 1lh;
661 overflow: hidden;741 overflow: hidden;
@@ -666,11 +746,13 @@
666 }746 }
667 }747 }
668 }748 }
749
669 >.qr--modal-messageContainer {750 >.qr--modal-messageContainer {
670 flex: 1 1 auto;751 flex: 1 1 auto;
671 display: flex;752 display: flex;
672 flex-direction: column;753 flex-direction: column;
673 overflow: hidden;754 overflow: hidden;
755
674 >.qr--modal-editorSettings {756 >.qr--modal-editorSettings {
675 display: flex;757 display: flex;
676 flex-wrap: wrap;758 flex-wrap: wrap;
@@ -679,38 +761,48 @@
679 color: var(--grey70);761 color: var(--grey70);
680 font-size: smaller;762 font-size: smaller;
681 align-items: baseline;763 align-items: baseline;
764
682 >.checkbox_label {765 >.checkbox_label {
683 white-space: nowrap;766 white-space: nowrap;
767
684 >input {768 >input {
685 font-size: inherit;769 font-size: inherit;
686 }770 }
687 }771 }
688 }772 }
773
689 >#qr--modal-messageHolder {774 >#qr--modal-messageHolder {
690 flex: 1 1 auto;775 flex: 1 1 auto;
691 display: grid;776 display: grid;
692 text-align: left;777 text-align: left;
693 overflow: hidden;778 overflow: hidden;
779
694 &.qr--noSyntax {780 &.qr--noSyntax {
695 >#qr--modal-messageSyntax {781 >#qr--modal-messageSyntax {
696 display: none;782 display: none;
697 }783 }
784
698 >#qr--modal-message {785 >#qr--modal-message {
699 background-color: var(--ac-style-color-background);786 background-color: var(--ac-style-color-background);
700 color: var(--ac-style-color-text);787 color: var(--ac-style-color-text);
701 &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {788
789 &::-webkit-scrollbar,
790 &::-webkit-scrollbar-thumb {
702 visibility: visible;791 visibility: visible;
703 cursor: unset;792 cursor: unset;
704 }793 }
794
705 &::selection {795 &::selection {
706 color: unset;796 color: unset;
707 background-color: rgba(108 171 251 / 0.25);797 background-color: rgba(108 171 251 / 0.25);
798
708 @supports (color: rgb(from white r g b / 0.25)) {799 @supports (color: rgb(from white r g b / 0.25)) {
709 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);800 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
710 }801 }
711 }802 }
712 }803 }
713 }804 }
805
714 >#qr--modal-messageSyntax {806 >#qr--modal-messageSyntax {
715 grid-column: 1;807 grid-column: 1;
716 grid-row: 1;808 grid-row: 1;
@@ -720,10 +812,12 @@
720 overflow: hidden;812 overflow: hidden;
721 min-width: 100%;813 min-width: 100%;
722 width: 0;814 width: 0;
815
723 >#qr--modal-messageSyntaxInner {816 >#qr--modal-messageSyntaxInner {
724 height: 100%;817 height: 100%;
725 }818 }
726 }819 }
820
727 >#qr--modal-message {821 >#qr--modal-message {
728 background-color: transparent;822 background-color: transparent;
729 color: transparent;823 color: transparent;
@@ -731,19 +825,25 @@
731 grid-row: 1;825 grid-row: 1;
732 caret-color: var(--ac-style-color-text);826 caret-color: var(--ac-style-color-text);
733 overflow: auto;827 overflow: auto;
734 &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {828
829 &::-webkit-scrollbar,
830 &::-webkit-scrollbar-thumb {
735 visibility: hidden;831 visibility: hidden;
736 cursor: default;832 cursor: default;
737 }833 }
834
738 &::selection {835 &::selection {
739 color: transparent;836 color: transparent;
740 background-color: rgba(108 171 251 / 0.25);837 background-color: rgba(108 171 251 / 0.25);
838
741 @supports (color: rgb(from white r g b / 0.25)) {839 @supports (color: rgb(from white r g b / 0.25)) {
742 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);840 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
743 }841 }
744 }842 }
745 }843 }
746 #qr--modal-message, #qr--modal-messageSyntaxInner {844
845 #qr--modal-message,
846 #qr--modal-messageSyntaxInner {
747 font-family: var(--monoFontFamily);847 font-family: var(--monoFontFamily);
748 padding: 0.75em;848 padding: 0.75em;
749 margin: 0;849 margin: 0;
@@ -756,10 +856,12 @@
756 }856 }
757 }857 }
758 }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 }
924
822 #qr--modal-debugButtons {925 #qr--modal-debugButtons {
823 display: none;926 display: none;
824 gap: 1em;927 gap: 1em;
928
825 .qr--modal-debugButton {929 .qr--modal-debugButton {
826 aspect-ratio: 1.25 / 1;930 aspect-ratio: 1.25 / 1;
827 width: 2.25em;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,18 +945,23 @@
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 }
@@ -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 {
@@ -941,11 +1052,14 @@
941 width: 0;1052 width: 0;
942 white-space: pre-wrap;1053 white-space: pre-wrap;
943 }1054 }
1055
944 #qr--modal-debugState {1056 #qr--modal-debugState {
945 display: none;1057 display: none;
1058
946 &.qr--active {1059 &.qr--active {
947 display: block;1060 display: block;
948 }1061 }
1062
949 text-align: left;1063 text-align: left;
950 font-size: smaller;1064 font-size: smaller;
951 font-family: var(--monoFontFamily);1065 font-family: var(--monoFontFamily);
@@ -961,6 +1075,7 @@
961 display: grid;1075 display: grid;
962 grid-template-columns: 0fr 1fr 1fr;1076 grid-template-columns: 0fr 1fr 1fr;
963 column-gap: 0em;1077 column-gap: 0em;
1078
964 .qr--title {1079 .qr--title {
965 grid-column: 1 / 4;1080 grid-column: 1 / 4;
966 font-weight: bold;1081 font-weight: bold;
@@ -969,47 +1084,66 @@
969 padding: 0.25em;1084 padding: 0.25em;
970 margin-top: 0.5em;1085 margin-top: 0.5em;
971 }1086 }
972 .qr--var, .qr--macro, .qr--pipe {1087
1088 .qr--var,
1089 .qr--macro,
1090 .qr--pipe {
973 display: contents;1091 display: contents;
1092
974 &:nth-child(2n + 1) {1093 &:nth-child(2n + 1) {
975 .qr--key, .qr--val {1094
1095 .qr--key,
1096 .qr--val {
976 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);1097 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
977 }1098 }
1099
978 .qr--val {1100 .qr--val {
979 &:nth-child(2n) {1101 &:nth-child(2n) {
980 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);1102 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
981 }1103 }
1104
982 &:hover {1105 &:hover {
983 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);1106 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
984 }1107 }
985 }1108 }
986 }1109 }
1110
987 &:nth-child(2n) {1111 &:nth-child(2n) {
988 .qr--val {1112 .qr--val {
989 &:nth-child(2n) {1113 &:nth-child(2n) {
990 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);1114 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
991 }1115 }
1116
992 &:hover {1117 &:hover {
993 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);1118 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
994 }1119 }
995 }1120 }
996 }1121 }
1122
997 &.qr--isHidden {1123 &.qr--isHidden {
998 .qr--key, .qr--val {1124
1125 .qr--key,
1126 .qr--val {
999 opacity: 0.5;1127 opacity: 0.5;
1000 }1128 }
1001 }1129 }
1130
1002 .qr--val {1131 .qr--val {
1003 grid-column: 2 / 4;1132 grid-column: 2 / 4;
1133
1004 &.qr--singleCol {1134 &.qr--singleCol {
1005 grid-column: unset;1135 grid-column: unset;
1006 }1136 }
1137
1007 &.qr--simple {1138 &.qr--simple {
1008 &:before, &:after {1139
1140 &:before,
1141 &:after {
1009 content: '"';1142 content: '"';
1010 color: var(--SmartThemeQuoteColor);1143 color: var(--SmartThemeQuoteColor);
1011 }1144 }
1012 }1145 }
1146
1013 &.qr--unresolved {1147 &.qr--unresolved {
1014 &:after {1148 &:after {
1015 content: '-UNRESOLVED-';1149 content: '-UNRESOLVED-';
@@ -1019,21 +1153,36 @@
1019 }1153 }
1020 }1154 }
1021 }1155 }
1156
1022 .qr--key {1157 .qr--key {
1023 margin-left: 0.5em;1158 margin-left: 0.5em;
1024 padding-right: 1em;1159 padding-right: 1em;
1025 &:after { content: ": "; }1160
1161 &:after {
1162 content: ": ";
1026 }1163 }
1027 .qr--pipe, .qr--macro {1164 }
1165
1166 .qr--pipe,
1167 .qr--macro {
1028 >.qr--key {1168 >.qr--key {
1029 &:before { content: "{{"; }1169 &:before {
1030 &:after { content: "}}: "; }1170 content: "{{";
1171 }
1172
1173 &:after {
1174 content: "}}: ";
1175 }
1031 }1176 }
1032 }1177 }
1178
1033 .qr--scope {1179 .qr--scope {
1034 display: contents;1180 display: contents;
1181
1035 .qr--pipe {1182 .qr--pipe {
1036 .qr--key, .qr--val {1183
1184 .qr--key,
1185 .qr--val {
1037 opacity: 0.5;1186 opacity: 0.5;
1038 }1187 }
1039 }1188 }
@@ -1043,6 +1192,7 @@
1043 .qr--stack {1192 .qr--stack {
1044 display: grid;1193 display: grid;
1045 grid-template-columns: 1fr 0fr;1194 grid-template-columns: 1fr 0fr;
1195
1046 .qr--title {1196 .qr--title {
1047 grid-column: 1 / 3;1197 grid-column: 1 / 3;
1048 font-weight: bold;1198 font-weight: bold;
@@ -1051,16 +1201,22 @@
1051 padding: 0.25em;1201 padding: 0.25em;
1052 margin-top: 1em;1202 margin-top: 1em;
1053 }1203 }
1204
1054 .qr--item {1205 .qr--item {
1055 display: contents;1206 display: contents;
1207
1056 &:nth-child(2n + 1) {1208 &:nth-child(2n + 1) {
1057 .qr--name, .qr--source {1209
1210 .qr--name,
1211 .qr--source {
1058 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);1212 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
1059 }1213 }
1060 }1214 }
1215
1061 .qr--name {1216 .qr--name {
1062 margin-left: 0.5em;1217 margin-left: 0.5em;
1063 }1218 }
1219
1064 .qr--source {1220 .qr--source {
1065 opacity: 0.5;1221 opacity: 0.5;
1066 text-align: right;1222 text-align: right;
@@ -1086,6 +1242,7 @@
1086}1242}
10871243
1088@keyframes qr--debugPulse {1244@keyframes qr--debugPulse {
1245
1089 0%,1246 0%,
1090 100% {1247 100% {
1091 border-color: rgb(81, 163, 81);1248 border-color: rgb(81, 163, 81);
@@ -1098,6 +1255,7 @@
10981255
1099.popup.qr--hide {1256.popup.qr--hide {
1100 opacity: 0 !important;1257 opacity: 0 !important;
1258
1101 &::backdrop {1259 &::backdrop {
1102 opacity: 0 !important;1260 opacity: 0 !important;
1103 }1261 }
@@ -1117,14 +1275,16 @@
1117 overflow: hidden;1275 overflow: hidden;
1118 font-weight: bold;1276 font-weight: bold;
1119 }1277 }
1278
1120 display: flex;1279 display: flex;
1121 align-items: center;1280 align-items: center;
1122 flex-direction: column;1281 flex-direction: column;
1123 white-space: pre;1282 white-space: pre;
1124 font-weight: normal;1283 font-weight: normal;
1125 box-shadow: 0 0 0;1284 box-shadow: 0 0 0;
1126 transition: 200ms;1285 transition: var(--animation-duration-2x);
1127 }1286 }
1287
1128 .qr--copy {1288 .qr--copy {
1129 &:after {1289 &:after {
1130 content: 'Copy';1290 content: 'Copy';
@@ -1132,24 +1292,28 @@
1132 overflow: hidden;1292 overflow: hidden;
1133 font-weight: bold;1293 font-weight: bold;
1134 }1294 }
1295
1135 display: flex;1296 display: flex;
1136 align-items: center;1297 align-items: center;
1137 flex-direction: column;1298 flex-direction: column;
1138 white-space: pre;1299 white-space: pre;
1139 font-weight: normal;1300 font-weight: normal;
1140 box-shadow: 0 0 0;1301 box-shadow: 0 0 0;
1141 transition: 200ms;1302 transition: var(--animation-duration-2x);
1142 }1303 }
1304
1143 &:has(.qr--transferSelect:focus) {1305 &:has(.qr--transferSelect:focus) {
1144 .popup-button-ok {1306 .popup-button-ok {
1145 font-weight: bold;1307 font-weight: bold;
1146 box-shadow: 0 0 10px;1308 box-shadow: 0 0 10px;
1147 }1309 }
1310
1148 &.qr--isCopy {1311 &.qr--isCopy {
1149 .popup-button-ok {1312 .popup-button-ok {
1150 font-weight: normal;1313 font-weight: normal;
1151 box-shadow: 0 0 0;1314 box-shadow: 0 0 0;
1152 }1315 }
1316
1153 .qr--copy {1317 .qr--copy {
1154 font-weight: bold;1318 font-weight: bold;
1155 box-shadow: 0 0 10px;1319 box-shadow: 0 0 10px;
public/scripts/extensions/regex/dropdown.html+28 -2
@@ -21,6 +21,32 @@
21 <small data-i18n="ext_regex_import_script">Import</small>21 <small data-i18n="ext_regex_import_script">Import</small>
22 </div>22 </div>
23 <input type="file" id="import_regex_file" hidden accept="*.json" multiple />23 <input type="file" id="import_regex_file" hidden accept="*.json" multiple />
24 <label for="regex_bulk_edit" class="menu_button menu_button_icon">
25 <input id="regex_bulk_edit" type="checkbox" class="displayNone" />
26 <i class="fa-solid fa-edit"></i>
27 <small data-i18n="ext_regex_bulk_edit">Bulk Edit</small>
28 </label>
29 </div>
30 <div class="regex_bulk_operations flex-container justifyCenter">
31 <div id="bulk_select_all_toggle" class="menu_button menu_button_icon" title="Toggle Select All">
32 <i class="fa-solid fa-check-double"></i>
33 </div>
34 <div id="bulk_enable_regex" class="menu_button menu_button_icon">
35 <i class="fa-solid fa-toggle-on"></i>
36 <small data-i18n="Enable">Enable</small>
37 </div>
38 <div id="bulk_disable_regex" class="menu_button menu_button_icon">
39 <i class="fa-solid fa-toggle-off"></i>
40 <small data-i18n="Disable">Disable</small>
41 </div>
42 <div id="bulk_export_regex" class="menu_button menu_button_icon">
43 <i class="fa-solid fa-file-export"></i>
44 <small data-i18n="Export">Export</small>
45 </div>
46 <div id="bulk_delete_regex" class="menu_button menu_button_icon">
47 <i class="fa-solid fa-trash"></i>
48 <small data-i18n="Delete">Delete</small>
49 </div>
24 </div>50 </div>
25 <hr />51 <hr />
26 <div id="global_scripts_block" class="padding5">52 <div id="global_scripts_block" class="padding5">
@@ -30,7 +56,7 @@
30 <small data-i18n="ext_regex_global_scripts_desc">56 <small data-i18n="ext_regex_global_scripts_desc">
31 Available for all characters. Saved to local settings.57 Available for all characters. Saved to local settings.
32 </small>58 </small>
33 <div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>59 <div id="saved_regex_scripts" no-scripts-text="No scripts found" data-i18n="[no-scripts-text]No scripts found" class="flex-container regex-script-container flexFlowColumn"></div>
34 </div>60 </div>
35 <hr />61 <hr />
36 <div id="scoped_scripts_block" class="padding5">62 <div id="scoped_scripts_block" class="padding5">
@@ -45,7 +71,7 @@
45 <small data-i18n="ext_regex_scoped_scripts_desc">71 <small data-i18n="ext_regex_scoped_scripts_desc">
46 Only available for this character. Saved to the card data.72 Only available for this character. Saved to the card data.
47 </small>73 </small>
48 <div id="saved_scoped_scripts" class="flex-container regex-script-container flexFlowColumn"></div>74 <div id="saved_scoped_scripts" no-scripts-text="No scripts found" data-i18n="[no-scripts-text]No scripts found" class="flex-container regex-script-container flexFlowColumn"></div>
49 </div>75 </div>
50 </div>76 </div>
51 </div>77 </div>
public/scripts/extensions/regex/editor.html+1 -1
@@ -146,7 +146,7 @@
146 </select>146 </select>
147 </label>147 </label>
148 <span>148 <span>
149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>149 <small data-i18n="Ephemerality">Ephemerality</small>
150 <span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>150 <span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
151 </span>151 </span>
152 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">152 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
public/scripts/extensions/regex/embeddedScripts.html+3 -3
@@ -1,5 +1,5 @@
1<div>1<div>
2 <h3>This character has embedded regex script(s).</h3>2 <h3 data-i18n="This character has embedded regex script(s).">This character has embedded regex script(s).</h3>
3 <h3>Would you like to allow using them?</h3>3 <h3 data-i18n="Would you like to allow using them?">Would you like to allow using them?</h3>
4 <div class="m-b-1">If you want to do it later, select "Regex" from the extensions menu.</div>4 <div class="m-b-1" data-i18n="If you want to do it later, select 'Regex' from the extensions menu.">If you want to do it later, select "Regex" from the extensions menu.</div>
5</div>5</div>
public/scripts/extensions/regex/index.js+156 -33
@@ -1,4 +1,4 @@
1import { callPopup, characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';1import { characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';
2import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';2import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
3import { selected_group } from '../../group-chats.js';3import { selected_group } from '../../group-chats.js';
4import { callGenericPopup, POPUP_TYPE } from '../../popup.js';4import { callGenericPopup, POPUP_TYPE } from '../../popup.js';
@@ -12,6 +12,8 @@ import { regex_placement, runRegexScript, substitute_find_regex } from './engine
12import { t } from '../../i18n.js';12import { t } from '../../i18n.js';
13import { accountStorage } from '../../util/AccountStorage.js';13import { accountStorage } from '../../util/AccountStorage.js';
1414
15const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '_').toLowerCase();
16
15/**17/**
16 * @typedef {import('../../char-data.js').RegexScriptData} RegexScript18 * @typedef {import('../../char-data.js').RegexScriptData} RegexScript
17 */19 */
@@ -26,6 +28,18 @@ export function getRegexScripts() {
26}28}
2729
28/**30/**
31 * Toggle the icon for the "select all" checkbox in the regex settings.
32 * - Use `fa-check-double` when the checkbox is unchecked (indicating all scripts are not selected).
33 * - Use `fa-minus` when the checkbox is checked (indicating all scripts are selected).
34 * @param {boolean} allAreChecked Should the "select all" icon be in the checked state?
35 */
36function setToggleAllIcon(allAreChecked) {
37 const selectAllIcon = $('#bulk_select_all_toggle').find('i');
38 selectAllIcon.toggleClass('fa-check-double', !allAreChecked);
39 selectAllIcon.toggleClass('fa-minus', allAreChecked);
40}
41
42/**
29 * Saves a regex script to the extension settings or character data.43 * Saves a regex script to the extension settings or character data.
30 * @param {import('../../char-data.js').RegexScriptData} regexScript44 * @param {import('../../char-data.js').RegexScriptData} regexScript
31 * @param {number} existingScriptIndex Index of the existing script45 * @param {number} existingScriptIndex Index of the existing script
@@ -43,18 +57,18 @@ async function saveRegexScript(regexScript, existingScriptIndex, isScoped) {
4357
44 // Is the script name undefined or empty?58 // Is the script name undefined or empty?
45 if (!regexScript.scriptName) {59 if (!regexScript.scriptName) {
46 toastr.error('Could not save regex script: The script name was undefined or empty!');60 toastr.error(t`Could not save regex script: The script name was undefined or empty!`);
47 return;61 return;
48 }62 }
4963
50 // Is a find regex present?64 // Is a find regex present?
51 if (regexScript.findRegex.length === 0) {65 if (regexScript.findRegex.length === 0) {
52 toastr.warning('This regex script will not work, but was saved anyway: A find regex isn\'t present.');66 toastr.warning(t`This regex script will not work, but was saved anyway: A find regex isn't present.`);
53 }67 }
5468
55 // Is there someplace to place results?69 // Is there someplace to place results?
56 if (regexScript.placement.length === 0) {70 if (regexScript.placement.length === 0) {
57 toastr.warning('This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!');71 toastr.warning(t`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!`);
58 }72 }
5973
60 if (existingScriptIndex !== -1) {74 if (existingScriptIndex !== -1) {
@@ -101,6 +115,7 @@ async function deleteRegexScript({ id, isScoped }) {
101async function loadRegexScripts() {115async function loadRegexScripts() {
102 $('#saved_regex_scripts').empty();116 $('#saved_regex_scripts').empty();
103 $('#saved_scoped_scripts').empty();117 $('#saved_scoped_scripts').empty();
118 setToggleAllIcon(false);
104119
105 const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate'));120 const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate'));
106121
@@ -137,7 +152,7 @@ async function loadRegexScripts() {
137 await onRegexEditorOpenClick(scriptHtml.attr('id'), isScoped);152 await onRegexEditorOpenClick(scriptHtml.attr('id'), isScoped);
138 });153 });
139 scriptHtml.find('.move_to_global').on('click', async function () {154 scriptHtml.find('.move_to_global').on('click', async function () {
140 const confirm = await callGenericPopup('Are you sure you want to move this regex script to global?', POPUP_TYPE.CONFIRM);155 const confirm = await callGenericPopup(t`Are you sure you want to move this regex script to global?`, POPUP_TYPE.CONFIRM);
141156
142 if (!confirm) {157 if (!confirm) {
143 return;158 return;
@@ -148,16 +163,16 @@ async function loadRegexScripts() {
148 });163 });
149 scriptHtml.find('.move_to_scoped').on('click', async function () {164 scriptHtml.find('.move_to_scoped').on('click', async function () {
150 if (this_chid === undefined) {165 if (this_chid === undefined) {
151 toastr.error('No character selected.');166 toastr.error(t`No character selected.`);
152 return;167 return;
153 }168 }
154169
155 if (selected_group) {170 if (selected_group) {
156 toastr.error('Cannot edit scoped scripts in group chats.');171 toastr.error(t`Cannot edit scoped scripts in group chats.`);
157 return;172 return;
158 }173 }
159174
160 const confirm = await callGenericPopup('Are you sure you want to move this regex script to scoped?', POPUP_TYPE.CONFIRM);175 const confirm = await callGenericPopup(t`Are you sure you want to move this regex script to scoped?`, POPUP_TYPE.CONFIRM);
161176
162 if (!confirm) {177 if (!confirm) {
163 return;178 return;
@@ -167,12 +182,12 @@ async function loadRegexScripts() {
167 await saveRegexScript(script, -1, true);182 await saveRegexScript(script, -1, true);
168 });183 });
169 scriptHtml.find('.export_regex').on('click', async function () {184 scriptHtml.find('.export_regex').on('click', async function () {
170 const fileName = `${script.scriptName.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '_').toLowerCase()}.json`;185 const fileName = `regex-${sanitizeFileName(script.scriptName)}.json`;
171 const fileData = JSON.stringify(script, null, 4);186 const fileData = JSON.stringify(script, null, 4);
172 download(fileData, fileName, 'application/json');187 download(fileData, fileName, 'application/json');
173 });188 });
174 scriptHtml.find('.delete_regex').on('click', async function () {189 scriptHtml.find('.delete_regex').on('click', async function () {
175 const confirm = await callGenericPopup('Are you sure you want to delete this regex script?', POPUP_TYPE.CONFIRM);190 const confirm = await callGenericPopup(t`Are you sure you want to delete this regex script?`, POPUP_TYPE.CONFIRM);
176191
177 if (!confirm) {192 if (!confirm) {
178 return;193 return;
@@ -181,12 +196,17 @@ async function loadRegexScripts() {
181 await deleteRegexScript({ id: script.id, isScoped });196 await deleteRegexScript({ id: script.id, isScoped });
182 await reloadCurrentChat();197 await reloadCurrentChat();
183 });198 });
199 scriptHtml.find('.regex_bulk_checkbox').on('change', function () {
200 const checkboxes = $('#regex_container .regex_bulk_checkbox');
201 const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length;
202 setToggleAllIcon(allAreChecked);
203 });
184204
185 $(container).append(scriptHtml);205 $(container).append(scriptHtml);
186 }206 }
187207
188 extension_settings?.regex?.forEach((script, index, array) => renderScript('#saved_regex_scripts', script, false, index, array));208 extension_settings?.regex?.forEach((script, index) => renderScript('#saved_regex_scripts', script, false, index));
189 characters[this_chid]?.data?.extensions?.regex_scripts?.forEach((script, index, array) => renderScript('#saved_scoped_scripts', script, true, index, array));209 characters[this_chid]?.data?.extensions?.regex_scripts?.forEach((script, index) => renderScript('#saved_scoped_scripts', script, true, index));
190210
191 const isAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar);211 const isAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar);
192 $('#regex_scoped_toggle').prop('checked', isAllowed);212 $('#regex_scoped_toggle').prop('checked', isAllowed);
@@ -260,11 +280,18 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
260280
261 const testScript = {281 const testScript = {
262 id: uuidv4(),282 id: uuidv4(),
263 scriptName: editorHtml.find('.regex_script_name').val(),283 scriptName: editorHtml.find('.regex_script_name').val().toString(),
264 findRegex: editorHtml.find('.find_regex').val(),284 findRegex: editorHtml.find('.find_regex').val().toString(),
265 replaceString: editorHtml.find('.regex_replace_string').val(),285 replaceString: editorHtml.find('.regex_replace_string').val().toString(),
266 trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [],286 trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [],
267 substituteRegex: Number(editorHtml.find('select[name="substitute_regex"]').val()),287 substituteRegex: Number(editorHtml.find('select[name="substitute_regex"]').val()),
288 disabled: false,
289 promptOnly: false,
290 markdownOnly: false,
291 runOnEdit: false,
292 minDepth: null,
293 maxDepth: null,
294 placement: null,
268 };295 };
269 const rawTestString = String(editorHtml.find('#regex_test_input').val());296 const rawTestString = String(editorHtml.find('#regex_test_input').val());
270 const result = runRegexScript(testScript, rawTestString);297 const result = runRegexScript(testScript, rawTestString);
@@ -274,19 +301,19 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
274 editorHtml.find('input, textarea, select').on('input', updateTestResult);301 editorHtml.find('input, textarea, select').on('input', updateTestResult);
275 updateInfoBlock(editorHtml);302 updateInfoBlock(editorHtml);
276303
277 const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: t`Save` });304 const popupResult = await callGenericPopup(editorHtml, POPUP_TYPE.CONFIRM, '', { okButton: t`Save`, cancelButton: t`Cancel`, allowVerticalScrolling: true });
278 if (popupResult) {305 if (popupResult) {
279 const newRegexScript = {306 const newRegexScript = {
280 id: existingId ? String(existingId) : uuidv4(),307 id: existingId ? String(existingId) : uuidv4(),
281 scriptName: String(editorHtml.find('.regex_script_name').val()),308 scriptName: String(editorHtml.find('.regex_script_name').val()),
282 findRegex: String(editorHtml.find('.find_regex').val()),309 findRegex: String(editorHtml.find('.find_regex').val()),
283 replaceString: String(editorHtml.find('.regex_replace_string').val()),310 replaceString: String(editorHtml.find('.regex_replace_string').val()),
284 trimStrings: editorHtml.find('.regex_trim_strings').val().split('\n').filter((e) => e.length !== 0) || [],311 trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [],
285 placement:312 placement:
286 editorHtml313 editorHtml
287 .find('input[name="replace_position"]')314 .find('input[name="replace_position"]')
288 .filter(':checked')315 .filter(':checked')
289 .map(function () { return parseInt($(this).val()); })316 .map(function () { return parseInt($(this).val().toString()); })
290 .get()317 .get()
291 .filter((e) => !isNaN(e)) || [],318 .filter((e) => !isNaN(e)) || [],
292 disabled: editorHtml.find('input[name="disabled"]').prop('checked'),319 disabled: editorHtml.find('input[name="disabled"]').prop('checked'),
@@ -459,19 +486,12 @@ async function toggleRegexCallback(args, scriptName) {
459}486}
460487
461/**488/**
462 * Performs the import of the regex file.489 * Performs the import of the regex object.
463 * @param {File} file Input file490 * @param {Object} regexScript Input object
464 * @param {boolean} isScoped Is the script scoped to a character?491 * @param {boolean} isScoped Is the script scoped to a character?
465 */492 */
466async function onRegexImportFileChange(file, isScoped) {493async function onRegexImportObjectChange(regexScript, isScoped) {
467 if (!file) {
468 toastr.error('No file provided.');
469 return;
470 }
471
472 try {494 try {
473 const fileText = await getFileText(file);
474 const regexScript = JSON.parse(fileText);
475 if (!regexScript.scriptName) {495 if (!regexScript.scriptName) {
476 throw new Error('No script name provided.');496 throw new Error('No script name provided.');
477 }497 }
@@ -488,7 +508,34 @@ async function onRegexImportFileChange(file, isScoped) {
488508
489 saveSettingsDebounced();509 saveSettingsDebounced();
490 await loadRegexScripts();510 await loadRegexScripts();
491 toastr.success(`Regex script "${regexScript.scriptName}" imported.`);511 toastr.success(t`Regex script "${regexScript.scriptName}" imported.`);
512 } catch (error) {
513 console.log(error);
514 toastr.error(t`Invalid regex object.`);
515 return;
516 }
517}
518
519/**
520 * Performs the import of the regex file.
521 * @param {File} file Input file
522 * @param {boolean} isScoped Is the script scoped to a character?
523 */
524async function onRegexImportFileChange(file, isScoped) {
525 if (!file) {
526 toastr.error('No file provided.');
527 return;
528 }
529
530 try {
531 const regexScripts = JSON.parse(await getFileText(file));
532 if (Array.isArray(regexScripts)) {
533 for (const regexScript of regexScripts) {
534 await onRegexImportObjectChange(regexScript, isScoped);
535 }
536 } else {
537 await onRegexImportObjectChange(regexScripts, isScoped);
538 }
492 } catch (error) {539 } catch (error) {
493 console.log(error);540 console.log(error);
494 toastr.error('Invalid JSON file.');541 toastr.error('Invalid JSON file.');
@@ -556,12 +603,12 @@ jQuery(async () => {
556 });603 });
557 $('#open_scoped_editor').on('click', function () {604 $('#open_scoped_editor').on('click', function () {
558 if (this_chid === undefined) {605 if (this_chid === undefined) {
559 toastr.error('No character selected.');606 toastr.error(t`No character selected.`);
560 return;607 return;
561 }608 }
562609
563 if (selected_group) {610 if (selected_group) {
564 toastr.error('Cannot edit scoped scripts in group chats.');611 toastr.error(t`Cannot edit scoped scripts in group chats.`);
565 return;612 return;
566 }613 }
567614
@@ -585,6 +632,82 @@ jQuery(async () => {
585 $('#import_regex_file').trigger('click');632 $('#import_regex_file').trigger('click');
586 });633 });
587634
635 function getSelectedScripts() {
636 const scripts = getRegexScripts();
637 const selector = '#regex_container .regex-script-label:has(.regex_bulk_checkbox:checked)';
638 const selectedIds = Array.from(document.querySelectorAll(selector)).map(e => e.getAttribute('id')).filter(id => id);
639 return scripts.filter(script => selectedIds.includes(script.id));
640 }
641
642 $('#bulk_select_all_toggle').on('click', async function () {
643 const checkboxes = $('#regex_container .regex_bulk_checkbox');
644 if (checkboxes.length === 0) {
645 return;
646 }
647
648 const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length;
649 const newState = !allAreChecked; // true if we just checked all, false if we just unchecked all
650
651 checkboxes.prop('checked', newState);
652 setToggleAllIcon(newState);
653 });
654
655 $('#bulk_enable_regex').on('click', async function () {
656 const scripts = getSelectedScripts().filter(script => script.disabled);
657 if (scripts.length === 0) {
658 toastr.warning(t`No regex scripts selected for enabling.`);
659 return;
660 }
661 for (const script of scripts) {
662 script.disabled = false;
663 }
664 saveSettingsDebounced();
665 await loadRegexScripts();
666 });
667
668 $('#bulk_disable_regex').on('click', async function () {
669 const scripts = getSelectedScripts().filter(script => !script.disabled);
670 if (scripts.length === 0) {
671 toastr.warning(t`No regex scripts selected for disabling.`);
672 return;
673 }
674 for (const script of scripts) {
675 script.disabled = true;
676 }
677 saveSettingsDebounced();
678 await loadRegexScripts();
679 });
680
681 $('#bulk_delete_regex').on('click', async function () {
682 const scripts = getSelectedScripts();
683 if (scripts.length === 0) {
684 toastr.warning(t`No regex scripts selected for deletion.`);
685 return;
686 }
687 const confirm = await callGenericPopup('Are you sure you want to delete the selected regex scripts?', POPUP_TYPE.CONFIRM);
688 if (!confirm) {
689 return;
690 }
691 for (const script of scripts) {
692 const isScoped = characters[this_chid]?.data?.extensions?.regex_scripts?.some(s => s.id === script.id);
693 await deleteRegexScript({ id: script.id, isScoped: isScoped });
694 }
695 await reloadCurrentChat();
696 saveSettingsDebounced();
697 });
698
699 $('#bulk_export_regex').on('click', async function () {
700 const scripts = getSelectedScripts();
701 if (scripts.length === 0) {
702 toastr.warning(t`No regex scripts selected for export.`);
703 return;
704 }
705 const fileName = `regex-${new Date().toISOString()}.json`;
706 const fileData = JSON.stringify(scripts, null, 4);
707 download(fileData, fileName, 'application/json');
708 await loadRegexScripts();
709 });
710
588 let sortableDatas = [711 let sortableDatas = [
589 {712 {
590 selector: '#saved_regex_scripts',713 selector: '#saved_regex_scripts',
@@ -622,12 +745,12 @@ jQuery(async () => {
622745
623 $('#regex_scoped_toggle').on('input', function () {746 $('#regex_scoped_toggle').on('input', function () {
624 if (this_chid === undefined) {747 if (this_chid === undefined) {
625 toastr.error('No character selected.');748 toastr.error(t`No character selected.`);
626 return;749 return;
627 }750 }
628751
629 if (selected_group) {752 if (selected_group) {
630 toastr.error('Cannot edit scoped scripts in group chats.');753 toastr.error(t`Cannot edit scoped scripts in group chats.`);
631 return;754 return;
632 }755 }
633756
public/scripts/extensions/regex/scriptTemplate.html+1 -0
@@ -1,4 +1,5 @@
1<div class="regex-script-label flex-container flexnowrap">1<div class="regex-script-label flex-container flexnowrap">
2 <input type="checkbox" class="regex_bulk_checkbox" />
2 <span class="drag-handle menu-handle">&#9776;</span>3 <span class="drag-handle menu-handle">&#9776;</span>
3 <div class="regex_script_name flexGrow overflow-hidden"></div>4 <div class="regex_script_name flexGrow overflow-hidden"></div>
4 <div class="flex-container flexnowrap">5 <div class="flex-container flexnowrap">
public/scripts/extensions/regex/style.css+26 -4
@@ -15,7 +15,7 @@
15}15}
1616
17.regex-script-container:empty::after {17.regex-script-container:empty::after {
18 content: "No scripts found";18 content: attr(no-scripts-text);
19 font-size: 0.95em;19 font-size: 0.95em;
20 opacity: 0.7;20 opacity: 0.7;
21 display: block;21 display: block;
@@ -24,7 +24,7 @@
2424
25#scoped_scripts_block {25#scoped_scripts_block {
26 opacity: 1;26 opacity: 1;
27 transition: opacity 0.2s ease-in-out;27 transition: opacity var(--animation-duration-2x) ease-in-out;
28}28}
2929
30#scoped_scripts_block .move_to_scoped {30#scoped_scripts_block .move_to_scoped {
@@ -56,7 +56,7 @@
56}56}
5757
58.regex-script-label {58.regex-script-label {
59 align-items: center;59 align-items: baseline;
60 border: 1px solid var(--SmartThemeBorderColor);60 border: 1px solid var(--SmartThemeBorderColor);
61 border-radius: 10px;61 border-radius: 10px;
62 padding: 0 5px;62 padding: 0 5px;
@@ -78,7 +78,7 @@ input.enable_scoped {
78 cursor: pointer;78 cursor: pointer;
79 opacity: 0.5;79 opacity: 0.5;
80 filter: grayscale(0.5);80 filter: grayscale(0.5);
81 transition: opacity 0.2s ease-in-out;81 transition: opacity var(--animation-duration-2x) ease-in-out;
82}82}
8383
84.regex-toggle-off:hover {84.regex-toggle-off:hover {
@@ -126,3 +126,25 @@ input.enable_scoped {
126 right: 10px;126 right: 10px;
127 transform: translateY(-50%);127 transform: translateY(-50%);
128}128}
129
130.regex_settings label[for="regex_bulk_edit"]:has(#regex_bulk_edit:checked) {
131 color: var(--golden);
132}
133
134.regex_settings .regex-script-container .regex-script-label .regex_bulk_checkbox {
135 margin-left: 5px;
136 margin-right: 5px;
137}
138
139.regex_settings .regex_bulk_operations,
140.regex_settings .regex_bulk_checkbox {
141 display: none;
142}
143
144.regex_settings:has(#regex_bulk_edit:checked) .regex_bulk_operations {
145 display: flex;
146}
147
148.regex_settings:has(#regex_bulk_edit:checked) .regex_bulk_checkbox {
149 display: inline-grid;
150}
public/scripts/extensions/shared.js+29 -2
@@ -56,6 +56,13 @@ export async function getMultimodalCaption(base64Img, prompt) {
56 model: extension_settings.caption.multimodal_model || 'gpt-4-turbo',56 model: extension_settings.caption.multimodal_model || 'gpt-4-turbo',
57 };57 };
5858
59 // Add Vertex AI specific parameters if using Vertex AI
60 if (extension_settings.caption.multimodal_api === 'vertexai') {
61 requestBody.vertexai_auth_mode = oai_settings.vertexai_auth_mode;
62 requestBody.vertexai_region = oai_settings.vertexai_region;
63 requestBody.vertexai_express_project_id = oai_settings.vertexai_express_project_id;
64 }
65
59 if (isOllama) {66 if (isOllama) {
60 if (extension_settings.caption.multimodal_model === 'ollama_current') {67 if (extension_settings.caption.multimodal_model === 'ollama_current') {
61 requestBody.model = textgenerationwebui_settings.ollama_model;68 requestBody.model = textgenerationwebui_settings.ollama_model;
@@ -164,8 +171,24 @@ function throwIfInvalidModel(useReverseProxy) {
164 throw new Error('Google AI Studio API key is not set.');171 throw new Error('Google AI Studio API key is not set.');
165 }172 }
166173
167 if (multimodalApi === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !useReverseProxy) {174 if (multimodalApi === 'vertexai' && !useReverseProxy) {
168 throw new Error('Google Vertex AI API key is not set.');175 // Check based on authentication mode
176 const authMode = oai_settings.vertexai_auth_mode || 'express';
177
178 if (authMode === 'express') {
179 // Express mode requires API key
180 if (!secret_state[SECRET_KEYS.VERTEXAI]) {
181 throw new Error('Google Vertex AI API key is not set for Express mode.');
182 }
183 } else if (authMode === 'full') {
184 // Full mode requires Service Account JSON and region settings
185 if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
186 throw new Error('Service Account JSON is required for Vertex AI Full mode. Please validate and save your Service Account JSON.');
187 }
188 if (!oai_settings.vertexai_region) {
189 throw new Error('Region is required for Vertex AI Full mode.');
190 }
191 }
169 }192 }
170193
171 if (multimodalApi === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {194 if (multimodalApi === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {
@@ -211,6 +234,10 @@ function throwIfInvalidModel(useReverseProxy) {
211 if (multimodalApi === 'custom' && !oai_settings.custom_url) {234 if (multimodalApi === 'custom' && !oai_settings.custom_url) {
212 throw new Error('Custom API URL is not set.');235 throw new Error('Custom API URL is not set.');
213 }236 }
237
238 if (multimodalApi === 'aimlapi' && !secret_state[SECRET_KEYS.AIMLAPI]) {
239 throw new Error('AI/ML API key is not set.');
240 }
214}241}
215242
216/**243/**
public/scripts/extensions/stable-diffusion/index.js+215 -47
@@ -41,7 +41,7 @@ import {
41 stringFormat,41 stringFormat,
42} from '../../utils.js';42} from '../../utils.js';
43import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';43import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
44import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';44import { SECRET_KEYS, secret_state } from '../../secrets.js';
45import { getNovelAnlas, getNovelUnlimitedImageGeneration, loadNovelSubscriptionData } from '../../nai-settings.js';45import { getNovelAnlas, getNovelUnlimitedImageGeneration, loadNovelSubscriptionData } from '../../nai-settings.js';
46import { getMultimodalCaption } from '../shared.js';46import { getMultimodalCaption } from '../shared.js';
47import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';47import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
@@ -53,11 +53,12 @@ import {
53} from '../../slash-commands/SlashCommandArgument.js';53} from '../../slash-commands/SlashCommandArgument.js';
54import { debounce_timeout } from '../../constants.js';54import { debounce_timeout } from '../../constants.js';
55import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';55import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
56import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';56import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
57import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';57import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
58import { ToolManager } from '../../tool-calling.js';58import { ToolManager } from '../../tool-calling.js';
59import { MacrosParser } from '../../macros.js';59import { MacrosParser } from '../../macros.js';
60import { t } from '../../i18n.js';60import { t } from '../../i18n.js';
61import { oai_settings } from '../../openai.js';
6162
62export { MODULE_NAME };63export { MODULE_NAME };
6364
@@ -74,6 +75,7 @@ const sources = {
74 novel: 'novel',75 novel: 'novel',
75 vlad: 'vlad',76 vlad: 'vlad',
76 openai: 'openai',77 openai: 'openai',
78 aimlapi: 'aimlapi',
77 comfy: 'comfy',79 comfy: 'comfy',
78 togetherai: 'togetherai',80 togetherai: 'togetherai',
79 drawthings: 'drawthings',81 drawthings: 'drawthings',
@@ -84,6 +86,7 @@ const sources = {
84 bfl: 'bfl',86 bfl: 'bfl',
85 falai: 'falai',87 falai: 'falai',
86 xai: 'xai',88 xai: 'xai',
89 google: 'google',
87};90};
8891
89const initiators = {92const initiators = {
@@ -329,6 +332,10 @@ const defaultSettings = {
329332
330 // BFL API settings333 // BFL API settings
331 bfl_upsampling: false,334 bfl_upsampling: false,
335
336 // Google settings
337 google_api: 'makersuite',
338 google_enhance: true,
332};339};
333340
334const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed);341const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed);
@@ -508,6 +515,8 @@ async function loadSettings() {
508 $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id);515 $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id);
509 $('#sd_function_tool').prop('checked', extension_settings.sd.function_tool);516 $('#sd_function_tool').prop('checked', extension_settings.sd.function_tool);
510 $('#sd_bfl_upsampling').prop('checked', extension_settings.sd.bfl_upsampling);517 $('#sd_bfl_upsampling').prop('checked', extension_settings.sd.bfl_upsampling);
518 $('#sd_google_api').val(extension_settings.sd.google_api);
519 $('#sd_google_enhance').prop('checked', extension_settings.sd.google_enhance);
511520
512 for (const style of extension_settings.sd.styles) {521 for (const style of extension_settings.sd.styles) {
513 const option = document.createElement('option');522 const option = document.createElement('option');
@@ -1144,42 +1153,6 @@ function onComfyWorkflowChange() {
1144 saveSettingsDebounced();1153 saveSettingsDebounced();
1145}1154}
11461155
1147async function onApiKeyClick(popupText, secretKey) {
1148 const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
1149 customButtons: [{
1150 text: 'Remove Key',
1151 appendAtEnd: true,
1152 result: POPUP_RESULT.NEGATIVE,
1153 action: async () => {
1154 await writeSecret(secretKey, '');
1155 toastr.success('API Key removed');
1156 await loadSettingOptions();
1157 },
1158 }],
1159 });
1160
1161 if (!key) {
1162 return;
1163 }
1164
1165 await writeSecret(secretKey, String(key));
1166
1167 toastr.success('API Key saved');
1168 await loadSettingOptions();
1169}
1170
1171async function onStabilityKeyClick() {
1172 return onApiKeyClick('Stability AI API Key:', SECRET_KEYS.STABILITY);
1173}
1174
1175async function onBflKeyClick() {
1176 return onApiKeyClick('BFL API Key:', SECRET_KEYS.BFL);
1177}
1178
1179async function onFalaiKeyClick() {
1180 return onApiKeyClick('FALAI API Key:', SECRET_KEYS.FALAI);
1181}
1182
1183function onBflUpsamplingInput() {1156function onBflUpsamplingInput() {
1184 extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked');1157 extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked');
1185 saveSettingsDebounced();1158 saveSettingsDebounced();
@@ -1303,6 +1276,7 @@ async function onModelChange() {
1303 sources.horde,1276 sources.horde,
1304 sources.novel,1277 sources.novel,
1305 sources.openai,1278 sources.openai,
1279 sources.aimlapi,
1306 sources.togetherai,1280 sources.togetherai,
1307 sources.pollinations,1281 sources.pollinations,
1308 sources.stability,1282 sources.stability,
@@ -1311,6 +1285,7 @@ async function onModelChange() {
1311 sources.bfl,1285 sources.bfl,
1312 sources.falai,1286 sources.falai,
1313 sources.xai,1287 sources.xai,
1288 sources.google,
1314 ];1289 ];
13151290
1316 if (cloudSources.includes(extension_settings.sd.source)) {1291 if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1505,6 +1480,9 @@ async function loadSamplers() {
1505 case sources.openai:1480 case sources.openai:
1506 samplers = ['N/A'];1481 samplers = ['N/A'];
1507 break;1482 break;
1483 case sources.aimlapi:
1484 samplers = ['N/A'];
1485 break;
1508 case sources.comfy:1486 case sources.comfy:
1509 samplers = await loadComfySamplers();1487 samplers = await loadComfySamplers();
1510 break;1488 break;
@@ -1529,6 +1507,9 @@ async function loadSamplers() {
1529 case sources.xai:1507 case sources.xai:
1530 samplers = ['N/A'];1508 samplers = ['N/A'];
1531 break;1509 break;
1510 case sources.google:
1511 samplers = ['N/A'];
1512 break;
1532 }1513 }
15331514
1534 for (const sampler of samplers) {1515 for (const sampler of samplers) {
@@ -1695,6 +1676,9 @@ async function loadModels() {
1695 case sources.openai:1676 case sources.openai:
1696 models = await loadOpenAiModels();1677 models = await loadOpenAiModels();
1697 break;1678 break;
1679 case sources.aimlapi:
1680 models = await loadAimlapiModels();
1681 break;
1698 case sources.comfy:1682 case sources.comfy:
1699 models = await loadComfyModels();1683 models = await loadComfyModels();
1700 break;1684 break;
@@ -1722,6 +1706,9 @@ async function loadModels() {
1722 case sources.xai:1706 case sources.xai:
1723 models = await loadXAIModels();1707 models = await loadXAIModels();
1724 break;1708 break;
1709 case sources.google:
1710 models = await loadGoogleModels();
1711 break;
1725 }1712 }
17261713
1727 for (const model of models) {1714 for (const model of models) {
@@ -1959,6 +1946,23 @@ async function loadOpenAiModels() {
1959 ];1946 ];
1960}1947}
19611948
1949async function loadAimlapiModels() {
1950 $('#sd_aimlapi_key').toggleClass('success', !!secret_state[SECRET_KEYS.AIMLAPI]);
1951
1952 const result = await fetch('/api/sd/aimlapi/models', {
1953 method: 'POST',
1954 headers: getRequestHeaders(),
1955 });
1956
1957 if (!result.ok) {
1958 return [];
1959 }
1960
1961 const json = await result.json();
1962
1963 return (json.data || []);
1964}
1965
1962async function loadVladModels() {1966async function loadVladModels() {
1963 if (!extension_settings.sd.vlad_url) {1967 if (!extension_settings.sd.vlad_url) {
1964 return [];1968 return [];
@@ -2004,12 +2008,20 @@ async function loadVladModels() {
2004async function loadNovelModels() {2008async function loadNovelModels() {
2005 return [2009 return [
2006 {2010 {
2011 value: 'nai-diffusion-4-5-full',
2012 text: 'NAI Diffusion Anime V4.5 (Full)',
2013 },
2014 {
2015 value: 'nai-diffusion-4-5-curated',
2016 text: 'NAI Diffusion Anime V4.5 (Curated)',
2017 },
2018 {
2007 value: 'nai-diffusion-4-full',2019 value: 'nai-diffusion-4-full',
2008 text: 'NAI Diffusion Anime V4 (Full)',2020 text: 'NAI Diffusion Anime V4 (Full)',
2009 },2021 },
2010 {2022 {
2011 value: 'nai-diffusion-4-curated-preview',2023 value: 'nai-diffusion-4-curated-preview',
2012 text: 'NAI Diffusion Anime V4 (Curated Preview)',2024 text: 'NAI Diffusion Anime V4 (Curated)',
2013 },2025 },
2014 {2026 {
2015 value: 'nai-diffusion-3',2027 value: 'nai-diffusion-3',
@@ -2026,6 +2038,21 @@ async function loadNovelModels() {
2026 ];2038 ];
2027}2039}
20282040
2041async function loadGoogleModels() {
2042 return [
2043 'imagen-4.0-generate-preview-06-06',
2044 'imagen-4.0-fast-generate-preview-06-06',
2045 'imagen-4.0-ultra-generate-preview-06-06',
2046 'imagen-3.0-generate-002',
2047 'imagen-3.0-generate-001',
2048 'imagen-3.0-fast-generate-001',
2049 'imagen-3.0-capability-001',
2050 'imagegeneration@006',
2051 'imagegeneration@005',
2052 'imagegeneration@002',
2053 ].map(name => ({ value: name, text: name }));
2054}
2055
2029function loadNovelSchedulers() {2056function loadNovelSchedulers() {
2030 return ['karras', 'native', 'exponential', 'polyexponential'];2057 return ['karras', 'native', 'exponential', 'polyexponential'];
2031}2058}
@@ -2078,6 +2105,9 @@ async function loadSchedulers() {
2078 case sources.openai:2105 case sources.openai:
2079 schedulers = ['N/A'];2106 schedulers = ['N/A'];
2080 break;2107 break;
2108 case sources.aimlapi:
2109 schedulers = ['N/A'];
2110 break;
2081 case sources.togetherai:2111 case sources.togetherai:
2082 schedulers = ['N/A'];2112 schedulers = ['N/A'];
2083 break;2113 break;
@@ -2105,6 +2135,9 @@ async function loadSchedulers() {
2105 case sources.xai:2135 case sources.xai:
2106 schedulers = ['N/A'];2136 schedulers = ['N/A'];
2107 break;2137 break;
2138 case sources.google:
2139 schedulers = ['N/A'];
2140 break;
2108 }2141 }
21092142
2110 for (const scheduler of schedulers) {2143 for (const scheduler of schedulers) {
@@ -2169,6 +2202,9 @@ async function loadVaes() {
2169 case sources.openai:2202 case sources.openai:
2170 vaes = ['N/A'];2203 vaes = ['N/A'];
2171 break;2204 break;
2205 case sources.aimlapi:
2206 vaes = ['N/A'];
2207 break;
2172 case sources.togetherai:2208 case sources.togetherai:
2173 vaes = ['N/A'];2209 vaes = ['N/A'];
2174 break;2210 break;
@@ -2196,6 +2232,9 @@ async function loadVaes() {
2196 case sources.xai:2232 case sources.xai:
2197 vaes = ['N/A'];2233 vaes = ['N/A'];
2198 break;2234 break;
2235 case sources.google:
2236 vaes = ['N/A'];
2237 break;
2199 }2238 }
22002239
2201 for (const vae of vaes) {2240 for (const vae of vaes) {
@@ -2465,9 +2504,14 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2465 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };2504 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
24662505
2467 // generate the text prompt for the image2506 // generate the text prompt for the image
2468 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);2507 let prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
2469 console.log('Processed image prompt:', prompt);2508 console.log('Processed image prompt:', prompt);
24702509
2510 // Extension hook for prompt processing
2511 const eventData = { prompt, generationType, message, trigger };
2512 await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData);
2513 prompt = eventData.prompt; // Allow extensions to modify the prompt
2514
2471 $(stopButton).show();2515 $(stopButton).show();
2472 eventSource.once(CUSTOM_STOP_EVENT, stopListener);2516 eventSource.once(CUSTOM_STOP_EVENT, stopListener);
24732517
@@ -2741,6 +2785,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2741 case sources.openai:2785 case sources.openai:
2742 result = await generateOpenAiImage(prefixedPrompt, signal);2786 result = await generateOpenAiImage(prefixedPrompt, signal);
2743 break;2787 break;
2788 case sources.aimlapi:
2789 result = await generateAimlapiImage(prefixedPrompt, signal);
2790 break;
2744 case sources.comfy:2791 case sources.comfy:
2745 result = await generateComfyImage(prefixedPrompt, negativePrompt, signal);2792 result = await generateComfyImage(prefixedPrompt, negativePrompt, signal);
2746 break;2793 break;
@@ -2768,6 +2815,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2768 case sources.xai:2815 case sources.xai:
2769 result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);2816 result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
2770 break;2817 break;
2818 case sources.google:
2819 result = await generateGoogleImage(prefixedPrompt, negativePrompt, signal);
2820 break;
2771 }2821 }
27722822
2773 if (!result.data) {2823 if (!result.data) {
@@ -2905,10 +2955,14 @@ async function generateExtrasImage(prompt, negativePrompt, signal) {
2905 * Gets an aspect ratio for Stability that is the closest to the given width and height.2955 * Gets an aspect ratio for Stability that is the closest to the given width and height.
2906 * @param {number} width Target width2956 * @param {number} width Target width
2907 * @param {number} height Target height2957 * @param {number} height Target height
2958 * @param {'google'|'stability'} source Source of the request, used to determine aspect ratio
2908 * @returns {string} Closest aspect ratio as a string2959 * @returns {string} Closest aspect ratio as a string
2909 */2960 */
2910function getClosestAspectRatio(width, height) {2961function getClosestAspectRatio(width, height, source) {
2911 const aspectRatios = {2962 function getAspectRatios() {
2963 switch (source) {
2964 case 'stability':
2965 return {
2912 '16:9': 16 / 9,2966 '16:9': 16 / 9,
2913 '1:1': 1,2967 '1:1': 1,
2914 '21:9': 21 / 9,2968 '21:9': 21 / 9,
@@ -2919,6 +2973,21 @@ function getClosestAspectRatio(width, height) {
2919 '9:16': 9 / 16,2973 '9:16': 9 / 16,
2920 '9:21': 9 / 21,2974 '9:21': 9 / 21,
2921 };2975 };
2976 case 'google':
2977 return {
2978 '1:1': 1,
2979 '16:9': 16 / 9,
2980 '9:16': 9 / 16,
2981 '4:3': 4 / 3,
2982 '3:4': 3 / 4,
2983 };
2984 default:
2985 console.warn(`Unknown source "${source}" for aspect ratio calculation.`);
2986 return null;
2987 }
2988 }
2989
2990 const aspectRatios = getAspectRatios() || { '1:1': 1 };
29222991
2923 const aspectRatio = width / height;2992 const aspectRatio = width / height;
29242993
@@ -2957,7 +3026,7 @@ async function generateStabilityImage(prompt, negativePrompt, signal) {
2957 payload: {3026 payload: {
2958 prompt: prompt.slice(0, PROMPT_LIMIT),3027 prompt: prompt.slice(0, PROMPT_LIMIT),
2959 negative_prompt: negativePrompt.slice(0, PROMPT_LIMIT),3028 negative_prompt: negativePrompt.slice(0, PROMPT_LIMIT),
2960 aspect_ratio: getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height),3029 aspect_ratio: getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height, 'stability'),
2961 seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,3030 seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
2962 style_preset: extension_settings.sd.stability_style_preset,3031 style_preset: extension_settings.sd.stability_style_preset,
2963 output_format: IMAGE_FORMAT,3032 output_format: IMAGE_FORMAT,
@@ -3326,6 +3395,47 @@ async function generateOpenAiImage(prompt, signal) {
3326}3395}
33273396
3328/**3397/**
3398 * Universal image generation via AIMLAPI:
3399 * - Builds the right request body for any model (OpenAI vs SD/Flux/Recraft).
3400 * - Extracts the URL or base64 response.
3401 * - If it’s a URL, fetches the image and converts to base64.
3402 * - Returns { format: 'png', data: '<base64 string>' }, ready for saveBase64AsFile().
3403 */
3404async function generateAimlapiImage(prompt, signal) {
3405 const model = extension_settings.sd.model.toLowerCase();
3406 const isSdLike =
3407 model.startsWith('flux/') ||
3408 model.startsWith('stable') ||
3409 model === 'recraft-v3' ||
3410 model === 'triposr';
3411
3412 const body = { prompt, model };
3413 if (isSdLike) {
3414 body.steps = clamp(extension_settings.sd.steps, 1, 50);
3415 body.guidance = clamp(extension_settings.sd.scale, 1.5, 5);
3416 body.width = clamp(extension_settings.sd.width, 256, 1440);
3417 body.height = clamp(extension_settings.sd.height, 256, 1440);
3418 if (extension_settings.sd.seed >= 0) body.seed = extension_settings.sd.seed;
3419 } else {
3420 body.n = 1;
3421 body.size = `${extension_settings.sd.width}x${extension_settings.sd.height}`;
3422 body.quality = extension_settings.sd.openai_quality;
3423 body.style = extension_settings.sd.openai_style;
3424 }
3425
3426 const res = await fetch('/api/sd/aimlapi/generate-image', {
3427 method: 'POST',
3428 headers: getRequestHeaders(),
3429 signal,
3430 body: JSON.stringify(body),
3431 });
3432 if (!res.ok) throw new Error(await res.text());
3433
3434 const { format, data } = await res.json();
3435 return { format, data };
3436}
3437
3438/**
3329 * Generates an image in ComfyUI using the provided prompt and configuration settings.3439 * Generates an image in ComfyUI using the provided prompt and configuration settings.
3330 *3440 *
3331 * @param {string} prompt - The main instruction used to guide the image generation.3441 * @param {string} prompt - The main instruction used to guide the image generation.
@@ -3567,7 +3677,41 @@ async function generateFalaiImage(prompt, negativePrompt, signal) {
3567 return { format: 'jpg', data: data.image };3677 return { format: 'jpg', data: data.image };
3568 } else {3678 } else {
3569 const text = await result.text();3679 const text = await result.text();
3570 console.log(text);3680 throw new Error(text);
3681 }
3682}
3683
3684/**
3685 * Generates an image using the Google Vertex AI API.
3686 * @param {string} prompt The main instruction used to guide the image generation.
3687 * @param {string} negativePrompt The instruction used to restrict the image generation.
3688 * @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request.
3689 * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
3690 */
3691async function generateGoogleImage(prompt, negativePrompt, signal) {
3692 const result = await fetch('/api/google/generate-image', {
3693 method: 'POST',
3694 headers: getRequestHeaders(),
3695 signal: signal,
3696 body: JSON.stringify({
3697 prompt: prompt,
3698 aspect_ratio: getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height, 'google'),
3699 negative_prompt: negativePrompt,
3700 model: extension_settings.sd.model,
3701 enhance: extension_settings.sd.google_enhance,
3702 api: extension_settings.sd.google_api || 'makersuite',
3703 seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
3704 vertexai_auth_mode: oai_settings.vertexai_auth_mode,
3705 vertexai_region: oai_settings.vertexai_region,
3706 vertexai_express_project_id: oai_settings.vertexai_express_project_id,
3707 }),
3708 });
3709
3710 if (result.ok) {
3711 const data = await result.json();
3712 return { format: 'jpg', data: data.image };
3713 } else {
3714 const text = await result.text();
3571 throw new Error(text);3715 throw new Error(text);
3572 }3716 }
3573}3717}
@@ -3841,6 +3985,8 @@ function isValidState() {
3841 return secret_state[SECRET_KEYS.NOVEL];3985 return secret_state[SECRET_KEYS.NOVEL];
3842 case sources.openai:3986 case sources.openai:
3843 return secret_state[SECRET_KEYS.OPENAI];3987 return secret_state[SECRET_KEYS.OPENAI];
3988 case sources.aimlapi:
3989 return secret_state[SECRET_KEYS.AIMLAPI];
3844 case sources.comfy:3990 case sources.comfy:
3845 return true;3991 return true;
3846 case sources.togetherai:3992 case sources.togetherai:
@@ -3859,6 +4005,8 @@ function isValidState() {
3859 return secret_state[SECRET_KEYS.FALAI];4005 return secret_state[SECRET_KEYS.FALAI];
3860 case sources.xai:4006 case sources.xai:
3861 return secret_state[SECRET_KEYS.XAI];4007 return secret_state[SECRET_KEYS.XAI];
4008 case sources.google:
4009 return secret_state[SECRET_KEYS.MAKERSUITE] || secret_state[SECRET_KEYS.VERTEXAI] || secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT];
3862 }4010 }
3863}4011}
38644012
@@ -4040,6 +4188,7 @@ async function onImageSwiped({ message, element, direction }) {
4040 const hasNegative = message.extra.negative;4188 const hasNegative = message.extra.negative;
4041 const prompt = await refinePrompt(message.extra.title, false);4189 const prompt = await refinePrompt(message.extra.title, false);
4042 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : '';4190 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : '';
4191 message.extra.title = prompt;
4043 const characterName = context.groupId4192 const characterName = context.groupId
4044 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()4193 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
4045 : context.characters[context.characterId]?.name;4194 : context.characters[context.characterId]?.name;
@@ -4514,13 +4663,19 @@ jQuery(async () => {
4514 $('#sd_interactive_visible').on('input', onInteractiveVisibleInput);4663 $('#sd_interactive_visible').on('input', onInteractiveVisibleInput);
4515 $('#sd_tool_visible').on('input', onToolVisibleInput);4664 $('#sd_tool_visible').on('input', onToolVisibleInput);
4516 $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);4665 $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
4517 $('#sd_stability_key').on('click', onStabilityKeyClick);
4518 $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);4666 $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
4519 $('#sd_huggingface_model_id').on('input', onHFModelInput);4667 $('#sd_huggingface_model_id').on('input', onHFModelInput);
4520 $('#sd_function_tool').on('input', onFunctionToolInput);4668 $('#sd_function_tool').on('input', onFunctionToolInput);
4521 $('#sd_bfl_key').on('click', onBflKeyClick);
4522 $('#sd_bfl_upsampling').on('input', onBflUpsamplingInput);4669 $('#sd_bfl_upsampling').on('input', onBflUpsamplingInput);
4523 $('#sd_falai_key').on('click', onFalaiKeyClick);4670
4671 $('#sd_google_api').on('input', function () {
4672 extension_settings.sd.google_api = String($(this).val());
4673 saveSettingsDebounced();
4674 });
4675 $('#sd_google_enhance').on('input', function () {
4676 extension_settings.sd.google_enhance = $(this).prop('checked');
4677 saveSettingsDebounced();
4678 });
45244679
4525 if (!CSS.supports('field-sizing', 'content')) {4680 if (!CSS.supports('field-sizing', 'content')) {
4526 $('.sd_settings .inline-drawer-toggle').on('click', function () {4681 $('.sd_settings .inline-drawer-toggle').on('click', function () {
@@ -4548,6 +4703,19 @@ jQuery(async () => {
45484703
4549 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);4704 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
45504705
4706 [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => {
4707 eventSource.on(event, async (/** @type {string} */ key) => {
4708 switch (key) {
4709 case SECRET_KEYS.BFL:
4710 case SECRET_KEYS.FALAI:
4711 case SECRET_KEYS.STABILITY:
4712 case SECRET_KEYS.AIMLAPI:
4713 await loadSettingOptions();
4714 break;
4715 }
4716 });
4717 });
4718
4551 await loadSettings();4719 await loadSettings();
4552 $('body').addClass('sd');4720 $('body').addClass('sd');
45534721
public/scripts/extensions/stable-diffusion/settings.html+35 -4
@@ -37,11 +37,13 @@
37 </label>37 </label>
38 <label for="sd_source" data-i18n="Source">Source</label>38 <label for="sd_source" data-i18n="Source">Source</label>
39 <select id="sd_source">39 <select id="sd_source">
40 <option value="aimlapi">AI/ML API</option>
40 <option value="bfl">BFL (Black Forest Labs)</option>41 <option value="bfl">BFL (Black Forest Labs)</option>
41 <option value="comfy">ComfyUI</option>42 <option value="comfy">ComfyUI</option>
42 <option value="drawthings">DrawThings HTTP API</option>43 <option value="drawthings">DrawThings HTTP API</option>
43 <option value="extras">Extras API (deprecated)</option>44 <option value="extras">Extras API (deprecated)</option>
44 <option value="falai">FAL.AI</option>45 <option value="falai">FAL.AI</option>
46 <option value="google">Google AI</option>
45 <option value="huggingface">HuggingFace Inference API (serverless)</option>47 <option value="huggingface">HuggingFace Inference API (serverless)</option>
46 <option value="nanogpt">NanoGPT</option>48 <option value="nanogpt">NanoGPT</option>
47 <option value="novel">NovelAI Diffusion</option>49 <option value="novel">NovelAI Diffusion</option>
@@ -139,7 +141,16 @@
139 </div>141 </div>
140 <i>Hint: Save an API key in the NovelAI API settings to use it here.</i>142 <i>Hint: Save an API key in the NovelAI API settings to use it here.</i>
141 </div>143 </div>
142 <div data-sd-source="openai">144 <div data-sd-source="aimlapi">
145 <div class="flex-container flexnowrap alignItemsBaseline marginBot5">
146 <strong class="flex1" data-i18n="API Key">API Key</strong>
147 <div id="sd_aimlapi_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_aimlapi">
148 <i class="fa-fw fa-solid fa-key"></i>
149 <span data-i18n="Click to set">Click to set</span>
150 </div>
151 </div>
152 </div>
153 <div data-sd-source="openai,aimlapi">
143 <small data-i18n="These settings only apply to DALL-E 3">These settings only apply to DALL-E 3</small>154 <small data-i18n="These settings only apply to DALL-E 3">These settings only apply to DALL-E 3</small>
144 <div class="flex-container">155 <div class="flex-container">
145 <div class="flex1">156 <div class="flex1">
@@ -200,7 +211,7 @@
200 <div data-sd-source="stability">211 <div data-sd-source="stability">
201 <div class="flex-container flexnowrap alignItemsBaseline marginBot5">212 <div class="flex-container flexnowrap alignItemsBaseline marginBot5">
202 <strong class="flex1" data-i18n="API Key">API Key</strong>213 <strong class="flex1" data-i18n="API Key">API Key</strong>
203 <div id="sd_stability_key" class="menu_button menu_button_icon">214 <div id="sd_stability_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_stability">
204 <i class="fa-fw fa-solid fa-key"></i>215 <i class="fa-fw fa-solid fa-key"></i>
205 <span data-i18n="Click to set">Click to set</span>216 <span data-i18n="Click to set">Click to set</span>
206 </div>217 </div>
@@ -244,7 +255,7 @@
244 <i class="fa-solid fa-share-from-square"></i>255 <i class="fa-solid fa-share-from-square"></i>
245 </a>256 </a>
246 <span class="expander"></span>257 <span class="expander"></span>
247 <div id="sd_bfl_key" class="menu_button menu_button_icon">258 <div id="sd_bfl_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_bfl">
248 <i class="fa-fw fa-solid fa-key"></i>259 <i class="fa-fw fa-solid fa-key"></i>
249 <span data-i18n="Click to set">Click to set</span>260 <span data-i18n="Click to set">Click to set</span>
250 </div>261 </div>
@@ -264,13 +275,33 @@
264 <i class="fa-solid fa-share-from-square"></i>275 <i class="fa-solid fa-share-from-square"></i>
265 </a>276 </a>
266 <span class="expander"></span>277 <span class="expander"></span>
267 <div id="sd_falai_key" class="menu_button menu_button_icon">278 <div id="sd_falai_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_falai">
268 <i class="fa-fw fa-solid fa-key"></i>279 <i class="fa-fw fa-solid fa-key"></i>
269 <span data-i18n="Click to set">Click to set</span>280 <span data-i18n="Click to set">Click to set</span>
270 </div>281 </div>
271 </div>282 </div>
272 </div>283 </div>
273284
285 <div data-sd-source="google">
286 <div class="flex-container">
287 <div class="flex1">
288 <label for="sd_google_api" data-i18n="API Type">API Type</label>
289 <select id="sd_google_api" class="text_pole">
290 <option value="makersuite">Google AI Studio</option>
291 <option value="vertexai">Google Vertex AI</option>
292 </select>
293 </div>
294 </div>
295 <div class="flex-container">
296 <label class="flex1 checkbox_label" for="sd_google_enhance" title="Enables prompt enhancing (passes prompts through an LLM to add detail).">
297 <input id="sd_google_enhance" type="checkbox" />
298 <span data-i18n="Enhance">
299 Enhance
300 </span>
301 </label>
302 </div>
303 </div>
304
274 <div class="flex-container">305 <div class="flex-container">
275 <div class="flex1">306 <div class="flex1">
276 <label for="sd_model" data-i18n="Model">Model</label>307 <label for="sd_model" data-i18n="Model">Model</label>
public/scripts/extensions/translate/index.html+1 -1
@@ -23,7 +23,7 @@
23 <option value="bing">Bing</option>23 <option value="bing">Bing</option>
24 <option value="oneringtranslator">OneRingTranslator</option>24 <option value="oneringtranslator">OneRingTranslator</option>
25 <option value="yandex">Yandex</option>25 <option value="yandex">Yandex</option>
26 <select>26 </select>
27 <select id="deepl_api_endpoint" class="margin0 text_pole flex1" title="DeepL API Endpoint">27 <select id="deepl_api_endpoint" class="margin0 text_pole flex1" title="DeepL API Endpoint">
28 <option value="free">Free</option>28 <option value="free">Free</option>
29 <option value="pro">Pro</option>29 <option value="pro">Pro</option>
public/scripts/extensions/tts/chatterbox.js+452 -0
@@ -0,0 +1,649 @@
1import { saveTtsProviderSettings } from './index.js';
2
3export { ChatterboxTtsProvider };
4
5class ChatterboxTtsProvider {
6 //########//
7 // Config //
8 //########//
9
10 settings = {};
11 constructor() {
12 // Initialize with default settings
13 this.settings = {
14 provider_endpoint: this.settings.provider_endpoint || 'http://localhost:8004',
15 voice_mode: this.settings.voice_mode || 'predefined',
16 predefined_voice: this.settings.predefined_voice || 'S1',
17 reference_voice: this.settings.reference_voice || '',
18 temperature: this.settings.temperature || 0.8,
19 exaggeration: this.settings.exaggeration || 0.5,
20 cfg_weight: this.settings.cfg_weight || 0.5,
21 seed: this.settings.seed || -1,
22 speed_factor: this.settings.speed_factor || 1.0,
23 language: this.settings.language || 'en',
24 split_text: this.settings.split_text || true,
25 chunk_size: this.settings.chunk_size || 120,
26 output_format: this.settings.output_format || 'wav',
27 voiceMap: this.settings.voiceMap || {},
28 };
29 }
30
31 ready = false;
32 voices = [];
33 separator = '. ';
34 audioElement = document.createElement('audio');
35
36 languageLabels = {
37 'English': 'en',
38 'Spanish': 'es',
39 'French': 'fr',
40 'German': 'de',
41 'Italian': 'it',
42 'Portuguese': 'pt',
43 'Polish': 'pl',
44 'Turkish': 'tr',
45 'Russian': 'ru',
46 'Dutch': 'nl',
47 'Czech': 'cs',
48 'Arabic': 'ar',
49 'Chinese': 'zh-cn',
50 'Japanese': 'ja',
51 'Korean': 'ko',
52 'Hindi': 'hi',
53 };
54
55 get settingsHtml() {
56 let html = `<div class="chatterbox-settings-container">
57 <div class="chatterbox-settings-header">
58 <h3>Chatterbox TTS Settings</h3>
59 <div class="status-indicator">
60 Status: <span id="chatterbox-status" class="offline">Offline</span>
61 </div>
62 </div>`;
63
64 // Server endpoint
65 html += `<div class="chatterbox-setting-row">
66 <label for="chatterbox-endpoint">Server Endpoint:</label>
67 <input id="chatterbox-endpoint" type="text" class="text_pole" value="${this.settings.provider_endpoint}" />
68 </div>`;
69
70 // Language selection
71 html += `<div class="chatterbox-setting-row">
72 <label for="chatterbox-language">Language:</label>
73 <select id="chatterbox-language">`;
74 for (let language in this.languageLabels) {
75 html += `<option value="${this.languageLabels[language]}" ${this.languageLabels[language] === this.settings.language ? 'selected' : ''}>${language}</option>`;
76 }
77 html += `</select>
78 </div>`;
79
80 // Generation parameters
81 html += `<div class="chatterbox-params-section">
82 <h4>Generation Parameters</h4>`;
83
84 // Temperature
85 html += `<div class="chatterbox-setting-row">
86 <label for="chatterbox-temperature">Temperature: <span id="chatterbox-temperature-value">${this.settings.temperature}</span></label>
87 <input id="chatterbox-temperature" type="range" min="0" max="1" step="0.1" value="${this.settings.temperature}" />
88 </div>`;
89
90 // Exaggeration
91 html += `<div class="chatterbox-setting-row">
92 <label for="chatterbox-exaggeration">Exaggeration: <span id="chatterbox-exaggeration-value">${this.settings.exaggeration}</span></label>
93 <input id="chatterbox-exaggeration" type="range" min="0" max="2" step="0.1" value="${this.settings.exaggeration}" />
94 </div>`;
95
96 // CFG Weight
97 html += `<div class="chatterbox-setting-row">
98 <label for="chatterbox-cfg-weight">CFG Weight: <span id="chatterbox-cfg-weight-value">${this.settings.cfg_weight}</span></label>
99 <input id="chatterbox-cfg-weight" type="range" min="0" max="1" step="0.1" value="${this.settings.cfg_weight}" />
100 </div>`;
101
102 // Speed Factor
103 html += `<div class="chatterbox-setting-row">
104 <label for="chatterbox-speed">Speed Factor: <span id="chatterbox-speed-value">${this.settings.speed_factor}</span></label>
105 <input id="chatterbox-speed" type="range" min="0.5" max="2" step="0.1" value="${this.settings.speed_factor}" />
106 </div>`;
107
108 // Seed
109 html += `<div class="chatterbox-setting-row">
110 <label for="chatterbox-seed">Seed (-1 for random):</label>
111 <input id="chatterbox-seed" class="text_pole" type="number" min="-1" value="${this.settings.seed}" />
112 </div>`;
113
114 // Text chunking
115 html += `<div class="chatterbox-setting-row">
116 <label class="checkbox_label">
117 <input type="checkbox" id="chatterbox-split-text" ${this.settings.split_text ? 'checked' : ''} />
118 Split long texts into chunks
119 </label>
120 </div>`;
121
122 // Chunk size
123 html += `<div class="chatterbox-setting-row" id="chunk-size-row" ${!this.settings.split_text ? 'style="display: none;"' : ''}>
124 <label for="chatterbox-chunk-size">Chunk Size:</label>
125 <input id="chatterbox-chunk-size" class="text_pole" type="number" min="50" max="500" value="${this.settings.chunk_size}" />
126 </div>`;
127
128 // Output format
129 html += `<div class="chatterbox-setting-row">
130 <label for="chatterbox-format">Output Format:</label>
131 <select id="chatterbox-format">
132 <option value="wav" ${this.settings.output_format === 'wav' ? 'selected' : ''}>WAV</option>
133 <option value="opus" ${this.settings.output_format === 'opus' ? 'selected' : ''}>Opus</option>
134 </select>
135 </div>`;
136
137 html += '</div>'; // End params section
138
139 // Footer with links
140 html += `<div class="chatterbox-footer">
141 <a href="${this.settings.provider_endpoint}" target="_blank">Chatterbox Web UI</a> |
142 <a href="https://github.com/devnen/Chatterbox-TTS-Server" target="_blank">Documentation</a>
143 </div>`;
144
145 html += '</div>'; // End container
146
147 // Add CSS styles
148 html += `<style>
149 .chatterbox-settings-container {
150 padding: 10px;
151 }
152 .chatterbox-settings-header {
153 display: flex;
154 justify-content: space-between;
155 align-items: center;
156 margin-bottom: 15px;
157 }
158 .chatterbox-settings-header h3 {
159 margin: 0;
160 }
161 .chatterbox-settings-container .status-indicator {
162 font-weight: bold;
163 }
164 #chatterbox-status.ready { color: #4CAF50; }
165 #chatterbox-status.offline { color: #f44336; }
166 #chatterbox-status.processing { color: #2196F3; }
167 .chatterbox-setting-row {
168 margin-bottom: 10px;
169 display: flex;
170 align-items: center;
171 gap: 10px;
172 }
173 .chatterbox-setting-row label {
174 flex: 0 0 150px;
175 }
176 .chatterbox-setting-row label.checkbox_label {
177 flex-basis: auto;
178 }
179 .chatterbox-setting-row input[type="text"],
180 .chatterbox-setting-row input[type="number"],
181 .chatterbox-setting-row select {
182 flex: 1;
183 }
184 .chatterbox-setting-row input[type="range"] {
185 flex: 1;
186 }
187 .chatterbox-params-section {
188 margin-top: 15px;
189 padding-top: 15px;
190 border-top: 1px solid #ccc;
191 }
192 .chatterbox-params-section h4 {
193 margin-top: 0;
194 margin-bottom: 10px;
195 }
196 .chatterbox-footer {
197 margin-top: 15px;
198 padding-top: 15px;
199 border-top: 1px solid #ccc;
200 text-align: center;
201 font-size: 0.9em;
202 }
203 </style>`;
204
205 return html;
206 }
207
208 //######################//
209 // Startup & Initialize //
210 //######################//
211
212 async loadSettings(settings) {
213 this.updateStatus('Offline');
214
215 if (Object.keys(settings).length === 0) {
216 console.info('Using default Chatterbox TTS Provider settings');
217 } else {
218 // Populate settings with provided values
219 for (const key in settings) {
220 if (key in this.settings) {
221 this.settings[key] = settings[key];
222 }
223 }
224 }
225
226 // Update UI elements
227 this.updateUIFromSettings();
228
229 console.debug('ChatterboxTTS: Settings loaded');
230
231 try {
232 // Check if TTS provider is ready
233 await this.checkReady();
234
235 if (this.ready) {
236 // Fetch all voice types for the voice map
237 await this.fetchTtsVoiceObjects();
238 this.updateStatus('Ready');
239 }
240
241 this.setupEventListeners();
242
243 } catch (error) {
244 console.error('Error loading Chatterbox settings:', error);
245 this.updateStatus('Offline');
246 }
247 }
248
249 updateUIFromSettings() {
250 $('#chatterbox-endpoint').val(this.settings.provider_endpoint);
251 $('#chatterbox-language').val(this.settings.language);
252 $('#chatterbox-temperature').val(this.settings.temperature);
253 $('#chatterbox-temperature-value').text(this.settings.temperature);
254 $('#chatterbox-exaggeration').val(this.settings.exaggeration);
255 $('#chatterbox-exaggeration-value').text(this.settings.exaggeration);
256 $('#chatterbox-cfg-weight').val(this.settings.cfg_weight);
257 $('#chatterbox-cfg-weight-value').text(this.settings.cfg_weight);
258 $('#chatterbox-speed').val(this.settings.speed_factor);
259 $('#chatterbox-speed-value').text(this.settings.speed_factor);
260 $('#chatterbox-seed').val(this.settings.seed);
261 $('#chatterbox-split-text').prop('checked', this.settings.split_text);
262 $('#chatterbox-chunk-size').val(this.settings.chunk_size);
263 $('#chatterbox-format').val(this.settings.output_format);
264
265 // Show/hide chunk size based on split text
266 if (this.settings.split_text) {
267 $('#chunk-size-row').show();
268 } else {
269 $('#chunk-size-row').hide();
270 }
271 }
272
273 //##############################//
274 // Check Server is Available //
275 //##############################//
276
277 async checkReady() {
278 try {
279 const response = await fetch(`${this.settings.provider_endpoint}/api/ui/initial-data`);
280
281 if (!response.ok) {
282 throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
283 }
284
285 const data = await response.json();
286
287 // Check if we got valid data
288 if (data) {
289 this.ready = true;
290 console.log('Chatterbox TTS service is ready.');
291 } else {
292 this.ready = false;
293 console.log('Chatterbox TTS service returned invalid data.');
294 }
295 } catch (error) {
296 console.error('Error checking Chatterbox TTS service readiness:', error);
297 this.ready = false;
298 }
299 }
300
301 //######################//
302 // Get Available Voices //
303 //######################//
304
305 async fetchTtsVoiceObjects() {
306 try {
307 // Always fetch predefined voices
308 const predefinedResponse = await fetch(`${this.settings.provider_endpoint}/get_predefined_voices`);
309 if (!predefinedResponse.ok) {
310 throw new Error(`HTTP ${predefinedResponse.status}: ${predefinedResponse.statusText}`);
311 }
312
313 const predefinedData = await predefinedResponse.json();
314
315 // Transform predefined voices
316 const predefinedVoices = predefinedData.map(voice => ({
317 name: voice.display_name,
318 voice_id: voice.voice_id || voice.filename,
319 preview_url: null,
320 lang: voice.language || 'en',
321 }));
322
323 // Always try to fetch reference voices
324 let referenceVoices = [];
325 try {
326 const refResponse = await fetch(`${this.settings.provider_endpoint}/get_reference_files`);
327 if (refResponse.ok) {
328 const refData = await refResponse.json();
329 referenceVoices = refData.map(filename => ({
330 name: `[Clone] ${filename}`,
331 voice_id: `ref_${filename}`,
332 preview_url: null,
333 lang: 'en',
334 }));
335 }
336 } catch (error) {
337 console.warn('Failed to fetch reference voices:', error);
338 }
339
340 // Combine all voices
341 this.voices = [...predefinedVoices, ...referenceVoices];
342
343 console.log(`Loaded ${this.voices.length} voices (${predefinedVoices.length} predefined, ${referenceVoices.length} reference)`);
344 return this.voices;
345 } catch (error) {
346 console.error('Error fetching Chatterbox voices:', error);
347 this.voices = [];
348 return [];
349 }
350 }
351
352 // Alias for internal use
353 async fetchVoices() {
354 return this.fetchTtsVoiceObjects();
355 }
356
357 //###########################//
358 // Setup Event Listeners //
359 //###########################//
360
361 setupEventListeners() {
362 // Server endpoint change
363 $('#chatterbox-endpoint').on('input', () => {
364 this.settings.provider_endpoint = $('#chatterbox-endpoint').val();
365 this.onSettingsChange();
366 });
367
368 // Language
369 $('#chatterbox-language').on('change', (e) => {
370 this.settings.language = e.target.value;
371 this.onSettingsChange();
372 });
373
374 // Parameter sliders
375 $('#chatterbox-temperature').on('input', (e) => {
376 this.settings.temperature = parseFloat(e.target.value);
377 $('#chatterbox-temperature-value').text(this.settings.temperature);
378 this.onSettingsChange();
379 });
380
381 $('#chatterbox-exaggeration').on('input', (e) => {
382 this.settings.exaggeration = parseFloat(e.target.value);
383 $('#chatterbox-exaggeration-value').text(this.settings.exaggeration);
384 this.onSettingsChange();
385 });
386
387 $('#chatterbox-cfg-weight').on('input', (e) => {
388 this.settings.cfg_weight = parseFloat(e.target.value);
389 $('#chatterbox-cfg-weight-value').text(this.settings.cfg_weight);
390 this.onSettingsChange();
391 });
392
393 $('#chatterbox-speed').on('input', (e) => {
394 this.settings.speed_factor = parseFloat(e.target.value);
395 $('#chatterbox-speed-value').text(this.settings.speed_factor);
396 this.onSettingsChange();
397 });
398
399 // Seed
400 $('#chatterbox-seed').on('change', (e) => {
401 this.settings.seed = parseInt(e.target.value);
402 this.onSettingsChange();
403 });
404
405 // Text splitting
406 $('#chatterbox-split-text').on('change', (e) => {
407 this.settings.split_text = e.target.checked;
408 if (e.target.checked) {
409 $('#chunk-size-row').show();
410 } else {
411 $('#chunk-size-row').hide();
412 }
413 this.onSettingsChange();
414 });
415
416 $('#chatterbox-chunk-size').on('change', (e) => {
417 this.settings.chunk_size = parseInt(e.target.value);
418 this.onSettingsChange();
419 });
420
421 // Output format
422 $('#chatterbox-format').on('change', (e) => {
423 this.settings.output_format = e.target.value;
424 this.onSettingsChange();
425 });
426 }
427
428 //#############################//
429 // Store ST interface settings //
430 //#############################//
431
432 onSettingsChange() {
433 // Save the updated settings
434 saveTtsProviderSettings();
435 }
436
437 //#########################//
438 // Handle Reload button //
439 //#########################//
440
441 async onRefreshClick() {
442 try {
443 this.updateStatus('Processing');
444 await this.checkReady();
445
446 if (this.ready) {
447 await this.fetchTtsVoiceObjects();
448 this.updateStatus('Ready');
449 } else {
450 this.updateStatus('Offline');
451 }
452 } catch (error) {
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