Merge pull request #4189 from SillyTavern/staging Staging
Signed| @@ -321,24 +321,24 @@ Start.bat --port 8000 --listen false | ||
| 321 | 321 | ### Unterstützte Argumente |
| 322 | 322 | |
| 323 | 323 | | Option | Beschreibung | Typ | |
| 324 | 324 | |-------------------------|---------|--------------------------------------------------------------------------------------------------|----------| |
| 325 | 325 | | `--version` | Versionsnummer anzeigen | boolean | |
| 326 | 326 | | `--enableIPv6` | Aktiviert IPv6. | boolean | |
| 327 | 327 | | `--enableIPv4` | Aktiviert IPv4. | boolean | |
| 328 | 328 | | `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. Wenn nicht angegeben, wird auf YAML-Konfiguration „Port“ zurückgegriffen. | number | |
| 329 | 329 | | „`--dnsPreferIPv6“ ` | Bevorzugt IPv6 für DNS. Wenn nicht angegeben, wird auf YAML-Konfiguration „preferIPv6“ zurückgegriffen. | boolean | |
| 330 | 330 | | „`--autorun“ browserLaunchEnabled` | Startet SillyTavern automatisch im Browser. Wenn nicht angegeben, wird auf YAML-Konfiguration „autorun“ zurückgegriffen. | boolean | |
| 331 | 331 | | „`--autorunHostname“ browserLaunchHostname` | Der Autorun-Hostname, am besten auf „auto“" belassen. | string | |
| 332 | 332 | | „`--autorunPortOverride“ browserLaunchPort` | Überschreibt den Port für Autorun. | string | |
| 333 | -| „--listen“ | SillyTavern lauscht auf allen Netzwerkschnittstellen. Wenn nicht angegeben, wird auf YAML-Konfiguration „listen“ zurückgegriffen.| boolean | | |
| 333 | +| `--browserLaunchAvoidLocalhost` | Vermeidet die Verwendung von „localhost" für Autorun im Auto-Modus. | boolean | | |
| 334 | -| „--corsProxy“ | Aktiviert CORS-Proxy. Wenn nicht angegeben, wird auf YAML-Konfiguration „enableCorsProxy“ zurückgegriffen. | boolean | | |
| 334 | +| `--listen` | SillyTavern lauscht auf allen Netzwerkschnittstellen. | boolean | | |
| 335 | +| `--corsProxy` | Aktiviert CORS-Proxy. Wenn nicht angegeben. | boolean | | |
| 335 | 336 | | `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean | |
| 336 | 337 | | `--ssl` | Aktiviert SSL | boolean | |
| 337 | 338 | | `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string | |
| 338 | 339 | | `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string | |
| 339 | 340 | | `--whitelist` | Aktiviert den Whitelist-Modus | boolean | |
| 340 | 341 | | `--dataRoot` | Stammverzeichnis für Datenspeicherung | string | |
| 341 | -| `--avoidLocalhost` | Vermeidet die Verwendung von „localhost“ für Autorun im Auto-Modus. | boolean | | |
| 342 | 342 | | `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean | |
| 343 | 343 | | `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean | |
| 344 | 344 | | `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string | |
| @@ -335,24 +335,24 @@ Start.bat --port 8000 --listen false | ||
| 335 | 335 | ### 지원되는 인수 |
| 336 | 336 | |
| 337 | 337 | | 옵션 | 설명 | 타입 | |
| 338 | 338 | |-------------------------|----------------|--------------------------------------------------------------------------------------|----------| |
| 339 | 339 | | `--version` | 버전 표시 | boolean | |
| 340 | 340 | | `--enableIPv6` | IPv6 활성화 | boolean | |
| 341 | 341 | | `--enableIPv4` | IPv4 활성화 | boolean | |
| 342 | 342 | | `--port` | SillyTavern이 실행될 포트를 설정합니다. 설정되지 않은 경우 yaml config 'port'를 불러옵니다. | number | |
| 343 | 343 | | `--dnsPreferIPv6` | DNS에 IPv6를 우선으로 할당합니다. 설정되지 않은 경우 yaml config를 불러옵니다. | boolean | |
| 344 | 344 | | `--autorunbrowserLaunchEnabled` | 브라우저에서 SillyTavern을 자동으로 실행합니다. 설정되지 않은 경우 yaml config 'autorun'를 불러옵니다. | boolean | |
| 345 | 345 | | `--autorunHostnamebrowserLaunchHostname` | 자동 실행 호스트 이름, 'auto'가 최적의 설정일 것입니다. | string | |
| 346 | 346 | | `--autorunPortOverridebrowserLaunchPort` | 자동 실행 포트 덮어쓰기 | string | |
| 347 | 347 | | `--listenbrowserLaunchAvoidLocalhost` | SillyTavern이 모든 네트워크 인터페이스에서 수신 대기합니다. 설정되지자동 않은모드에서 경우자동 yaml실행 구성시 'listenlocalhost'을 불러옵니다. 사용 방지 | boolean | |
| 348 | 348 | | `--corsProxylisten` | CORS 프록시 활성화. 설정되지 않은SillyTavern이 경우모든 yaml네트워크 구성인터페이스에서 'enableCorsProxy'을수신 불러옵니다대기합니다. | boolean | |
| 349 | +| `--corsProxy` | CORS 프록시 활성화. | boolean | | |
| 349 | 350 | | `--disableCsrf` | CSRF 보호 비활성화 | boolean | |
| 350 | 351 | | `--ssl` | SSL 활성화 | boolean | |
| 351 | 352 | | `--certPath` | 인증서 파일 경로 | string | |
| 352 | 353 | | `--keyPath` | 프라이빗 키 파일 경로 | string | |
| 353 | 354 | | `--whitelist` | 화이트리스트 모드 활성화 | boolean | |
| 354 | 355 | | `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string | |
| 355 | -| `--avoidLocalhost` | 자동 모드에서 자동 실행 시 'localhost' 사용 방지 | boolean | | |
| 356 | 356 | | `--basicAuthMode` | 기본 인증 활성화 | boolean | |
| 357 | 357 | | `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean | |
| 358 | 358 | | `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string | |
| @@ -284,24 +284,24 @@ Start.bat --port 8000 --listen false | ||
| 284 | 284 | ### Поддерживаемые аргументы |
| 285 | 285 | |
| 286 | 286 | | Аргумент | Описание | Тип | |
| 287 | 287 | |-------------------------|-----|-----------------------------------------------------------------------------------------------------------|----------| |
| 288 | 288 | | `--version` | Показывает номер версии. | boolean | |
| 289 | 289 | | `--enableIPv6` | Включает IPv6. | boolean | |
| 290 | 290 | | `--enableIPv4` | Включает IPv4. | boolean | |
| 291 | 291 | | `--port` | Устанавливает порт, котрыйкоторый будет использовать SillyTavern. Если не указан, то используется yaml-конфиг 'port'. | number | |
| 292 | 292 | | `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. Если не указан, то используется yaml-конфиг 'preferIPv6'. | boolean | |
| 293 | 293 | | `--autorunbrowserLaunchEnabled` | Автоматический запуск SillyTavern в браузере. Если не указан, то используется yaml-конфиг 'autorun'. | boolean | |
| 294 | 294 | | `--autorunHostnamebrowserLaunchHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string | |
| 295 | 295 | | `--autorunPortOverridebrowserLaunchPort` | Переопределяет порт для автозапуска. | string | |
| 296 | 296 | | `--listenbrowserLaunchAvoidLocalhost` | SillyTavern будет прослушивать все сетевые интерфейсы.Избегает Еслииспользования не'localhost' указан,для тоавтозапуска используетсяв yaml-конфигрежиме 'listenauto'. | boolean | |
| 297 | 297 | | `--corsProxylisten` | Включает CORS-прокси. Если неSillyTavern указан,будет топрослушивать используетсявсе yaml-конфигсетевые 'enableCorsProxy'интерфейсы. | boolean | |
| 298 | +| `--corsProxy` | Включает CORS-прокси. | boolean | | |
| 298 | 299 | | `--disableCsrf` | Отключает защиту от CSRF. | boolean | |
| 299 | 300 | | `--ssl` | Включает SSL. | boolean | |
| 300 | 301 | | `--certPath` | Путь к файлу c сертификатом. | string | |
| 301 | 302 | | `--keyPath` | Путь к файлу с закрытым ключом. | string | |
| 302 | 303 | | `--whitelist` | Включает режим белого списка. | boolean | |
| 303 | 304 | | `--dataRoot` | Корневой каталог для хранения данных. | string | |
| 304 | -| `--avoidLocalhost` | Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean | | |
| 305 | 305 | | `--basicAuthMode` | Включает простую аутентификацию. | boolean | |
| 306 | 306 | | `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean | |
| 307 | 307 | | `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string | |
| @@ -1,6 +1,8 @@ | ||
| 1 | 1 | > [!IMPORTANT] |
| 2 | 2 | > 这里的信息可能已经过时或不完整,仅供您参考。请使用英文版本获取最新信息。 |
| 3 | 3 | |
| 4 | +<a name="readme-top"></a> | |
| 5 | + | |
| 4 | 6 | ![][cover] |
| 5 | 7 | |
| 6 | 8 | <div align="center"> |
| @@ -8,7 +10,7 @@ | ||
| 8 | 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) |
| 9 | 11 | |
| 10 | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 11 | 13 | [](https://github.com/SillyTavern/SillyTavern/networkforks) |
| 12 | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 13 | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 14 | 16 | |
| @@ -16,307 +18,379 @@ | ||
| 16 | 18 | |
| 17 | 19 | --- |
| 18 | 20 | |
| 19 | -移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 的聊天信息总结。 | |
| 21 | +SillyTavern 为众多 LLM API(KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)提供统一界面,拥有移动设备友好的布局、视觉小说模式、Automatic1111 & ComfyUI API 图像生成集成、TTS、世界书(lorebooks)、可自定义的 UI、自动翻译、超乎您想象的丰富 Prompt 选项,以及通过第三方扩展实现的无限增长潜力。 | |
| 20 | 22 | |
| 21 | -基于 TavernAI 1.2.8 的分叉版本 | |
| 23 | +我们有一个[文档网站](https://docs.sillytavern.app/)来回答您的大部分问题并帮助您入门。 | |
| 22 | 24 | |
| 23 | -### 由 Cohee、RossAscends 和 SillyTavern 社区为您呈现 | |
| 25 | +## SillyTavern 是什么? | |
| 24 | 26 | |
| 25 | -注意:我们创建了一个 [帮助文档](https://docs.sillytavern.app/) 网站来回答各类问题与帮助您开始使用。 | |
| 27 | +SillyTavern(简称 ST)是一个本地安装的用户界面,允许您与文本生成 LLM、图像生成引擎和 TTS 语音模型进行交互。 | |
| 26 | 28 | |
| 27 | -### SillyTavern 或 TavernAI 是什么? | |
| 29 | +SillyTavern 于 2023 年 2 月作为 TavernAI 1.2.8 的一个分支开始,如今已拥有超过 200 名贡献者和 2 年的独立开发经验,并继续作为资深 AI 爱好者领先的软件。 | |
| 28 | 30 | |
| 29 | -SillyTavern 是一个可以安装在电脑(和安卓手机)上的用户界面,让您可以与文本生成的人工智能互动,并与您或社区创建的角色聊天/玩角色扮演游戏。 | |
| 31 | +## 我们的愿景 | |
| 30 | 32 | |
| 31 | -SillyTavern 是 TavernAI 1.2.8 的一个分支,正在进行更积极地开发,并添加了许多重要功能。在这一点上,它可以被视为完全独立的程序。 | |
| 33 | +1. 我们的目标是尽可能为用户提供 LLM Prompt 的最大效用和控制权。陡峭的学习曲线是乐趣的一部分! | |
| 34 | +2. 我们不提供任何在线或托管服务,也不会以编程方式跟踪任何用户数据。 | |
| 35 | +3. SillyTavern 是一个由专注的 LLM 爱好者社区为您带来的充满激情的项目,并且将永远是免费和开源的。 | |
| 32 | 36 | |
| 33 | 37 | ### 分支 |
| 34 | 38 | |
| 35 | 39 | SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。 |
| 36 | 40 | |
| 37 | 41 | *- `release` -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。通常每月更新一次。 |
| 38 | 42 | *- `staging` - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。每天更新数次。 |
| 39 | 43 | |
| 40 | 44 | 如果你不熟悉使用 Gitgit 命令命令行,或者不了解什么是分支,别担心!`release` 分支始终是您的首选。 |
| 41 | 45 | |
| 42 | 46 | ### 除了 SillyTavern,我还需要什么? |
| 43 | 47 | |
| 44 | 48 | 由于 SillyTavern 本身并无用处只是一个界面,因为它只是一个用户聊天界面您需要接入一个 LLM 后端来进行推理。你必须接入一个能充当角色扮演的人工智能系统您可以使用 AI Horde 进行开箱即用的聊天。支持的人工智能系统有多种除此之外,我们还支持许多其他本地和基于云的 LLM 后端:OpenAPIOpenAI API兼容 (GPT)API、KoboldAI(可在本地或 Google Colab、Tabby 上运行)等等等。您可以在 [常见问题文档](https://docs.sillytavern.app/usage/faqapi-connections/)中阅读更多关于我们支持的 中阅读更多相关信息API 的信息。 |
| 45 | 49 | |
| 46 | 50 | ### 我需要一台性能强大的电脑来运行 SillyTavern 吗? |
| 47 | 51 | |
| 48 | -由于 SillyTavern 只是一个用户聊天界面,它对硬件性能的要求很低,可以在任何电脑上运行。需要强大性能的是人工智能系统。 | |
| 52 | +硬件要求很低:任何可以运行 NodeJS 18 或更高版本的设备都可以运行它。如果您打算在本地计算机上进行 LLM 推理,我们建议使用至少具有 6GB VRAM 的 3000 系列 NVIDIA 显卡。有关更多详细信息,请查看您后端的文档。 | |
| 49 | - | |
| 50 | -### 移动设备支持 | |
| 51 | - | |
| 52 | -> 注意 | |
| 53 | 53 | |
| 54 | -> **此分叉可使用 Termux 在安卓手机上原生运行。请参考 ArroganceComplex#2659 编写的指南:** | |
| 54 | +### 建议的后端(非附属) | |
| 55 | 55 | |
| 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。 | |
| 57 | 60 | |
| 58 | 61 | ## 有问题或建议? |
| 59 | 62 | |
| 60 | 63 | ### 我们现在有了 Discord 社区服务器 |
| 61 | 64 | |
| 62 | -获取支持,或分享喜爱的角色和 Prompt: | |
| 65 | +| [![][discord-shield-badge]][discord-link] | [加入我们的 Discord 社区!](https://discord.gg/sillytavern) 获取支持,分享喜爱的角色和 Prompt。 | | |
| 66 | +| :---------------------------------------- | :---------------------------------------------------------------------------------------------- | | |
| 63 | 67 | |
| 64 | -### [加入 Discord 社区](https://discord.gg/sillytavern) | |
| 68 | +或者直接与开发人员联系: | |
| 65 | 69 | |
| 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) | |
| 67 | 73 | |
| 68 | -直接与开发人员联系: | |
| 74 | +### 我喜欢你的项目!我该如何贡献自己的力量? | |
| 69 | 75 | |
| 70 | -* Discord: cohee 或 rossascends | |
| 76 | +1. 发送 Pull Request。学习如何贡献:[CONTRIBUTING.md](../CONTRIBUTING.md) | |
| 71 | -* Reddit:/u/RossAscends 或 /u/sillylossy | |
| 77 | +2. 使用提供的模板发送功能建议和问题报告。 | |
| 72 | -* [发布 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues) | |
| 78 | +3. 请先阅读整个 readme 文件并查看文档网站,以避免提交重复的问题。 | |
| 73 | 79 | |
| 74 | 80 | ## 此版本包括屏幕截图 |
| 75 | 81 | |
| 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 仍是内部格式) | |
| 87 | 84 | |
| 88 | 85 | ## 扩展角色卡 |
| 89 | 86 | |
| 90 | -SillyTavern 支持扩展服务。 | |
| 87 | +SillyTavern 围绕“角色卡”的概念构建。角色卡是设定 LLM 行为的 Prompt 集合,是在 SillyTavern 中进行持久对话所必需的。它们的功能类似于 ChatGPT 的 GPTs 或 Poe 的 bots。角色卡的内容可以是任何东西:一个抽象的场景、一个为特定任务量身定制的助手、一个著名人物或一个虚构角色。 | |
| 91 | 88 | |
| 92 | -* 角色情绪识别 | |
| 89 | +要在不选择角色卡的情况下进行快速对话或仅测试 LLM 连接,只需在打开 SillyTavern 后在欢迎屏幕的输入栏中键入您的 Prompt 输入。这将创建一个空的“助手”角色卡,您可以稍后自定义。 | |
| 93 | -* 聊天记录自动摘要 | |
| 94 | -* 在聊天窗口发送图片,并由人工智能解释图片内容 | |
| 95 | -* 文本图像生成(5 预设,以及 "自由模式") | |
| 96 | -* 聊天信息的文字转语音(通过 ElevenLabs、Silero 或操作系统的语音生成) | |
| 97 | 90 | |
| 98 | -扩展服务的完整功能介绍和使用教程,请参阅 [Docs](https://docs.sillytavern.app/)。 | |
| 91 | +要大致了解如何定义角色卡,请参阅默认角色(Seraphina)或从“下载扩展和资源”菜单中下载选定的社区制作卡片。 | |
| 99 | 92 | |
| 100 | -## 界面/CSS/性能,由 RossAscends 调整并优化 | |
| 93 | +## 主要功能 | |
| 101 | 94 | |
| 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 = 重新生成人工智能最后的回复 | |
| 111 | 102 | |
| 112 | -* 用户名更改和角色删除不再强制重新刷新页面。 | |
| 103 | +## 扩展 | |
| 113 | 104 | |
| 114 | -* 增加在页面加载时自动连接 API 的选项。 | |
| 105 | +SillyTavern 支持扩展。 | |
| 115 | -* 增加选项,在页面加载时自动加载最近的聊天信息。 | |
| 116 | -* 更好的 Tokens 计算器 - 适用于未保存的文字,并显示永久和临时 Tokens 数量 | |
| 117 | 106 | |
| 118 | -* 更好的聊天历史查询窗口 | |
| 107 | +- 角色情绪表达 | |
| 119 | - * 聊天的文件名以"(角色卡名称)+(创建时间)"的可读格式保存 | |
| 108 | +- 聊天记录自动摘要 | |
| 120 | - * 聊天历史预览从 40 个字符增加到 300 个字符。 | |
| 109 | +- 自动 UI 和聊天翻译 | |
| 121 | - * 聊天历史排序有多种选择(按名称、创建日期、聊天记录大小)。 | |
| 110 | +- Stable Diffusion/FLUX/DALL-E 图像生成 | |
| 111 | +- AI 回复消息的文本转语音(通过 ElevenLabs、Silero 或操作系统的 TTS) | |
| 112 | +- 网络搜索功能,为您的 Prompt 添加额外的现实世界背景信息 | |
| 113 | +- 更多扩展可从“下载扩展和资源”菜单中下载。 | |
| 122 | 114 | |
| 123 | -* 默认情况下,左侧和右侧弹出的设置面板会在点击其他区域时自动关闭。 | |
| 115 | +有关如何使用它们的使用教程,请参阅[文档](https://docs.sillytavern.app/)。 | |
| 124 | -* 点击导航面板上的 "锁按钮" 将保持弹出面板打开,并在不同聊天中记住此设置。 | |
| 125 | -* 导航面板的打开或关闭状态也会跨聊天保存。 | |
| 126 | 116 | |
| 127 | -* 自定义聊天界面: | |
| 117 | +## ⌛ 安装 | |
| 128 | - * 收到新消息时播放提示音 | |
| 129 | - * 切换圆形或长方形头像样式 | |
| 130 | - * 在台式电脑上拥有更宽的聊天窗口 | |
| 131 | - * 可选的半透明玻璃效果聊天窗口 | |
| 132 | - * 可定制 "主文本"、"引用文本 "和 "斜体文本 "的字体颜色。 | |
| 133 | - * 可定制聊天界面的背景颜色和透明模糊程度 | |
| 134 | 118 | |
| 135 | 119 | ### 安装🪟 Windows |
| 136 | 120 | |
| 137 | -*注意:SillyTavern 用于本地安装,尚未在 Colab 或其他云服务上进行全面测试。 | |
| 121 | +> \[!WARNING] | |
| 122 | +> | |
| 123 | +> - 请勿安装到任何受 Windows 控制的文件夹(Program Files、System32 等)中。 | |
| 124 | +> - 请勿以管理员权限运行 Start.bat | |
| 125 | +> - 无法在 Windows 7 上安装,因为它无法运行 NodeJS 18.16 | |
| 138 | 126 | |
| 139 | -> **警告** | |
| 127 | +#### 通过 Git 安装(推荐) | |
| 140 | 128 | |
| 141 | -> 切勿安装到任何受 Windows 控制的系统文件夹(Program Files, System32, etc)中。 | |
| 129 | +1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本) | |
| 130 | +2. 安装 [Git for Windows](https://gitforwindows.org/) | |
| 131 | +3. 打开 Windows 资源管理器 (`Win+E`) | |
| 132 | +4. 浏览或创建一个不受 Windows 控制或监控的文件夹(例如:C:\MySpecialFolder\) | |
| 133 | +5. 通过点击顶部的“地址栏”,输入 `cmd`,然后按 Enter,在该文件夹内打开命令提示符。 | |
| 134 | +6. 弹出黑框(命令提示符)后,键入以下其中一项并按 Enter: | |
| 142 | 135 | |
| 143 | -> 不要以管理员权限运行 start.bat | |
| 136 | +- Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | |
| 137 | +- Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | |
| 144 | 138 | |
| 145 | -### Windows | |
| 139 | +7. 等待所有内容克隆完成后,双击 `Start.bat` 以使 NodeJS 安装其依赖项。 | |
| 140 | +8. 然后服务器将启动,SillyTavern 将在您的浏览器中弹出。 | |
| 146 | 141 | |
| 147 | -通过 Git 安装(推荐使用,便于更新) | |
| 142 | +#### 通过 GitHub Desktop 安装 | |
| 148 | 143 | |
| 149 | -附有精美图片示例的简易指南: | |
| 144 | +(这**仅**允许在 GitHub Desktop 中使用 git,如果您也想在命令行上使用 `git`,则还需要安装 [Git for Windows](https://gitforwindows.org/)) | |
| 150 | -<https://docs.sillytavern.app/installation/windows/> | |
| 151 | 145 | |
| 152 | 146 | 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本) |
| 153 | 147 | 2. 安装 [GitHub 客户端Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) |
| 154 | - 3. 打开 Windows 资源管理器 (`Win+E`) | |
| 148 | +3. 安装 GitHub Desktop 后,点击 `Clone a repository from the internet....`(注意:此步骤**无需**创建 GitHub 帐户) | |
| 155 | - 4. 浏览或创建一个不受 Windows 控制或监控的文件夹。(例如:C:\MySpecialFolder\) | |
| 149 | +4. 在菜单中,点击 URL 选项卡,输入此 URL `https://github.com/SillyTavern/SillyTavern`,然后点击 Clone。您可以更改本地路径以更改 SillyTavern 的下载位置。 | |
| 156 | - 5. 点击顶部的 "地址栏",在该文件夹内打开命令提示符,输入 `cmd`,然后按回车。 | |
| 150 | +5. 要打开 SillyTavern,请使用 Windows 资源管理器浏览到克隆存储库的文件夹。默认情况下,存储库将克隆到此处:`C:\Users\[您的 Windows 用户名]\Documents\GitHub\SillyTavern` | |
| 157 | - 6. 弹出黑框(CMD 命令提示符)后,键入以下其中一项并按 Enter: | |
| 151 | +6. 双击 `start.bat` 文件。(注意:文件名的 `.bat` 部分可能被您的操作系统隐藏,在这种情况下,它将显示为一个名为“`Start`”的文件。双击此文件以运行 SillyTavern) | |
| 152 | +7. 双击后,应打开一个大的黑色命令控制台窗口,SillyTavern 将开始安装其运行所需的组件。 | |
| 153 | +8. 安装过程完成后,如果一切正常,命令控制台窗口应如下所示,并且您的浏览器中应打开一个 SillyTavern 选项卡: | |
| 154 | +9. 连接到任何[支持的 API](https://docs.sillytavern.app/usage/api-connections/) 并开始聊天! | |
| 158 | 155 | |
| 159 | -* 稳定分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | |
| 156 | +### 🐧 Linux & 🍎 MacOS | |
| 160 | -* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | |
| 161 | 157 | |
| 162 | - 7. 等待 Git 克隆完成后,双击文件夹中的 `Start.bat` 将启动 NodeJS 并开始自动安装需要的软件包。 | |
| 158 | +对于 MacOS / Linux,所有这些都将在终端中完成。 | |
| 163 | - 8. 然后 SillyTavern 服务就会自动启动,同时在浏览器新标签页中自动打开。 | |
| 164 | 159 | |
| 165 | -通过压缩包下载安装(不推荐) | |
| 160 | +1. 安装 git 和 nodeJS(具体方法取决于您的操作系统) | |
| 161 | +2. 克隆仓库 | |
| 166 | 162 | |
| 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 服务自动为你准备好一切后,会在你的浏览器中打开一个新标签页。 | |
| 172 | 165 | |
| 173 | -### Linux | |
| 166 | +3. `cd SillyTavern` 导航到安装文件夹。 | |
| 167 | +4. 使用以下命令之一运行 `start.sh` 脚本: | |
| 174 | 168 | |
| 175 | 169 | 1.运行- `./start.sh` 脚本。 |
| 176 | - 2.等待自动完成,然后开始享受 | |
| 170 | +- `bash start.sh` | |
| 177 | 171 | |
| 178 | 172 | ## API🐋 密钥管理通过 Docker 安装 |
| 179 | 173 | |
| 180 | -SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。 | |
| 174 | +这些说明假定您已安装 Docker,能够访问命令行以安装容器,并熟悉其常规操作。 | |
| 181 | 175 | |
| 182 | -默认情况下,输入密钥并重新加载页面后,密钥会自动隐藏以保证安全。 | |
| 176 | +### 使用 GitHub Container Registry | |
| 183 | 177 | |
| 184 | -如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置: | |
| 178 | +#### Docker Compose (最简单) | |
| 185 | 179 | |
| 186 | -1. 打开 `config.yaml` 文件,将里面的 `allowKeysExposure` 设置为 `true`。 | |
| 180 | +从 [GitHub 仓库](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) 获取 `docker-compose.yml` 文件,并在文件所在目录中运行以下命令。这将从 GitHub Container Registry 中拉取最新的 release 镜像并启动容器,自动创建必要的卷。 | |
| 187 | -2. 然后重启 SillyTavern 服务。 | |
| 188 | 181 | |
| 189 | -## 远程访问 | |
| 182 | +```shell | |
| 183 | +docker-compose up | |
| 184 | +``` | |
| 190 | 185 | |
| 191 | -这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。 | |
| 186 | +根据您的需求自定义 `docker-compose.yml` 文件。默认端口为 8000。如果您想使用环境变量调整服务器配置,请在此处阅读文档:[链接](https://docs.sillytavern.app/administration/config-yaml/#environment-variables)。 | |
| 192 | 187 | |
| 193 | -不过,SillyTavern 也可以被设置为允许从任何地方进行远程访问。 | |
| 188 | +#### Docker CLI (高级) | |
| 194 | 189 | |
| 195 | -**重要提示:SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。 | |
| 190 | +您将需要两个强制性的目录映射和一个端口映射才能使 SillyTavern 正常运行。在命令中,替换以下位置中的选项: | |
| 196 | 191 | |
| 197 | 192 | ### 1.管理白名单# IP容器变量 |
| 198 | 193 | |
| 199 | -* 在你的 SillyTavern 文件夹中新建一个文本文件,名为 `whitelist.txt`。 | |
| 194 | +##### 卷映射 | |
| 200 | -* 用文本编辑器打开该文件,添加你希望允许连接的 IP 地址列表。 | |
| 201 | -* 接受单个 IP 地址和 IP 范围,示例: | |
| 202 | 195 | |
| 203 | -``` | |
| 196 | +- `CONFIG_PATH` - SillyTavern 配置文件将存储在主机上的目录 | |
| 204 | -192.168.0.1 | |
| 197 | +- `DATA_PATH` - SillyTavern 用户数据(包括角色)将存储在主机上的目录 | |
| 205 | -192.168.0.20 | |
| 198 | +- `PLUGINS_PATH` - (可选) SillyTavern 服务器插件将存储在主机上的目录 | |
| 206 | -``` | |
| 199 | +- `EXTENSIONS_PATH` - (可选) 全局 UI 扩展将存储在主机上的目录 | |
| 207 | 200 | |
| 208 | -或者 | |
| 201 | +##### 端口映射 | |
| 209 | 202 | |
| 210 | -``` | |
| 203 | +- `PUBLIC_PORT` - 暴露流量的端口。这是强制性的,因为您将从其虚拟机容器外部访问实例。**在未实现单独的安全服务的情况下,请勿将其暴露给互联网。** | |
| 211 | -192.168.0.* | |
| 212 | -``` | |
| 213 | 204 | |
| 214 | -(上述 IP 范围将允许局域网中的任何设备连接) | |
| 205 | +##### 附加设置 | |
| 215 | 206 | |
| 216 | -也接受子网掩码设置(如 10.0.0.0/24)。 | |
| 207 | +- `SILLYTAVERN_VERSION` - 在此 GitHub 页面的右侧,您会看到“Packages”。选择“sillytavern”包,您将看到镜像版本。镜像标签“latest”将使您与当前 release 保持同步。您还可以使用指向相应分支的每日镜像的“staging”标签。 | |
| 217 | 208 | |
| 218 | -* 保存`whitelist.txt`文件。 | |
| 209 | +#### 运行容器 | |
| 219 | -* 重启 SillyTavern 服务。 | |
| 220 | 210 | |
| 221 | -然后,文件中设置的 IP 就可以访问 SillyTavern 了。 | |
| 211 | +1. 打开您的命令行 | |
| 212 | +2. 在您要存储配置和数据文件的文件夹中运行以下命令: | |
| 222 | 213 | |
| 223 | -*注意:"config.yaml" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。 | |
| 214 | +```bash | |
| 215 | +SILLYTAVERN_VERSION="latest" | |
| 216 | +PUBLIC_PORT="8000" | |
| 217 | +CONFIG_PATH="./config" | |
| 218 | +DATA_PATH="./data" | |
| 219 | +PLUGINS_PATH="./plugins" | |
| 220 | +EXTENSIONS_PATH="./extensions" | |
| 224 | 221 | |
| 225 | -### 2.获取 SillyTavern 服务的 IP 地址 | |
| 222 | +docker run \ | |
| 223 | + --name="sillytavern" \ | |
| 224 | + -p "$PUBLIC_PORT:8000/tcp" \ | |
| 225 | + -v "$CONFIG_PATH:/home/node/app/config:rw" \ | |
| 226 | + -v "$DATA_PATH:/home/node/app/data:rw" \ | |
| 227 | + -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \ | |
| 228 | + -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \ | |
| 229 | + ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION" | |
| 230 | +``` | |
| 226 | 231 | |
| 227 | -白名单设置完成后,您需要 SillyTavern 服务的 IP 地址。 | |
| 232 | +> 默认情况下,容器将在前台运行。如果要在后台运行它,请将 `-d` 标志添加到 `docker run` 命令中。 | |
| 228 | 233 | |
| 229 | -如果 SillyTavern 服务设备在同一个局域网上,则使用安装 SillyTavern 服务的电脑的局域网 IP 地址: | |
| 234 | +### 自己构建镜像 | |
| 230 | 235 | |
| 231 | -* Windows:Windows 按钮 > 在搜索栏中输入 `cmd.exe` > 在打开的控制台中输入 `ipconfig`,回车 > 然后在输出中查找 `IPv4` 地址。 | |
| 236 | +我们有一个关于在 Docker 中使用 SillyTavern 的综合指南[在此处](http://docs.sillytavern.app/installation/docker/),涵盖了 Windows、macOS 和 Linux 上的安装!如果您希望自己构建镜像,请阅读它。 | |
| 232 | 237 | |
| 233 | -如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。 | |
| 238 | +## ⚡ 通过 SillyTavern Launcher 安装 | |
| 234 | 239 | |
| 235 | -* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。 | |
| 240 | +SillyTavern Launcher 是一个安装向导,可帮助您进行多种选项的设置,包括为本地推理安装后端。 | |
| 236 | 241 | |
| 237 | 242 | ### 3. 使用其他设备访问对于 SillyTavernWindows 服务用户 |
| 238 | 243 | |
| 239 | -无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。 | |
| 244 | +1. 在键盘上:按 **`WINDOWS + R`** 打开“运行”对话框。然后,运行以下命令安装 git: | |
| 240 | 245 | |
| 241 | -同一局域网中的 SillyTavern 服务的典型默认地址如下: | |
| 246 | +```shell | |
| 247 | +cmd /c winget install -e --id Git.Git | |
| 248 | +``` | |
| 242 | 249 | |
| 243 | -`http://192.168.0.5:8000` | |
| 250 | +2. 在键盘上:按 **`WINDOWS + E`** 打开文件资源管理器,然后导航到要安装启动器的文件夹。进入所需文件夹后,在地址栏中键入 `cmd` 并按 Enter。然后,运行以下命令: | |
| 244 | 251 | |
| 245 | -使用 http:// 而不是 https:// | |
| 252 | +```shell | |
| 253 | +git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | |
| 254 | +``` | |
| 246 | 255 | |
| 247 | 256 | ### 向所有 IP 开放您的对于 SillyTavernLinux 服务用户 |
| 248 | 257 | |
| 249 | -我们不建议这样做,但您可以打开 `config.yaml` 并将里面的 `whitelistMode` 设置改为 `false`。 | |
| 258 | +1. 打开您喜欢的终端并安装 git | |
| 259 | +2. 使用以下命令克隆 SillyTavern-Launcher: | |
| 250 | 260 | |
| 251 | -你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。 | |
| 261 | +```shell | |
| 262 | +git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | |
| 263 | +``` | |
| 252 | 264 | |
| 253 | -这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。 | |
| 265 | +3. 使用以下命令启动 installer.sh: | |
| 254 | 266 | |
| 255 | -用户名和密码在`config.yaml`文件中设置。 | |
| 267 | +```shell | |
| 268 | +chmod +x install.sh && ./install.sh | |
| 269 | +``` | |
| 256 | 270 | |
| 257 | -重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。 | |
| 271 | +4. 安装后使用以下命令启动 launcher.sh: | |
| 272 | + | |
| 273 | +```shell | |
| 274 | +chmod +x launcher.sh && ./launcher.sh | |
| 275 | +``` | |
| 276 | + | |
| 277 | +### 对于 Mac 用户 | |
| 278 | + | |
| 279 | +1. 打开终端并使用以下命令安装 brew: | |
| 280 | + | |
| 281 | +```shell | |
| 282 | +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
| 283 | +``` | |
| 258 | 284 | |
| 259 | -### 还是无法访问? | |
| 285 | +2. 使用以下命令安装 git: | |
| 260 | 286 | |
| 261 | -* 为 `config.yaml` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。 | |
| 287 | +```shell | |
| 262 | -* 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。 | |
| 288 | +brew install git | |
| 289 | +``` | |
| 263 | 290 | |
| 264 | -### 性能问题? | |
| 291 | +3. 使用以下命令克隆 SillyTavern-Launcher: | |
| 265 | 292 | |
| 266 | -尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。 | |
| 293 | +```shell | |
| 294 | +git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | |
| 295 | +``` | |
| 267 | 296 | |
| 268 | -## 我喜欢你的项目!我该如何贡献自己的力量? | |
| 297 | +4. 使用以下命令启动 installer.sh: | |
| 269 | 298 | |
| 270 | -### 应该 | |
| 299 | +```shell | |
| 300 | +chmod +x install.sh && ./install.sh | |
| 301 | +``` | |
| 271 | 302 | |
| 272 | -1. 发送 Fork 请求 | |
| 303 | +5. 安装后使用以下命令启动 launcher.sh: | |
| 273 | -2. 使用规定的模板发送功能建议和问题报告 | |
| 274 | -3. 在提出任何问题之前,请先阅读 Readme 文件和文档 | |
| 275 | 304 | |
| 276 | -#### 不应该 | |
| 305 | +```shell | |
| 306 | +chmod +x launcher.sh && ./launcher.sh | |
| 307 | +``` | |
| 277 | 308 | |
| 278 | -1. 提供金钱捐赠 | |
| 309 | +## 📱 通过 Termux 在 Android OS 上安装 | |
| 279 | -2. 发送错误报告而不提供任何详细信息 | |
| 280 | -3. 提出已经回答过无数次的问题 | |
| 281 | 310 | |
| 282 | -## 我在哪里可以找到以前的聊天背景图片? | |
| 311 | +> \[!NOTE] | |
| 312 | +> **SillyTavern 可以在 Android 设备上使用 Termux 原生运行,但我们不为此用例提供官方支持。** | |
| 313 | +> | |
| 314 | +> **请参阅 ArroganceComplex#2659 编写的本指南:** | |
| 315 | +> | |
| 316 | +> - <https://rentry.org/STAI-Termux> | |
| 283 | 317 | |
| 284 | -我们正在实行 100% 原创内容的政策,因此旧的背景图片已从该资源库中删除。 | |
| 318 | +**不支持的平台:android arm LEtime-web。** 32 位 Android 需要一个无法通过 npm 安装的外部依赖项。使用以下命令安装它:`pkg install esbuild`。然后运行常规安装步骤。 | |
| 285 | 319 | |
| 286 | -不过你可以在这里找到它们的存档: | |
| 320 | +## 命令行参数 | |
| 287 | 321 | |
| 288 | -<https://files.catbox.moe/1xevnc.zip> | |
| 322 | +您可以将命令行参数传递给 SillyTavern 服务器启动脚本,以覆盖 `config.yaml` 中的某些设置。 | |
| 289 | 323 | |
| 290 | 324 | ### 屏幕截图示例 |
| 291 | 325 | |
| 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"> | |
| 327 | +node server.js --port 8000 --listen false | |
| 328 | +# 或 | |
| 329 | +npm run start -- --port 8000 --listen false | |
| 330 | +# 或 (仅限 Windows) | |
| 331 | +Start.bat --port 8000 --listen false | |
| 332 | +``` | |
| 294 | 333 | |
| 295 | 334 | ### 许可证和贡献支持的参数 |
| 296 | 335 | |
| 297 | -**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。** | |
| 336 | +> \[!TIP] | |
| 298 | - | |
| 337 | +> 所有参数都不是必需的。如果您不提供它们,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 | + | |
| 300 | - | |
| 339 | +| 选项 | 描述 | 类型 | | |
| 301 | -* TAI Base by Humi: Unknown license | |
| 340 | +| -------------------------------- | ---------------------------------------------- | ------- | | |
| 302 | -* Cohee's modifications and derived code: AGPL v3 | |
| 341 | +| `--version` | 显示版本号 | boolean | | |
| 303 | -* RossAscends' additions: AGPL v3 | |
| 342 | +| `--configPath` | 覆盖 config.yaml 文件的路径 | string | | |
| 304 | -* Portions of CncAnon's TavernAITurbo mod: Unknown license | |
| 343 | +| `--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 TextGen | |
| 348 | +| `--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 AlpinDale | |
| 353 | +| `--ssl` | 启用 SSL | boolean | | |
| 315 | -* Thanks paniphons for providing a FAQ document | |
| 354 | +| `--certPath` | 您的证书文件路径 | string | | |
| 316 | -* 10K Discord Users Celebratory Background by @kallmeflocc | |
| 355 | +| `--keyPath` | 您的私钥文件路径 | string | | |
| 317 | -* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc | |
| 356 | +| `--browserLaunchEnabled` | 自动在浏览器中启动 SillyTavern | boolean | | |
| 318 | -* Korean translation by @doloroushyeonse | |
| 357 | +| `--browserLaunchHostname` | 自动运行主机名 | string | | |
| 319 | -* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3 | |
| 358 | +| `--browserLaunchPort` | 覆盖自动运行的端口 | string | | |
| 359 | +| `--browserLaunchAvoidLocalhost` | 在自动模式下避免使用 'localhost' 进行自动运行 | boolean | | |
| 360 | +| `--corsProxy` | 启用 CORS 代理 | boolean | | |
| 361 | +| `--requestProxyEnabled` | 为传出请求启用代理 | boolean | | |
| 362 | +| `--requestProxyUrl` | 请求代理 URL(HTTP 或 SOCKS 协议) | string | | |
| 363 | +| `--requestProxyBypass` | 请求代理绕过列表(以空格分隔的主机列表) | array | | |
| 364 | +| `--disableCsrf` | 禁用 CSRF 保护(不推荐) | boolean | | |
| 365 | + | |
| 366 | +## 远程连接 | |
| 367 | + | |
| 368 | +这通常适用于那些想在手机上使用 SillyTavern,而他们的电脑在同一 Wi-Fi 网络上运行 ST 服务器的人。但是,它也可以用于允许从任何地方进行远程连接。 | |
| 369 | + | |
| 370 | +请在[文档](https://docs.sillytavern.app/usage/remoteconnections/)中阅读有关如何设置远程连接的详细指南。 | |
| 371 | + | |
| 372 | +您可能还需要配置 SillyTavern 用户配置文件(可选密码保护):[用户](https://docs.sillytavern.app/administration/multi-user/)。 | |
| 373 | + | |
| 374 | +## 许可证和致谢 | |
| 375 | + | |
| 376 | +**本程序的分发是希望它能有用,但不提供任何保证;甚至没有对适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU Affero 通用公共许可证。** | |
| 377 | + | |
| 378 | +- [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT 许可证 | |
| 379 | +- CncAnon 的 TavernAITurbo mod 的部分内容经许可使用 | |
| 380 | +- 视觉小说模式的灵感来自 PepperTaco 的工作 (<https://github.com/peppertaco/Tavern/>) | |
| 381 | +- Noto Sans 字体 by Google (OFL 许可证) | |
| 382 | +- 图标主题 by Font Awesome <https://fontawesome.com> (图标: CC BY 4.0, 字体: SIL OFL 1.1, 代码: MIT 许可证) | |
| 383 | +- 默认内容由 @OtisAlejandro (Seraphina 角色和世界书) 和 @kallmeflocc (10K Discord 用户庆祝背景) 提供 | |
| 384 | +- Docker 指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 提供 | |
| 385 | +- kokoro-js 库由 [@hexgrad](https://github.com/hexgrad) 提供 (Apache-2.0 许可证) | |
| 386 | +- 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3 | |
| 387 | + | |
| 388 | +## 主要贡献者 | |
| 389 | + | |
| 390 | +[](https://github.com/SillyTavern/SillyTavern/graphs/contributors) | |
| 320 | 391 | |
| 321 | 392 | <!-- LINK GROUP --> |
| 393 | + | |
| 322 | 394 | [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 | |
| @@ -320,24 +320,24 @@ Start.bat --port 8000 --listen false | ||
| 320 | 320 | ### Supported arguments |
| 321 | 321 | |
| 322 | 322 | | Option | Description | Type | |
| 323 | 323 | |-------------------------|---------------------------------------------------------|---------------------------------------------|----------| |
| 324 | 324 | | `--version` | 顯示版本序號 | boolean | |
| 325 | 325 | | `--enableIPv6` | 啟用 IPv6 | boolean | |
| 326 | 326 | | `--enableIPv4` | 啟用 IPv4 | boolean | |
| 327 | 327 | | `--port` | 設定 SillyTavern 運行的端口。若未提供,則預設使用 `config.yaml` 中的 'port' | number | |
| 328 | 328 | | `--dnsPreferIPv6` | 偏好使用 IPv6 解析 DNS。未提供則默認使用 `config.yaml` 中的 'preferIPv6' | boolean | |
| 329 | 329 | | `--autorunbrowserLaunchEnabled` | 自動在瀏覽器中啟動 SillyTavern。未提供則默認使用 `config.yaml` 中的 'autorun' | boolean | |
| 330 | 330 | | `--autorunHostnamebrowserLaunchHostname` | 自動啟動時的主機名稱,通常建議保持為 'auto' | string | |
| 331 | 331 | | `--autorunPortOverridebrowserLaunchPort` | 覆蓋自動啟動的端口設定 | string | |
| 332 | 332 | | `--listenbrowserLaunchAvoidLocalhost` | SillyTavern 是否可監聽所有網路接口。若未提供,則默認使用 `config.yaml` 中的在自動模式下避免使用 'listenlocalhost' | boolean | |
| 333 | -| `--corsProxy` | 啟用 CORS 代理。若未提供,則默認使用 `config.yaml` 中的 'enableCorsProxy' | boolean | | |
| 333 | +| `--listen` | SillyTavern 是否可監聽所有網路接口。 | boolean | | |
| 334 | +| `--corsProxy` | 啟用 CORS 代理。 | boolean | | |
| 334 | 335 | | `--disableCsrf` | 停用 CSRF 保護 | boolean | |
| 335 | 336 | | `--ssl` | 啟用 SSL | boolean | |
| 336 | 337 | | `--certPath` | 設定您證書文件的路徑 | string | |
| 337 | 338 | | `--keyPath` | 設定您私人金鑰文件的路徑 | string | |
| 338 | 339 | | `--whitelist` | 啟用白名單模式 | boolean | |
| 339 | 340 | | `--dataRoot` | 設定數據儲存的根目錄 | string | |
| 340 | -| `--avoidLocalhost` | 在自動模式下避免使用 'localhost' | boolean | | |
| 341 | 341 | | `--basicAuthMode` | 啟用基本身份驗證模式 | boolean | |
| 342 | 342 | | `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean | |
| 343 | 343 | | `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議)| string | |
| @@ -232,6 +232,28 @@ docker run \ | ||
| 232 | 232 | |
| 233 | 233 | We 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. |
| 234 | 234 | |
| 235 | +### Common issues with Docker | |
| 236 | + | |
| 237 | +#### SELinux Permission Issues with Mounted Volumes | |
| 238 | + | |
| 239 | +Linux distributions with SELinux enabled (such as RHEL, CentOS, Fedora, etc.) may prevent Docker containers from accessing mounted volumes due to security policies. This can result in permission denied errors when the container tries to read or write to the mounted directories. | |
| 240 | + | |
| 241 | +Two suffixes `:z` or `:Z` can be added to the volume mount. These suffixes tell Docker to relabel file objects on the shared volumes. | |
| 242 | + | |
| 243 | +* The `z` option is used when the volume content will be shared between containers. | |
| 244 | +* The `Z` option is used when the volume content should only be used by the current container. | |
| 245 | + | |
| 246 | +Example: | |
| 247 | + | |
| 248 | +```yaml | |
| 249 | +# docker-compose.yml | |
| 250 | +volumes: | |
| 251 | + ## Shared volume | |
| 252 | + - ./config:/home/node/app/config:z | |
| 253 | + ## Private volume | |
| 254 | + - ./data:/home/node/app/data:Z | |
| 255 | +``` | |
| 256 | + | |
| 235 | 257 | ## ⚡ Installing via SillyTavern Launcher |
| 236 | 258 | |
| 237 | 259 | SillyTavern 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 | 356 | > None of the arguments are required. If you don't provide them, SillyTavern will use the settings in `config.yaml`. |
| 335 | 357 | |
| 336 | 358 | | Option | Description | Type | |
| 337 | 359 | |---------------------------------|----------------------------------------------------------------------|----------| |
| 338 | 360 | | `--version` | Show version number | boolean | |
| 339 | 361 | | `--configPath` | Override the path to the config.yaml file | string | |
| 340 | 362 | | `--dataRoot` | Root directory for data storage | string | |
| @@ -350,10 +372,10 @@ Start.bat --port 8000 --listen false | ||
| 350 | 372 | | `--ssl` | Enables SSL | boolean | |
| 351 | 373 | | `--certPath` | Path to your certificate file | string | |
| 352 | 374 | | `--keyPath` | Path to your private key file | string | |
| 353 | 375 | | `--autorunbrowserLaunchEnabled` | Automatically launch SillyTavern in the browser | boolean | |
| 354 | 376 | | `--autorunHostnamebrowserLaunchHostname` | AutorunBrowser launch hostname | string | |
| 355 | 377 | | `--autorunPortOverridebrowserLaunchPort` | Overrides the port for autorun browser launch | string | |
| 356 | 378 | | `--avoidLocalhostbrowserLaunchAvoidLocalhost` | Avoids using 'localhost' for autorunbrowser launch in auto mode | boolean | |
| 357 | 379 | | `--corsProxy` | Enables CORS proxy | boolean | |
| 358 | 380 | | `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean | |
| 359 | 381 | | `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string | |
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | FROM node:lts-alpine3.1921 |
| 2 | 2 | |
| 3 | 3 | # Arguments |
| 4 | 4 | ARG APP_HOME=/home/node/app |
| @@ -19,7 +19,7 @@ RUN \ | ||
| 19 | 19 | echo "*** Install npm packages ***" && \ |
| 20 | 20 | npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force |
| 21 | 21 | |
| 22 | -# Copy default chats, characters and user avatars to <folder>.default folder | |
| 22 | +# Create config directory and link config.yaml | |
| 23 | 23 | RUN \ |
| 24 | 24 | rm -f "config.yaml" || true && \ |
| 25 | 25 | ln -s "./config/config.yaml" "config.yaml" || true && \ |
| @@ -30,7 +30,7 @@ RUN \ | ||
| 30 | 30 | echo "*** Run Webpack ***" && \ |
| 31 | 31 | node "./docker/build-lib.js" |
| 32 | 32 | |
| 33 | 33 | # CleanupSet unnecessarythe filesentrypoint script |
| 34 | 34 | RUN \ |
| 35 | 35 | echo "*** Cleanup ***" && \ |
| 36 | 36 | mv "./docker/docker-entrypoint.sh" "./" && \ |
| @@ -16,16 +16,28 @@ protocol: | ||
| 16 | 16 | ipv6: false |
| 17 | 17 | # Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6 |
| 18 | 18 | dnsPreferIPv6: false |
| 19 | -# The hostname that autorun opens. | |
| 19 | +# -- BROWSER LAUNCH CONFIGURATION -- | |
| 20 | +browserLaunch: | |
| 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 | 29 | # - Use "auto" to let the server decide |
| 21 | 30 | # - Use options like 'localhost', 'st.example.com' |
| 22 | -autorunHostname: "auto" | |
| 31 | + hostname: 'auto' | |
| 23 | -# Server port | |
| 32 | + # Overrides the port for run in the browser. | |
| 24 | -port: 8000 | |
| 25 | -# Overrides the port for autorun in browser. | |
| 26 | 33 | # - Use -1 to use the server port. |
| 27 | 34 | # - Specify a port to override the default. |
| 28 | 35 | autorunPortOverride 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 | |
| 40 | +port: 8000 | |
| 29 | 41 | # -- SSL options -- |
| 30 | 42 | ssl: |
| 31 | 43 | enabled: false |
| @@ -70,7 +82,7 @@ enableDiscreetLogin: false | ||
| 70 | 82 | # https://www.authelia.com/ |
| 71 | 83 | # This will use auto login to an account with the same username |
| 72 | 84 | # as that used for authlia. (Ensure the username in authlia |
| 73 | 85 | # is an exact match in lowercase with that in sillytavern) |
| 74 | 86 | autheliaAuth: false |
| 75 | 87 | # If `basicAuthMode` and this are enabled then |
| 76 | 88 | # the username and passwords for basic auth are the same as those |
| @@ -98,12 +110,6 @@ rateLimiting: | ||
| 98 | 110 | # Use X-Real-IP header instead of socket IP for rate limiting |
| 99 | 111 | # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy) |
| 100 | 112 | preferRealIpHeader: false |
| 101 | -# -- ADVANCED CONFIGURATION -- | |
| 102 | -# Open the browser automatically | |
| 103 | -autorun: true | |
| 104 | -# Avoids using 'localhost' for autorun in auto mode. | |
| 105 | -# use if you don't have 'localhost' in your hosts file | |
| 106 | -avoidLocalhost: false | |
| 107 | 113 | |
| 108 | 114 | ## BACKUP CONFIGURATION |
| 109 | 115 | backups: |
| @@ -810,5 +810,13 @@ | ||
| 810 | 810 | { |
| 811 | 811 | "filename": "presets/reasoning/Blank.json", |
| 812 | 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 | ] |
| @@ -0,0 +1,11 @@ | ||
| 1 | +{ | |
| 2 | + "story_string": "<|system|>\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|endofsystem|>", | |
| 3 | + "example_separator": "", | |
| 4 | + "chat_start": "", | |
| 5 | + "use_stop_strings": false, | |
| 6 | + "names_as_stop_strings": true, | |
| 7 | + "always_force_name2": true, | |
| 8 | + "trim_sentences": false, | |
| 9 | + "single_line": false, | |
| 10 | + "name": "Dots1" | |
| 11 | +} | |
| 11 | \ No newline at end of file | |
| @@ -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 | +} | |
| @@ -1,16 +1,16 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "sillytavern", |
| 3 | 3 | "version": "1.13.01", |
| 4 | 4 | "lockfileVersion": 3, |
| 5 | 5 | "requires": true, |
| 6 | 6 | "packages": { |
| 7 | 7 | "": { |
| 8 | 8 | "name": "sillytavern", |
| 9 | 9 | "version": "1.13.01", |
| 10 | 10 | "hasInstallScript": true, |
| 11 | 11 | "license": "AGPL-3.0", |
| 12 | 12 | "dependencies": { |
| 13 | 13 | "@adobe/css-tools": "^4.4.23", |
| 14 | 14 | "@agnai/sentencepiece-js": "^1.1.1", |
| 15 | 15 | "@agnai/web-tokenizers": "^0.1.3", |
| 16 | 16 | "@iconfu/svg-inject": "^1.2.3", |
| @@ -51,19 +51,19 @@ | ||
| 51 | 51 | "cookie-session": "^2.1.0", |
| 52 | 52 | "cors": "^2.8.5", |
| 53 | 53 | "crc": "^4.3.2", |
| 54 | 54 | "csrf-sync": "^4.02.31", |
| 55 | 55 | "diff-match-patch": "^1.0.5", |
| 56 | 56 | "dompurify": "^3.2.46", |
| 57 | 57 | "droll": "^0.2.1", |
| 58 | 58 | "express": "^4.21.0", |
| 59 | 59 | "form-data": "^4.0.23", |
| 60 | 60 | "fuse.js": "^7.1.0", |
| 61 | 61 | "google-translate-api-browser": "^3.0.1", |
| 62 | 62 | "google-translate-api-x": "^10.7.2", |
| 63 | 63 | "handlebars": "^4.7.8", |
| 64 | 64 | "helmet": "^78.21.0", |
| 65 | 65 | "highlight.js": "^11.11.1", |
| 66 | 66 | "html-entities": "^2.56.20", |
| 67 | 67 | "iconv-lite": "^0.6.3", |
| 68 | 68 | "ip-matching": "^2.1.2", |
| 69 | 69 | "ip-regex": "^5.0.0", |
| @@ -71,13 +71,13 @@ | ||
| 71 | 71 | "is-docker": "^3.0.0", |
| 72 | 72 | "localforage": "^1.10.0", |
| 73 | 73 | "lodash": "^4.17.21", |
| 74 | 74 | "mime-types": "^23.10.351", |
| 75 | 75 | "moment": "^2.30.1", |
| 76 | 76 | "morphdom": "^2.7.45", |
| 77 | 77 | "multer": "^2.0.1", |
| 78 | 78 | "node-fetch": "^3.3.2", |
| 79 | 79 | "node-persist": "^4.0.4", |
| 80 | 80 | "open": "^810.41.2", |
| 81 | 81 | "png-chunk-text": "^1.0.0", |
| 82 | 82 | "png-chunks-extract": "^1.0.0", |
| 83 | 83 | "proxy-agent": "^6.5.0", |
| @@ -87,18 +87,18 @@ | ||
| 87 | 87 | "seedrandom": "^3.0.5", |
| 88 | 88 | "showdown": "^2.1.0", |
| 89 | 89 | "sillytavern-transformers": "2.14.6", |
| 90 | 90 | "simple-git": "^3.2728.0", |
| 91 | 91 | "slidetoggle": "^4.0.0", |
| 92 | 92 | "tiktoken": "^1.0.2021", |
| 93 | 93 | "url-join": "^5.0.0", |
| 94 | 94 | "vectra": "^0.2.2", |
| 95 | 95 | "wavefile": "^11.0.0", |
| 96 | 96 | "webpack": "^5.9899.09", |
| 97 | 97 | "write-file-atomic": "^5.0.1", |
| 98 | 98 | "ws": "^8.18.12", |
| 99 | 99 | "yaml": "^2.78.0", |
| 100 | 100 | "yargs": "^17.7.1", |
| 101 | 101 | "yauzl": "^23.102.0" |
| 102 | 102 | }, |
| 103 | 103 | "bin": { |
| 104 | 104 | "sillytavern": "server.js" |
| @@ -107,24 +107,24 @@ | ||
| 107 | 107 | "@types/archiver": "^6.0.3", |
| 108 | 108 | "@types/bytes": "^3.1.5", |
| 109 | 109 | "@types/command-exists": "^1.2.3", |
| 110 | 110 | "@types/compression": "^1.78.51", |
| 111 | 111 | "@types/cookie-parser": "^1.4.89", |
| 112 | 112 | "@types/cookie-session": "^2.0.49", |
| 113 | 113 | "@types/cors": "^2.8.1719", |
| 114 | 114 | "@types/deno": "^2.23.0", |
| 115 | 115 | "@types/express": "^4.17.2123", |
| 116 | 116 | "@types/jquery": "^3.5.32", |
| 117 | 117 | "@types/jquery-cropper": "^1.0.4", |
| 118 | 118 | "@types/jquery.transit": "^0.9.33", |
| 119 | 119 | "@types/jqueryui": "^1.12.24", |
| 120 | 120 | "@types/lodash": "^4.17.1617", |
| 121 | 121 | "@types/mime-types": "^23.10.41", |
| 122 | 122 | "@types/multer": "^1.4.1213", |
| 123 | 123 | "@types/node": "^18.19.8084", |
| 124 | 124 | "@types/node-persist": "^3.1.8", |
| 125 | 125 | "@types/png-chunk-text": "^1.0.3", |
| 126 | 126 | "@types/png-chunks-extract": "^1.0.2", |
| 127 | 127 | "@types/response-time": "^2.3.89", |
| 128 | 128 | "@types/select2": "^4.0.63", |
| 129 | 129 | "@types/toastr": "^2.1.43", |
| 130 | 130 | "@types/write-file-atomic": "^4.0.3", |
| @@ -148,9 +148,9 @@ | ||
| 148 | 148 | } |
| 149 | 149 | }, |
| 150 | 150 | "node_modules/@adobe/css-tools": { |
| 151 | 151 | "version": "4.4.23", |
| 152 | 152 | "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.23.tgz", |
| 153 | 153 | "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfySVQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+1C4BOv27q0A69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", |
| 154 | 154 | "license": "MIT" |
| 155 | 155 | }, |
| 156 | 156 | "node_modules/@agnai/sentencepiece-js": { |
| @@ -350,6 +350,7 @@ | ||
| 350 | 350 | "version": "8.0.2", |
| 351 | 351 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", |
| 352 | 352 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", |
| 353 | + "license": "ISC", | |
| 353 | 354 | "dependencies": { |
| 354 | 355 | "string-width": "^5.1.2", |
| 355 | 356 | "string-width-cjs": "npm:string-width@^4.2.0", |
| @@ -363,9 +364,10 @@ | ||
| 363 | 364 | } |
| 364 | 365 | }, |
| 365 | 366 | "node_modules/@isaacs/cliui/node_modules/ansi-regex": { |
| 366 | 367 | "version": "6.0.1.0", |
| 367 | 368 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.0.tgz", |
| 368 | 369 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", |
| 370 | + "license": "MIT", | |
| 369 | 371 | "engines": { |
| 370 | 372 | "node": ">=12" |
| 371 | 373 | }, |
| @@ -377,6 +379,7 @@ | ||
| 377 | 379 | "version": "6.2.1", |
| 378 | 380 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", |
| 379 | 381 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", |
| 382 | + "license": "MIT", | |
| 380 | 383 | "engines": { |
| 381 | 384 | "node": ">=12" |
| 382 | 385 | }, |
| @@ -387,12 +390,14 @@ | ||
| 387 | 390 | "node_modules/@isaacs/cliui/node_modules/emoji-regex": { |
| 388 | 391 | "version": "9.2.2", |
| 389 | 392 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", |
| 390 | 393 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", |
| 394 | + "license": "MIT" | |
| 391 | 395 | }, |
| 392 | 396 | "node_modules/@isaacs/cliui/node_modules/string-width": { |
| 393 | 397 | "version": "5.1.2", |
| 394 | 398 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", |
| 395 | 399 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", |
| 400 | + "license": "MIT", | |
| 396 | 401 | "dependencies": { |
| 397 | 402 | "eastasianwidth": "^0.2.0", |
| 398 | 403 | "emoji-regex": "^9.2.2", |
| @@ -409,6 +414,7 @@ | ||
| 409 | 414 | "version": "7.1.0", |
| 410 | 415 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", |
| 411 | 416 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", |
| 417 | + "license": "MIT", | |
| 412 | 418 | "dependencies": { |
| 413 | 419 | "ansi-regex": "^6.0.1" |
| 414 | 420 | }, |
| @@ -423,6 +429,7 @@ | ||
| 423 | 429 | "version": "8.1.0", |
| 424 | 430 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", |
| 425 | 431 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", |
| 432 | + "license": "MIT", | |
| 426 | 433 | "dependencies": { |
| 427 | 434 | "ansi-styles": "^6.1.0", |
| 428 | 435 | "string-width": "^5.0.1", |
| @@ -1597,6 +1604,7 @@ | ||
| 1597 | 1604 | "version": "0.11.0", |
| 1598 | 1605 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", |
| 1599 | 1606 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", |
| 1607 | + "license": "MIT", | |
| 1600 | 1608 | "optional": true, |
| 1601 | 1609 | "engines": { |
| 1602 | 1610 | "node": ">=14" |
| @@ -1779,13 +1787,14 @@ | ||
| 1779 | 1787 | "license": "MIT" |
| 1780 | 1788 | }, |
| 1781 | 1789 | "node_modules/@types/compression": { |
| 1782 | 1790 | "version": "1.78.51", |
| 1783 | 1791 | "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.78.51.tgz", |
| 1784 | 1792 | "integrity": "sha512-AAQvK5pxMpaTkCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDgHVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", |
| 1785 | 1793 | "dev": true, |
| 1786 | 1794 | "license": "MIT", |
| 1787 | 1795 | "dependencies": { |
| 1788 | 1796 | "@types/express": "*", |
| 1797 | + "@types/node": "*" | |
| 1789 | 1798 | } |
| 1790 | 1799 | }, |
| 1791 | 1800 | "node_modules/@types/connect": { |
| @@ -1799,9 +1808,9 @@ | ||
| 1799 | 1808 | } |
| 1800 | 1809 | }, |
| 1801 | 1810 | "node_modules/@types/cookie-parser": { |
| 1802 | 1811 | "version": "1.4.89", |
| 1803 | 1812 | "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.89.tgz", |
| 1804 | 1813 | "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjrtGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+pLRZ0IyZ49V4BQ8vbF4Ut2C2eVApbL2T14Dew8HA7c+WH4al3xD3ZwYwIUfnbT4NQ04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", |
| 1805 | 1814 | "dev": true, |
| 1806 | 1815 | "license": "MIT", |
| 1807 | 1816 | "peerDependencies": { |
| @@ -1820,9 +1829,9 @@ | ||
| 1820 | 1829 | } |
| 1821 | 1830 | }, |
| 1822 | 1831 | "node_modules/@types/cors": { |
| 1823 | 1832 | "version": "2.8.1719", |
| 1824 | 1833 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.1719.tgz", |
| 1825 | 1834 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxsmFNylyeyqN93lfe/9CSxOGREz8cpzAhH+78df0Rs3rc9THP4JkOhLsAaE93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+15VdpAqkcUAGGHpkbLe7pVoUEskg==", |
| 1826 | 1835 | "dev": true, |
| 1827 | 1836 | "license": "MIT", |
| 1828 | 1837 | "dependencies": { |
| @@ -1830,9 +1839,9 @@ | ||
| 1830 | 1839 | } |
| 1831 | 1840 | }, |
| 1832 | 1841 | "node_modules/@types/deno": { |
| 1833 | 1842 | "version": "2.23.0", |
| 1834 | 1843 | "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.23.0.tgz", |
| 1835 | 1844 | "integrity": "sha512-4x6M/ZSyoQy6fJeMArP0dvvNT4IOolfySyukuqqKhsLmSXDV4wGanqXIZ+xFihw3TlReS6JTa4hRG9nAZInpmw4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==", |
| 1836 | 1845 | "dev": true, |
| 1837 | 1846 | "license": "MIT" |
| 1838 | 1847 | }, |
| @@ -1863,10 +1872,11 @@ | ||
| 1863 | 1872 | "license": "MIT" |
| 1864 | 1873 | }, |
| 1865 | 1874 | "node_modules/@types/express": { |
| 1866 | 1875 | "version": "4.17.2123", |
| 1867 | 1876 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2123.tgz", |
| 1868 | 1877 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7ECrp6WY9aTYP3qPi2wGDo9iUe/7HlDAN0rV2GxTG0HQrceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", |
| 1869 | 1878 | "dev": true, |
| 1879 | + "license": "MIT", | |
| 1870 | 1880 | "dependencies": { |
| 1871 | 1881 | "@types/body-parser": "*", |
| 1872 | 1882 | "@types/express-serve-static-core": "^4.17.33", |
| @@ -1962,9 +1972,9 @@ | ||
| 1962 | 1972 | } |
| 1963 | 1973 | }, |
| 1964 | 1974 | "node_modules/@types/lodash": { |
| 1965 | 1975 | "version": "4.17.1617", |
| 1966 | 1976 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.1617.tgz", |
| 1967 | 1977 | "integrity": "sha512-HX7Em5NYQAXKWRRVJ+1TJ3J+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxTWmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+giF6Tgxv1eXEHjTAxO4xHvxQB6QuNm+/czV/iTtBw27WTU9gs4WRbn6rSiap8+EA+ykFQ==", |
| 1968 | 1978 | "dev": true, |
| 1969 | 1979 | "license": "MIT" |
| 1970 | 1980 | }, |
| @@ -1982,16 +1992,16 @@ | ||
| 1982 | 1992 | "license": "MIT" |
| 1983 | 1993 | }, |
| 1984 | 1994 | "node_modules/@types/mime-types": { |
| 1985 | 1995 | "version": "23.10.41", |
| 1986 | 1996 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-23.10.41.tgz", |
| 1987 | 1997 | "integrity": "sha512-lfU4b34HOrixRMsfuQbnRq1Ef+kAY5UheuFMWPDOIC+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/q8sWx5w29xlnFihkTlbqFrGOVQ==", |
| 1988 | 1998 | "dev": true, |
| 1989 | 1999 | "license": "MIT" |
| 1990 | 2000 | }, |
| 1991 | 2001 | "node_modules/@types/multer": { |
| 1992 | 2002 | "version": "1.4.1213", |
| 1993 | 2003 | "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.1213.tgz", |
| 1994 | 2004 | "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiImbhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQgfQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", |
| 1995 | 2005 | "dev": true, |
| 1996 | 2006 | "license": "MIT", |
| 1997 | 2007 | "dependencies": { |
| @@ -1999,9 +2009,9 @@ | ||
| 1999 | 2009 | } |
| 2000 | 2010 | }, |
| 2001 | 2011 | "node_modules/@types/node": { |
| 2002 | 2012 | "version": "18.19.8084", |
| 2003 | 2013 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8084.tgz", |
| 2004 | 2014 | "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6ACYy2HGcZPHxEeWTqowTF7dhXN+l1u6xxQJU1o7Gr4b41klnn6pj2LD6rsiGqSZojMdk1Jh2ys3m76ap+ae1vvE4+5+vg==", |
| 2005 | 2015 | "license": "MIT", |
| 2006 | 2016 | "dependencies": { |
| 2007 | 2017 | "undici-types": "~5.26.4" |
| @@ -2064,9 +2074,9 @@ | ||
| 2064 | 2074 | } |
| 2065 | 2075 | }, |
| 2066 | 2076 | "node_modules/@types/response-time": { |
| 2067 | 2077 | "version": "2.3.89", |
| 2068 | 2078 | "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.89.tgz", |
| 2069 | 2079 | "integrity": "sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQw5i5/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzwy/1N3hkSBru1dat7Pf/YzdFLAANbKR78i2VIPnKw1Ub2ZNXE/n3K4v1BBMIIbAccgxpGZT8lIuLW284Dw==", |
| 2070 | 2080 | "dev": true, |
| 2071 | 2081 | "license": "MIT", |
| 2072 | 2082 | "dependencies": { |
| @@ -2378,6 +2388,18 @@ | ||
| 2378 | 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 | 2403 | "node_modules/acorn": { |
| 2382 | 2404 | "version": "8.14.0", |
| 2383 | 2405 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", |
| @@ -2557,38 +2579,39 @@ | ||
| 2557 | 2579 | } |
| 2558 | 2580 | }, |
| 2559 | 2581 | "node_modules/archiver-utils/node_modules/brace-expansion": { |
| 2560 | 2582 | "version": "2.0.12", |
| 2561 | 2583 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.12.tgz", |
| 2562 | 2584 | "integrity": "sha512-XnAIvQ8eMJt0vHyM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsyjmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+mY6iL1sHTxWEFCytDAE2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", |
| 2585 | + "license": "MIT", | |
| 2563 | 2586 | "dependencies": { |
| 2564 | 2587 | "balanced-match": "^1.0.0" |
| 2565 | 2588 | } |
| 2566 | 2589 | }, |
| 2567 | 2590 | "node_modules/archiver-utils/node_modules/glob": { |
| 2568 | 2591 | "version": "10.34.125", |
| 2569 | 2592 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.34.125.tgz", |
| 2570 | 2593 | "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw7Bv8RF0k6xjo7d4A/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNPPxYLbUCfb6c+ApH66HRX8us3KhqgVpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", |
| 2594 | + "license": "ISC", | |
| 2571 | 2595 | "dependencies": { |
| 2572 | 2596 | "foreground-child": "^3.1.0", |
| 2573 | 2597 | "jackspeak": "^2.3.61.2", |
| 2574 | 2598 | "minimatch": "^9.0.14", |
| 2575 | 2599 | "minipass": "^7.01.42", |
| 2576 | 2600 | "pathpackage-scurryjson-from-dist": "^1.100.20", |
| 2601 | + "path-scurry": "^1.11.1" | |
| 2577 | 2602 | }, |
| 2578 | 2603 | "bin": { |
| 2579 | 2604 | "glob": "dist/esm/bin.mjs" |
| 2580 | 2605 | }, |
| 2581 | - "engines": { | |
| 2582 | - "node": ">=16 || 14 >=14.17" | |
| 2583 | - }, | |
| 2584 | 2606 | "funding": { |
| 2585 | 2607 | "url": "https://github.com/sponsors/isaacs" |
| 2586 | 2608 | } |
| 2587 | 2609 | }, |
| 2588 | 2610 | "node_modules/archiver-utils/node_modules/minimatch": { |
| 2589 | 2611 | "version": "9.0.45", |
| 2590 | 2612 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.45.tgz", |
| 2591 | 2613 | "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fwG6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", |
| 2614 | + "license": "ISC", | |
| 2592 | 2615 | "dependencies": { |
| 2593 | 2616 | "brace-expansion": "^2.0.1" |
| 2594 | 2617 | }, |
| @@ -2878,9 +2901,9 @@ | ||
| 2878 | 2901 | "license": "MIT" |
| 2879 | 2902 | }, |
| 2880 | 2903 | "node_modules/brace-expansion": { |
| 2881 | 2904 | "version": "1.1.1112", |
| 2882 | 2905 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1112.tgz", |
| 2883 | 2906 | "integrity": "sha512-iCuPHDFgrHX7H2vEI9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiAW1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", |
| 2884 | 2907 | "dev": true, |
| 2885 | 2908 | "license": "MIT", |
| 2886 | 2909 | "dependencies": { |
| @@ -2968,6 +2991,21 @@ | ||
| 2968 | 2991 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", |
| 2969 | 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 | 3009 | "node_modules/busboy": { |
| 2972 | 3010 | "version": "1.6.0", |
| 2973 | 3011 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", |
| @@ -3665,9 +3703,9 @@ | ||
| 3665 | 3703 | } |
| 3666 | 3704 | }, |
| 3667 | 3705 | "node_modules/csrf-sync": { |
| 3668 | 3706 | "version": "4.02.31", |
| 3669 | 3707 | "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.02.31.tgz", |
| 3670 | 3708 | "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G+q9tlUSCi/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwAkbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==", |
| 3671 | 3709 | "license": "ISC", |
| 3672 | 3710 | "dependencies": { |
| 3673 | 3711 | "http-errors": "^2.0.0" |
| @@ -3741,6 +3779,34 @@ | ||
| 3741 | 3779 | "dev": true, |
| 3742 | 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 | 3810 | "node_modules/defer-to-connect": { |
| 3745 | 3811 | "version": "2.0.1", |
| 3746 | 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 | 3836 | "node_modules/define-lazy-prop": { |
| 3771 | 3837 | "version": "23.0.0", |
| 3772 | 3838 | "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-23.0.0.tgz", |
| 3773 | 3839 | "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4OgN+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", |
| 3774 | 3840 | "license": "MIT", |
| 3775 | 3841 | "engines": { |
| 3776 | 3842 | "node": ">=812" |
| 3843 | + }, | |
| 3844 | + "funding": { | |
| 3845 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 3777 | 3846 | } |
| 3778 | 3847 | }, |
| 3779 | 3848 | "node_modules/degenerator": { |
| @@ -3894,9 +3963,9 @@ | ||
| 3894 | 3963 | } |
| 3895 | 3964 | }, |
| 3896 | 3965 | "node_modules/dompurify": { |
| 3897 | 3966 | "version": "3.2.46", |
| 3898 | 3967 | "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.46.tgz", |
| 3899 | 3968 | "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Ms/mgHed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", |
| 3900 | 3969 | "license": "(MPL-2.0 OR Apache-2.0)", |
| 3901 | 3970 | "optionalDependencies": { |
| 3902 | 3971 | "@types/trusted-types": "^2.0.7" |
| @@ -3953,7 +4022,8 @@ | ||
| 3953 | 4022 | "node_modules/eastasianwidth": { |
| 3954 | 4023 | "version": "0.2.0", |
| 3955 | 4024 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", |
| 3956 | 4025 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", |
| 4026 | + "license": "MIT" | |
| 3957 | 4027 | }, |
| 3958 | 4028 | "node_modules/ee-first": { |
| 3959 | 4029 | "version": "1.1.1", |
| @@ -4564,15 +4634,6 @@ | ||
| 4564 | 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 | 4637 | "node_modules/fetch-blob": { |
| 4577 | 4638 | "version": "3.2.0", |
| 4578 | 4639 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", |
| @@ -4724,14 +4785,15 @@ | ||
| 4724 | 4785 | } |
| 4725 | 4786 | }, |
| 4726 | 4787 | "node_modules/form-data": { |
| 4727 | 4788 | "version": "4.0.23", |
| 4728 | 4789 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.23.tgz", |
| 4729 | 4790 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGVqsITQPfmvMOSAdeyZ+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs7512I1c+nr6FM8wCKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", |
| 4730 | 4791 | "license": "MIT", |
| 4731 | 4792 | "dependencies": { |
| 4732 | 4793 | "asynckit": "^0.4.0", |
| 4733 | 4794 | "combined-stream": "^1.0.8", |
| 4734 | 4795 | "es-set-tostringtag": "^2.1.0", |
| 4796 | + "hasown": "^2.0.2", | |
| 4735 | 4797 | "mime-types": "^2.1.12" |
| 4736 | 4798 | }, |
| 4737 | 4799 | "engines": { |
| @@ -4744,6 +4806,18 @@ | ||
| 4744 | 4806 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", |
| 4745 | 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 | 4821 | "node_modules/formdata-node": { |
| 4748 | 4822 | "version": "4.4.1", |
| 4749 | 4823 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", |
| @@ -5144,12 +5218,12 @@ | ||
| 5144 | 5218 | } |
| 5145 | 5219 | }, |
| 5146 | 5220 | "node_modules/helmet": { |
| 5147 | 5221 | "version": "78.21.0", |
| 5148 | 5222 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-78.21.0.tgz", |
| 5149 | 5223 | "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSwwjOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xwu1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", |
| 5150 | 5224 | "license": "MIT", |
| 5151 | 5225 | "engines": { |
| 5152 | 5226 | "node": ">=1618.0.0" |
| 5153 | 5227 | } |
| 5154 | 5228 | }, |
| 5155 | 5229 | "node_modules/highlight.js": { |
| @@ -5162,9 +5236,9 @@ | ||
| 5162 | 5236 | } |
| 5163 | 5237 | }, |
| 5164 | 5238 | "node_modules/html-entities": { |
| 5165 | 5239 | "version": "2.56.20", |
| 5166 | 5240 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.56.20.tgz", |
| 5167 | 5241 | "integrity": "sha512-K/kig+rMn/PSRMQk4FZ78KyauQOVRvr7c86gQ8lWXq+mZurHn3FH0VwrHkv6CbAH1hLu+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaARG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", |
| 5168 | 5242 | "funding": [ |
| 5169 | 5243 | { |
| 5170 | 5244 | "type": "github", |
| @@ -5517,6 +5591,24 @@ | ||
| 5517 | 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 | 5612 | "node_modules/is-path-inside": { |
| 5521 | 5613 | "version": "3.0.3", |
| 5522 | 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 | 5633 | "node_modules/is-wsl": { |
| 5542 | 5634 | "version": "23.21.0", |
| 5543 | 5635 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-23.21.0.tgz", |
| 5544 | 5636 | "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcnUcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7LwwzVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", |
| 5545 | 5637 | "license": "MIT", |
| 5546 | 5638 | "dependencies": { |
| 5547 | 5639 | "is-dockerinside-container": "^21.0.0" |
| 5548 | 5640 | }, |
| 5549 | 5641 | "engines": { |
| 5550 | 5642 | "node": ">=816" |
| 5551 | - } | |
| 5552 | - }, | |
| 5553 | - "node_modules/is-wsl/node_modules/is-docker": { | |
| 5554 | - "version": "2.2.1", | |
| 5555 | - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", | |
| 5556 | - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", | |
| 5557 | - "license": "MIT", | |
| 5558 | - "bin": { | |
| 5559 | - "is-docker": "cli.js" | |
| 5560 | - }, | |
| 5561 | - "engines": { | |
| 5562 | - "node": ">=8" | |
| 5563 | 5643 | }, |
| 5564 | 5644 | "funding": { |
| 5565 | 5645 | "url": "https://github.com/sponsors/sindresorhus" |
| @@ -5608,15 +5688,13 @@ | ||
| 5608 | 5688 | } |
| 5609 | 5689 | }, |
| 5610 | 5690 | "node_modules/jackspeak": { |
| 5611 | 5691 | "version": "2.3.64.3", |
| 5612 | 5692 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.64.3.tgz", |
| 5613 | 5693 | "integrity": "sha512-N3yCSOGlZQpz2yfahA/NegsOBokc8GAdM8UcmfsKiSS8cipheDRd1Y8Cd9SIEsqvXkLVoSw/nivzr700HcgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQRQBw==", |
| 5694 | + "license": "BlueOak-1.0.0", | |
| 5614 | 5695 | "dependencies": { |
| 5615 | 5696 | "@isaacs/cliui": "^8.0.2" |
| 5616 | 5697 | }, |
| 5617 | - "engines": { | |
| 5618 | - "node": ">=14" | |
| 5619 | - }, | |
| 5620 | 5698 | "funding": { |
| 5621 | 5699 | "url": "https://github.com/sponsors/isaacs" |
| 5622 | 5700 | }, |
| @@ -5925,12 +6003,10 @@ | ||
| 5925 | 6003 | } |
| 5926 | 6004 | }, |
| 5927 | 6005 | "node_modules/lru-cache": { |
| 5928 | 6006 | "version": "10.24.03", |
| 5929 | 6007 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.24.03.tgz", |
| 5930 | 6008 | "integrity": "sha512-2bIM8xJNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7QOIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", |
| 5931 | 6009 | "engineslicense": {"ISC" |
| 5932 | - "node": "14 || >=16.14" | |
| 5933 | - } | |
| 5934 | 6010 | }, |
| 5935 | 6011 | "node_modules/math-intrinsics": { |
| 5936 | 6012 | "version": "1.1.0", |
| @@ -6007,17 +6083,26 @@ | ||
| 6007 | 6083 | } |
| 6008 | 6084 | }, |
| 6009 | 6085 | "node_modules/mime-types": { |
| 6010 | 6086 | "version": "23.10.351", |
| 6011 | 6087 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-23.10.351.tgz", |
| 6012 | 6088 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6xRc4oEhT6eaBpU1XF7AjpOFD+4R2uI4MjQjzysHB1zxuWL50hzaeXiwxQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", |
| 6013 | 6089 | "license": "MIT", |
| 6014 | 6090 | "dependencies": { |
| 6015 | 6091 | "mime-db": "^1.5254.0" |
| 6016 | 6092 | }, |
| 6017 | 6093 | "engines": { |
| 6018 | 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 | 6106 | "node_modules/mimic-response": { |
| 6022 | 6107 | "version": "3.1.0", |
| 6023 | 6108 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", |
| @@ -6061,9 +6146,10 @@ | ||
| 6061 | 6146 | } |
| 6062 | 6147 | }, |
| 6063 | 6148 | "node_modules/minipass": { |
| 6064 | 6149 | "version": "7.01.42", |
| 6065 | 6150 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.01.42.tgz", |
| 6066 | 6151 | "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5VqOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQcMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", |
| 6152 | + "license": "ISC", | |
| 6067 | 6153 | "engines": { |
| 6068 | 6154 | "node": ">=16 || 14 >=14.17" |
| 6069 | 6155 | } |
| @@ -6078,9 +6164,9 @@ | ||
| 6078 | 6164 | } |
| 6079 | 6165 | }, |
| 6080 | 6166 | "node_modules/morphdom": { |
| 6081 | 6167 | "version": "2.7.45", |
| 6082 | 6168 | "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.45.tgz", |
| 6083 | 6169 | "integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQz6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==", |
| 6084 | 6170 | "license": "MIT" |
| 6085 | 6171 | }, |
| 6086 | 6172 | "node_modules/ms": { |
| @@ -6333,32 +6419,18 @@ | ||
| 6333 | 6419 | } |
| 6334 | 6420 | }, |
| 6335 | 6421 | "node_modules/open": { |
| 6336 | 6422 | "version": "810.41.2", |
| 6337 | 6423 | "resolved": "https://registry.npmjs.org/open/-/open-810.41.2.tgz", |
| 6338 | 6424 | "integrity": "sha512-7x81NCL719oNbsq/3mhcxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQBZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", |
| 6339 | 6425 | "license": "MIT", |
| 6340 | 6426 | "dependencies": { |
| 6341 | 6427 | "define-lazydefault-propbrowser": "^25.02.01", |
| 6342 | 6428 | "isdefine-dockerlazy-prop": "^23.10.10", |
| 6343 | 6429 | "is-wslinside-container": "^21.20.0", |
| 6344 | - }, | |
| 6430 | + "is-wsl": "^3.1.0" | |
| 6345 | - "engines": { | |
| 6346 | - "node": ">=12" | |
| 6347 | - }, | |
| 6348 | - "funding": { | |
| 6349 | - "url": "https://github.com/sponsors/sindresorhus" | |
| 6350 | - } | |
| 6351 | - }, | |
| 6352 | - "node_modules/open/node_modules/is-docker": { | |
| 6353 | - "version": "2.2.1", | |
| 6354 | - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", | |
| 6355 | - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", | |
| 6356 | - "license": "MIT", | |
| 6357 | - "bin": { | |
| 6358 | - "is-docker": "cli.js" | |
| 6359 | 6431 | }, |
| 6360 | 6432 | "engines": { |
| 6361 | 6433 | "node": ">=818" |
| 6362 | 6434 | }, |
| 6363 | 6435 | "funding": { |
| 6364 | 6436 | "url": "https://github.com/sponsors/sindresorhus" |
| @@ -6517,6 +6589,12 @@ | ||
| 6517 | 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 | 6598 | "node_modules/pako": { |
| 6521 | 6599 | "version": "1.0.11", |
| 6522 | 6600 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", |
| @@ -6642,15 +6720,16 @@ | ||
| 6642 | 6720 | } |
| 6643 | 6721 | }, |
| 6644 | 6722 | "node_modules/path-scurry": { |
| 6645 | 6723 | "version": "1.1011.21", |
| 6646 | 6724 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.1011.21.tgz", |
| 6647 | 6725 | "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjVXa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/UwNDdKuUv6M5qcAv1HA==", |
| 6726 | + "license": "BlueOak-1.0.0", | |
| 6648 | 6727 | "dependencies": { |
| 6649 | 6728 | "lru-cache": "^10.2.0", |
| 6650 | 6729 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" |
| 6651 | 6730 | }, |
| 6652 | 6731 | "engines": { |
| 6653 | 6732 | "node": ">=16 || 14 >=14.1718" |
| 6654 | 6733 | }, |
| 6655 | 6734 | "funding": { |
| 6656 | 6735 | "url": "https://github.com/sponsors/isaacs" |
| @@ -7067,9 +7146,10 @@ | ||
| 7067 | 7146 | } |
| 7068 | 7147 | }, |
| 7069 | 7148 | "node_modules/readdir-glob/node_modules/brace-expansion": { |
| 7070 | 7149 | "version": "2.0.12", |
| 7071 | 7150 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.12.tgz", |
| 7072 | 7151 | "integrity": "sha512-XnAIvQ8eMJt0vHyM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsyjmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+mY6iL1sHTxWEFCytDAE2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", |
| 7152 | + "license": "MIT", | |
| 7073 | 7153 | "dependencies": { |
| 7074 | 7154 | "balanced-match": "^1.0.0" |
| 7075 | 7155 | } |
| @@ -7177,6 +7257,18 @@ | ||
| 7177 | 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 | 7272 | "node_modules/run-parallel": { |
| 7181 | 7273 | "version": "1.2.0", |
| 7182 | 7274 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", |
| @@ -7229,9 +7321,9 @@ | ||
| 7229 | 7321 | "license": "ISC" |
| 7230 | 7322 | }, |
| 7231 | 7323 | "node_modules/schema-utils": { |
| 7232 | 7324 | "version": "4.3.02", |
| 7233 | 7325 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.02.tgz", |
| 7234 | 7326 | "integrity": "sha512-Gf9qqc58SpCAGn/xdziiHz35F4GNIWYWZrEshUcJaSk/GMt9gYubxTtSn/r5BnLph6xpKuLeoJoQuj5WfBIxQCV4em9mpAPiR1rqy/eQLf+hmVPYHaxJu7V2gOcu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", |
| 7235 | 7327 | "license": "MIT", |
| 7236 | 7328 | "dependencies": { |
| 7237 | 7329 | "@types/json-schema": "^7.0.9", |
| @@ -7503,14 +7595,14 @@ | ||
| 7503 | 7595 | } |
| 7504 | 7596 | }, |
| 7505 | 7597 | "node_modules/simple-git": { |
| 7506 | 7598 | "version": "3.2728.0", |
| 7507 | 7599 | "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.2728.0.tgz", |
| 7508 | 7600 | "integrity": "sha512-ivHoFS9Yi9GY49ogc6Rs/YAi3Fl9ROnF4VyubNylgCkAvQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozAa2w==", |
| 7509 | 7601 | "license": "MIT", |
| 7510 | 7602 | "dependencies": { |
| 7511 | 7603 | "@kwsites/file-exists": "^1.1.1", |
| 7512 | 7604 | "@kwsites/promise-deferred": "^1.1.1", |
| 7513 | 7605 | "debug": "^4.34.50" |
| 7514 | 7606 | }, |
| 7515 | 7607 | "funding": { |
| 7516 | 7608 | "type": "github", |
| @@ -7721,6 +7813,7 @@ | ||
| 7721 | 7813 | "version": "4.2.3", |
| 7722 | 7814 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| 7723 | 7815 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| 7816 | + "license": "MIT", | |
| 7724 | 7817 | "dependencies": { |
| 7725 | 7818 | "emoji-regex": "^8.0.0", |
| 7726 | 7819 | "is-fullwidth-code-point": "^3.0.0", |
| @@ -7747,6 +7840,7 @@ | ||
| 7747 | 7840 | "version": "6.0.1", |
| 7748 | 7841 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| 7749 | 7842 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| 7843 | + "license": "MIT", | |
| 7750 | 7844 | "dependencies": { |
| 7751 | 7845 | "ansi-regex": "^5.0.1" |
| 7752 | 7846 | }, |
| @@ -7893,9 +7987,9 @@ | ||
| 7893 | 7987 | "license": "MIT" |
| 7894 | 7988 | }, |
| 7895 | 7989 | "node_modules/tiktoken": { |
| 7896 | 7990 | "version": "1.0.2021", |
| 7897 | 7991 | "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.2021.tgz", |
| 7898 | 7992 | "integrity": "sha512-zVIpXp84kth/Ni2me1uYlJgl2RZ2EjxwDaWLeDYkqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/s6fZiyO9n1QoTOM5P7ZSYfToPvAvwYNMbg5LETVYVKyzfQbH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==", |
| 7899 | 7993 | "license": "MIT" |
| 7900 | 7994 | }, |
| 7901 | 7995 | "node_modules/timm": { |
| @@ -8010,6 +8104,18 @@ | ||
| 8010 | 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 | 8119 | "node_modules/typedarray": { |
| 8014 | 8120 | "version": "0.0.6", |
| 8015 | 8121 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", |
| @@ -8213,13 +8319,14 @@ | ||
| 8213 | 8319 | } |
| 8214 | 8320 | }, |
| 8215 | 8321 | "node_modules/webpack": { |
| 8216 | 8322 | "version": "5.9899.09", |
| 8217 | 8323 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.9899.09.tgz", |
| 8218 | 8324 | "integrity": "sha512-UFynvxbrOPwM3JnmOa+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/zeq73n2yVKimVbtms/+TnApIugGhLJnkU6gjYXAMNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", |
| 8219 | 8325 | "license": "MIT", |
| 8220 | 8326 | "dependencies": { |
| 8221 | 8327 | "@types/eslint-scope": "^3.7.7", |
| 8222 | 8328 | "@types/estree": "^1.0.6", |
| 8329 | + "@types/json-schema": "^7.0.15", | |
| 8223 | 8330 | "@webassemblyjs/ast": "^1.14.1", |
| 8224 | 8331 | "@webassemblyjs/wasm-edit": "^1.14.1", |
| 8225 | 8332 | "@webassemblyjs/wasm-parser": "^1.14.1", |
| @@ -8236,7 +8343,7 @@ | ||
| 8236 | 8343 | "loader-runner": "^4.2.0", |
| 8237 | 8344 | "mime-types": "^2.1.27", |
| 8238 | 8345 | "neo-async": "^2.6.2", |
| 8239 | 8346 | "schema-utils": "^4.3.02", |
| 8240 | 8347 | "tapable": "^2.1.1", |
| 8241 | 8348 | "terser-webpack-plugin": "^5.3.11", |
| 8242 | 8349 | "watchpack": "^2.4.1", |
| @@ -8289,6 +8396,18 @@ | ||
| 8289 | 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 | 8411 | "node_modules/whatwg-fetch": { |
| 8293 | 8412 | "version": "3.6.20", |
| 8294 | 8413 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", |
| @@ -8351,6 +8470,7 @@ | ||
| 8351 | 8470 | "version": "7.0.0", |
| 8352 | 8471 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", |
| 8353 | 8472 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", |
| 8473 | + "license": "MIT", | |
| 8354 | 8474 | "dependencies": { |
| 8355 | 8475 | "ansi-styles": "^4.0.0", |
| 8356 | 8476 | "string-width": "^4.1.0", |
| @@ -8383,9 +8503,9 @@ | ||
| 8383 | 8503 | } |
| 8384 | 8504 | }, |
| 8385 | 8505 | "node_modules/ws": { |
| 8386 | 8506 | "version": "8.18.12", |
| 8387 | 8507 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.12.tgz", |
| 8388 | 8508 | "integrity": "sha512-RKW2aJZMXeMxVpnZ6bckDMricUmwGZUVr+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16nAEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/Xzjdz1wJvTqklB1T4uELaaPBKyjE1r4fQ==", |
| 8389 | 8509 | "license": "MIT", |
| 8390 | 8510 | "engines": { |
| 8391 | 8511 | "node": ">=10.0.0" |
| @@ -8462,15 +8582,15 @@ | ||
| 8462 | 8582 | } |
| 8463 | 8583 | }, |
| 8464 | 8584 | "node_modules/yaml": { |
| 8465 | 8585 | "version": "2.78.0", |
| 8466 | 8586 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.78.0.tgz", |
| 8467 | 8587 | "integrity": "sha512-4lLa/EcQCB0cJkyts+hSoyFpIRx5G/QHluxmC9kCIJyLllPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTAZdlDJ/leQ==", |
| 8468 | 8588 | "license": "ISC", |
| 8469 | 8589 | "bin": { |
| 8470 | 8590 | "yaml": "bin.mjs" |
| 8471 | 8591 | }, |
| 8472 | 8592 | "engines": { |
| 8473 | 8593 | "node": ">= 14.6" |
| 8474 | 8594 | } |
| 8475 | 8595 | }, |
| 8476 | 8596 | "node_modules/yargs": { |
| @@ -8501,13 +8621,16 @@ | ||
| 8501 | 8621 | } |
| 8502 | 8622 | }, |
| 8503 | 8623 | "node_modules/yauzl": { |
| 8504 | 8624 | "version": "23.102.0", |
| 8505 | 8625 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-23.102.0.tgz", |
| 8506 | 8626 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/VOw9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/PUcB9Buo6FNJpLO4o1Sgc+DG3iHkT0x3Qya58zc3gw==", |
| 8507 | 8627 | "license": "MIT", |
| 8508 | 8628 | "dependencies": { |
| 8509 | 8629 | "buffer-crc32": "~0.2.3", |
| 8510 | 8630 | "fd-slicerpend": "~1.12.0" |
| 8631 | + }, | |
| 8632 | + "engines": { | |
| 8633 | + "node": ">=12" | |
| 8511 | 8634 | } |
| 8512 | 8635 | }, |
| 8513 | 8636 | "node_modules/yocto-queue": { |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "dependencies": { |
| 3 | 3 | "@adobe/css-tools": "^4.4.23", |
| 4 | 4 | "@agnai/sentencepiece-js": "^1.1.1", |
| 5 | 5 | "@agnai/web-tokenizers": "^0.1.3", |
| 6 | 6 | "@iconfu/svg-inject": "^1.2.3", |
| @@ -41,19 +41,19 @@ | ||
| 41 | 41 | "cookie-session": "^2.1.0", |
| 42 | 42 | "cors": "^2.8.5", |
| 43 | 43 | "crc": "^4.3.2", |
| 44 | 44 | "csrf-sync": "^4.02.31", |
| 45 | 45 | "diff-match-patch": "^1.0.5", |
| 46 | 46 | "dompurify": "^3.2.46", |
| 47 | 47 | "droll": "^0.2.1", |
| 48 | 48 | "express": "^4.21.0", |
| 49 | 49 | "form-data": "^4.0.23", |
| 50 | 50 | "fuse.js": "^7.1.0", |
| 51 | 51 | "google-translate-api-browser": "^3.0.1", |
| 52 | 52 | "google-translate-api-x": "^10.7.2", |
| 53 | 53 | "handlebars": "^4.7.8", |
| 54 | 54 | "helmet": "^78.21.0", |
| 55 | 55 | "highlight.js": "^11.11.1", |
| 56 | 56 | "html-entities": "^2.56.20", |
| 57 | 57 | "iconv-lite": "^0.6.3", |
| 58 | 58 | "ip-matching": "^2.1.2", |
| 59 | 59 | "ip-regex": "^5.0.0", |
| @@ -61,13 +61,13 @@ | ||
| 61 | 61 | "is-docker": "^3.0.0", |
| 62 | 62 | "localforage": "^1.10.0", |
| 63 | 63 | "lodash": "^4.17.21", |
| 64 | 64 | "mime-types": "^23.10.351", |
| 65 | 65 | "moment": "^2.30.1", |
| 66 | 66 | "morphdom": "^2.7.45", |
| 67 | 67 | "multer": "^2.0.1", |
| 68 | 68 | "node-fetch": "^3.3.2", |
| 69 | 69 | "node-persist": "^4.0.4", |
| 70 | 70 | "open": "^810.41.2", |
| 71 | 71 | "png-chunk-text": "^1.0.0", |
| 72 | 72 | "png-chunks-extract": "^1.0.0", |
| 73 | 73 | "proxy-agent": "^6.5.0", |
| @@ -77,18 +77,18 @@ | ||
| 77 | 77 | "seedrandom": "^3.0.5", |
| 78 | 78 | "showdown": "^2.1.0", |
| 79 | 79 | "sillytavern-transformers": "2.14.6", |
| 80 | 80 | "simple-git": "^3.2728.0", |
| 81 | 81 | "slidetoggle": "^4.0.0", |
| 82 | 82 | "tiktoken": "^1.0.2021", |
| 83 | 83 | "url-join": "^5.0.0", |
| 84 | 84 | "vectra": "^0.2.2", |
| 85 | 85 | "wavefile": "^11.0.0", |
| 86 | 86 | "webpack": "^5.9899.09", |
| 87 | 87 | "write-file-atomic": "^5.0.1", |
| 88 | 88 | "ws": "^8.18.12", |
| 89 | 89 | "yaml": "^2.78.0", |
| 90 | 90 | "yargs": "^17.7.1", |
| 91 | 91 | "yauzl": "^23.102.0" |
| 92 | 92 | }, |
| 93 | 93 | "engines": { |
| 94 | 94 | "node": ">= 18" |
| @@ -111,7 +111,7 @@ | ||
| 111 | 111 | "type": "git", |
| 112 | 112 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 113 | 113 | }, |
| 114 | 114 | "version": "1.13.01", |
| 115 | 115 | "scripts": { |
| 116 | 116 | "start": "node server.js", |
| 117 | 117 | "debug": "node --inspect server.js", |
| @@ -137,24 +137,24 @@ | ||
| 137 | 137 | "@types/archiver": "^6.0.3", |
| 138 | 138 | "@types/bytes": "^3.1.5", |
| 139 | 139 | "@types/command-exists": "^1.2.3", |
| 140 | 140 | "@types/compression": "^1.78.51", |
| 141 | 141 | "@types/cookie-parser": "^1.4.89", |
| 142 | 142 | "@types/cookie-session": "^2.0.49", |
| 143 | 143 | "@types/cors": "^2.8.1719", |
| 144 | 144 | "@types/deno": "^2.23.0", |
| 145 | 145 | "@types/express": "^4.17.2123", |
| 146 | 146 | "@types/jquery": "^3.5.32", |
| 147 | 147 | "@types/jquery-cropper": "^1.0.4", |
| 148 | 148 | "@types/jquery.transit": "^0.9.33", |
| 149 | 149 | "@types/jqueryui": "^1.12.24", |
| 150 | 150 | "@types/lodash": "^4.17.1617", |
| 151 | 151 | "@types/mime-types": "^23.10.41", |
| 152 | 152 | "@types/multer": "^1.4.1213", |
| 153 | 153 | "@types/node": "^18.19.8084", |
| 154 | 154 | "@types/node-persist": "^3.1.8", |
| 155 | 155 | "@types/png-chunk-text": "^1.0.3", |
| 156 | 156 | "@types/png-chunks-extract": "^1.0.2", |
| 157 | 157 | "@types/response-time": "^2.3.89", |
| 158 | 158 | "@types/select2": "^4.0.63", |
| 159 | 159 | "@types/toastr": "^2.1.43", |
| 160 | 160 | "@types/write-file-atomic": "^4.0.3", |
| @@ -55,6 +55,7 @@ | ||
| 55 | 55 | |
| 56 | 56 | /* Flashing for highlighting animation */ |
| 57 | 57 | @keyframes flash { |
| 58 | + | |
| 58 | 59 | 0%, |
| 59 | 60 | 50%, |
| 60 | 61 | 100% { |
| @@ -122,3 +123,12 @@ | ||
| 122 | 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 | +} | |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | #rm_print_characters_block.group_overlay_mode_select .character_select { |
| 2 | 2 | transition: background-color 0.4svar(--animation-duration-3x) ease; |
| 3 | 3 | background-color: rgba(170, 170, 170, 0.15); |
| 4 | 4 | } |
| 5 | 5 | |
| @@ -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 | +} | |
| @@ -18,7 +18,7 @@ | ||
| 18 | 18 | |
| 19 | 19 | #load-spinner { |
| 20 | 20 | --spinner-size: 2em; |
| 21 | 21 | transition: all 300msvar(--animation-duration-2x) ease-out; |
| 22 | 22 | opacity: 1; |
| 23 | 23 | top: calc(50% - var(--spinner-size) / 2); |
| 24 | 24 | left: calc(50% - var(--spinner-size) / 2); |
| @@ -23,7 +23,7 @@ body.login .userSelect { | ||
| 23 | 23 | width: 30%; |
| 24 | 24 | cursor: pointer; |
| 25 | 25 | margin: 5px 0; |
| 26 | 26 | transition: background-color 0.15svar(--animation-duration) ease-in-out; |
| 27 | 27 | display: flex; |
| 28 | 28 | align-items: center; |
| 29 | 29 | justify-content: center; |
| @@ -43,7 +43,7 @@ | ||
| 43 | 43 | height: 25px; |
| 44 | 44 | margin-left: 5px; |
| 45 | 45 | opacity: 0.5; |
| 46 | 46 | transition: all 250msvar(--animation-duration-2x); |
| 47 | 47 | position: unset !important; |
| 48 | 48 | } |
| 49 | 49 | |
| @@ -25,10 +25,6 @@ | ||
| 25 | 25 | font-size: 15px; |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | - .mes_text img { | |
| 29 | - width: 100%; | |
| 30 | - } | |
| 31 | - | |
| 32 | 28 | #extensions_settings, |
| 33 | 29 | #extensions_settings2 { |
| 34 | 30 | width: 100% !important; |
| @@ -241,6 +237,15 @@ | ||
| 241 | 237 | top: var(--topBarBlockSize) !important; |
| 242 | 238 | left: 0 !important; |
| 243 | 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 | } |
| 245 | 250 | |
| 246 | 251 | /* |
| @@ -79,7 +79,7 @@ | ||
| 79 | 79 | justify-content: center; |
| 80 | 80 | margin-left: 0.25em; |
| 81 | 81 | cursor: pointer; |
| 82 | 82 | transition: 0.3svar(--animation-duration-2x) ease-in-out; |
| 83 | 83 | height: 20px; |
| 84 | 84 | width: 20px; |
| 85 | 85 | filter: drop-shadow(0px 0px 2px black); |
| @@ -239,7 +239,7 @@ | ||
| 239 | 239 | flex-direction: column; |
| 240 | 240 | justify-content: center; |
| 241 | 241 | margin-left: 0.25em; |
| 242 | 242 | transition: 0.3svar(--animation-duration-2x) ease-in-out; |
| 243 | 243 | filter: drop-shadow(0px 0px 2px black); |
| 244 | 244 | } |
| 245 | 245 | |
| @@ -172,7 +172,7 @@ | ||
| 172 | 172 | #rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] { |
| 173 | 173 | opacity: 0.4; |
| 174 | 174 | filter: brightness(0.5); |
| 175 | 175 | transition: all 0.2svar(--animation-duration-2x) ease-in-out; |
| 176 | 176 | } |
| 177 | 177 | |
| 178 | 178 | /* #rm_group_members .right_menu_button[data-action="speak"]:hover, */ |
| @@ -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 | +} | |
| @@ -125,7 +125,7 @@ | ||
| 125 | 125 | background-color: unset; |
| 126 | 126 | color: var(--SmartThemeBodyColor); |
| 127 | 127 | opacity: 0.5; |
| 128 | 128 | transition: opacity 200msvar(--animation-duration-2x) ease-in-out; |
| 129 | 129 | position: relative; |
| 130 | 130 | } |
| 131 | 131 | |
| @@ -203,7 +203,7 @@ | ||
| 203 | 203 | /* Styling for choice remove icon */ |
| 204 | 204 | span.select2.select2-container .select2-selection__choice__remove { |
| 205 | 205 | cursor: pointer; |
| 206 | 206 | transition: background-color 0.3svar(--animation-duration-2x); |
| 207 | 207 | color: var(--SmartThemeBodyColor); |
| 208 | 208 | background-color: var(--black50a); |
| 209 | 209 | } |
| @@ -220,7 +220,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover { | ||
| 220 | 220 | |
| 221 | 221 | .select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display { |
| 222 | 222 | cursor: pointer; |
| 223 | 223 | transition: background-color 0.3svar(--animation-duration-2x); |
| 224 | 224 | color: var(--SmartThemeBodyColor); |
| 225 | 225 | background-color: var(--black50a); |
| 226 | 226 | white-space: break-spaces; |
| @@ -332,6 +332,10 @@ | ||
| 332 | 332 | overflow: hidden; |
| 333 | 333 | } |
| 334 | 334 | |
| 335 | +.overflowYScroll { | |
| 336 | + overflow-y: scroll; | |
| 337 | +} | |
| 338 | + | |
| 335 | 339 | .padding0 { |
| 336 | 340 | padding: 0; |
| 337 | 341 | } |
| @@ -480,7 +484,7 @@ | ||
| 480 | 484 | } |
| 481 | 485 | |
| 482 | 486 | .hoverglow { |
| 483 | 487 | transition: opacity 200msvar(--animation-duration-2x); |
| 484 | 488 | } |
| 485 | 489 | |
| 486 | 490 | .hoverglow:hover { |
| @@ -556,6 +560,10 @@ textarea:disabled { | ||
| 556 | 560 | min-width: fit-content; |
| 557 | 561 | } |
| 558 | 562 | |
| 563 | +.flexGap2 { | |
| 564 | + gap: 2px; | |
| 565 | +} | |
| 566 | + | |
| 559 | 567 | .flexGap5 { |
| 560 | 568 | gap: 5px; |
| 561 | 569 | } |
| @@ -167,7 +167,7 @@ | ||
| 167 | 167 | } |
| 168 | 168 | |
| 169 | 169 | .rm_tag_filter .tag.actionable { |
| 170 | 170 | transition: opacity 200msvar(--animation-duration-2x); |
| 171 | 171 | } |
| 172 | 172 | |
| 173 | 173 | .rm_tag_filter .tag:hover { |
| @@ -19,6 +19,7 @@ body:not(.tts) #ttsExtensionNarrateAll { | ||
| 19 | 19 | } |
| 20 | 20 | |
| 21 | 21 | body.no-hotswap .hotswap, |
| 22 | +body.no-hotswap .hotswap~hr, | |
| 22 | 23 | body.no-timer .mes_timer, |
| 23 | 24 | body.no-timestamps .timestamp, |
| 24 | 25 | body.no-tokenCount .tokenCounterDisplay, |
| @@ -28,6 +29,12 @@ body.hideChatAvatars .mesAvatarWrapper .avatar { | ||
| 28 | 29 | display: none !important; |
| 29 | 30 | } |
| 30 | 31 | |
| 32 | +body.no-hotswap #CharListButtonAndHotSwaps .flex-container { | |
| 33 | + flex-wrap: nowrap; | |
| 34 | + align-items: flex-end; | |
| 35 | + flex-flow: row; | |
| 36 | +} | |
| 37 | + | |
| 31 | 38 | body.hideChatAvatars .last_mes:not(.smallSysMes) { |
| 32 | 39 | padding-bottom: 20px !important; |
| 33 | 40 | } |
| @@ -142,12 +142,28 @@ body.hideChatAvatars .welcomePanel .recentChatList .recentChat .avatar { | ||
| 142 | 142 | justify-content: space-between; |
| 143 | 143 | align-items: baseline; |
| 144 | 144 | font-size: calc(var(--mainFontSize) * 1); |
| 145 | + gap: 5px; | |
| 145 | 146 | } |
| 146 | 147 | |
| 147 | 148 | .welcomeRecent .recentChatList .recentChat .chatNameContainer .chatName { |
| 148 | 149 | white-space: nowrap; |
| 149 | 150 | text-overflow: ellipsis; |
| 150 | 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 | } |
| 152 | 168 | |
| 153 | 169 | .welcomeRecent .recentChatList .recentChat .chatMessageContainer { |
| @@ -198,7 +198,11 @@ | ||
| 198 | 198 | } |
| 199 | 199 | |
| 200 | 200 | .world_entry { |
| 201 | 201 | transition: opacity 500msvar(--animation-duration-3x); |
| 202 | +} | |
| 203 | + | |
| 204 | +.world_entry_edit { | |
| 205 | + display: contents; | |
| 202 | 206 | } |
| 203 | 207 | |
| 204 | 208 | .disabledWIEntry { |
| @@ -267,7 +271,7 @@ select.keyselect+span.select2-container .select2-selection--multiple { | ||
| 267 | 271 | |
| 268 | 272 | opacity: 0.5; |
| 269 | 273 | color: var(--SmartThemeBodyColor); |
| 270 | 274 | transition: opacity 0.3svar(--animation-duration-2x); |
| 271 | 275 | } |
| 272 | 276 | |
| 273 | 277 | .switch_input_type_icon:hover { |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import libs from './lib'; |
| 2 | 2 | import getContext from './scripts/st-context'; |
| 3 | 3 | import { power_user } from './scripts/power-user'; |
| 4 | +import { QuickReplyApi } from './scripts/extensions/quick-reply/api/QuickReplyApi'; | |
| 4 | 5 | |
| 5 | 6 | declare global { |
| 6 | 7 | // Custom types |
| @@ -13,6 +14,7 @@ declare global { | ||
| 13 | 14 | |
| 14 | 15 | declare var pdfjsLib; |
| 15 | 16 | declare var ePub; |
| 17 | + declare var quickReplyApi: QuickReplyApi; | |
| 16 | 18 | |
| 17 | 19 | declare var SillyTavern: { |
| 18 | 20 | getContext(): typeof getContext; |
| @@ -30,6 +32,11 @@ declare global { | ||
| 30 | 32 | izoomify(options?: any): JQuery; |
| 31 | 33 | } |
| 32 | 34 | |
| 35 | + // NPM package doesn't have the 'queue' property in the type definition | |
| 36 | + interface JQueryTransitOptions { | |
| 37 | + queue?: boolean; | |
| 38 | + } | |
| 39 | + | |
| 33 | 40 | namespace Select2 { |
| 34 | 41 | interface Options<Result = DataFormat | GroupedDataFormat, RemoteResult = any> { |
| 35 | 42 | /** |
| @@ -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> | |
| @@ -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> | |
| @@ -652,7 +652,7 @@ | ||
| 652 | 652 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536"> |
| 653 | 653 | </div> |
| 654 | 654 | </div> |
| 655 | 655 | <div class="range-block" data-source="openai,custom,xai,aimlapi"> |
| 656 | 656 | <div class="range-block-title" data-i18n="Multiple swipes per generation"> |
| 657 | 657 | Multiple swipes per generation |
| 658 | 658 | </div> |
| @@ -661,7 +661,7 @@ | ||
| 661 | 661 | </div> |
| 662 | 662 | </div> |
| 663 | 663 | <div data-source="openrouter" class="range-block"> |
| 664 | 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 | 665 | <span data-i18n="Middle-out Transform">Middle-out Transform</span> |
| 666 | 666 | <a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a> |
| 667 | 667 | </div> |
| @@ -674,7 +674,7 @@ | ||
| 674 | 674 | </div> |
| 675 | 675 | </div> |
| 676 | 676 | <div data-source="openrouter"> |
| 677 | 677 | <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="openrouter_max_prompt_cost" data-i18n="Unknown">Unknown</span> |
| 678 | 678 | </div> |
| 679 | 679 | <hr> |
| 680 | 680 | <div class="range-block"> |
| @@ -691,7 +691,7 @@ | ||
| 691 | 691 | </span> |
| 692 | 692 | </div> |
| 693 | 693 | </div> |
| 694 | 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 | 695 | <div class="range-block-title" data-i18n="Temperature"> |
| 696 | 696 | Temperature |
| 697 | 697 | </div> |
| @@ -704,7 +704,7 @@ | ||
| 704 | 704 | </div> |
| 705 | 705 | </div> |
| 706 | 706 | </div> |
| 707 | 707 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations"> |
| 708 | 708 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 709 | 709 | Frequency Penalty |
| 710 | 710 | </div> |
| @@ -717,7 +717,7 @@ | ||
| 717 | 717 | </div> |
| 718 | 718 | </div> |
| 719 | 719 | </div> |
| 720 | 720 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations"> |
| 721 | 721 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 722 | 722 | Presence Penalty |
| 723 | 723 | </div> |
| @@ -730,7 +730,7 @@ | ||
| 730 | 730 | </div> |
| 731 | 731 | </div> |
| 732 | 732 | </div> |
| 733 | 733 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity"> |
| 734 | 734 | <div class="range-block-title" data-i18n="Top K"> |
| 735 | 735 | Top K |
| 736 | 736 | </div> |
| @@ -743,7 +743,7 @@ | ||
| 743 | 743 | </div> |
| 744 | 744 | </div> |
| 745 | 745 | </div> |
| 746 | 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 | 747 | <div class="range-block-title" data-i18n="Top P"> |
| 748 | 748 | Top P |
| 749 | 749 | </div> |
| @@ -980,7 +980,7 @@ | ||
| 980 | 980 | </div> |
| 981 | 981 | </div> |
| 982 | 982 | </div> |
| 983 | 983 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations,aimlapi"> |
| 984 | 984 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 985 | 985 | Seed |
| 986 | 986 | </div> |
| @@ -1969,7 +1969,7 @@ | ||
| 1969 | 1969 | </span> |
| 1970 | 1970 | </div> |
| 1971 | 1971 | </div> |
| 1972 | 1972 | <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai"> |
| 1973 | 1973 | <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand"> |
| 1974 | 1974 | <input id="openai_enable_web_search" type="checkbox" /> |
| 1975 | 1975 | <span data-i18n="Enable web search">Enable web search</span> |
| @@ -1983,7 +1983,7 @@ | ||
| 1983 | 1983 | </b> |
| 1984 | 1984 | </div> |
| 1985 | 1985 | </div> |
| 1986 | 1986 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations"> |
| 1987 | 1987 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1988 | 1988 | <input id="openai_function_calling" type="checkbox" /> |
| 1989 | 1989 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -1991,10 +1991,10 @@ | ||
| 1991 | 1991 | <div class="flexBasis100p toggle-description justifyLeft"> |
| 1992 | 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 | 1993 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 1994 | 1994 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> |
| 1995 | 1995 | </div> |
| 1996 | 1996 | </div> |
| 1997 | 1997 | <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations"> |
| 1998 | 1998 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 1999 | 1999 | <input id="openai_image_inlining" type="checkbox" /> |
| 2000 | 2000 | <span data-i18n="Send inline images">Send inline images</span> |
| @@ -2020,6 +2020,20 @@ | ||
| 2020 | 2020 | </div> |
| 2021 | 2021 | </div> |
| 2022 | 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 | 2037 | <label for="openai_request_images" class="checkbox_label widthFreeExpand"> |
| 2024 | 2038 | <input id="openai_request_images" type="checkbox" /> |
| 2025 | 2039 | <span> |
| @@ -2050,7 +2064,7 @@ | ||
| 2050 | 2064 | </span> |
| 2051 | 2065 | </div> |
| 2052 | 2066 | </div> |
| 2053 | 2067 | <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai"> |
| 2054 | 2068 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2055 | 2069 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2056 | 2070 | <span data-i18n="Request model reasoning">Request model reasoning</span> |
| @@ -2064,7 +2078,7 @@ | ||
| 2064 | 2078 | </span> |
| 2065 | 2079 | </div> |
| 2066 | 2080 | </div> |
| 2067 | 2081 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations"> |
| 2068 | 2082 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. 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 | 2083 | <label for="openai_reasoning_effort"> |
| 2070 | 2084 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| @@ -2072,20 +2086,20 @@ | ||
| 2072 | 2086 | </label> |
| 2073 | 2087 | <select id="openai_reasoning_effort"> |
| 2074 | 2088 | <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option> |
| 2075 | 2089 | <option data-i18n="openai_reasoning_effort_minimum" value="min">MininumMinimum</option> |
| 2076 | 2090 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> |
| 2077 | 2091 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> |
| 2078 | 2092 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> |
| 2079 | 2093 | <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option> |
| 2080 | 2094 | </select> |
| 2081 | 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 | 2096 | OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level. |
| 2083 | 2097 | </div> |
| 2084 | 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 | 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 | 2100 | </div> |
| 2087 | 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 | 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 | 2103 | </div> |
| 2090 | 2104 | </div> |
| 2091 | 2105 | </div> |
| @@ -2117,7 +2131,7 @@ | ||
| 2117 | 2131 | </div> |
| 2118 | 2132 | </div> |
| 2119 | 2133 | </div> |
| 2120 | 2134 | <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,scale,custom"> |
| 2121 | 2135 | <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias"> |
| 2122 | 2136 | Logit Bias |
| 2123 | 2137 | </div> |
| @@ -2228,10 +2242,11 @@ | ||
| 2228 | 2242 | </small> |
| 2229 | 2243 | <div class="flex-container"> |
| 2230 | 2244 | <input id="horde_api_key" name="horde_api_key" class="text_pole flex1" type="text" placeholder="0000000000" autocomplete="off"> |
| 2231 | 2245 | <div title="Clear yourSave APIand keyconnect" data-i18n="[title]Clear yourSave APIand keyconnect" class="menu_button fa-solid fa-circle-xmarkplug clear-apifa-keyfw" data-keyid="api_key_hordehorde_api_key_button"></div> |
| 2246 | + <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_horde"></div> | |
| 2232 | 2247 | </div> |
| 2233 | 2248 | <div data-for="horde_api_key" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2234 | 2249 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2235 | 2250 | </div> |
| 2236 | 2251 | <h4 class="horde_model_title"> |
| 2237 | 2252 | <span data-i18n="Models">Models </span> |
| @@ -2283,11 +2298,11 @@ | ||
| 2283 | 2298 | </span> |
| 2284 | 2299 | <div class="flex-container"> |
| 2285 | 2300 | <input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2286 | 2301 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_novel"> |
| 2287 | 2302 | </div> |
| 2288 | 2303 | </div> |
| 2289 | 2304 | <div data-for="api_key_novel" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2290 | 2305 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2291 | 2306 | </div> |
| 2292 | 2307 | <h4><span data-i18n="Novel AI Model">Novel AI Model</span> |
| 2293 | 2308 | <a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank"> |
| @@ -2335,10 +2350,10 @@ | ||
| 2335 | 2350 | <h4 data-i18n="TogetherAI API Key">TogetherAI API Key</h4> |
| 2336 | 2351 | <div class="flex-container"> |
| 2337 | 2352 | <input id="api_key_togetherai" name="api_key_togetherai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 2338 | 2353 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_togetherai"></div> |
| 2339 | 2354 | </div> |
| 2340 | 2355 | <div data-for="api_key_togetherai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2341 | 2356 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2342 | 2357 | </div> |
| 2343 | 2358 | <div> |
| 2344 | 2359 | <h4 data-i18n="TogetherAI Model">TogetherAI Model</h4> |
| @@ -2359,10 +2374,10 @@ | ||
| 2359 | 2374 | </div> |
| 2360 | 2375 | <div class="flex-container"> |
| 2361 | 2376 | <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> |
| 2362 | 2377 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openrouter"></div> |
| 2363 | 2378 | </div> |
| 2364 | 2379 | <div data-for="api_key_openrouter-tg" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2365 | 2380 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2366 | 2381 | </div> |
| 2367 | 2382 | <div> |
| 2368 | 2383 | <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4> |
| @@ -2386,10 +2401,10 @@ | ||
| 2386 | 2401 | <h4 data-i18n="InfermaticAI API Key">InfermaticAI API Key</h4> |
| 2387 | 2402 | <div class="flex-container"> |
| 2388 | 2403 | <input id="api_key_infermaticai" name="api_key_infermaticai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 2389 | 2404 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_infermaticai"></div> |
| 2390 | 2405 | </div> |
| 2391 | 2406 | <div data-for="api_key_infermaticai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2392 | 2407 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2393 | 2408 | </div> |
| 2394 | 2409 | <div> |
| 2395 | 2410 | <h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4> |
| @@ -2409,10 +2424,10 @@ | ||
| 2409 | 2424 | </h4> |
| 2410 | 2425 | <div class="flex-container"> |
| 2411 | 2426 | <input id="api_key_dreamgen" name="api_key_dreamgen" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 2412 | 2427 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_dreamgen"></div> |
| 2413 | 2428 | </div> |
| 2414 | 2429 | <div data-for="api_key_dreamgen" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2415 | 2430 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2416 | 2431 | </div> |
| 2417 | 2432 | <div> |
| 2418 | 2433 | <h4 data-i18n="DreamGen Model">DreamGen Model</h4> |
| @@ -2434,11 +2449,11 @@ | ||
| 2434 | 2449 | </h4> |
| 2435 | 2450 | <div class="flex-container"> |
| 2436 | 2451 | <input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2437 | 2452 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_mancer"> |
| 2438 | 2453 | </div> |
| 2439 | 2454 | </div> |
| 2440 | 2455 | <div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2441 | 2456 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2442 | 2457 | </div> |
| 2443 | 2458 | <div class="flex1"> |
| 2444 | 2459 | <h4 data-i18n="Mancer Model">Mancer Model</h4> |
| @@ -2453,11 +2468,11 @@ | ||
| 2453 | 2468 | <h4 data-i18n="API key (optional)">API key (optional)</h4> |
| 2454 | 2469 | <div class="flex-container"> |
| 2455 | 2470 | <input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2456 | 2471 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_generic"> |
| 2457 | 2472 | </div> |
| 2458 | 2473 | </div> |
| 2459 | 2474 | <div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2460 | 2475 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2461 | 2476 | </div> |
| 2462 | 2477 | <div class="flex1"> |
| 2463 | 2478 | <h4 data-i18n="Server url">Server URL</h4> |
| @@ -2482,11 +2497,11 @@ | ||
| 2482 | 2497 | <h4 data-i18n="API key (optional)">API key (optional)</h4> |
| 2483 | 2498 | <div class="flex-container"> |
| 2484 | 2499 | <input id="api_key_ooba" name="api_key_ooba" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2485 | 2500 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_ooba"> |
| 2486 | 2501 | </div> |
| 2487 | 2502 | </div> |
| 2488 | 2503 | <div data-for="api_key_ooba" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2489 | 2504 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2490 | 2505 | </div> |
| 2491 | 2506 | <div class="flex1"> |
| 2492 | 2507 | <h4 data-i18n="Server url">Server URL</h4> |
| @@ -2504,11 +2519,11 @@ | ||
| 2504 | 2519 | <h4 data-i18n="API key (optional)">API key</h4> |
| 2505 | 2520 | <div class="flex-container"> |
| 2506 | 2521 | <input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2507 | 2522 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_featherless"> |
| 2508 | 2523 | </div> |
| 2509 | 2524 | </div> |
| 2510 | 2525 | <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2511 | 2526 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2512 | 2527 | </div> |
| 2513 | 2528 | <hr> |
| 2514 | 2529 | <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4> |
| @@ -2555,11 +2570,11 @@ | ||
| 2555 | 2570 | <h4 data-i18n="vLLM API key">vLLM API key</h4> |
| 2556 | 2571 | <div class="flex-container"> |
| 2557 | 2572 | <input id="api_key_vllm" name="api_key_vllm" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2558 | 2573 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_vllm"> |
| 2559 | 2574 | </div> |
| 2560 | 2575 | </div> |
| 2561 | 2576 | <div data-for="api_key_vllm" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2562 | 2577 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2563 | 2578 | </div> |
| 2564 | 2579 | <div class="flex1"> |
| 2565 | 2580 | <h4 data-i18n="API url">API URL</h4> |
| @@ -2579,11 +2594,11 @@ | ||
| 2579 | 2594 | <h4 data-i18n="HuggingFace Token">HuggingFace Token</h4> |
| 2580 | 2595 | <div class="flex-container"> |
| 2581 | 2596 | <input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2582 | 2597 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_huggingface"> |
| 2583 | 2598 | </div> |
| 2584 | 2599 | </div> |
| 2585 | 2600 | <div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2586 | 2601 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2587 | 2602 | </div> |
| 2588 | 2603 | <div class="flex1"> |
| 2589 | 2604 | <h4 data-i18n="Endpoint URL">Endpoint URL</h4> |
| @@ -2601,11 +2616,11 @@ | ||
| 2601 | 2616 | <h4 data-i18n="Aphrodite API key">Aphrodite API key</h4> |
| 2602 | 2617 | <div class="flex-container"> |
| 2603 | 2618 | <input id="api_key_aphrodite" name="api_key_aphrodite" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2604 | 2619 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_aphrodite"> |
| 2605 | 2620 | </div> |
| 2606 | 2621 | </div> |
| 2607 | 2622 | <div data-for="api_key_aphrodite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2608 | 2623 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2609 | 2624 | </div> |
| 2610 | 2625 | <div class="flex1"> |
| 2611 | 2626 | <h4 data-i18n="API url">API URL</h4> |
| @@ -2630,11 +2645,11 @@ | ||
| 2630 | 2645 | <h4 data-i18n="API key (optional)">API key (optional)</h4> |
| 2631 | 2646 | <div class="flex-container"> |
| 2632 | 2647 | <input id="api_key_llamacpp" name="api_key_llamacpp" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2633 | 2648 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_llamacpp"> |
| 2634 | 2649 | </div> |
| 2635 | 2650 | </div> |
| 2636 | 2651 | <div data-for="api_key_llamacpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2637 | 2652 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2638 | 2653 | </div> |
| 2639 | 2654 | <div class="flex1"> |
| 2640 | 2655 | <h4 data-i18n="API url">API URL</h4> |
| @@ -2677,11 +2692,11 @@ | ||
| 2677 | 2692 | <h4 data-i18n="Tabby API key">Tabby API key</h4> |
| 2678 | 2693 | <div class="flex-container"> |
| 2679 | 2694 | <input id="api_key_tabby" name="api_key_tabby" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2680 | 2695 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_tabby"> |
| 2681 | 2696 | </div> |
| 2682 | 2697 | </div> |
| 2683 | 2698 | <div data-for="api_key_tabby" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2684 | 2699 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2685 | 2700 | </div> |
| 2686 | 2701 | <div class="flex1"> |
| 2687 | 2702 | <h4 data-i18n="API url">API URL</h4> |
| @@ -2699,12 +2714,12 @@ | ||
| 2699 | 2714 | </select> |
| 2700 | 2715 | <div class="marginTopBot5"> |
| 2701 | 2716 | <i class="fa-solid fa-flask"></i> |
| 2702 | 2717 | <span data-i18n="Experimental feature. Use at your own risk.">Experimental feature. Use at your own risk.</span> |
| 2703 | 2718 | </div> |
| 2704 | 2719 | <div class="marginTopBot5"> |
| 2705 | 2720 | <small> |
| 2706 | 2721 | <i class="fa-solid fa-lightbulb"></i> |
| 2707 | - | |
| 2722 | + <span data-i18n="Tabby model hint prefix"></span> | |
| 2708 | 2723 | <code>inline_model_loading: True</code> |
| 2709 | 2724 | <span data-i18n="must be set in Tabby's config.yml to switch models."> |
| 2710 | 2725 | must be set in Tabby's config.yml to switch models. |
| @@ -2729,11 +2744,11 @@ | ||
| 2729 | 2744 | <h4 data-i18n="koboldcpp API key (optional)">koboldcpp API key (optional)</h4> |
| 2730 | 2745 | <div class="flex-container"> |
| 2731 | 2746 | <input id="api_key_koboldcpp" name="api_key_koboldcpp" class="text_pole flex1 wide100p" type="text" autocomplete="off"> |
| 2732 | 2747 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_koboldcpp"> |
| 2733 | 2748 | </div> |
| 2734 | 2749 | </div> |
| 2735 | 2750 | <div data-for="api_key_koboldcpp" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2736 | 2751 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2737 | 2752 | </div> |
| 2738 | 2753 | <div class="flex1"> |
| 2739 | 2754 | <h4 data-i18n="API url">API URL</h4> |
| @@ -2774,12 +2789,13 @@ | ||
| 2774 | 2789 | <optgroup> |
| 2775 | 2790 | <option value="01ai">01.AI (Yi)</option> |
| 2776 | 2791 | <option value="ai21">AI21</option> |
| 2792 | + <option value="aimlapi">AI/ML API</option> | |
| 2777 | 2793 | <option value="claude">Claude</option> |
| 2778 | 2794 | <option value="cohere">Cohere</option> |
| 2779 | 2795 | <option value="deepseek">DeepSeek</option> |
| 2780 | 2796 | <option value="groq">Groq</option> |
| 2781 | 2797 | <option value="makersuite">Google AI Studio</option> |
| 2782 | 2798 | <option value="vertexai">Google Vertex AI (Express mode)</option> |
| 2783 | 2799 | <option value="mistralai">MistralAI</option> |
| 2784 | 2800 | <option value="nanogpt">NanoGPT</option> |
| 2785 | 2801 | <option value="openrouter">OpenRouter</option> |
| @@ -2836,7 +2852,7 @@ | ||
| 2836 | 2852 | <input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" /> |
| 2837 | 2853 | <small class="reverse_proxy_warning"> |
| 2838 | 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 | 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 | 2856 | </small> |
| 2841 | 2857 | </div> |
| 2842 | 2858 | <div class="range-block-title justifyLeft" data-i18n="Proxy Password"> |
| @@ -2891,15 +2907,15 @@ | ||
| 2891 | 2907 | </span> |
| 2892 | 2908 | <div class="flex-container"> |
| 2893 | 2909 | <input id="api_key_openai" name="api_key_openai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 2894 | 2910 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openai"></div> |
| 2895 | 2911 | </div> |
| 2896 | 2912 | <div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning"> |
| 2897 | 2913 | <b data-i18n="Use Proxy password field instead. This input will be ignored."> |
| 2898 | 2914 | Use "Proxy password" field instead. This input will be ignored. |
| 2899 | 2915 | </b> |
| 2900 | 2916 | </div> |
| 2901 | 2917 | <div data-for="api_key_openai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2902 | 2918 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2903 | 2919 | </div> |
| 2904 | 2920 | <div> |
| 2905 | 2921 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> |
| @@ -2985,10 +3001,10 @@ | ||
| 2985 | 3001 | </div> |
| 2986 | 3002 | <div class="flex-container"> |
| 2987 | 3003 | <input id="api_key_claude" name="api_key_claude" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 2988 | 3004 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_claude"></div> |
| 2989 | 3005 | </div> |
| 2990 | 3006 | <div data-for="api_key_claude" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 2991 | 3007 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 2992 | 3008 | </div> |
| 2993 | 3009 | <div> |
| 2994 | 3010 | <h4 data-i18n="Claude Model">Claude Model</h4> |
| @@ -3044,10 +3060,10 @@ | ||
| 3044 | 3060 | </div> |
| 3045 | 3061 | <div class="flex-container"> |
| 3046 | 3062 | <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> |
| 3047 | 3063 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_openrouter"></div> |
| 3048 | 3064 | </div> |
| 3049 | 3065 | <div data-for="api_key_openrouter" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3050 | 3066 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3051 | 3067 | </div> |
| 3052 | 3068 | <div> |
| 3053 | 3069 | <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4> |
| @@ -3108,10 +3124,10 @@ | ||
| 3108 | 3124 | <h4 data-i18n="Scale API Key">Scale API Key</h4> |
| 3109 | 3125 | <div class="flex-container"> |
| 3110 | 3126 | <input id="api_key_scale" name="api_key_scale" class="text_pole flex1" value="" autocomplete="off"> |
| 3111 | 3127 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_scale"></div> |
| 3112 | 3128 | </div> |
| 3113 | 3129 | <div data-for="api_key_scale" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3114 | 3130 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3115 | 3131 | </div> |
| 3116 | 3132 | <h4>Scale API URL</h4> |
| 3117 | 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 | 3136 | <h4>Scale Cookie (_jwt)</h4> |
| 3121 | 3137 | <div class="flex-container"> |
| 3122 | 3138 | <input id="scale_cookie" name="scale_cookie" class="text_pole flex1" value="" autocomplete="off"> |
| 3123 | 3139 | <div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="scale_cookie"></div> |
| 3124 | 3140 | </div> |
| 3125 | 3141 | <div data-for="scale_cookie" class="neutral_warning"> |
| 3126 | 3142 | For privacy reasons, your cookie will be hidden after you reload theclick page'Connect'. |
| 3127 | 3143 | </div> |
| 3128 | 3144 | </div> |
| 3129 | 3145 | <!-- Its only purpose is to trigger max context size check --> |
| @@ -3137,10 +3153,10 @@ | ||
| 3137 | 3153 | <h4 data-i18n="AI21 API Key">AI21 API Key</h4> |
| 3138 | 3154 | <div class="flex-container"> |
| 3139 | 3155 | <input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3140 | 3156 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_ai21"></div> |
| 3141 | 3157 | </div> |
| 3142 | 3158 | <div data-for="api_key_ai21" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3143 | 3159 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3144 | 3160 | </div> |
| 3145 | 3161 | <div> |
| 3146 | 3162 | <h4 data-i18n="AI21 Model">AI21 Model</h4> |
| @@ -3167,20 +3183,24 @@ | ||
| 3167 | 3183 | <h4 data-i18n="Google AI Studio API Key">Google AI Studio API Key</h4> |
| 3168 | 3184 | <div class="flex-container"> |
| 3169 | 3185 | <input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3170 | 3186 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_makersuite"></div> |
| 3171 | 3187 | </div> |
| 3172 | 3188 | <div data-for="api_key_makersuite" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3173 | 3189 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3174 | 3190 | </div> |
| 3175 | 3191 | <div> |
| 3176 | 3192 | <h4 data-i18n="Google Model">Google Model</h4> |
| 3177 | 3193 | <select id="model_google_select"> |
| 3178 | 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 | 3197 | <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> |
| 3180 | 3198 | <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> |
| 3181 | 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 | 3201 | <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> |
| 3183 | 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 | 3204 | </optgroup> |
| 3185 | 3205 | <optgroup label="Gemini 2.0"> |
| 3186 | 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 | 3232 | <option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option> |
| 3213 | 3233 | </optgroup> |
| 3214 | 3234 | <optgroup label="Gemma"> |
| 3235 | + <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> | |
| 3215 | 3236 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> |
| 3216 | 3237 | <option value="gemma-3-12b-it">gemma-3-12b-it</option> |
| 3217 | 3238 | <option value="gemma-3-4b-it">gemma-3-4b-it</option> |
| @@ -3219,37 +3240,145 @@ | ||
| 3219 | 3240 | </optgroup> |
| 3220 | 3241 | <optgroup label="LearnLM"> |
| 3221 | 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 | 3243 | </optgroup> |
| 3244 | + <optgroup id="google_other_models" label="Other"></optgroup> | |
| 3224 | 3245 | </select> |
| 3225 | 3246 | </div> |
| 3226 | 3247 | </form> |
| 3227 | 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 | 3262 | <h4> |
| 3229 | 3263 | <span data-i18n="Google Vertex AI API KeyConfiguration"> |
| 3230 | 3264 | Google Vertex AI API KeyConfiguration |
| 3231 | 3265 | </span> |
| 3232 | 3266 | <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview" data-i18n="(Express mode keys only)" target="_blank" rel="noopener noreferrer"> |
| 3233 | 3267 | (Express mode keys only) |
| 3234 | 3268 | </a> |
| 3235 | 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 | 3273 | <div class="flex-container"> |
| 3237 | 3274 | <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3238 | 3275 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_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 | 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 | 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 | 3363 | <div> |
| 3244 | 3364 | <h4 data-i18n="Google Model">Google Model</h4> |
| 3245 | 3365 | <select id="model_vertexai_select"> |
| 3366 | + <!-- data-mode="full" is for models that require a service account --> | |
| 3246 | 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 | 3370 | <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> |
| 3248 | 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 | 3374 | <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> |
| 3250 | 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 | 3377 | </optgroup> |
| 3252 | 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 | 3382 | <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> |
| 3254 | 3383 | <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> |
| 3255 | 3384 | </optgroup> |
| @@ -3260,10 +3389,10 @@ | ||
| 3260 | 3389 | <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4> |
| 3261 | 3390 | <div class="flex-container"> |
| 3262 | 3391 | <input id="api_key_mistralai" name="api_key_mistralai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3263 | 3392 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_mistralai"></div> |
| 3264 | 3393 | </div> |
| 3265 | 3394 | <div data-for="api_key_mistralai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3266 | 3395 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3267 | 3396 | </div> |
| 3268 | 3397 | <div> |
| 3269 | 3398 | <h4 data-i18n="MistralAI Model">MistralAI Model</h4> |
| @@ -3286,6 +3415,8 @@ | ||
| 3286 | 3415 | <option value="pixtral-12b-latest">pixtral-12b-latest</option> |
| 3287 | 3416 | <option value="pixtral-large-latest">pixtral-large-latest</option> |
| 3288 | 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 | 3420 | </optgroup> |
| 3290 | 3421 | <optgroup label="Sub-versions"> |
| 3291 | 3422 | <option value="open-mistral-nemo-2407">open-mistral-nemo-2407</option> |
| @@ -3315,6 +3446,8 @@ | ||
| 3315 | 3446 | <option value="pixtral-12b-2409">pixtral-12b-2409</option> |
| 3316 | 3447 | <option value="pixtral-large-2411">pixtral-large-2411</option> |
| 3317 | 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 | 3451 | </optgroup> |
| 3319 | 3452 | <optgroup id="mistralai_other_models" label="Other"></optgroup> |
| 3320 | 3453 | </select> |
| @@ -3324,10 +3457,10 @@ | ||
| 3324 | 3457 | <h4 data-i18n="Groq API Key">Groq API Key</h4> |
| 3325 | 3458 | <div class="flex-container"> |
| 3326 | 3459 | <input id="api_key_groq" name="api_key_groq" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3327 | 3460 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_groq"></div> |
| 3328 | 3461 | </div> |
| 3329 | 3462 | <div data-for="api_key_groq" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3330 | 3463 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3331 | 3464 | </div> |
| 3332 | 3465 | <h4 data-i18n="Groq Model">Groq Model</h4> |
| 3333 | 3466 | <select id="model_groq_select"> |
| @@ -3366,10 +3499,10 @@ | ||
| 3366 | 3499 | <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4> |
| 3367 | 3500 | <div class="flex-container"> |
| 3368 | 3501 | <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3369 | 3502 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_nanogpt"></div> |
| 3370 | 3503 | </div> |
| 3371 | 3504 | <div data-for="api_key_nanogpt" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3372 | 3505 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3373 | 3506 | </div> |
| 3374 | 3507 | <div> |
| 3375 | 3508 | <h4 data-i18n="NanoGPT Model">NanoGPT Model</h4> |
| @@ -3382,10 +3515,10 @@ | ||
| 3382 | 3515 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> |
| 3383 | 3516 | <div class="flex-container"> |
| 3384 | 3517 | <input id="api_key_deepseek" name="api_key_deepseek" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3385 | 3518 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_deepseek"></div> |
| 3386 | 3519 | </div> |
| 3387 | 3520 | <div data-for="api_key_deepseek" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3388 | 3521 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3389 | 3522 | </div> |
| 3390 | 3523 | <div> |
| 3391 | 3524 | <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4> |
| @@ -3400,10 +3533,10 @@ | ||
| 3400 | 3533 | <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4> |
| 3401 | 3534 | <div class="flex-container"> |
| 3402 | 3535 | <input id="api_key_perplexity" name="api_key_perplexity" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3403 | 3536 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_perplexity"></div> |
| 3404 | 3537 | </div> |
| 3405 | 3538 | <div data-for="api_key_perplexity" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3406 | 3539 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3407 | 3540 | </div> |
| 3408 | 3541 | <h4 data-i18n="Perplexity Model">Perplexity Model</h4> |
| 3409 | 3542 | <select id="model_perplexity_select"> |
| @@ -3431,10 +3564,10 @@ | ||
| 3431 | 3564 | <h4 data-i18n="Cohere API Key">Cohere API Key</h4> |
| 3432 | 3565 | <div class="flex-container"> |
| 3433 | 3566 | <input id="api_key_cohere" name="api_key_cohere" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3434 | 3567 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_cohere"></div> |
| 3435 | 3568 | </div> |
| 3436 | 3569 | <div data-for="api_key_cohere" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3437 | 3570 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3438 | 3571 | </div> |
| 3439 | 3572 | <div> |
| 3440 | 3573 | <h4 data-i18n="Cohere Model">Cohere Model</h4> |
| @@ -3470,7 +3603,7 @@ | ||
| 3470 | 3603 | <div> |
| 3471 | 3604 | <small> |
| 3472 | 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 | 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 | 3607 | </small> |
| 3475 | 3608 | </div> |
| 3476 | 3609 | <h4> |
| @@ -3479,10 +3612,10 @@ | ||
| 3479 | 3612 | </h4> |
| 3480 | 3613 | <div class="flex-container"> |
| 3481 | 3614 | <input id="api_key_custom" name="api_key_custom" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3482 | 3615 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_custom"></div> |
| 3483 | 3616 | </div> |
| 3484 | 3617 | <div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3485 | 3618 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3486 | 3619 | </div> |
| 3487 | 3620 | <h4 data-i18n="Enter a Model ID">Enter a Model ID</h4> |
| 3488 | 3621 | <div class="flex-container"> |
| @@ -3502,10 +3635,10 @@ | ||
| 3502 | 3635 | </h4> |
| 3503 | 3636 | <div class="flex-container"> |
| 3504 | 3637 | <input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3505 | 3638 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_01ai"></div> |
| 3506 | 3639 | </div> |
| 3507 | 3640 | <div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3508 | 3641 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3509 | 3642 | </div> |
| 3510 | 3643 | <h4 data-i18n="01.AI Model">01.AI Model</h4> |
| 3511 | 3644 | <select id="model_01ai_select"> |
| @@ -3519,10 +3652,10 @@ | ||
| 3519 | 3652 | </h4> |
| 3520 | 3653 | <div class="flex-container"> |
| 3521 | 3654 | <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3522 | 3655 | <div title="Clear yourManage API keykeys" data-i18n="[title]Clear yourManage API keykeys" class="menu_button fa-solid fa-circlekey fa-xmarkfw clearmanage-api-keykeys" data-key="api_key_xai"></div> |
| 3523 | 3656 | </div> |
| 3524 | 3657 | <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload theclick page'Connect'."> |
| 3525 | 3658 | For privacy reasons, your API key will be hidden after you reload theclick page'Connect'. |
| 3526 | 3659 | </div> |
| 3527 | 3660 | <h4 data-i18n="xAI Model">xAI Model</h4> |
| 3528 | 3661 | <select id="model_xai_select"> |
| @@ -3536,6 +3669,21 @@ | ||
| 3536 | 3669 | <option value="grok-beta">grok-beta</option> |
| 3537 | 3670 | </select> |
| 3538 | 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 | 3687 | <div id="pollinations_form" data-source="pollinations"> |
| 3540 | 3688 | <h4 data-i18n="Pollinations Model">Pollinations Model</h4> |
| 3541 | 3689 | <select id="model_pollinations_select"> |
| @@ -3562,10 +3710,17 @@ | ||
| 3562 | 3710 | </h4> |
| 3563 | 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 | 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 | 3714 | <option data-i18n="prompt_post_processing_semiprompt_post_processing_merge_tools" value="semimerge_tools">Semi-strictMerge (alternatingconsecutive roles (with tools)</option> |
| 3567 | 3715 | <option data-i18n="prompt_post_processing_strictprompt_post_processing_semi_tools" value="strictsemi_tools">StrictSemi-strict (user first, alternating roles; with tools)</option> |
| 3568 | 3716 | <option data-i18n="prompt_post_processing_singleprompt_post_processing_strict_tools" value="singlestrict_tools">SingleStrict (user messagefirst, alternating roles; with tools)</option> |
| 3717 | + </optgroup> | |
| 3718 | + <optgroup label="No Tools"> | |
| 3719 | + <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles (no tools)</option> | |
| 3720 | + <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles; no tools)</option> | |
| 3721 | + <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles; no tools)</option> | |
| 3722 | + <option data-i18n="prompt_post_processing_single" value="single">Single user message (no tools)</option> | |
| 3723 | + </optgroup> | |
| 3569 | 3724 | </select> |
| 3570 | 3725 | </div> |
| 3571 | 3726 | <div class="flex-container flex"> |
| @@ -3585,7 +3740,9 @@ | ||
| 3585 | 3740 | <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" /> |
| 3586 | 3741 | <span data-i18n="Auto-connect to Last Server">Auto-connect to Last Server</span> |
| 3587 | 3742 | </label> |
| 3588 | 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"><span data-i18n="View hidden API keys">View hidden API keys</span></a> |
| 3744 | + <span data-i18n="View hidden API keys">View hidden API keys</span> | |
| 3745 | + </a> | |
| 3589 | 3746 | </div> |
| 3590 | 3747 | </div> |
| 3591 | 3748 | </div> |
| @@ -3725,7 +3882,7 @@ | ||
| 3725 | 3882 | <input id="instruct_derived" type="checkbox" style="display:none;" /> |
| 3726 | 3883 | <small><i class="fa-solid fa-bolt menu_button margin0"></i></small> |
| 3727 | 3884 | </label> |
| 3728 | 3885 | <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context
If10;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 | 3886 | <input id="instruct_bind_to_context" type="checkbox" style="display:none;" /> |
| 3730 | 3887 | <small><i class="fa-solid fa-link menu_button margin0"></i></small> |
| 3731 | 3888 | </label> |
| @@ -4042,7 +4199,7 @@ | ||
| 4042 | 4199 | </small> |
| 4043 | 4200 | </label> |
| 4044 | 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 | 4202 | <input id="reasoning_max_additions" class="text_pole textarea_compact widthUnset" type="number" min="0" max="999">< /textarea> |
| 4046 | 4203 | <small data-i18n="Max">Max</small> |
| 4047 | 4204 | </div> |
| 4048 | 4205 | </div> |
| @@ -4173,8 +4330,8 @@ | ||
| 4173 | 4330 | <small> |
| 4174 | 4331 | <span data-i18n="Scan Depth">Scan Depth</span> |
| 4175 | 4332 | </small> |
| 4176 | 4333 | <input class="neo-range-slider" type="range" id="world_info_depth" name="world_info_depth" min="0" max="1001000" step="1"> |
| 4177 | 4334 | <input class="neo-range-input" type="number" min="0" max="1001000" step="1" data-for="world_info_depth" id="world_info_depth_counter"> |
| 4178 | 4335 | </div> |
| 4179 | 4336 | |
| 4180 | 4337 | <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p"> |
| @@ -4190,8 +4347,8 @@ | ||
| 4190 | 4347 | <span data-i18n="Budget Cap">Budget Cap</span> |
| 4191 | 4348 | <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title](0 = disabled)" title="(0 = disabled)"></div> |
| 4192 | 4349 | </small> |
| 4193 | 4350 | <input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="819265536" step="1"> |
| 4194 | 4351 | <input class="neo-range-input" type="number" min="0" max="819265536" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter"> |
| 4195 | 4352 | </div> |
| 4196 | 4353 | |
| 4197 | 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 | 4800 | <input id="never_resize_avatars" type="checkbox" /> |
| 4644 | 4801 | <small data-i18n="Never resize avatars">Never resize avatars</small> |
| 4645 | 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 | 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 | 4808 | <input id="show_card_avatar_urls" type="checkbox" /> |
| 4648 | 4809 | <small data-i18n="Show avatar filenames">Show avatar filenames</small> |
| @@ -4656,13 +4817,16 @@ | ||
| 4656 | 4817 | |
| 4657 | 4818 | <div name="MiscellaneousToggles"> |
| 4658 | 4819 | <h4><span data-i18n="Miscellaneous">Miscellaneous</span></h4> |
| 4659 | 4820 | <div class="flex-container flexGap2"> |
| 4660 | 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 | 4822 | <small data-i18n="Reload Chat">Reload Chat</small> |
| 4662 | 4823 | </div> |
| 4663 | 4824 | <div id="debug_menu" class="menu_button whitespacenowrap"> |
| 4664 | 4825 | <small data-i18n="Debug Menu">Debug Menu</small> |
| 4665 | 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 | 4830 | </div> |
| 4667 | 4831 | <label class="checkbox_label flexWrap" for="smooth_streaming"> |
| 4668 | 4832 | <input id="smooth_streaming" type="checkbox" /> |
| @@ -4942,6 +5106,14 @@ | ||
| 4942 | 5106 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| 4943 | 5107 | </div> |
| 4944 | 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 | 5117 | <label class="checkbox_label" for="stscript_autocomplete_autoHide"> |
| 4946 | 5118 | <input id="stscript_autocomplete_autoHide" type="checkbox" /> |
| 4947 | 5119 | <small data-i18n="Automatically hide details"> |
| @@ -5348,10 +5520,12 @@ | ||
| 5348 | 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 | 5521 | </div> |
| 5350 | 5522 | <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p"> |
| 5351 | 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"></div> |
| 5524 | + </div> | |
| 5525 | + <hr class="wide100p"> | |
| 5352 | 5526 | </div> |
| 5353 | 5527 | </div> |
| 5354 | - <hr> | |
| 5528 | + | |
| 5355 | 5529 | <!-- this div structure must be preserved until group peeking can adjust --> |
| 5356 | 5530 | <div id="rm_PinAndTabs"> |
| 5357 | 5531 | <div id="right-nav-panel-tabs" class=""> |
| @@ -5376,6 +5550,8 @@ | ||
| 5376 | 5550 | </div> |
| 5377 | 5551 | </div> |
| 5378 | 5552 | <!-- end group peeking cope structure--> |
| 5553 | + | |
| 5554 | + <div class="scrollableInner"> | |
| 5379 | 5555 | <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;"> |
| 5380 | 5556 | <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| 5381 | 5557 | <div id="avatar-and-name-block"> |
| @@ -5711,6 +5887,7 @@ | ||
| 5711 | 5887 | </div> |
| 5712 | 5888 | <div id="rm_print_characters_block" class="flexFlowColumn"></div> |
| 5713 | 5889 | </div> |
| 5890 | + </div> | |
| 5714 | 5891 | </nav> |
| 5715 | 5892 | </div> |
| 5716 | 5893 | </div> |
| @@ -6086,79 +6263,7 @@ | ||
| 6086 | 6263 | </div> |
| 6087 | 6264 | </div> |
| 6088 | 6265 | <div id="entry_edit_template" class="template_element"> |
| 6089 | 6266 | <div class="world_entryworld_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">☰</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: 🔵 Constant 🟢 Normal 🔗 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 ↓Char: After Character Definitions ↑EM: Before Example Messages ↓EM: After Example Messages ↑AN: Before Author's Note ↓AN: After Author's Note @D ⚙️: at Depth (System) @D 👤: at Depth (User) @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 | 6267 | <div class="flex-container wide100p alignitemscenter"> |
| 6163 | 6268 | <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter"> |
| 6164 | 6269 | <div class="world_entry_form_control keyprimary flex1"> |
| @@ -6255,13 +6360,13 @@ | ||
| 6255 | 6360 | </span> |
| 6256 | 6361 | <div> |
| 6257 | 6362 | <label class="checkbox flex-container alignitemscenter flexNoGap"> |
| 6258 | 6363 | <input type="checkbox" name="exclude_recursionexcludeRecursion" /> |
| 6259 | 6364 | <span data-i18n="Non-recursable (will not be activated by another)"> |
| 6260 | 6365 | Non-recursable (will not be activated by another) |
| 6261 | 6366 | </span> |
| 6262 | 6367 | </label> |
| 6263 | 6368 | <label class="checkbox flex-container alignitemscenter flexNoGap"> |
| 6264 | 6369 | <input type="checkbox" name="prevent_recursionpreventRecursion" /> |
| 6265 | 6370 | <span data-i18n="Prevent further recursion (this entry will not activate others)"> |
| 6266 | 6371 | Prevent further recursion (will not activate others) |
| 6267 | 6372 | </span> |
| @@ -6450,6 +6555,81 @@ | ||
| 6450 | 6555 | </div> |
| 6451 | 6556 | </div> |
| 6452 | 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">☰</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: 🔵 Constant 🟢 Normal 🔗 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 ↓Char: After Character Definitions ↑EM: Before Example Messages ↓EM: After Example Messages ↑AN: Before Author's Note ↓AN: After Author's Note @D ⚙️: at Depth (System) @D 👤: at Depth (User) @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 | 6633 | </div> |
| 6454 | 6634 | </form> |
| 6455 | 6635 | </div> |
| @@ -6587,7 +6767,6 @@ | ||
| 6587 | 6767 | <span id="completion_prompt_manager_popup_entry_source"></span> |
| 6588 | 6768 | </div> |
| 6589 | 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 | 6770 | </div> |
| 6592 | 6771 | <div class="completion_prompt_manager_popup_entry_form_footer"> |
| 6593 | 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 | 7526 | </div> |
| 7348 | 7527 | </div> |
| 7349 | 7528 | |
| 7529 | + <!-- Area for hidden data lists for secrets autosuggests --> | |
| 7530 | + <div id="secrets_datalists" class="displayNone"></div> | |
| 7531 | + | |
| 7350 | 7532 | <!-- Script includes --> |
| 7351 | 7533 | <script src="lib/polyfill.js"></script> |
| 7352 | 7534 | <script src="lib/jquery-3.5.1.min.js"></script> |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "رابط PNG المباشر (راجع", |
| 1380 | 1380 | "char_import_7": "للمضيفين المسموح بهم)", |
| 1381 | 1381 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", |
| 1382 | + "char_import_9": "شخصية Soulkyn (رابط مباشر)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.", |
| 1384 | 1385 | "Export for character": "تصدير للشخصية", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Direkter PNG-Link (siehe", |
| 1380 | 1380 | "char_import_7": "für erlaubte Hosts)", |
| 1381 | 1381 | "char_import_8": "RisuRealm-Charakter (Direktlink)", |
| 1382 | + "char_import_9": "Soulkyn-Charakter (Direktlink)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.", |
| 1384 | 1385 | "Export for character": "Export für Zeichen", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Enlace PNG directo (consulte", |
| 1380 | 1380 | "char_import_7": "para hosts permitidos)", |
| 1381 | 1381 | "char_import_8": "Personaje RisuRealm (Enlace directo)", |
| 1382 | + "char_import_9": "Personaje Soulkyn (Enlace directo)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.", |
| 1384 | 1385 | "Export for character": "Exportar para personaje", |
| @@ -1301,6 +1301,7 @@ | ||
| 1301 | 1301 | "char_import_6": "Lien PNG direct (voir", |
| 1302 | 1302 | "char_import_7": "pour les hôtes autorisés)", |
| 1303 | 1303 | "char_import_8": "Personnage de RisuRealm (lien direct)", |
| 1304 | + "char_import_9": "Personnage de Soulkyn (lien direct)", | |
| 1304 | 1305 | "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.", |
| 1305 | 1306 | "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.", |
| 1306 | 1307 | "Export for character": "Exportation pour le personnage", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Beinn PNG hlekkur (sjá", |
| 1380 | 1380 | "char_import_7": "fyrir leyfilega gestgjafa)", |
| 1381 | 1381 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", |
| 1382 | + "char_import_9": "Soulkyn karakter (beinn hlekkur)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.", |
| 1384 | 1385 | "Export for character": "Flytja út fyrir persónu", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Collegamento PNG diretto (fare riferimento a", |
| 1380 | 1380 | "char_import_7": "per gli host consentiti)", |
| 1381 | 1381 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", |
| 1382 | + "char_import_9": "Personaggio Soulkyn (collegamento diretto)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.", |
| 1384 | 1385 | "Export for character": "Esporta per carattere", |
| @@ -1381,6 +1381,7 @@ | ||
| 1381 | 1381 | "char_import_6": "直接PNGリンク(参照", |
| 1382 | 1382 | "char_import_7": "許可されたホストの場合)", |
| 1383 | 1383 | "char_import_8": "RisuRealm キャラクター (直接リンク)", |
| 1384 | + "char_import_9": "Soulkyn キャラクター (直接リンク)", | |
| 1384 | 1385 | "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。", |
| 1385 | 1386 | "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。", |
| 1386 | 1387 | "Export for character": "キャラクターのエクスポート", |
| @@ -1398,6 +1398,7 @@ | ||
| 1398 | 1398 | "char_import_6": "직접 PNG 링크(참조", |
| 1399 | 1399 | "char_import_7": "허용된 호스트의 경우)", |
| 1400 | 1400 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", |
| 1401 | + "char_import_9": "Soulkyn 캐릭터 (직접링크)", | |
| 1401 | 1402 | "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.", |
| 1402 | 1403 | "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.", |
| 1403 | 1404 | "Export for character": "캐릭터 내보내기", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Directe PNG-link (zie", |
| 1380 | 1380 | "char_import_7": "voor toegestane hosts)", |
| 1381 | 1381 | "char_import_8": "RisuRealm-personage (directe link)", |
| 1382 | + "char_import_9": "Soulkyn-personage (directe link)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.", |
| 1384 | 1385 | "Export for character": "Exporteren voor karakter", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Link PNG direto (consulte", |
| 1380 | 1380 | "char_import_7": "para hosts permitidos)", |
| 1381 | 1381 | "char_import_8": "Personagem RisuRealm (link direto)", |
| 1382 | + "char_import_9": "Personagem Soulkyn (link direto)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Suporta importação de vários caracteres.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.", |
| 1384 | 1385 | "Export for character": "Exportar para personagem", |
| @@ -105,7 +105,7 @@ | ||
| 105 | 105 | "Adjust context size to worker capabilities": "Подстраивать размер контекста под возможности рабочих машин", |
| 106 | 106 | "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин", |
| 107 | 107 | "API key": "API-ключ", |
| 108 | 108 | "Tabby API key": "Ключ от Tabby API-ключ", |
| 109 | 109 | "Get it here:": "Получите здесь:", |
| 110 | 110 | "Register": "Зарегистрироваться", |
| 111 | 111 | "TogetherAI Model": "Модель TogetherAI", |
| @@ -275,7 +275,7 @@ | ||
| 275 | 275 | "Chat History": "История чатов", |
| 276 | 276 | "Content": "Содержание", |
| 277 | 277 | "Selective": "Выборочно", |
| 278 | 278 | "Disable": "ОтключеноОткл.", |
| 279 | 279 | "Back to parent chat": "Вернуться в основной чат", |
| 280 | 280 | "Convert to group": "Сделать групповым", |
| 281 | 281 | "Start new chat": "Начать новый чат", |
| @@ -509,7 +509,7 @@ | ||
| 509 | 509 | "Restore default prompt": "Восстановить станндартный промпт", |
| 510 | 510 | "New preset": "Новый пресет", |
| 511 | 511 | "Delete preset": "Удалить пресет", |
| 512 | 512 | "API Connections": "СоединенияСоединение с API", |
| 513 | 513 | "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.", |
| 514 | 514 | "Clear your API key": "Стереть ключ от API", |
| 515 | 515 | "Refresh models": "Обновить модели", |
| @@ -965,6 +965,7 @@ | ||
| 965 | 965 | "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)", |
| 966 | 966 | "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в", |
| 967 | 967 | "char_import_7": ")", |
| 968 | + "char_import_9": "Персонаж с Soulkyn (прямая ссылка)", | |
| 968 | 969 | "Grammar String": "Грамматика", |
| 969 | 970 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", |
| 970 | 971 | "Account": "Аккаунт", |
| @@ -2418,5 +2419,144 @@ | ||
| 2418 | 2419 | "Update enabled": "Обновить включенные", |
| 2419 | 2420 | "Could not connect to API": "Не удалось подключиться к API", |
| 2420 | 2421 | "Connected to API": "Соединение с API установлено", |
| 2421 | 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 | } |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Пряме посилання на PNG (див", |
| 1380 | 1380 | "char_import_7": "для дозволених хостів)", |
| 1381 | 1381 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", |
| 1382 | + "char_import_9": "Персонаж Soulkyn (пряме посилання)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Підтримується імпорт кількох символів.", |
| 1383 | 1384 | "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.", |
| 1384 | 1385 | "Export for character": "Експорт для персонажа", |
| @@ -1379,6 +1379,7 @@ | ||
| 1379 | 1379 | "char_import_6": "Nhập PNG trực tiếp (tham khảo", |
| 1380 | 1380 | "char_import_7": "đối với các máy chủ được phép)", |
| 1381 | 1381 | "char_import_8": "RisuRealm (URL trực tiếp)", |
| 1382 | + "char_import_9": "Soulkyn (URL trực tiếp)", | |
| 1382 | 1383 | "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.", |
| 1383 | 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 | 1385 | "Export for character": "Xuất cho nhân vật", |
| @@ -670,7 +670,7 @@ | ||
| 670 | 670 | "Square": "正方形", |
| 671 | 671 | "Rectangle": "矩形", |
| 672 | 672 | "Chat Style:": "聊天风格:", |
| 673 | 673 | "Flat": "扁平\n气泡\n文档", |
| 674 | 674 | "Bubbles": "气泡", |
| 675 | 675 | "Document": "文档", |
| 676 | 676 | "Specify colors for your theme.": "指定您的主题的颜色。", |
| @@ -1893,6 +1893,7 @@ | ||
| 1893 | 1893 | "char_import_6": "被允许的PNG直链(请参阅", |
| 1894 | 1894 | "char_import_7": ")", |
| 1895 | 1895 | "char_import_8": "RisuRealm 角色(直链)", |
| 1896 | + "char_import_9": "Soulkyn 角色(直链)", | |
| 1896 | 1897 | "Supports importing multiple characters.": "支持导入多个角色。", |
| 1897 | 1898 | "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。", |
| 1898 | 1899 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", |
| @@ -19,7 +19,7 @@ | ||
| 19 | 19 | "Delete the preset": "刪除預設設定檔", |
| 20 | 20 | "novelaipresets": "NovelAI 預設設定檔", |
| 21 | 21 | "Default": "預設", |
| 22 | 22 | "openaipresets": "OpenAI 預設設定檔聊天補全預設設定檔", |
| 23 | 23 | "Text Completion presets": "文字補全預設設定檔", |
| 24 | 24 | "AI Module": "AI 模組", |
| 25 | 25 | "Changes the style of the generated text.": "變更生成文字的樣式。", |
| @@ -506,9 +506,9 @@ | ||
| 506 | 506 | "Global Lore First": "全域知識書優先", |
| 507 | 507 | "Entries can activate other entries by mentioning their keywords": "條目可以透過提及其關鍵字來啟用其他條目", |
| 508 | 508 | "Recursive Scan": "遞迴掃描", |
| 509 | 509 | "Lookup for the entry keys in the context will respect the case": "在上下文中查詢條目鍵將區分大小寫在上下文中查詢條目關鍵字將區分大小寫", |
| 510 | 510 | "Case Sensitive": "區分大小寫", |
| 511 | 511 | "If the entry key consists of only one word, it would not be matched as part of other words": "如果條目鍵僅包含一個詞如果條目關鍵字僅包含一個詞,則不會作為其他詞的一部分進行配對", |
| 512 | 512 | "Match Whole Words": "完全配對", |
| 513 | 513 | "Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "只有符合最多鍵值數量的條目將被選中進行包含群組過濾", |
| 514 | 514 | "Use Group Scoring": "使用群組評分", |
| @@ -615,9 +615,9 @@ | ||
| 615 | 615 | "Compact Input Area (Mobile)": "緊湊的聊天訊息輸入欄(行動版)", |
| 616 | 616 | "In the Character Management panel, show quick selection buttons for favorited characters": "在角色管理面板中,顯示加入到最愛角色的快速選擇按鈕。", |
| 617 | 617 | "Characters Hotswap": "角色卡快捷選單", |
| 618 | 618 | "Enable magnification for zoomed avatar display.": "啟用放大顯示頭像啟用放大功能檢視頭像。", |
| 619 | 619 | "Avatar Hover Magnification": "頭像懸停時的放大倍數", |
| 620 | 620 | "Enables a magnification effect on hover when you display the zoomed avatar after clicking an avatar's image in chat.": "當你在聊天中點選頭像的圖片後當您點選訊息中的角色頭像後,這會啟用滑鼠懸停時的放大效果將啟用滑鼠懸停時的放大效果。", |
| 621 | 621 | "Show tagged character folders in the character list": "在角色列表中顯示標籤角色資料夾。", |
| 622 | 622 | "Tags as Folders": "標籤作為資料夾", |
| 623 | 623 | "Tags_as_Folders_desc": "標籤必須在「標籤管理」選單中標記為資料夾才可適用。點選這裡開啟。", |
| @@ -769,7 +769,7 @@ | ||
| 769 | 769 | "Name": "名稱", |
| 770 | 770 | "Enter your name": "輸入您的名字", |
| 771 | 771 | "Click to set a new User Name": "設定新的使用者名稱", |
| 772 | 772 | "Click to lock your selected persona to the current chat. Click again to remove the lock.": "鎖定目前所選的使用者角色至本次聊天綁定目前所選的使用者角色至本次聊天。再次點選將移除鎖定再次點選將移除綁定。", |
| 773 | 773 | "Click to set user name for all messages": "設定所有訊息的使用者名稱", |
| 774 | 774 | "Persona Description": "使用者角色描述", |
| 775 | 775 | "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "範例:[{{user}} 是一個 28 歲的羅馬尼亞貓娘。]", |
| @@ -903,8 +903,8 @@ | ||
| 903 | 903 | "@ Depth": "@ 深度", |
| 904 | 904 | "Role": "角色", |
| 905 | 905 | "Talkativeness": "健談度", |
| 906 | 906 | "How often the character speaks in group chats!": "角色在群組聊天中說話的頻率角色在群組聊天中的發言頻率!", |
| 907 | 907 | "How often the character speaks in": "角色說話的頻率角色發言頻率", |
| 908 | 908 | "group chats!": "群組聊天!", |
| 909 | 909 | "Shy": "寡言", |
| 910 | 910 | "Normal": "正常", |
| @@ -970,10 +970,10 @@ | ||
| 970 | 970 | "Comma separated list": "逗號分隔列表", |
| 971 | 971 | "Switch to plaintext mode": "切換到純文字模式", |
| 972 | 972 | "Logic": "邏輯", |
| 973 | 973 | "AND ANY": "AND 任何且「包含任一」", |
| 974 | 974 | "AND ALL": "AND 所有且「包含全部」", |
| 975 | 975 | "NOT ALL": "NOT 所有且「未完全包含」", |
| 976 | 976 | "NOT ANY": "NOT 任何且「完全不含」", |
| 977 | 977 | "(ignored if empty)": "(如果為空則忽略)", |
| 978 | 978 | "Optional Filter": "選填過濾器", |
| 979 | 979 | "Keywords or Regexes (ignored if empty)": "關鍵字或正規表示式(如果為空則忽略)", |
| @@ -993,11 +993,11 @@ | ||
| 993 | 993 | "Filter to Character(s)": "角色篩選", |
| 994 | 994 | "Character Exclusion": "角色排除", |
| 995 | 995 | "-- Characters not found --": "-- 未找到角色 --", |
| 996 | 996 | "Inclusion Group": "包含的群組納入群組", |
| 997 | 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文件r詳見 SillyTavern 官方文件:世界資訊——包容性集團World Info - Inclusion Group", |
| 998 | 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如果有多個優先順序r若有多個條目同時設為優先,則選擇將選擇「順序(Order)」最高的一個數值最高的條目。", |
| 999 | 999 | "Only one entry with the same label will be activated": "僅會啟用具有相同標籤的一個條目僅啟用一個具有相同標籤的條目", |
| 1000 | 1000 | "A relative likelihood of entry activation within the group": "群組內條目啟用的相對可能性此條目在群組中被選中的相對可能性", |
| 1001 | 1001 | "Group Weight": "群組權重", |
| 1002 | 1002 | "Selective": "選擇性", |
| 1003 | 1003 | "Use Probability": "使用機率", |
| @@ -1134,7 +1134,7 @@ | ||
| 1134 | 1134 | "Manage chat files": "管理聊天檔案", |
| 1135 | 1135 | "Delete messages": "刪除訊息", |
| 1136 | 1136 | "Regenerate": "重新生成", |
| 1137 | 1137 | "Ask AI to write your message for you": "請由 AI 為您撰寫訊息扮演您的角色撰寫訊息", |
| 1138 | 1138 | "Impersonate": "AI 扮演使用者", |
| 1139 | 1139 | "Continue": "繼續", |
| 1140 | 1140 | "Bind user name to that avatar": "將使用者名稱附加到該頭像", |
| @@ -1384,6 +1384,7 @@ | ||
| 1384 | 1384 | "char_import_6": "直接 PNG 連結(請參閱", |
| 1385 | 1385 | "char_import_7": "對於允許的主機)", |
| 1386 | 1386 | "char_import_8": "RisuRealm 角色(直接連結)", |
| 1387 | + "char_import_9": "Soulkyn 角色(直接連結)", | |
| 1387 | 1388 | "Supports importing multiple characters.": "支援匯入多個字元。", |
| 1388 | 1389 | "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。", |
| 1389 | 1390 | "Export for character": "匯出字元", |
| @@ -2382,7 +2383,7 @@ | ||
| 2382 | 2383 | "Do you want to remove these fields before exporting?": "是否要在匯出前移除這些欄位?", |
| 2383 | 2384 | "Delete the preset? This action is irreversible and your current settings will be overwritten.": "確定刪除此預設設定檔?刪除後無法復原,且此設定將被覆蓋。", |
| 2384 | 2385 | "Update all": "全部更新", |
| 2385 | 2386 | "Automatically chooses an alternative provider if chosen providers can't serve your request.": "當所選提供者無法滿足您的請求時當所選供應商無法滿足您的請求時,自動選擇替代提供者自動選擇替代供應商。", |
| 2386 | 2387 | "Use extension settings": "使用擴充功能設定", |
| 2387 | 2388 | "To use instruct formatting, switch to OpenRouter under Text Completion API.": "若要使用指令格式,請在文字補全 API 下切換至 OpenRouter。", |
| 2388 | 2389 | "Automatically 'continue' a response if the model stopped before reaching a certain amount of tokens.": "如果模型在達到一定數量的符元前停止,則自動繼續生成回應。", |
| @@ -2456,7 +2457,7 @@ | ||
| 2456 | 2457 | "openai_reasoning_effort_low": "低", |
| 2457 | 2458 | "openai_reasoning_effort_medium": "中", |
| 2458 | 2459 | "openai_reasoning_effort_high": "高", |
| 2459 | 2460 | "Reasoning": "推理 Reasoning", |
| 2460 | 2461 | "reasoning_auto_parse": "自動解析主要內容中推理區塊,需定義且不為空的前綴與後綴欄位。", |
| 2461 | 2462 | "Auto-Parse": "自動解析", |
| 2462 | 2463 | "reasoning_auto_expand": "自動展開推理區塊。", |
| @@ -2556,7 +2557,7 @@ | ||
| 2556 | 2557 | "Swiping a message will automatically trigger a re-summarization if it has already been summarized.": "滑動訊息後若已進行摘要,將自動觸發重新摘要。", |
| 2557 | 2558 | "Re-summarize on Swipe": "滑動後重新摘要", |
| 2558 | 2559 | "Block chat input while summarizing.": "在摘要進行時暫時禁用聊天訊息輸入。", |
| 2559 | 2560 | "Block Chat": "訊息輸入鎖定鎖定訊息輸入", |
| 2560 | 2561 | "Whether to use messages and/or summaries as context for summarization. You must use {{history}} in the summary prompt.": "決定是否在摘要中使用訊息及/或過往摘要作為背景資訊。需於摘要提示詞中,使用 {{history}}。", |
| 2561 | 2562 | "Message History": "訊息歷史", |
| 2562 | 2563 | "Messages": "僅訊息", |
| @@ -2630,7 +2631,7 @@ | ||
| 2630 | 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 | 2632 | "Attach a file or image to a current chat.": "附加檔案或圖片至目前的聊天。", |
| 2632 | 2633 | "Change Persona Image": "變更使用者角色頭像", |
| 2633 | 2634 | "Click to lock your selected persona to the current character. Click again to remove the lock.": "點選以將所選使用者角色鎖定目前聊天的角色點選以將所選使用者角色與目前聊天的角色綁定。再次點選以解除鎖定再次點選可解除綁定。", |
| 2634 | 2635 | "Click to select this as default persona for the new chats. Click again to remove it.": "點選以將此設為新聊天的預設使用者角色,再次點選可取消設定。", |
| 2635 | 2636 | "Connected Personas": "已連結的使用者角色", |
| 2636 | 2637 | "Create a new prompt": "新增新提示詞", |
| @@ -2639,21 +2640,21 @@ | ||
| 2639 | 2640 | "Duplicate Persona": "複製使用者角色", |
| 2640 | 2641 | "Expand and zoom": "展開並縮放", |
| 2641 | 2642 | "Move Entry to Another Lorebook": "將條目移動到其他知識書", |
| 2642 | 2643 | "Persona is locked to the current character": "使用者角色已鎖定至目前角色使用者角色已綁定至目前角色", |
| 2643 | 2644 | "Persona is locked to the current chat": "使用者角色已鎖定至目前聊天使用者角色已綁定至目前聊天", |
| 2644 | 2645 | "Rename a prompt": "重新命名提示詞", |
| 2645 | 2646 | "Rename Persona": "重新命名使用者角色", |
| 2646 | 2647 | "Select your current Reasoning Template": "選擇你目前使用的推理範本", |
| 2647 | 2648 | "Toggles sprite scaling for character sprites.": "切換角色立繪的縮放比例", |
| 2648 | 2649 | "When multiple personas are connected to a character, a popup will appear to select which one to use.": "當多個使用者角色連接至一個角色時,將顯示選擇彈窗。", |
| 2649 | 2650 | "When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.": "使用 LLM 或 WebLLM 分類器時,僅顯示並使用已指派的表情立繪。", |
| 2650 | 2651 | "Whenever a persona is selected, it will be locked and automatically selected when the chat is opened.": "若已選擇使用者角色,將在開啟聊天時自動進行鎖定將在開啟聊天時自動進行綁定。", |
| 2651 | 2652 | "Additional Matching Sources": "額外匹配來源", |
| 2652 | 2653 | "All Classes": "所有類別", |
| 2653 | 2654 | "Allow multiple persona connections per character": "允許每個角色連接多個使用者角色", |
| 2654 | 2655 | "Allows the model to return image attachments.": "允許模型返回圖片附件。", |
| 2655 | 2656 | "Auto-Hide Sprite Settings": "自動隱藏立繪設定", |
| 2656 | 2657 | "Auto-lock a chosen persona to the chat": "自動鎖定使用者角色至聊天中自動綁定使用者角色至聊天中", |
| 2657 | 2658 | "Character": "角色", |
| 2658 | 2659 | "Character Personality": "角色個性", |
| 2659 | 2660 | "Character Tint/Share World Tint With Characters requires Prome to be enabled.": "角色著色/與角色共享世界著色功能需啟用 Prome。", |
| @@ -2703,7 +2704,7 @@ | ||
| 2703 | 2704 | "Scan depth cannot be negative": "掃描深度不能為負值", |
| 2704 | 2705 | "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}", |
| 2705 | 2706 | "Reasoning Template": "推理範本", |
| 2706 | 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 | 2708 | "Branch or tag name (optional)": "分支/標籤名稱(可留空)", |
| 2708 | 2709 | "Update enabled": "僅更新啟用項目", |
| 2709 | 2710 | "Sort: Loading Order": "排序:載入順序", |
| @@ -2742,5 +2743,126 @@ | ||
| 2742 | 2743 | "You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "角色或對話已被刪除。系統為安全考量導回此頁,請重新選擇角色。", |
| 2743 | 2744 | "Could not connect to API": "無法連線至 API", |
| 2744 | 2745 | "Connected to API": "已成功連線至 API", |
| 2745 | 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 | } |
| @@ -10,7 +10,6 @@ import { | ||
| 10 | 10 | SVGInject, |
| 11 | 11 | Popper, |
| 12 | 12 | initLibraryShims, |
| 13 | - slideToggle, | |
| 14 | 13 | default as libs, |
| 15 | 14 | } from './lib.js'; |
| 16 | 15 | |
| @@ -209,6 +208,7 @@ import { | ||
| 209 | 208 | } from './scripts/tags.js'; |
| 210 | 209 | import { |
| 211 | 210 | SECRET_KEYS, |
| 211 | + initSecrets, | |
| 212 | 212 | readSecretState, |
| 213 | 213 | secret_state, |
| 214 | 214 | writeSecret, |
| @@ -250,7 +250,20 @@ import { | ||
| 250 | 250 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; |
| 251 | 251 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 252 | 252 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 253 | -import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; | |
| 253 | +import { | |
| 254 | + loadFeatherlessModels, | |
| 255 | + loadMancerModels, | |
| 256 | + loadOllamaModels, | |
| 257 | + loadTogetherAIModels, | |
| 258 | + loadInfermaticAIModels, | |
| 259 | + loadOpenRouterModels, | |
| 260 | + loadVllmModels, | |
| 261 | + loadAphroditeModels, | |
| 262 | + loadDreamGenModels, | |
| 263 | + initTextGenModels, | |
| 264 | + loadTabbyModels, | |
| 265 | + loadGenericModels, | |
| 266 | +} from './scripts/textgen-models.js'; | |
| 254 | 267 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js'; |
| 255 | 268 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 256 | 269 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| @@ -283,6 +296,7 @@ import { getContext } from './scripts/st-context.js'; | ||
| 283 | 296 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; |
| 284 | 297 | import { accountStorage } from './scripts/util/AccountStorage.js'; |
| 285 | 298 | import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js'; |
| 299 | +import { initDataMaid } from './scripts/data-maid.js'; | |
| 286 | 300 | |
| 287 | 301 | // API OBJECT FOR EXTERNAL WIRING |
| 288 | 302 | globalThis.SillyTavern = { |
| @@ -482,6 +496,7 @@ export const event_types = { | ||
| 482 | 496 | GENERATION_STARTED: 'generation_started', |
| 483 | 497 | GENERATION_STOPPED: 'generation_stopped', |
| 484 | 498 | GENERATION_ENDED: 'generation_ended', |
| 499 | + SD_PROMPT_PROCESSING: 'sd_prompt_processing', | |
| 485 | 500 | EXTENSIONS_FIRST_LOAD: 'extensions_first_load', |
| 486 | 501 | EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded', |
| 487 | 502 | SETTINGS_LOADED: 'settings_loaded', |
| @@ -541,6 +556,10 @@ export const event_types = { | ||
| 541 | 556 | TOOL_CALLS_PERFORMED: 'tool_calls_performed', |
| 542 | 557 | TOOL_CALLS_RENDERED: 'tool_calls_rendered', |
| 543 | 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 | }; |
| 545 | 564 | |
| 546 | 565 | export const eventSource = new EventEmitter([event_types.APP_READY]); |
| @@ -672,16 +691,8 @@ export const extension_prompt_roles = { | ||
| 672 | 691 | |
| 673 | 692 | export const MAX_INJECTION_DEPTH = 10000; |
| 674 | 693 | |
| 675 | -const SAFETY_CHAT = [ | |
| 694 | +// Initialized in getSystemMessages() | |
| 676 | - { | |
| 695 | +const SAFETY_CHAT = []; | |
| 677 | - name: systemUserName, | |
| 678 | - force_avatar: system_avatar, | |
| 679 | - is_system: true, | |
| 680 | - is_user: false, | |
| 681 | - create_date: 0, | |
| 682 | - mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!', | |
| 683 | - }, | |
| 684 | -]; | |
| 685 | 696 | |
| 686 | 697 | export let system_messages = {}; |
| 687 | 698 | |
| @@ -768,22 +779,17 @@ async function getSystemMessages() { | ||
| 768 | 779 | }, |
| 769 | 780 | }, |
| 770 | 781 | }; |
| 771 | -} | |
| 772 | - | |
| 773 | -// Register configuration migrations | |
| 774 | -registerPromptManagerMigration(); | |
| 775 | 782 | |
| 776 | -$(document).ajaxError(function myErrorHandler(_, xhr) { | |
| 783 | + const safetyMessage = { | |
| 777 | - // Cohee: CSRF doesn't error out in multiple tabs anymore, so this is unnecessary | |
| 784 | + name: systemUserName, | |
| 778 | - /* | |
| 785 | + force_avatar: system_avatar, | |
| 779 | - if (xhr.status == 403) { | |
| 786 | + is_system: true, | |
| 780 | - toastr.warning( | |
| 787 | + is_user: false, | |
| 781 | - 'doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.', | |
| 788 | + create_date: 0, | |
| 782 | - 'Looks like you\'ve opened SillyTavern in another browser tab', | |
| 789 | + mes: t`You deleted a character/chat and arrived back here for safety reasons! Pick another character!`, | |
| 783 | - { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }, | |
| 790 | + }; | |
| 784 | - ); | |
| 791 | + SAFETY_CHAT.splice(0, SAFETY_CHAT.length, safetyMessage); | |
| 785 | - } */ | |
| 792 | +} | |
| 786 | -}); | |
| 787 | 793 | |
| 788 | 794 | async function getClientVersion() { |
| 789 | 795 | try { |
| @@ -865,7 +871,8 @@ export let create_save = { | ||
| 865 | 871 | creator: '', |
| 866 | 872 | personality: '', |
| 867 | 873 | first_message: '', |
| 868 | - avatar: '', | |
| 874 | + /** @type {FileList|null} */ | |
| 875 | + avatar: null, | |
| 869 | 876 | scenario: '', |
| 870 | 877 | mes_example: '', |
| 871 | 878 | world: '', |
| @@ -982,11 +989,14 @@ async function firstLoadInit() { | ||
| 982 | 989 | } |
| 983 | 990 | |
| 984 | 991 | showLoader(); |
| 992 | + registerPromptManagerMigration(); | |
| 993 | + initStandaloneMode(); | |
| 985 | 994 | initLibraryShims(); |
| 986 | 995 | addShowdownPatch(showdown); |
| 987 | 996 | reloadMarkdownProcessor(); |
| 988 | 997 | applyBrowserFixes(); |
| 989 | 998 | await getClientVersion(); |
| 999 | + await initSecrets(); | |
| 990 | 1000 | await readSecretState(); |
| 991 | 1001 | await initLocales(); |
| 992 | 1002 | initChatUtilities(); |
| @@ -1026,6 +1036,7 @@ async function firstLoadInit() { | ||
| 1026 | 1036 | initWelcomeScreen(); |
| 1027 | 1037 | await initScrapers(); |
| 1028 | 1038 | initCustomSelectedSamplers(); |
| 1039 | + initDataMaid(); | |
| 1029 | 1040 | addDebugFunctions(); |
| 1030 | 1041 | doDailyExtensionUpdatesCheck(); |
| 1031 | 1042 | await hideLoader(); |
| @@ -1039,6 +1050,13 @@ async function fixViewport() { | ||
| 1039 | 1050 | document.body.style.position = ''; |
| 1040 | 1051 | } |
| 1041 | 1052 | |
| 1053 | +function initStandaloneMode() { | |
| 1054 | + const isPwaMode = window.matchMedia('(display-mode: standalone)').matches; | |
| 1055 | + if (isPwaMode) { | |
| 1056 | + $('body').addClass('PWA'); | |
| 1057 | + } | |
| 1058 | +} | |
| 1059 | + | |
| 1042 | 1060 | function cancelStatusCheck(reason = 'Manually cancelled status check') { |
| 1043 | 1061 | abortStatusCheck?.abort(new AbortReason(reason)); |
| 1044 | 1062 | abortStatusCheck = new AbortController(); |
| @@ -1395,7 +1413,6 @@ export function resultCheckStatus() { | ||
| 1395 | 1413 | stopStatusLoading(); |
| 1396 | 1414 | } |
| 1397 | 1415 | |
| 1398 | - | |
| 1399 | 1416 | /** |
| 1400 | 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 | } |
| 1882 | 1899 | |
| 1900 | +/** | |
| 1901 | + * Deletes a character chat by its name. | |
| 1902 | + * @param {string} characterId Character ID to delete chat for | |
| 1903 | + * @param {string} fileName Name of the chat file to delete (without .jsonl extension) | |
| 1904 | + * @returns {Promise<void>} A promise that resolves when the chat is deleted. | |
| 1905 | + */ | |
| 1906 | +export async function deleteCharacterChatByName(characterId, fileName) { | |
| 1907 | + // Make sure all the data is loaded. | |
| 1908 | + await unshallowCharacter(characterId); | |
| 1909 | + | |
| 1910 | + /** @type {import('./scripts/char-data.js').v1CharData} */ | |
| 1911 | + const character = characters[characterId]; | |
| 1912 | + if (!character) { | |
| 1913 | + console.warn(`Character with ID ${characterId} not found.`); | |
| 1914 | + return; | |
| 1915 | + } | |
| 1916 | + | |
| 1917 | + const response = await fetch('/api/chats/delete', { | |
| 1918 | + method: 'POST', | |
| 1919 | + headers: getRequestHeaders(), | |
| 1920 | + body: JSON.stringify({ | |
| 1921 | + chatfile: `${fileName}.jsonl`, | |
| 1922 | + avatar_url: character.avatar, | |
| 1923 | + }), | |
| 1924 | + }); | |
| 1925 | + | |
| 1926 | + if (!response.ok) { | |
| 1927 | + console.error('Failed to delete chat for character.'); | |
| 1928 | + return; | |
| 1929 | + } | |
| 1930 | + | |
| 1931 | + if (fileName === character.chat) { | |
| 1932 | + const chatsResponse = await fetch('/api/characters/chats', { | |
| 1933 | + method: 'POST', | |
| 1934 | + headers: getRequestHeaders(), | |
| 1935 | + body: JSON.stringify({ avatar_url: character.avatar }), | |
| 1936 | + }); | |
| 1937 | + const chats = Object.values(await chatsResponse.json()); | |
| 1938 | + chats.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes))); | |
| 1939 | + const newChatName = chats.length && typeof chats[0] === 'object' ? chats[0].file_name.replace('.jsonl', '') : `${character.name} - ${humanizedDateTime()}`; | |
| 1940 | + await updateRemoteChatName(characterId, newChatName); | |
| 1941 | + } | |
| 1942 | + | |
| 1943 | + await eventSource.emit(event_types.CHAT_DELETED, fileName); | |
| 1944 | +} | |
| 1945 | + | |
| 1883 | 1946 | export async function replaceCurrentChat() { |
| 1884 | 1947 | await clearChat(); |
| 1885 | 1948 | chat.length = 0; |
| @@ -2391,7 +2454,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | ||
| 2391 | 2454 | const image = messageElement.find('.mes_img'); |
| 2392 | 2455 | const text = messageElement.find('.mes_text'); |
| 2393 | 2456 | const isInline = !!mes.extra?.inline_image; |
| 2394 | - image.off('load').on('load', function () { | |
| 2457 | + const doAdjustScroll = () => { | |
| 2395 | 2458 | if (!adjustScroll) { |
| 2396 | 2459 | return; |
| 2397 | 2460 | } |
| @@ -2399,6 +2462,16 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | ||
| 2399 | 2462 | const newChatHeight = $('#chat').prop('scrollHeight'); |
| 2400 | 2463 | const diff = newChatHeight - chatHeight; |
| 2401 | 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 | 2476 | image.attr('src', mes.extra?.image); |
| 2404 | 2477 | image.attr('title', mes.extra?.title || mes.title || ''); |
| @@ -2425,6 +2498,31 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | ||
| 2425 | 2498 | } |
| 2426 | 2499 | } |
| 2427 | 2500 | |
| 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 | 2526 | // Add file to message |
| 2429 | 2527 | if (mes.extra?.file) { |
| 2430 | 2528 | messageElement.find('.mes_file_container').remove(); |
| @@ -2646,7 +2744,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2646 | 2744 | |
| 2647 | 2745 | /** |
| 2648 | 2746 | * Returns the URL of the avatar for the given character Id. |
| 2649 | 2747 | * @param {number|string} characterId Character Id |
| 2650 | 2748 | * @returns {string} Avatar URL |
| 2651 | 2749 | */ |
| 2652 | 2750 | export function getCharacterAvatar(characterId) { |
| @@ -3520,7 +3618,7 @@ class StreamingProcessor { | ||
| 3520 | 3618 | } |
| 3521 | 3619 | |
| 3522 | 3620 | if (this.image) { |
| 3523 | 3621 | await processImageAttachment(chat[messageId], { imageUrl: this.image, parsedImage: null }); |
| 3524 | 3622 | appendMediaToMessage(chat[messageId], $(this.messageDom)); |
| 3525 | 3623 | } |
| 3526 | 3624 | |
| @@ -3678,6 +3776,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | ||
| 3678 | 3776 | if (responseLengthCustomized) { |
| 3679 | 3777 | TempResponseLength.save(api, responseLength); |
| 3680 | 3778 | } |
| 3779 | + /** @type {object|any[]} */ | |
| 3681 | 3780 | let generateData = {}; |
| 3682 | 3781 | |
| 3683 | 3782 | switch (api) { |
| @@ -6027,7 +6126,11 @@ export function extractMessageFromData(data, activeApi = null) { | ||
| 6027 | 6126 | case 'koboldhorde': |
| 6028 | 6127 | return data.text; |
| 6029 | 6128 | case 'textgenerationwebui': |
| 6030 | 6129 | return data.choices?.[0]?.text ?? data.content ?? data.response ?? ''; |
| 6130 | + ?? data.choices?.[0]?.message?.content | |
| 6131 | + ?? data.content | |
| 6132 | + ?? data.response | |
| 6133 | + ?? ''; | |
| 6031 | 6134 | case 'novel': |
| 6032 | 6135 | return data.output; |
| 6033 | 6136 | case 'openai': |
| @@ -6263,17 +6366,11 @@ export function cleanUpMessage({ getMessage, isImpersonate, isContinue, displayI | ||
| 6263 | 6366 | * Adds an image to the message. |
| 6264 | 6367 | * @param {object} message Message object |
| 6265 | 6368 | * @param {object} sources Image sources |
| 6266 | - * @param {ParsedImage} [sources.parsedImage] Parsed image | |
| 6267 | 6369 | * @param {string} [sources.imageUrl] Image URL |
| 6268 | 6370 | * |
| 6269 | 6371 | * @returns {Promise<void>} |
| 6270 | 6372 | */ |
| 6271 | 6373 | async function processImageAttachment(message, { parsedImage, imageUrl }) { |
| 6272 | - if (parsedImage?.image) { | |
| 6273 | - saveImageToMessage(parsedImage, message); | |
| 6274 | - return; | |
| 6275 | - } | |
| 6276 | - | |
| 6277 | 6374 | if (!imageUrl) { |
| 6278 | 6375 | return; |
| 6279 | 6376 | } |
| @@ -6332,8 +6429,6 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title | ||
| 6332 | 6429 | |
| 6333 | 6430 | let oldMessage = ''; |
| 6334 | 6431 | const generationFinished = new Date(); |
| 6335 | - const parsedImage = extractImageFromMessage(getMessage); | |
| 6336 | - getMessage = parsedImage.getMessage; | |
| 6337 | 6432 | if (type === 'swipe') { |
| 6338 | 6433 | oldMessage = chat[chat.length - 1]['mes']; |
| 6339 | 6434 | chat[chat.length - 1]['swipes'].length++; |
| @@ -6347,7 +6442,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title | ||
| 6347 | 6442 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 6348 | 6443 | chat[chat.length - 1]['extra']['reasoning'] = reasoning; |
| 6349 | 6444 | chat[chat.length - 1]['extra']['reasoning_duration'] = null; |
| 6350 | 6445 | await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl }); |
| 6351 | 6446 | if (power_user.message_token_count_enabled) { |
| 6352 | 6447 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 6353 | 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 | 6466 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 6372 | 6467 | chat[chat.length - 1]['extra']['reasoning'] = reasoning; |
| 6373 | 6468 | chat[chat.length - 1]['extra']['reasoning_duration'] = null; |
| 6374 | 6469 | await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl }); |
| 6375 | 6470 | if (power_user.message_token_count_enabled) { |
| 6376 | 6471 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 6377 | 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 | 6486 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 6392 | 6487 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 6393 | 6488 | chat[chat.length - 1]['extra']['reasoning'] += reasoning; |
| 6394 | 6489 | await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl }); |
| 6395 | 6490 | // We don't know if the reasoning duration extended, so we don't update it here on purpose. |
| 6396 | 6491 | if (power_user.message_token_count_enabled) { |
| 6397 | 6492 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| @@ -6437,7 +6532,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title | ||
| 6437 | 6532 | chat[chat.length - 1]['extra']['gen_id'] = group_generation_id; |
| 6438 | 6533 | } |
| 6439 | 6534 | |
| 6440 | 6535 | await processImageAttachment(chat[chat.length - 1], { parsedImage, imageUrl: imageUrl }); |
| 6441 | 6536 | const chat_id = (chat.length - 1); |
| 6442 | 6537 | |
| 6443 | 6538 | !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type); |
| @@ -6654,15 +6749,6 @@ function getGeneratingModel(mes) { | ||
| 6654 | 6749 | return model; |
| 6655 | 6750 | } |
| 6656 | 6751 | |
| 6657 | -function extractImageFromMessage(getMessage) { | |
| 6658 | - const regex = /<img src="(.*?)".*?alt="(.*?)".*?>/g; | |
| 6659 | - const results = regex.exec(getMessage); | |
| 6660 | - const image = results ? results[1] : ''; | |
| 6661 | - const title = results ? results[2] : ''; | |
| 6662 | - getMessage = getMessage.replace(regex, ''); | |
| 6663 | - return { getMessage, image, title, inline: true }; | |
| 6664 | -} | |
| 6665 | - | |
| 6666 | 6752 | /** |
| 6667 | 6753 | * 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 | } |
| 7075 | 7161 | |
| 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 | + */ | |
| 7076 | 7167 | async function read_avatar_load(input) { |
| 7077 | 7168 | if (input.files && input.files[0]) { |
| 7078 | 7169 | if (selected_button == 'create') { |
| @@ -7104,7 +7195,7 @@ async function read_avatar_load(input) { | ||
| 7104 | 7195 | await createOrEditCharacter(); |
| 7105 | 7196 | await delay(DEFAULT_SAVE_EDIT_TIMEOUT); |
| 7106 | 7197 | |
| 7107 | 7198 | const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0))); |
| 7108 | 7199 | await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), { |
| 7109 | 7200 | method: 'GET', |
| 7110 | 7201 | cache: 'no-cache', |
| @@ -7114,22 +7205,21 @@ async function read_avatar_load(input) { | ||
| 7114 | 7205 | }, |
| 7115 | 7206 | }); |
| 7116 | 7207 | |
| 7117 | 7208 | const messages = $('.mes').each(async function toArray() {; |
| 7118 | - const nameMatch = $(this).attr('ch_name') == formData.get('ch_name'); | |
| 7209 | + for (const el of messages) { | |
| 7119 | - if ($(this).attr('is_system') == 'true' && !nameMatch) { | |
| 7210 | + const $el = $(el); | |
| 7120 | - return; | |
| 7211 | + const nameMatch = $el.attr('ch_name') == formData.get('ch_name'); | |
| 7121 | - } | |
| 7212 | + if ($el.attr('is_system') == 'true' && !nameMatch) continue; | |
| 7122 | 7213 | if ($(this)el.attr('is_user') == 'true') {continue; |
| 7123 | - return; | |
| 7214 | + | |
| 7124 | - } | |
| 7125 | 7215 | if (nameMatch) { |
| 7126 | 7216 | const previewSrc = $('#avatar_load_preview').attr('src'); |
| 7127 | 7217 | const avatar = $(this)el.find('.avatar img'); |
| 7128 | 7218 | avatar.attr('src', default_avatar); |
| 7129 | 7219 | await delay(1); |
| 7130 | 7220 | avatar.attr('src', previewSrc); |
| 7131 | 7221 | } |
| 7132 | 7222 | }); |
| 7133 | 7223 | |
| 7134 | 7224 | console.log('Avatar refreshed'); |
| 7135 | 7225 | } |
| @@ -7979,7 +8069,7 @@ export async function getChatsFromFiles(data, isGroupChat) { | ||
| 7979 | 8069 | * response is an object with an `error` property set to `true`. |
| 7980 | 8070 | */ |
| 7981 | 8071 | export async function getPastCharacterChats(characterId = null) { |
| 7982 | 8072 | characterId = characterId ?? parseInt(this_chid); |
| 7983 | 8073 | if (!characters[characterId]) return []; |
| 7984 | 8074 | |
| 7985 | 8075 | const response = await fetch('/api/characters/chats', { |
| @@ -8046,9 +8136,7 @@ export async function displayPastChats() { | ||
| 8046 | 8136 | // UX convenience: Focus the search field when the Manage Chat Files view opens. |
| 8047 | 8137 | setTimeout(function () { |
| 8048 | 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 | 8140 | }, 200); |
| 8053 | 8141 | } |
| 8054 | 8142 | |
| @@ -8315,11 +8403,10 @@ function select_rm_create({ switchMenu = true } = {}) { | ||
| 8315 | 8403 | switchMenu && setMenuType('create'); |
| 8316 | 8404 | |
| 8317 | 8405 | //console.log('select_rm_Create() -- selected button: '+selected_button); |
| 8318 | 8406 | if (selected_button == 'create' && create_save.avatar) { |
| 8319 | - if (create_save.avatar != '') { | |
| 8407 | + const addAvatarInput = /** @type {HTMLInputElement} */ ($('#add_avatar_button').get(0)); | |
| 8320 | 8408 | $('#add_avatar_button').get(0) addAvatarInput.files = create_save.avatar; |
| 8321 | - read_avatar_load($('#add_avatar_button').get(0)); | |
| 8409 | + read_avatar_load(addAvatarInput); | |
| 8322 | - } | |
| 8323 | 8410 | } |
| 8324 | 8411 | |
| 8325 | 8412 | switchMenu && selectRightMenuWithAnimation('rm_ch_create_block'); |
| @@ -8445,11 +8532,18 @@ export function updateChatMetadata(newValues, reset) { | ||
| 8445 | 8532 | chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues }; |
| 8446 | 8533 | } |
| 8447 | 8534 | |
| 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 | + */ | |
| 8448 | 8540 | function updateFavButtonState(state) { |
| 8541 | + // Update global state of the flag | |
| 8542 | + // TODO: This is bad and needs to be refactored. | |
| 8449 | 8543 | fav_ch_checked = state; |
| 8450 | 8544 | $('#fav_checkbox').valprop(fav_ch_checked'checked', state); |
| 8451 | 8545 | $('#favorite_button').toggleClass('fav_on', fav_ch_checkedstate); |
| 8452 | 8546 | $('#favorite_button').toggleClass('fav_off', !fav_ch_checkedstate); |
| 8453 | 8547 | } |
| 8454 | 8548 | |
| 8455 | 8549 | export async function setScenarioOverride() { |
| @@ -8566,7 +8660,7 @@ export function showSwipeButtons() { | ||
| 8566 | 8660 | |
| 8567 | 8661 | //had to add this to make the swipe counter work |
| 8568 | 8662 | //(copied from the onclick functions for swipe buttons.. |
| 8569 | 8663 | //don't know why the array isn't set for non-swipe messsagesmessages in Generate or addOneMessage..) |
| 8570 | 8664 | if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array |
| 8571 | 8665 | chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0 |
| 8572 | 8666 | chat[chat.length - 1]['swipes'] = []; // empty the array |
| @@ -8765,7 +8859,7 @@ function updateEditArrowClasses() { | ||
| 8765 | 8859 | export function closeMessageEditor(what = 'all') { |
| 8766 | 8860 | if (what === 'message' || what === 'all') { |
| 8767 | 8861 | if (this_edit_mes_id) { |
| 8768 | 8862 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).clicktrigger('click'); |
| 8769 | 8863 | } |
| 8770 | 8864 | } |
| 8771 | 8865 | if (what === 'reasoning' || what === 'all') { |
| @@ -8854,7 +8948,8 @@ function openCharacterWorldPopup() { | ||
| 8854 | 8948 | } |
| 8855 | 8949 | |
| 8856 | 8950 | function onExtraWorldInfoChanged() { |
| 8857 | 8951 | const selectedWorldsselectorFieldValue = $('.character_extra_world_info_selector').val(); |
| 8952 | + const selectedWorlds = Array.isArray(selectorFieldValue) ? selectorFieldValue : []; | |
| 8858 | 8953 | let charLore = world_info.charLore ?? []; |
| 8859 | 8954 | |
| 8860 | 8955 | // TODO: Maybe make this utility function not use the window context? |
| @@ -8899,7 +8994,7 @@ function openCharacterWorldPopup() { | ||
| 8899 | 8994 | // Apped to base dropdown |
| 8900 | 8995 | world_names.forEach((item, i) => { |
| 8901 | 8996 | const option = document.createElement('option'); |
| 8902 | 8997 | option.value = String(i); |
| 8903 | 8998 | option.innerText = item; |
| 8904 | 8999 | option.selected = item === worldId; |
| 8905 | 9000 | select.append(option); |
| @@ -8911,7 +9006,7 @@ function openCharacterWorldPopup() { | ||
| 8911 | 9006 | } |
| 8912 | 9007 | world_names.forEach((item, i) => { |
| 8913 | 9008 | const option = document.createElement('option'); |
| 8914 | 9009 | option.value = String(i); |
| 8915 | 9010 | option.innerText = item; |
| 8916 | 9011 | |
| 8917 | 9012 | const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename()); |
| @@ -9017,7 +9112,7 @@ function addAlternateGreeting(template, greeting, index, getArray, popup) { | ||
| 9017 | 9112 | */ |
| 9018 | 9113 | async function createOrEditCharacter(e) { |
| 9019 | 9114 | $('#rm_info_avatar').html(''); |
| 9020 | 9115 | const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0))); |
| 9021 | 9116 | formData.set('fav', String(fav_ch_checked)); |
| 9022 | 9117 | const isNewChat = e instanceof CustomEvent && e.type === 'newChat'; |
| 9023 | 9118 | |
| @@ -9099,7 +9194,7 @@ async function createOrEditCharacter(e) { | ||
| 9099 | 9194 | |
| 9100 | 9195 | $('#character_popup-button-h3').text('Create character'); |
| 9101 | 9196 | |
| 9102 | 9197 | create_save.avatar = ''null; |
| 9103 | 9198 | |
| 9104 | 9199 | $('#add_avatar_button').replaceWith( |
| 9105 | 9200 | $('#add_avatar_button').val('').clone(true), |
| @@ -9348,13 +9443,13 @@ export function swipe_left(_event, { source, repeated } = {}) { | ||
| 9348 | 9443 | |
| 9349 | 9444 | /** |
| 9350 | 9445 | * Handles the swipe to the right event. |
| 9351 | 9446 | * @param {JQuery.Event} [_event] Event. |
| 9352 | 9447 | * @param {object} params Additional parameters. |
| 9353 | 9448 | * @param {string} [params.source] The source of the swipe event. |
| 9354 | 9449 | * @param {boolean} [params.repeated] Is the swipe event repeated. |
| 9355 | 9450 | */ |
| 9356 | 9451 | //MARK: swipe_right |
| 9357 | 9452 | export function swipe_right(_event = null, { source, repeated } = {}) { |
| 9358 | 9453 | if (chat.length - 1 === Number(this_edit_mes_id)) { |
| 9359 | 9454 | closeMessageEditor(); |
| 9360 | 9455 | } |
| @@ -9366,6 +9461,7 @@ export function swipe_right(_event, { source, repeated } = {}) { | ||
| 9366 | 9461 | // Make sure ad-hoc changes to extras are saved before swiping away |
| 9367 | 9462 | syncMesToSwipe(); |
| 9368 | 9463 | |
| 9464 | + const isPristine = !chat_metadata?.tainted; | |
| 9369 | 9465 | const swipe_duration = 200; |
| 9370 | 9466 | const swipe_range = 700; |
| 9371 | 9467 | //console.log(swipe_range); |
| @@ -9384,14 +9480,16 @@ export function swipe_right(_event, { source, repeated } = {}) { | ||
| 9384 | 9480 | }; |
| 9385 | 9481 | //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 around | |
| 9483 | + // if swipe_right is called on the last alternate greeting in pristine chats, loop back around | |
| 9484 | + if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1 && isPristine) { | |
| 9388 | 9485 | chat[0]['swipe_id'] = 0; |
| 9389 | 9486 | } else { |
| 9390 | 9487 | // If the user is holding down the key and we're at the last swipe, don't do anything |
| 9391 | 9488 | if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === chat[chat.length - 1].swipes.length - 1) { |
| 9392 | 9489 | return; |
| 9393 | 9490 | } |
| 9394 | 9491 | chat[chat.length - 1]['swipe_id']++; // make new slot in array |
| 9492 | + chat[chat.length - 1]['swipe_id']++; | |
| 9395 | 9493 | } |
| 9396 | 9494 | if (chat[chat.length - 1].extra) { |
| 9397 | 9495 | // if message has memory attached - remove it to allow regen |
| @@ -9406,8 +9504,8 @@ export function swipe_right(_event, { source, repeated } = {}) { | ||
| 9406 | 9504 | if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) { |
| 9407 | 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 | 9508 | if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length && (chat.length !== 1) { //if swipe id of last message is the same as the length of the 'swipes' array and not|| the!isPristine)) greeting{ |
| 9411 | 9509 | delete chat[chat.length - 1].gen_started; |
| 9412 | 9510 | delete chat[chat.length - 1].gen_finished; |
| 9413 | 9511 | run_generate = true; |
| @@ -9985,16 +10083,21 @@ async function doRenameChat(_, chatName) { | ||
| 9985 | 10083 | } |
| 9986 | 10084 | |
| 9987 | 10085 | /** |
| 9988 | 10086 | * Renames thea currentlygroup selectedor character chat. |
| 9989 | 10087 | * @param {stringobject} oldFileName Oldparam nameParameters offor therenaming chat (no JSONL extension) |
| 9990 | 10088 | * @param {string} newName[param.characterId] NewCharacter nameID forto therename chat (no JSONL extension)for |
| 10089 | + * @param {string} [param.groupId] Group ID to rename chat for | |
| 10090 | + * @param {string} param.oldFileName Old name of the chat (no JSONL extension) | |
| 10091 | + * @param {string} param.newFileName New name for the chat (no JSONL extension) | |
| 10092 | + * @param {boolean} [param.loader=true] Whether to show loader during the operation | |
| 9991 | 10093 | */ |
| 9992 | 10094 | export async function renameChatrenameGroupOrCharacterChat({ characterId, groupId, oldFileName, newNamenewFileName, loader }) { |
| 10095 | + const currentChatId = getCurrentChatId(); | |
| 9993 | 10096 | const body = { |
| 9994 | 10097 | is_group: !!selected_groupgroupId, |
| 9995 | 10098 | avatar_url: characters[this_chidcharacterId]?.avatar, |
| 9996 | 10099 | original_file: `${oldFileName}.jsonl`, |
| 9997 | 10100 | renamed_file: `${newNamenewFileName.trim()}.jsonl`, |
| 9998 | 10101 | }; |
| 9999 | 10102 | |
| 10000 | 10103 | if (body.original_file === body.renamed_file) { |
| @@ -10007,7 +10110,8 @@ export async function renameChat(oldFileName, newName) { | ||
| 10007 | 10110 | } |
| 10008 | 10111 | |
| 10009 | 10112 | try { |
| 10010 | 10113 | loader && showLoader(); |
| 10114 | + | |
| 10011 | 10115 | const response = await fetch('/api/chats/rename', { |
| 10012 | 10116 | method: 'POST', |
| 10013 | 10117 | body: JSON.stringify(body), |
| @@ -10025,27 +10129,69 @@ export async function renameChat(oldFileName, newName) { | ||
| 10025 | 10129 | } |
| 10026 | 10130 | |
| 10027 | 10131 | if (data.sanitizedFileName) { |
| 10028 | 10132 | newNamenewFileName = data.sanitizedFileName; |
| 10029 | 10133 | } |
| 10030 | 10134 | |
| 10031 | 10135 | if (selected_groupgroupId) { |
| 10032 | 10136 | await renameGroupChat(selected_groupgroupId, oldFileName, newNamenewFileName); |
| 10033 | 10137 | } |
| 10034 | - else { | |
| 10138 | + else if (characterId !== undefined && String(characterId) === String(this_chid) && characters[characterId]?.chat === oldFileName) { | |
| 10035 | 10139 | if (characters[this_chidcharacterId].chat == oldFileName) {newFileName; |
| 10036 | - characters[this_chid].chat = newName; | |
| 10140 | + $('#selected_chat_pole').val(characters[characterId].chat); | |
| 10037 | - $('#selected_chat_pole').val(characters[this_chid].chat); | |
| 10038 | 10141 | await createOrEditCharacter(); |
| 10039 | 10142 | } |
| 10040 | - } | |
| 10041 | 10143 | |
| 10144 | + if (currentChatId) { | |
| 10042 | 10145 | await reloadCurrentChat(); |
| 10146 | + } | |
| 10043 | 10147 | } catch { |
| 10044 | 10148 | loader && hideLoader(); |
| 10045 | 10149 | await delay(500); |
| 10046 | 10150 | await callPopupcallGenericPopup('An error has occurred. Chat was not renamed.', 'text'POPUP_TYPE.TEXT); |
| 10047 | 10151 | } finally { |
| 10048 | 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 | + */ | |
| 10161 | +export async function renameChat(oldFileName, newName) { | |
| 10162 | + return await renameGroupOrCharacterChat({ | |
| 10163 | + characterId: this_chid, | |
| 10164 | + groupId: selected_group, | |
| 10165 | + oldFileName: oldFileName, | |
| 10166 | + newFileName: newName, | |
| 10167 | + loader: true, | |
| 10168 | + }); | |
| 10169 | +} | |
| 10170 | + | |
| 10171 | +/** | |
| 10172 | + * Forces the update of the chat name for a remote character. | |
| 10173 | + * @param {string|number} characterId Character ID to update chat name for | |
| 10174 | + * @param {string} newName New name for the chat | |
| 10175 | + * @returns {Promise<void>} | |
| 10176 | + */ | |
| 10177 | +export async function updateRemoteChatName(characterId, newName) { | |
| 10178 | + const character = characters[characterId]; | |
| 10179 | + if (!character) { | |
| 10180 | + console.warn(`Character not found for ID: ${characterId}`); | |
| 10181 | + return; | |
| 10182 | + } | |
| 10183 | + character.chat = newName; | |
| 10184 | + const mergeRequest = { | |
| 10185 | + avatar: character.avatar, | |
| 10186 | + chat: newName, | |
| 10187 | + }; | |
| 10188 | + const mergeResponse = await fetch('/api/characters/merge-attributes', { | |
| 10189 | + method: 'POST', | |
| 10190 | + headers: getRequestHeaders(), | |
| 10191 | + body: JSON.stringify(mergeRequest), | |
| 10192 | + }); | |
| 10193 | + if (!mergeResponse.ok) { | |
| 10194 | + console.error('Failed to save extension field', mergeResponse.statusText); | |
| 10049 | 10195 | } |
| 10050 | 10196 | } |
| 10051 | 10197 | |
| @@ -10056,9 +10202,6 @@ async function doGetChatName() { | ||
| 10056 | 10202 | return getCurrentChatDetails().sessionName; |
| 10057 | 10203 | } |
| 10058 | 10204 | |
| 10059 | -const isPwaMode = window.navigator.standalone; | |
| 10060 | -if (isPwaMode) { $('body').addClass('PWA'); } | |
| 10061 | - | |
| 10062 | 10205 | function doCharListDisplaySwitch() { |
| 10063 | 10206 | power_user.charListGrid = !power_user.charListGrid; |
| 10064 | 10207 | document.body.classList.toggle('charListGrid', power_user.charListGrid); |
| @@ -10203,86 +10346,43 @@ function doDrawerOpenClick() { | ||
| 10203 | 10346 | /** |
| 10204 | 10347 | * Event handler to open or close a navbar drawer when a navbar icon is clicked. |
| 10205 | 10348 | * Handles click events on .drawer-toggle elements. |
| 10206 | 10349 | * @returns {Promise<void>} |
| 10207 | 10350 | */ |
| 10208 | 10351 | export async function doNavbarIconClick() { |
| 10209 | 10352 | const icon = $(this).find('.drawer-icon'); |
| 10210 | 10353 | const drawer = $(this).parent().find('.drawer-content'); |
| 10211 | - if (drawer.hasClass('resizing')) { return; } | |
| 10212 | 10354 | const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer'); |
| 10213 | 10355 | const targetDrawerID = $(this).parent().find('.drawer-content').attr('id'); |
| 10214 | - const pinnedDrawerClicked = drawer.hasClass('pinnedOpen'); | |
| 10356 | + | |
| 10215 | - | |
| 10357 | + if (!drawerWasOpenAlready) { | |
| 10216 | - if (!drawerWasOpenAlready) { //to open the drawer | |
| 10358 | + const $openDrawers = $('.openDrawer:not(.pinnedOpen)'); | |
| 10217 | - $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => { | |
| 10359 | + const $openIcons = $('.openIcon:not(.drawerPinnedOpen)'); | |
| 10218 | - slideToggle(el, { | |
| 10360 | + for (const iconEl of $openIcons) { | |
| 10219 | - ...getSlideToggleOptions(), | |
| 10361 | + $(iconEl).toggleClass('closedIcon openIcon'); | |
| 10220 | - onAnimationEnd: function (el) { | |
| 10362 | + } | |
| 10221 | - el.closest('.drawer-content').classList.remove('resizing'); | |
| 10363 | + for (const el of $openDrawers) { | |
| 10222 | - }, | |
| 10364 | + $(el).toggleClass('closedDrawer openDrawer'); | |
| 10223 | - }); | |
| 10365 | + } | |
| 10224 | - }); | |
| 10366 | + if ($openDrawers.length) { | |
| 10225 | - $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon'); | |
| 10367 | + await delay(animation_duration); | |
| 10226 | - $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer'); | |
| 10368 | + } | |
| 10227 | 10369 | icon.toggleClass('openIcon closedIcon'); |
| 10228 | 10370 | drawer.toggleClass('openDrawer closedDrawer'); |
| 10229 | 10371 | |
| 10230 | - //console.log(targetDrawerID); | |
| 10231 | 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 | 10373 | favsToHotswap(); |
| 10239 | 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 | } |
| 10253 | 10376 | |
| 10254 | 10377 | // Set the height of "autoSetHeight" textareas within the drawer to their scroll height |
| 10255 | 10378 | if (!CSS.supports('field-sizing', 'content')) { |
| 10256 | 10379 | const textareas = $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {; |
| 10257 | - await resetScrollHeight($(this)); | |
| 10380 | + for (const textarea of textareas) { | |
| 10258 | - return; | |
| 10381 | + await resetScrollHeight($(textarea)); | |
| 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 | } |
| 10285 | - | |
| 10384 | + } else if (drawerWasOpenAlready) { | |
| 10385 | + icon.toggleClass('closedIcon openIcon'); | |
| 10286 | 10386 | drawer.toggleClass('closedDrawer openDrawer'); |
| 10287 | 10387 | } |
| 10288 | 10388 | } |
| @@ -10686,23 +10786,23 @@ jQuery(async function () { | ||
| 10686 | 10786 | |
| 10687 | 10787 | //menu buttons setup |
| 10688 | 10788 | |
| 10689 | 10789 | $('#rm_button_settings').clickon('click', function () { |
| 10690 | 10790 | selected_button = 'settings'; |
| 10691 | 10791 | selectRightMenuWithAnimation('rm_api_block'); |
| 10692 | 10792 | }); |
| 10693 | 10793 | $('#rm_button_characters').clickon('click', function () { |
| 10694 | 10794 | selected_button = 'characters'; |
| 10695 | 10795 | select_rm_characters(); |
| 10696 | 10796 | }); |
| 10697 | 10797 | $('#rm_button_back').clickon('click', function () { |
| 10698 | 10798 | selected_button = 'characters'; |
| 10699 | 10799 | select_rm_characters(); |
| 10700 | 10800 | }); |
| 10701 | 10801 | $('#rm_button_create').clickon('click', function () { |
| 10702 | 10802 | selected_button = 'create'; |
| 10703 | 10803 | select_rm_create(); |
| 10704 | 10804 | }); |
| 10705 | 10805 | $('#rm_button_selected_ch').clickon('click', function () { |
| 10706 | 10806 | if (selected_group) { |
| 10707 | 10807 | select_group_chats(selected_group); |
| 10708 | 10808 | } else { |
| @@ -10797,7 +10897,7 @@ jQuery(async function () { | ||
| 10797 | 10897 | callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat'); |
| 10798 | 10898 | }); |
| 10799 | 10899 | |
| 10800 | 10900 | $('#advanced_div').clickon('click', function () { |
| 10801 | 10901 | if (!is_advanced_char_open) { |
| 10802 | 10902 | is_advanced_char_open = true; |
| 10803 | 10903 | $('#character_popup').css({ 'display': 'flex', 'opacity': 0.0 }).addClass('open'); |
| @@ -10812,7 +10912,7 @@ jQuery(async function () { | ||
| 10812 | 10912 | } |
| 10813 | 10913 | }); |
| 10814 | 10914 | |
| 10815 | 10915 | $('#character_cross').clickon('click', function () { |
| 10816 | 10916 | is_advanced_char_open = false; |
| 10817 | 10917 | $('#character_popup').transition({ |
| 10818 | 10918 | opacity: 0, |
| @@ -10822,12 +10922,12 @@ jQuery(async function () { | ||
| 10822 | 10922 | setTimeout(function () { $('#character_popup').css('display', 'none'); }, animation_duration); |
| 10823 | 10923 | }); |
| 10824 | 10924 | |
| 10825 | 10925 | $('#character_popup_ok').clickon('click', function () { |
| 10826 | 10926 | is_advanced_char_open = false; |
| 10827 | 10927 | $('#character_popup').css('display', 'none'); |
| 10828 | 10928 | }); |
| 10829 | 10929 | |
| 10830 | 10930 | $('#dialogue_popup_ok').clickon('click', async function (e_e, customData) { |
| 10831 | 10931 | const fromSlashCommand = customData?.fromSlashCommand || false; |
| 10832 | 10932 | dialogueCloseStop = false; |
| 10833 | 10933 | $('#shadow_popup').transition({ |
| @@ -10857,7 +10957,7 @@ jQuery(async function () { | ||
| 10857 | 10957 | hideLoader(); |
| 10858 | 10958 | } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat). |
| 10859 | 10959 | setTimeout(function () { |
| 10860 | 10960 | $('#option_select_chat').clicktrigger('click'); |
| 10861 | 10961 | $('#options').hide(); // hide option popup menu |
| 10862 | 10962 | hideLoader(); |
| 10863 | 10963 | }, 2000); |
| @@ -10868,18 +10968,16 @@ jQuery(async function () { | ||
| 10868 | 10968 | if (popup_type == 'input') { |
| 10869 | 10969 | dialogueResolve($('#dialogue_popup_input').val()); |
| 10870 | 10970 | $('#dialogue_popup_input').val(''); |
| 10871 | - | |
| 10872 | 10971 | } |
| 10873 | 10972 | else { |
| 10874 | 10973 | dialogueResolve(true); |
| 10875 | - | |
| 10876 | 10974 | } |
| 10877 | 10975 | |
| 10878 | 10976 | dialogueResolve = null; |
| 10879 | 10977 | } |
| 10880 | 10978 | }); |
| 10881 | 10979 | |
| 10882 | 10980 | $('#dialogue_popup_cancel').clickon('click', function (e) { |
| 10883 | 10981 | dialogueCloseStop = false; |
| 10884 | 10982 | $('#shadow_popup').transition({ |
| 10885 | 10983 | opacity: 0, |
| @@ -10892,21 +10990,20 @@ jQuery(async function () { | ||
| 10892 | 10990 | $('#dialogue_popup').removeClass('large_dialogue_popup'); |
| 10893 | 10991 | }, animation_duration); |
| 10894 | 10992 | |
| 10895 | - //$("#shadow_popup").css("opacity:", 0.0); | |
| 10896 | 10993 | popup_type = ''; |
| 10897 | 10994 | |
| 10898 | 10995 | if (dialogueResolve) { |
| 10899 | 10996 | dialogueResolve(false); |
| 10900 | 10997 | dialogueResolve = null; |
| 10901 | 10998 | } |
| 10902 | - | |
| 10903 | 10999 | }); |
| 10904 | 11000 | |
| 10905 | 11001 | $('#add_avatar_button').changeon('change', function () { |
| 10906 | - read_avatar_load(this); | |
| 11002 | + const inputElement = /** @type {HTMLInputElement} */ (this); | |
| 11003 | + read_avatar_load(inputElement); | |
| 10907 | 11004 | }); |
| 10908 | 11005 | |
| 10909 | 11006 | $('#form_create').on('submit', (e) => createOrEditCharacter(e.originalEvent)); |
| 10910 | 11007 | |
| 10911 | 11008 | $('#delete_button').on('click', async function () { |
| 10912 | 11009 | if (this_chid === undefined || !characters[this_chid]) { |
| @@ -10917,7 +11014,7 @@ jQuery(async function () { | ||
| 10917 | 11014 | let deleteChats = false; |
| 10918 | 11015 | |
| 10919 | 11016 | const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), { |
| 10920 | 11017 | onClose: () => { deleteChats = !!$('#del_char_checkbox').prop('checked'); }, |
| 10921 | 11018 | }); |
| 10922 | 11019 | if (!confirm) { |
| 10923 | 11020 | return; |
| @@ -10977,9 +11074,9 @@ jQuery(async function () { | ||
| 10977 | 11074 | const oldFileName = oldFileNameFull.replace('.jsonl', ''); |
| 10978 | 11075 | |
| 10979 | 11076 | const popupText = await renderTemplateAsync('chatRename'); |
| 10980 | 11077 | const newName = await callPopupcallGenericPopup(popupText, 'input'POPUP_TYPE.INPUT, oldFileName); |
| 10981 | 11078 | |
| 10982 | 11079 | if (!newName || typeof newName !== 'string' || newName == oldFileName) { |
| 10983 | 11080 | console.log('no new name found, aborting'); |
| 10984 | 11081 | return; |
| 10985 | 11082 | } |
| @@ -11038,7 +11135,7 @@ jQuery(async function () { | ||
| 11038 | 11135 | |
| 11039 | 11136 | /////////////////////////////////////////////////////////////////////////////////// |
| 11040 | 11137 | |
| 11041 | 11138 | $('#api_button').clickon('click', function (e) { |
| 11042 | 11139 | if ($('#api_url_text').val() != '') { |
| 11043 | 11140 | let value = formatKoboldUrl(String($('#api_url_text').val()).trim()); |
| 11044 | 11141 | |
| @@ -11179,7 +11276,7 @@ jQuery(async function () { | ||
| 11179 | 11276 | if ((selected_group || this_chid !== undefined) && !is_send_press) { |
| 11180 | 11277 | let deleteCurrentChat = false; |
| 11181 | 11278 | const result = await Popup.show.confirm(t`Start new chat?`, await renderTemplateAsync('newChatConfirm'), { |
| 11182 | 11279 | onClose: () => { deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'); }, |
| 11183 | 11280 | }); |
| 11184 | 11281 | if (!result) { |
| 11185 | 11282 | return; |
| @@ -11283,7 +11380,7 @@ jQuery(async function () { | ||
| 11283 | 11380 | ////////////////////////////////////////////////////////////////////////////////////////////// |
| 11284 | 11381 | |
| 11285 | 11382 | //functionality for the cancel delete messages button, reverts to normal display of input form |
| 11286 | 11383 | $('#dialogue_del_mes_cancel').clickon('click', function () { |
| 11287 | 11384 | $('#dialogue_del_mes').css('display', 'none'); |
| 11288 | 11385 | $('#send_form').css('display', css_send_form_display); |
| 11289 | 11386 | $('.del_checkbox').each(function () { |
| @@ -11423,7 +11520,7 @@ jQuery(async function () { | ||
| 11423 | 11520 | |
| 11424 | 11521 | ////////////////////////////////////////////////////////////// |
| 11425 | 11522 | |
| 11426 | 11523 | $('#select_chat_cross').clickon('click', function () { |
| 11427 | 11524 | $('#shadow_select_chat_popup').transition({ |
| 11428 | 11525 | opacity: 0, |
| 11429 | 11526 | duration: animation_duration, |
| @@ -11517,8 +11614,10 @@ jQuery(async function () { | ||
| 11517 | 11614 | edit_textarea.height(0); |
| 11518 | 11615 | edit_textarea.height(edit_textarea[0].scrollHeight); |
| 11519 | 11616 | } |
| 11520 | 11617 | edit_textarea.focustrigger('focus'); |
| 11521 | - edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text | |
| 11618 | + const textAreaElement = /** @type {HTMLTextAreaElement} */ (edit_textarea[0]); | |
| 11619 | + // Sets the cursor at the end of the text | |
| 11620 | + textAreaElement.setSelectionRange( | |
| 11522 | 11621 | String(edit_textarea.val()).length, |
| 11523 | 11622 | String(edit_textarea.val()).length, |
| 11524 | 11623 | ); |
| @@ -11770,8 +11869,8 @@ jQuery(async function () { | ||
| 11770 | 11869 | //Select chat |
| 11771 | 11870 | |
| 11772 | 11871 | //**************************CHARACTER IMPORT EXPORT*************************// |
| 11773 | 11872 | $('#character_import_button').clickon('click', function () { |
| 11774 | 11873 | $('#character_import_file').clicktrigger('click'); |
| 11775 | 11874 | }); |
| 11776 | 11875 | |
| 11777 | 11876 | $('#character_import_file').on('change', async function (e) { |
| @@ -11839,12 +11938,16 @@ jQuery(async function () { | ||
| 11839 | 11938 | } |
| 11840 | 11939 | }); |
| 11841 | 11940 | //**************************CHAT IMPORT EXPORT*************************// |
| 11842 | 11941 | $('#chat_import_button').clickon('click', function () { |
| 11843 | 11942 | $('#chat_import_file').clicktrigger('click'); |
| 11844 | 11943 | }); |
| 11845 | 11944 | |
| 11846 | 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]; | |
| 11848 | 11951 | |
| 11849 | 11952 | if (!file) { |
| 11850 | 11953 | return; |
| @@ -11866,7 +11969,7 @@ jQuery(async function () { | ||
| 11866 | 11969 | const format = ext[1].toLowerCase(); |
| 11867 | 11970 | $('#chat_import_file_type').val(format); |
| 11868 | 11971 | |
| 11869 | 11972 | const formData = new FormData(/** @type {HTMLFormElement} */($('#form_import_chat').get(0))); |
| 11870 | 11973 | formData.append('user_name', name1); |
| 11871 | 11974 | $('#select_chat_div').html(''); |
| 11872 | 11975 | |
| @@ -11877,12 +11980,12 @@ jQuery(async function () { | ||
| 11877 | 11980 | } |
| 11878 | 11981 | }); |
| 11879 | 11982 | |
| 11880 | 11983 | $('#rm_button_group_chats').clickon('click', function () { |
| 11881 | 11984 | selected_button = 'group_chats'; |
| 11882 | 11985 | select_group_chats(); |
| 11883 | 11986 | }); |
| 11884 | 11987 | |
| 11885 | 11988 | $('#rm_button_back_from_group').clickon('click', function () { |
| 11886 | 11989 | selected_button = 'characters'; |
| 11887 | 11990 | select_rm_characters(); |
| 11888 | 11991 | }); |
| @@ -11911,8 +12014,8 @@ jQuery(async function () { | ||
| 11911 | 12014 | |
| 11912 | 12015 | $('.drawer-toggle').on('click', doNavbarIconClick); |
| 11913 | 12016 | |
| 11914 | 12017 | $('html').on('touchstart mousedown', async function (e) { |
| 11915 | 12018 | varconst clickTarget = $(e.target); |
| 11916 | 12019 | |
| 11917 | 12020 | if (isExportPopupOpen |
| 11918 | 12021 | && clickTarget.closest('#export_button').length == 0 |
| @@ -11933,33 +12036,26 @@ jQuery(async function () { | ||
| 11933 | 12036 | '#toast-container', |
| 11934 | 12037 | '.select2-results', |
| 11935 | 12038 | ]; |
| 12039 | + | |
| 11936 | 12040 | for (const id of forbiddenTargets) { |
| 11937 | 12041 | if (clickTarget.closest(id).length > 0) { |
| 11938 | 12042 | return; |
| 11939 | 12043 | } |
| 11940 | 12044 | } |
| 11941 | 12045 | |
| 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 | 12052 | $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon'); |
| 11956 | 12053 | $('.openDrawer').not('.pinnedOpen')openDrawers.toggleClass('closedDrawer openDrawer'); |
| 11957 | - } | |
| 11958 | 12054 | } |
| 11959 | 12055 | } |
| 11960 | 12056 | }); |
| 11961 | 12057 | |
| 11962 | 12058 | $(document).on('click', '.inline-drawer-toggle', async function (e) { |
| 11963 | 12059 | if ($(e.target).hasClass('text_pole')) { |
| 11964 | 12060 | return; |
| 11965 | 12061 | } |
| @@ -11968,6 +12064,7 @@ jQuery(async function () { | ||
| 11968 | 12064 | const drawerContent = drawer.find('>.inline-drawer-content'); |
| 11969 | 12065 | icon.toggleClass('down up'); |
| 11970 | 12066 | icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up'); |
| 12067 | + drawer.trigger('inline-drawer-toggle'); | |
| 11971 | 12068 | drawerContent.stop().slideToggle({ |
| 11972 | 12069 | complete: () => { |
| 11973 | 12070 | $(this).css('height', ''); |
| @@ -11976,10 +12073,10 @@ jQuery(async function () { | ||
| 11976 | 12073 | |
| 11977 | 12074 | // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height |
| 11978 | 12075 | if (!CSS.supports('field-sizing', 'content')) { |
| 11979 | 12076 | const textareas = drawerContent.find('textarea.autoSetHeight').each(async function () {; |
| 11980 | - await resetScrollHeight($(this)); | |
| 12077 | + for (const textarea of textareas) { | |
| 11981 | - return; | |
| 12078 | + await resetScrollHeight($(textarea)); | |
| 11982 | 12079 | }); |
| 11983 | 12080 | } |
| 11984 | 12081 | }); |
| 11985 | 12082 | |
| @@ -12064,7 +12161,7 @@ jQuery(async function () { | ||
| 12064 | 12161 | if (!(e.target instanceof HTMLElement)) return; |
| 12065 | 12162 | if (e.target.matches('#OpenAllWIEntries')) { |
| 12066 | 12163 | document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => { |
| 12067 | 12164 | delay(0).then(() => toggleDrawer(drawer, true)); |
| 12068 | 12165 | }); |
| 12069 | 12166 | } else if (e.target.matches('#CloseAllWIEntries')) { |
| 12070 | 12167 | document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => { |
| @@ -12094,7 +12191,7 @@ jQuery(async function () { | ||
| 12094 | 12191 | return; |
| 12095 | 12192 | } |
| 12096 | 12193 | if (isEditVisible && power_user.auto_save_msg_edits === true) { |
| 12097 | 12194 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).clicktrigger('click'); |
| 12098 | 12195 | closeMessageEditor('reasoning'); |
| 12099 | 12196 | $('#send_textarea').focus(); |
| 12100 | 12197 | return; |
| @@ -12109,7 +12206,8 @@ jQuery(async function () { | ||
| 12109 | 12206 | }); |
| 12110 | 12207 | |
| 12111 | 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 | 12211 | switch (target) { |
| 12114 | 12212 | case 'set_character_world': |
| 12115 | 12213 | openCharacterWorldPopup(); |
| @@ -12257,9 +12355,7 @@ jQuery(async function () { | ||
| 12257 | 12355 | |
| 12258 | 12356 | $(document).on('click', '.external_import_button, #external_import_button', async () => { |
| 12259 | 12357 | const html = await renderTemplateAsync('importCharacters'); |
| 12260 | - | |
| 12358 | + const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '', { allowVerticalScrolling: true, wider: true, okButton: $('#popup_template').attr('popup-button-import'), rows: 4 }); | |
| 12261 | - /** @type {string?} */ | |
| 12262 | - const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '', { wider: true, okButton: $('#popup_template').attr('popup-button-import'), rows: 4 }); | |
| 12263 | 12359 | |
| 12264 | 12360 | if (!input) { |
| 12265 | 12361 | console.debug('Custom content import cancelled'); |
| @@ -12267,7 +12363,7 @@ jQuery(async function () { | ||
| 12267 | 12363 | } |
| 12268 | 12364 | |
| 12269 | 12365 | // break input into one input per line |
| 12270 | 12366 | const inputs = String(input).split('\n').map(x => x.trim()).filter(x => x.length > 0); |
| 12271 | 12367 | |
| 12272 | 12368 | for (const url of inputs) { |
| 12273 | 12369 | let request; |
| @@ -2,7 +2,6 @@ | ||
| 2 | 2 | |
| 3 | 3 | import { |
| 4 | 4 | characterGroupOverlay, |
| 5 | - callPopup, | |
| 6 | 5 | characters, |
| 7 | 6 | event_types, |
| 8 | 7 | eventSource, |
| @@ -17,6 +16,7 @@ import { | ||
| 17 | 16 | import { favsToHotswap } from './RossAscends-mods.js'; |
| 18 | 17 | import { hideLoader, showLoader } from './loader.js'; |
| 19 | 18 | import { convertCharacterToPersona } from './personas.js'; |
| 19 | +import { callGenericPopup, POPUP_TYPE } from './popup.js'; | |
| 20 | 20 | import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js'; |
| 21 | 21 | |
| 22 | 22 | /** |
| @@ -835,12 +835,13 @@ class BulkEditOverlay { | ||
| 835 | 835 | */ |
| 836 | 836 | handleContextMenuDelete = () => { |
| 837 | 837 | const characterIds = this.selectedCharacters; |
| 838 | 838 | const popupContent = $(BulkEditOverlay.#getDeletePopupContentHtml(characterIds)); |
| 839 | 839 | const promisecheckbox = callPopup(popupContent, null.find('#del_char_checkbox'); |
| 840 | + const promise = callGenericPopup(popupContent, POPUP_TYPE.CONFIRM) | |
| 840 | 841 | .then((accept) => { |
| 841 | 842 | if (true !== accept) return; |
| 842 | 843 | |
| 843 | 844 | const deleteChats = documentcheckbox.getElementByIdprop('del_char_checkboxchecked').checked ?? false; |
| 844 | 845 | |
| 845 | 846 | showLoader(); |
| 846 | 847 | const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it'); |
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | import { DOMPurify, Bowser, slideToggle } from '../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { |
| 4 | 4 | characters, |
| @@ -19,7 +19,7 @@ import { | ||
| 19 | 19 | menu_type, |
| 20 | 20 | substituteParams, |
| 21 | 21 | sendTextareaMessage, |
| 22 | 22 | getSlideToggleOptionsdoNavbarIconClick, |
| 23 | 23 | } from '../script.js'; |
| 24 | 24 | |
| 25 | 25 | import { |
| @@ -402,7 +402,8 @@ function RA_autoconnect(PrevApi) { | ||
| 402 | 402 | || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) |
| 403 | 403 | || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21) |
| 404 | 404 | || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) |
| 405 | 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 | 407 | || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) |
| 407 | 408 | || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE) |
| 408 | 409 | || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY) |
| @@ -411,6 +412,7 @@ function RA_autoconnect(PrevApi) { | ||
| 411 | 412 | || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) |
| 412 | 413 | || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) |
| 413 | 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 | 416 | || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) |
| 415 | 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); | ||
| 472 | 474 | |
| 473 | 475 | // Make the DIV element draggable: |
| 474 | 476 | |
| 475 | -export function dragElement(elmnt) { | |
| 477 | +/** | |
| 476 | - var isHeaderBeingDragged = false; | |
| 478 | + * Make the given element draggable. This is used for Moving UI. | |
| 477 | - var isMouseDown = false; | |
| 479 | + * @param {JQuery} $elmnt - The element to make draggable. | |
| 480 | + */ | |
| 481 | +export function dragElement($elmnt) { | |
| 482 | + let actionType = null; // "drag" or "resize" | |
| 483 | + let isMouseDown = false; | |
| 478 | 484 | |
| 479 | 485 | varlet pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; |
| 480 | 486 | varlet height, width, top, left, right, bottom, |
| 481 | 487 | maxX, maxY, winHeight, winWidth, |
| 482 | 488 | topbar, topBarFirstX, topBarLastY; |
| 483 | 489 | |
| 484 | 490 | varconst elmntName = $elmnt.attr('id'); |
| 485 | - console.debug(`dragElement called for ${elmntName}`); | |
| 486 | 491 | const elmntNameEscaped = $.escapeSelector(elmntName); |
| 487 | 492 | const $elmntHeader = $(`#${elmntNameEscaped}header`); |
| 488 | 493 | |
| 489 | - if (elmntHeader.length) { | |
| 494 | + // Helper: Save position/size to state and emit events | |
| 490 | - elmntHeader.off('mousedown').on('mousedown', (e) => { //listener for drag handle repositioning | |
| 495 | + function savePositionAndSize() { | |
| 491 | - isHeaderBeingDragged = true; | |
| 496 | + if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {}; | |
| 492 | - isMouseDown = true; | |
| 497 | + power_user.movingUIState[elmntName].top = top; | |
| 493 | - observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] }); | |
| 498 | + power_user.movingUIState[elmntName].left = left; | |
| 494 | - dragMouseDown(e); | |
| 499 | + power_user.movingUIState[elmntName].right = right; | |
| 495 | - }); | |
| 500 | + power_user.movingUIState[elmntName].bottom = bottom; | |
| 496 | - $(elmnt).off('mousedown').on('mousedown', () => { //listener for resize | |
| 501 | + power_user.movingUIState[elmntName].margin = 'unset'; | |
| 497 | - isMouseDown = true; | |
| 502 | + if (actionType === 'resize') { | |
| 498 | - observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] }); | |
| 503 | + power_user.movingUIState[elmntName].width = width; | |
| 499 | - }); | |
| 504 | + power_user.movingUIState[elmntName].height = height; | |
| 505 | + eventSource.emit('resizeUI', elmntName); | |
| 506 | + } | |
| 507 | + saveSettingsDebounced(); | |
| 500 | 508 | } |
| 501 | 509 | |
| 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 | 519 | const observer = new MutationObserver((mutations) => { |
| 503 | 520 | const $target = $(mutations[0].target); |
| 504 | - if (!$(target).is(':visible') //abort if element is invisible | |
| 521 | + if ( | |
| 505 | - || $(target).hasClass('resizing') //being auto-resized by other JS code | |
| 522 | + !$target.is(':visible') || | |
| 506 | - || Number((String(target.height).replace('px', ''))) < 50 //too short | |
| 523 | + $target.hasClass('resizing') || | |
| 507 | - || Number((String(target.width).replace('px', ''))) < 50 //too narrow | |
| 524 | + $target.height() < 50 || | |
| 508 | - || power_user.movingUI === false // if MUI is not turned on | |
| 525 | + $target.width() < 50 || | |
| 509 | - || isMobile() // if it's a mobile screen | |
| 526 | + power_user.movingUI === false || | |
| 527 | + isMobile() || | |
| 528 | + !isMouseDown | |
| 510 | 529 | ) { |
| 530 | + observer.disconnect(); | |
| 511 | 531 | return; |
| 512 | 532 | } |
| 513 | 533 | |
| 514 | 534 | const style = getComputedStyle($target[0]); |
| 515 | 535 | height = parseInt(style.height); |
| 516 | 536 | width = parseInt(style.width); |
| 517 | 537 | top = parseInt(style.top); |
| 518 | 538 | left = parseInt(style.left); |
| 519 | 539 | right = parseInt(style.right); |
| 520 | 540 | bottom = parseInt(style.bottom); |
| 521 | 541 | maxX = parseInt(width + left); |
| 522 | 542 | maxY = parseInt(height + top); |
| 523 | 543 | winWidth = window.innerWidth; |
| 524 | 544 | winHeight = window.innerHeight; |
| 525 | 545 | |
| @@ -528,182 +548,119 @@ export function dragElement(elmnt) { | ||
| 528 | 548 | topBarFirstX = parseInt(topbarstyle.marginInline); |
| 529 | 549 | topBarLastY = parseInt(topbarstyle.height); |
| 530 | 550 | |
| 531 | - //prepare an empty poweruser object for the item being altered if we don't have one already | |
| 551 | + // Prepare state object if missing | |
| 532 | 552 | if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {}; |
| 533 | - console.debug(`adding config property for ${elmntName}`); | |
| 534 | - power_user.movingUIState[elmntName] = {}; | |
| 535 | - } | |
| 536 | 553 | |
| 537 | - //handle resizing | |
| 554 | + if (actionType === 'resize') { | |
| 538 | - if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header) | |
| 539 | - let imgHeight, imgWidth, imageAspectRatio; | |
| 540 | 555 | let containerAspectRatio = height / width; |
| 541 | - | |
| 556 | + if ($elmnt.attr('id').startsWith('zoomFor_')) { | |
| 542 | - //force aspect ratio for zoomed avatars | |
| 557 | + const zoomedAvatarImage = $elmnt.find('.zoomed_avatar_img'); | |
| 543 | - if ($(elmnt).attr('id').startsWith('zoomFor_')) { | |
| 558 | + const imgHeight = zoomedAvatarImage.height(); | |
| 544 | 559 | letconst zoomedAvatarImageimgWidth = $(elmnt)zoomedAvatarImage.findwidth('.zoomed_avatar_img'); |
| 545 | - imgHeight = zoomedAvatarImage.height(); | |
| 560 | + const imageAspectRatio = imgHeight / imgWidth; | |
| 546 | - imgWidth = zoomedAvatarImage.width(); | |
| 547 | - imageAspectRatio = imgHeight / imgWidth; | |
| 548 | - | |
| 549 | - // Maintain aspect ratio | |
| 550 | 561 | if (containerAspectRatio !== imageAspectRatio) { |
| 551 | 562 | $elmnt.css('width', $elmnt.width()); |
| 552 | 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 | } |
| 560 | - | |
| 565 | + if (top + $elmnt.height() >= winHeight) { | |
| 561 | - if (left + elmnt.width() >= winWidth) { | |
| 566 | + $elmnt.css('height', winHeight - top - 1 + 'px'); | |
| 562 | 567 | $elmnt.css('width', winWidth(winHeight - lefttop - 1) / imageAspectRatio + 'px'); |
| 563 | - elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px'); | |
| 564 | 568 | } |
| 565 | - } else { //prevent divs that are not zoomedAvatars from resizing offscreen | |
| 569 | + if (left + $elmnt.width() >= winWidth) { | |
| 566 | - | |
| 570 | + $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 | 577 | if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) { |
| 578 | 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 | } |
| 593 | - | |
| 580 | + $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 | 585 | imgHeight =) nullreturn; |
| 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 | } |
| 608 | 592 | |
| 609 | - //only record position changes if header is being dragged | |
| 593 | + // Always update position in state | |
| 610 | - power_user.movingUIState[elmntName].top = top; | |
| 594 | + savePositionAndSize(); | |
| 611 | - power_user.movingUIState[elmntName].left = left; | |
| 612 | - power_user.movingUIState[elmntName].right = right; | |
| 613 | - power_user.movingUIState[elmntName].bottom = bottom; | |
| 614 | - power_user.movingUIState[elmntName].margin = 'unset'; | |
| 615 | - | |
| 616 | - //handle dragging hit detection to prevent dragging offscreen | |
| 617 | - if (isHeaderBeingDragged && isMouseDown) { | |
| 618 | - | |
| 619 | - if (top <= 0) { | |
| 620 | - elmnt.css('top', '0px'); | |
| 621 | - } else if (maxY >= winHeight) { | |
| 622 | - elmnt.css('top', winHeight - maxY + top - 1 + 'px'); | |
| 623 | - } | |
| 624 | - | |
| 625 | - if (left <= 0) { | |
| 626 | - elmnt.css('left', '0px'); | |
| 627 | - } else if (maxX >= winWidth) { | |
| 628 | - elmnt.css('left', winWidth - maxX + left - 1 + 'px'); | |
| 629 | - } | |
| 630 | - } | |
| 631 | - | |
| 632 | - // Check if the element header exists and set the reposition listener on the grabber in the header | |
| 633 | - if (elmntHeader.length) { | |
| 634 | - elmntHeader.off('mousedown').on('mousedown', (e) => { | |
| 635 | - dragMouseDown(e); | |
| 636 | - }); | |
| 637 | - } else { //if no header, put the listener on the elmnt itself. | |
| 638 | - elmnt.off('mousedown').on('mousedown', dragMouseDown); | |
| 639 | - } | |
| 640 | 595 | }); |
| 641 | 596 | |
| 597 | + // Mouse event handlers | |
| 642 | 598 | function dragMouseDown(e) { |
| 643 | - | |
| 644 | 599 | if (e) { |
| 645 | 600 | isHeaderBeingDraggedactionType = true'drag'; |
| 601 | + isMouseDown = true; | |
| 646 | 602 | e.preventDefault(); |
| 647 | 603 | pos3 = e.clientX; //mouse X at click |
| 648 | 604 | pos4 = e.clientY; //mouse Y at click |
| 649 | 605 | } |
| 650 | 606 | $(document).on('mouseup', closeDragElement); |
| 651 | 607 | $(document).on('mousemove', elementDrag); |
| 652 | 608 | } |
| 653 | 609 | |
| 654 | 610 | function elementDrag(e) { |
| 655 | 611 | if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {}; |
| 656 | - power_user.movingUIState[elmntName] = {}; | |
| 657 | - } | |
| 658 | - | |
| 659 | - e = e || window.event; | |
| 660 | 612 | e.preventDefault(); |
| 661 | - | |
| 613 | + pos1 = pos3 - e.clientX; | |
| 662 | - pos1 = pos3 - e.clientX; //X change amt (-1 or 1) | |
| 614 | + pos2 = pos4 - e.clientY; | |
| 663 | - pos2 = pos4 - e.clientY; //Y change amt (-1 or 1) | |
| 615 | + pos3 = e.clientX; | |
| 664 | 616 | pos3pos4 = e.clientXclientY; //new mouse X |
| 665 | - pos4 = e.clientY; //new mouse Y | |
| 617 | + $elmnt.attr('data-dragged', 'true'); | |
| 666 | - | |
| 618 | + $elmnt.css('left', ($elmnt.offset().left - pos1) + 'px'); | |
| 667 | - elmnt.attr('data-dragged', 'true'); | |
| 619 | + $elmnt.css('top', ($elmnt.offset().top - pos2) + 'px'); | |
| 668 | - | |
| 620 | + $elmnt.css('margin', 'unset'); | |
| 669 | - //first set css to computed values to avoid CSS NaN results from 'auto', etc | |
| 621 | + $elmnt.css('height', height); | |
| 670 | 622 | $elmnt.css('leftwidth', (elmnt.offset().left) + 'px'width); |
| 671 | - elmnt.css('top', (elmnt.offset().top) + 'px'); | |
| 672 | - | |
| 673 | - //then update element position styles to account for drag changes | |
| 674 | - elmnt.css('margin', 'unset'); | |
| 675 | - elmnt.css('left', (elmnt.offset().left - pos1) + 'px'); | |
| 676 | - elmnt.css('top', (elmnt.offset().top - pos2) + 'px'); | |
| 677 | - /* elmnt.css('right', ((winWidth - maxX) + 'px')); | |
| 678 | - elmnt.css('bottom', ((winHeight - maxY) + 'px')); */ | |
| 679 | - | |
| 680 | - // Height/Width here are for visuals only, and are not saved to settings. | |
| 681 | - // This is required because some divs do hot have a set width/height | |
| 682 | - // and will default to shrink to min value of 100px set in CSS file | |
| 683 | - elmnt.css('height', height); | |
| 684 | - elmnt.css('width', width); | |
| 685 | - return; | |
| 686 | 623 | } |
| 687 | 624 | |
| 688 | 625 | function closeDragElement() { |
| 689 | - console.debug('drag finished'); | |
| 690 | - isHeaderBeingDragged = false; | |
| 691 | 626 | isMouseDown = false; |
| 627 | + actionType = null; | |
| 692 | 628 | $(document).off('mouseup', closeDragElement); |
| 693 | 629 | $(document).off('mousemove', elementDrag); |
| 694 | 630 | $('body')elmnt.cssattr('overflowdata-dragged', 'false'); |
| 695 | - // Clear the "data-dragged" attribute | |
| 696 | - elmnt.attr('data-dragged', 'false'); | |
| 697 | 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 | } |
| 708 | 665 | |
| 709 | 666 | export async function initMovingUI() { |
| @@ -774,9 +731,8 @@ export function initRossMods() { | ||
| 774 | 731 | $(RightNavDrawerIcon).removeClass('drawerPinnedOpen'); |
| 775 | 732 | |
| 776 | 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 | 748 | $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen'); |
| 793 | 749 | |
| 794 | 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 | }); |
| 801 | 756 | |
| 802 | 757 | $(WIPanelPin).on('click', async function () { |
| 803 | 758 | accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked')); |
| 804 | 759 | if ($(WIPanelPin).prop('checked') == true) { |
| 805 | 760 | console.debug('adding pin class to WI'); |
| @@ -812,9 +767,8 @@ export function initRossMods() { | ||
| 812 | 767 | |
| 813 | 768 | if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) { |
| 814 | 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 | }); |
| @@ -1,9 +1,11 @@ | ||
| 1 | 1 | import { |
| 2 | + MAX_INJECTION_DEPTH, | |
| 2 | 3 | animation_duration, |
| 3 | 4 | chat_metadata, |
| 4 | 5 | eventSource, |
| 5 | 6 | event_types, |
| 6 | 7 | extension_prompt_roles, |
| 8 | + extension_prompt_types, | |
| 7 | 9 | saveSettingsDebounced, |
| 8 | 10 | this_chid, |
| 9 | 11 | } from '../script.js'; |
| @@ -347,7 +349,7 @@ export function setFloatingPrompt() { | ||
| 347 | 349 | } |
| 348 | 350 | |
| 349 | 351 | if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) { |
| 350 | 352 | context.setExtensionPrompt(MODULE_NAME, '', extension_prompt_types.NONE, MAX_INJECTION_DEPTH); |
| 351 | 353 | $('#extension_floating_counter').text('(disabled)'); |
| 352 | 354 | shouldWIAddPrompt = false; |
| 353 | 355 | return; |
| @@ -380,7 +382,7 @@ export function setFloatingPrompt() { | ||
| 380 | 382 | } |
| 381 | 383 | context.setExtensionPrompt( |
| 382 | 384 | MODULE_NAME, |
| 383 | 385 | String(prompt), |
| 384 | 386 | chat_metadata[metadata_keys.position], |
| 385 | 387 | chat_metadata[metadata_keys.depth], |
| 386 | 388 | extension_settings.note.allowWIScan, |
| @@ -396,37 +398,38 @@ function onANMenuItemClick() { | ||
| 396 | 398 | } |
| 397 | 399 | |
| 398 | 400 | //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 | 403 | $('#floatingPrompt')ANcontainer.cssaddClass('display', 'flexresizing'); |
| 402 | 404 | $('#floatingPrompt')ANcontainer.css('opacitydisplay', 0.0'flex'); |
| 403 | - $('#floatingPrompt').transition({ | |
| 405 | + $ANcontainer.css('opacity', 0.0); | |
| 406 | + $ANcontainer.transition({ | |
| 404 | 407 | opacity: 1.0, |
| 405 | 408 | duration: animation_duration, |
| 406 | 409 | }, async function () { |
| 407 | 410 | await delay(50); |
| 408 | 411 | $('#floatingPrompt')ANcontainer.removeClass('resizing'); |
| 409 | 412 | }); |
| 410 | 413 | |
| 411 | 414 | //auto-open the main AN inline drawer |
| 412 | 415 | if ($('#ANBlockToggle') |
| 413 | 416 | .siblings('.inline-drawer-content') |
| 414 | 417 | .css('display') !== 'block') { |
| 415 | 418 | $('#floatingPrompt')ANcontainer.addClass('resizing'); |
| 416 | 419 | $('#ANBlockToggle').clicktrigger('click'); |
| 417 | 420 | } |
| 418 | 421 | } else { |
| 419 | 422 | //hide AN if it's already displayed |
| 420 | 423 | $('#floatingPrompt')ANcontainer.addClass('resizing'); |
| 421 | 424 | $('#floatingPrompt')ANcontainer.transition({ |
| 422 | 425 | opacity: 0.0, |
| 423 | 426 | duration: animation_duration, |
| 424 | 427 | }, async function () { |
| 425 | 428 | await delay(50); |
| 426 | 429 | $('#floatingPrompt')ANcontainer.removeClass('resizing'); |
| 427 | 430 | }); |
| 428 | 431 | setTimeout(function () { |
| 429 | 432 | $('#floatingPrompt')ANcontainer.hide(); |
| 430 | 433 | }, animation_duration); |
| 431 | 434 | } |
| 432 | 435 | |
| @@ -21,6 +21,14 @@ export const AUTOCOMPLETE_SELECT_KEY = { | ||
| 21 | 21 | 'ENTER': 2, // 2^1 |
| 22 | 22 | }; |
| 23 | 23 | |
| 24 | +/** @readonly */ | |
| 25 | +/** @enum {Number} */ | |
| 26 | +export const AUTOCOMPLETE_STATE = { | |
| 27 | + DISABLED: 0, | |
| 28 | + MIN_LENGTH: 1, | |
| 29 | + ALWAYS: 2, | |
| 30 | +}; | |
| 31 | + | |
| 24 | 32 | export class AutoComplete { |
| 25 | 33 | /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea; |
| 26 | 34 | /**@type {boolean}*/ isFloating = false; |
| @@ -254,8 +262,7 @@ export class AutoComplete { | ||
| 254 | 262 | + this.parserResult.name.length |
| 255 | 263 | + (this.startQuote ? 1 : 0) |
| 256 | 264 | + (this.endQuote ? 1 : 0) |
| 257 | 265 | + 1; |
| 258 | - ; | |
| 259 | 266 | } |
| 260 | 267 | |
| 261 | 268 | /** |
| @@ -387,8 +394,7 @@ export class AutoComplete { | ||
| 387 | 394 | return option; |
| 388 | 395 | }) |
| 389 | 396 | // sort by fuzzy score or alphabetical |
| 390 | 397 | .toSorted(this.matchType == 'fuzzy' ? this.fuzzyScoreCompare : (a, b) => a.name.localeCompare(b.name)); |
| 391 | - ; | |
| 392 | 398 | |
| 393 | 399 | |
| 394 | 400 | |
| @@ -1,20 +1,48 @@ | ||
| 1 | 1 | import { Fuse, localforage } from '../lib.js'; |
| 2 | - | |
| 2 | +import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js'; | |
| 3 | -import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js'; | |
| 4 | 3 | import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js'; |
| 5 | 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 6 | 5 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 7 | 6 | import { createThumbnail, flashHighlight, getBase64Async, stringFormat } from './utils.js'; |
| 8 | 7 | import { t } from './i18n.js'; |
| 9 | 8 | import { Popup } from './popup.js'; |
| 10 | 9 | |
| 11 | 10 | const BG_METADATA_KEY = 'custom_background'; |
| 12 | 11 | const LIST_METADATA_KEY = 'chat_backgrounds'; |
| 13 | 12 | |
| 13 | +// A single transparent PNG pixel used as a placeholder for errored backgrounds | |
| 14 | +const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; | |
| 15 | +const PNG_PIXEL_BLOB = new Blob([Uint8Array.from(atob(PNG_PIXEL), c => c.charCodeAt(0))], { type: 'image/png' }); | |
| 16 | +const PLACEHOLDER_IMAGE = `url('data:image/png;base64,${PNG_PIXEL}')`; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Storage for frontend-generated background thumbnails. | |
| 20 | + * This is used to store thumbnails for backgrounds that cannot be generated on the server. | |
| 21 | + */ | |
| 22 | +const THUMBNAIL_STORAGE = localforage.createInstance({ name: 'SillyTavern_Thumbnails' }); | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Cache for thumbnail blob URLs. | |
| 26 | + * @type {Map<string, string>} | |
| 27 | + */ | |
| 28 | +const THUMBNAIL_BLOBS = new Map(); | |
| 29 | + | |
| 30 | +const THUMBNAIL_CONFIG = { | |
| 31 | + width: 160, | |
| 32 | + height: 90, | |
| 33 | +}; | |
| 34 | + | |
| 35 | +/** | |
| 36 | + * Global IntersectionObserver instance for lazy loading backgrounds | |
| 37 | + * @type {IntersectionObserver|null} | |
| 38 | + */ | |
| 39 | +let lazyLoadObserver = null; | |
| 40 | + | |
| 14 | 41 | export let background_settings = { |
| 15 | 42 | name: '__transparent.png', |
| 16 | 43 | url: generateUrlParameter('__transparent.png', false), |
| 17 | 44 | fitting: 'classic', |
| 45 | + animation: false, | |
| 18 | 46 | }; |
| 19 | 47 | |
| 20 | 48 | export function loadBackgroundSettings(settings) { |
| @@ -25,16 +53,20 @@ export function loadBackgroundSettings(settings) { | ||
| 25 | 53 | if (!backgroundSettings.fitting) { |
| 26 | 54 | backgroundSettings.fitting = 'classic'; |
| 27 | 55 | } |
| 56 | + if (!Object.hasOwn(backgroundSettings, 'animation')) { | |
| 57 | + backgroundSettings.animation = false; | |
| 58 | + } | |
| 28 | 59 | setBackground(backgroundSettings.name, backgroundSettings.url); |
| 29 | 60 | setFittingClass(backgroundSettings.fitting); |
| 30 | 61 | $('#background_fitting').val(backgroundSettings.fitting); |
| 62 | + $('#background_thumbnails_animation').prop('checked', background_settings.animation); | |
| 31 | 63 | } |
| 32 | 64 | |
| 33 | 65 | /** |
| 34 | 66 | * Sets the background for the current chat and adds it to the list of custom backgrounds. |
| 35 | 67 | * @param {{url: string, path:string}} backgroundInfo |
| 36 | 68 | */ |
| 37 | 69 | async function forceSetBackground(backgroundInfo) { |
| 38 | 70 | saveBackgroundMetadata(backgroundInfo.url); |
| 39 | 71 | setCustomBackground(); |
| 40 | 72 | |
| @@ -43,7 +75,7 @@ function forceSetBackground(backgroundInfo) { | ||
| 43 | 75 | list.push(bg); |
| 44 | 76 | chat_metadata[LIST_METADATA_KEY] = list; |
| 45 | 77 | saveMetadataDebounced(); |
| 46 | 78 | await getChatBackgroundsList(); |
| 47 | 79 | highlightNewBackground(bg); |
| 48 | 80 | highlightLockedBackground(); |
| 49 | 81 | } |
| @@ -56,11 +88,11 @@ async function onChatChanged() { | ||
| 56 | 88 | unsetCustomBackground(); |
| 57 | 89 | } |
| 58 | 90 | |
| 59 | 91 | await getChatBackgroundsList(); |
| 60 | 92 | highlightLockedBackground(); |
| 61 | 93 | } |
| 62 | 94 | |
| 63 | 95 | async function getChatBackgroundsList() { |
| 64 | 96 | const list = chat_metadata[LIST_METADATA_KEY]; |
| 65 | 97 | const listEmpty = !Array.isArray(list) || list.length === 0; |
| 66 | 98 | |
| @@ -72,9 +104,10 @@ function getChatBackgroundsList() { | ||
| 72 | 104 | } |
| 73 | 105 | |
| 74 | 106 | for (const bg of list) { |
| 75 | 107 | const template = await getBackgroundFromTemplate(bg, true); |
| 76 | 108 | $('#bg_custom_content').append(template); |
| 77 | 109 | } |
| 110 | + activateLazyLoader(); | |
| 78 | 111 | } |
| 79 | 112 | |
| 80 | 113 | function getBackgroundPath(fileUrl) { |
| @@ -224,7 +257,49 @@ async function onCopyToSystemBackgroundClick(e) { | ||
| 224 | 257 | const index = list.indexOf(bgNames.oldBg); |
| 225 | 258 | list.splice(index, 1); |
| 226 | 259 | saveMetadataDebounced(); |
| 227 | 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 | + */ | |
| 270 | +async function getThumbnailFromStorage(bg) { | |
| 271 | + const cachedBlobUrl = THUMBNAIL_BLOBS.get(bg); | |
| 272 | + if (cachedBlobUrl) { | |
| 273 | + return cachedBlobUrl; | |
| 274 | + } | |
| 275 | + | |
| 276 | + const savedBlob = await THUMBNAIL_STORAGE.getItem(bg); | |
| 277 | + if (savedBlob) { | |
| 278 | + const savedBlobUrl = URL.createObjectURL(savedBlob); | |
| 279 | + THUMBNAIL_BLOBS.set(bg, savedBlobUrl); | |
| 280 | + return savedBlobUrl; | |
| 281 | + } | |
| 282 | + | |
| 283 | + try { | |
| 284 | + const response = await fetch(getBackgroundPath(bg), { cache: 'force-cache' }); | |
| 285 | + if (!response.ok) { | |
| 286 | + throw new Error('Fetch failed with status: ' + response.status); | |
| 287 | + } | |
| 288 | + const imageBlob = await response.blob(); | |
| 289 | + const imageBase64 = await getBase64Async(imageBlob); | |
| 290 | + const thumbnailBase64 = await createThumbnail(imageBase64, THUMBNAIL_CONFIG.width, THUMBNAIL_CONFIG.height); | |
| 291 | + const thumbnailBlob = await fetch(thumbnailBase64).then(res => res.blob()); | |
| 292 | + await THUMBNAIL_STORAGE.setItem(bg, thumbnailBlob); | |
| 293 | + const blobUrl = URL.createObjectURL(thumbnailBlob); | |
| 294 | + THUMBNAIL_BLOBS.set(bg, blobUrl); | |
| 295 | + return blobUrl; | |
| 296 | + } catch (error) { | |
| 297 | + console.error('Error fetching thumbnail, fallback image will be used:', error); | |
| 298 | + const fallbackBlob = PNG_PIXEL_BLOB; | |
| 299 | + const fallbackBlobUrl = URL.createObjectURL(fallbackBlob); | |
| 300 | + THUMBNAIL_BLOBS.set(bg, fallbackBlobUrl); | |
| 301 | + return fallbackBlobUrl; | |
| 302 | + } | |
| 228 | 303 | } |
| 229 | 304 | |
| 230 | 305 | /** |
| @@ -245,7 +320,7 @@ async function getNewBackgroundName(referenceElement) { | ||
| 245 | 320 | const fileExtension = oldBg.split('.').pop(); |
| 246 | 321 | const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg; |
| 247 | 322 | const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, ''); |
| 248 | 323 | const newBgExtensionless = await callPopupPopup.show.input('<h3>' + t`Enter new background name:` + '</h3>', 'input'null, oldBgExtensionless); |
| 249 | 324 | |
| 250 | 325 | if (!newBgExtensionless) { |
| 251 | 326 | console.debug('no new_bg_extensionless'); |
| @@ -327,7 +402,7 @@ async function onDeleteBackgroundClick(e) { | ||
| 327 | 402 | } |
| 328 | 403 | |
| 329 | 404 | if (isCustom) { |
| 330 | 405 | await getChatBackgroundsList(); |
| 331 | 406 | saveMetadataDebounced(); |
| 332 | 407 | } |
| 333 | 408 | } |
| @@ -372,22 +447,55 @@ export async function getBackgrounds() { | ||
| 372 | 447 | const response = await fetch('/api/backgrounds/all', { |
| 373 | 448 | method: 'POST', |
| 374 | 449 | headers: getRequestHeaders(), |
| 375 | 450 | body: JSON.stringify({}), |
| 376 | - '': '', | |
| 377 | - }), | |
| 378 | 451 | }); |
| 379 | 452 | if (response.ok) { |
| 380 | 453 | const getData{ images, config } = await response.json(); |
| 381 | - //background = getData; | |
| 454 | + Object.assign(THUMBNAIL_CONFIG, config); | |
| 382 | - //console.log(getData.length); | |
| 383 | 455 | $('#bg_menu_content').children('div').remove(); |
| 384 | 456 | for (const bg of getDataimages) { |
| 385 | 457 | const template = await getBackgroundFromTemplate(bg, false); |
| 386 | 458 | $('#bg_menu_content').append(template); |
| 387 | 459 | } |
| 460 | + activateLazyLoader(); | |
| 388 | 461 | } |
| 389 | 462 | } |
| 390 | 463 | |
| 464 | +function activateLazyLoader() { | |
| 465 | + // Disconnect previous observer to prevent memory leaks | |
| 466 | + if (lazyLoadObserver) { | |
| 467 | + lazyLoadObserver.disconnect(); | |
| 468 | + lazyLoadObserver = null; | |
| 469 | + } | |
| 470 | + | |
| 471 | + const lazyLoadElements = document.querySelectorAll('.lazy-load-background'); | |
| 472 | + | |
| 473 | + const options = { | |
| 474 | + root: null, | |
| 475 | + rootMargin: '200px', | |
| 476 | + threshold: 0.01, | |
| 477 | + }; | |
| 478 | + | |
| 479 | + lazyLoadObserver = new IntersectionObserver((entries, observer) => { | |
| 480 | + entries.forEach(entry => { | |
| 481 | + if (entry.target instanceof HTMLElement && entry.isIntersecting) { | |
| 482 | + const target = entry.target; | |
| 483 | + const bg = target.getAttribute('bgfile'); | |
| 484 | + const isCustom = target.getAttribute('custom') === 'true'; | |
| 485 | + resolveImageUrl(bg, isCustom) | |
| 486 | + .then(url => { target.style.backgroundImage = url; }) | |
| 487 | + .catch(() => { target.style.backgroundImage = PLACEHOLDER_IMAGE; }); | |
| 488 | + target.classList.remove('lazy-load-background'); | |
| 489 | + observer.unobserve(target); | |
| 490 | + } | |
| 491 | + }); | |
| 492 | + }, options); | |
| 493 | + | |
| 494 | + lazyLoadElements.forEach(element => { | |
| 495 | + lazyLoadObserver.observe(element); | |
| 496 | + }); | |
| 497 | +} | |
| 498 | + | |
| 391 | 499 | /** |
| 392 | 500 | * Gets the CSS URL of the background |
| 393 | 501 | * @param {Element} block |
| @@ -402,22 +510,41 @@ function generateUrlParameter(bg, isCustom) { | ||
| 402 | 510 | } |
| 403 | 511 | |
| 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 | + */ | |
| 518 | +async function resolveImageUrl(bg, isCustom) { | |
| 519 | + const fileExtension = bg.split('.').pop().toLowerCase(); | |
| 520 | + const isAnimated = ['mp4', 'webp'].includes(fileExtension); | |
| 521 | + const thumbnailUrl = isAnimated && !background_settings.animation | |
| 522 | + ? await getThumbnailFromStorage(bg) | |
| 523 | + : isCustom | |
| 524 | + ? bg | |
| 525 | + : getThumbnailUrl('bg', bg); | |
| 526 | + | |
| 527 | + return `url('${thumbnailUrl}')`; | |
| 528 | +} | |
| 529 | + | |
| 530 | +/** | |
| 405 | 531 | * Instantiates a background template |
| 406 | 532 | * @param {string} bg Path to background |
| 407 | 533 | * @param {boolean} isCustom Whether the background is custom |
| 408 | 534 | * @returns {Promise<JQuery<HTMLElement>>} Background template |
| 409 | 535 | */ |
| 410 | 536 | async function getBackgroundFromTemplate(bg, isCustom) { |
| 411 | 537 | const template = $('#background_template .bg_example').clone(); |
| 412 | - const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg); | |
| 413 | 538 | const url = generateUrlParameter(bg, isCustom); |
| 414 | 539 | const title = isCustom ? bg.split('/').pop() : bg; |
| 415 | 540 | const friendlyTitle = title.slice(0, title.lastIndexOf('.')); |
| 541 | + | |
| 416 | 542 | template.attr('title', title); |
| 417 | 543 | template.attr('bgfile', bg); |
| 418 | 544 | template.attr('custom', String(isCustom)); |
| 419 | 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 | 548 | template.find('.BGSampleTitle').text(friendlyTitle); |
| 422 | 549 | return template; |
| 423 | 550 | } |
| @@ -437,6 +564,12 @@ async function delBackground(bg) { | ||
| 437 | 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 | } |
| 441 | 574 | |
| 442 | 575 | async function onBackgroundUploadSelected() { |
| @@ -572,17 +705,20 @@ export function initBackgrounds() { | ||
| 572 | 705 | $('#auto_background').on('click', autoBackgroundCommand); |
| 573 | 706 | $('#add_bg_button').on('change', onBackgroundUploadSelected); |
| 574 | 707 | $('#bg-filter').on('input', onBackgroundFilterInput); |
| 575 | 708 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg', |
| 709 | + name: 'lockbg', | |
| 576 | 710 | callback: () => onLockBackgroundClick(new CustomEvent('click')), |
| 577 | 711 | aliases: ['bglock'], |
| 578 | 712 | helpString: 'Locks a background for the currently selected chat', |
| 579 | 713 | })); |
| 580 | 714 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg', |
| 715 | + name: 'unlockbg', | |
| 581 | 716 | callback: () => onUnlockBackgroundClick(new CustomEvent('click')), |
| 582 | 717 | aliases: ['bgunlock'], |
| 583 | 718 | helpString: 'Unlocks a background for the currently selected chat', |
| 584 | 719 | })); |
| 585 | 720 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'autobg', |
| 721 | + name: 'autobg', | |
| 586 | 722 | callback: autoBackgroundCommand, |
| 587 | 723 | aliases: ['bgauto'], |
| 588 | 724 | helpString: 'Automatically changes the background based on the chat context using the AI request prompt', |
| @@ -593,4 +729,13 @@ export function initBackgrounds() { | ||
| 593 | 729 | setFittingClass(background_settings.fitting); |
| 594 | 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 | } |
| @@ -14,7 +14,6 @@ import { | ||
| 14 | 14 | } from '../script.js'; |
| 15 | 15 | import { humanizedDateTime } from './RossAscends-mods.js'; |
| 16 | 16 | import { |
| 17 | - getGroupPastChats, | |
| 18 | 17 | group_activation_strategy, |
| 19 | 18 | groups, |
| 20 | 19 | openGroupById, |
| @@ -40,22 +39,42 @@ import { | ||
| 40 | 39 | |
| 41 | 40 | const bookmarkNameToken = 'Checkpoint #'; |
| 42 | 41 | |
| 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 | + */ | |
| 43 | 46 | async function getExistingChatNames() { |
| 44 | 47 | if (selected_group) { |
| 45 | 48 | const datagroup = await getGroupPastChatsgroups.find(x => x.id == selected_group); |
| 46 | - return data.map(x => x.file_name); | |
| 49 | + if (group && Array.isArray(group.chats)) { | |
| 47 | - } else { | |
| 50 | + return [...group.chats]; | |
| 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 | 65 | const response = await fetch('/api/characters/chats', { |
| 49 | 66 | method: 'POST', |
| 50 | 67 | headers: getRequestHeaders(), |
| 51 | 68 | body: JSON.stringify({ avatar_url: characters[this_chid]character.avatar, simple: true }), |
| 52 | 69 | }); |
| 53 | 70 | |
| 54 | 71 | if (response.ok) { |
| 55 | 72 | const data = await response.json(); |
| 56 | 73 | return const chats = Object.values(data).map(x => x.file_name.replace('.jsonl', '')); |
| 57 | - } | |
| 74 | + return [...chats]; | |
| 58 | 75 | } |
| 76 | + | |
| 77 | + return []; | |
| 59 | 78 | } |
| 60 | 79 | |
| 61 | 80 | async function getBookmarkName({ isReplace = false, forceName = null } = {}) { |
| @@ -211,6 +211,12 @@ export async function populateFileAttachment(message, inputId = 'file_form_input | ||
| 211 | 211 | const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension); |
| 212 | 212 | message.extra.image = imageUrl; |
| 213 | 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 | 220 | } else { |
| 215 | 221 | const uniqueFileName = `${fileNamePrefix}.txt`; |
| 216 | 222 | |
| @@ -279,9 +279,9 @@ export class TextCompletionService { | ||
| 279 | 279 | instructPreset, |
| 280 | 280 | ); |
| 281 | 281 | } else { // e.g. "<|im_start|>assistant: Hello, my name is" |
| 282 | 282 | const overridenInstructPresetoverriddenInstructPreset = structuredClone(instructPreset); |
| 283 | 283 | overridenInstructPresetoverriddenInstructPreset.output_suffix = ''; |
| 284 | 284 | overridenInstructPresetoverriddenInstructPreset.wrap = false; |
| 285 | 285 | messageContent = formatInstructModeChat( |
| 286 | 286 | message.role, |
| 287 | 287 | message.content, |
| @@ -291,7 +291,7 @@ export class TextCompletionService { | ||
| 291 | 291 | undefined, |
| 292 | 292 | undefined, |
| 293 | 293 | undefined, |
| 294 | 294 | overridenInstructPresetoverriddenInstructPreset, |
| 295 | 295 | ); |
| 296 | 296 | } |
| 297 | 297 | } |
| @@ -0,0 +1,393 @@ | ||
| 1 | +import { getRequestHeaders } from '../script.js'; | |
| 2 | +import { t } from './i18n.js'; | |
| 3 | +import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; | |
| 4 | +import { renderTemplateAsync } from './templates.js'; | |
| 5 | +import { humanFileSize, timestampToMoment } from './utils.js'; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * @typedef {object} DataMaidReportResult | |
| 9 | + * @property {import('../../src/endpoints/data-maid.js').DataMaidSanitizedReport} report - The sanitized report of the Data Maid. | |
| 10 | + * @property {string} token - The token to use for the Data Maid report. | |
| 11 | + */ | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Data Maid Dialog class for managing the cleanup dialog interface. | |
| 15 | + */ | |
| 16 | +class DataMaidDialog { | |
| 17 | + constructor() { | |
| 18 | + this.token = null; | |
| 19 | + this.container = null; | |
| 20 | + this.isScanning = false; | |
| 21 | + | |
| 22 | + this.DATA_MAID_CATEGORIES = { | |
| 23 | + files: { | |
| 24 | + name: t`Files`, | |
| 25 | + description: t`Files that are not associated with chat messages or Data Bank. WILL DELETE MANUAL UPLOADS!`, | |
| 26 | + }, | |
| 27 | + images: { | |
| 28 | + name: t`Images`, | |
| 29 | + description: t`Images that are not associated with chat messages. WILL DELETE MANUAL UPLOADS!`, | |
| 30 | + }, | |
| 31 | + chats: { | |
| 32 | + name: t`Chats`, | |
| 33 | + description: t`Chat files associated with deleted characters.`, | |
| 34 | + }, | |
| 35 | + groupChats: { | |
| 36 | + name: t`Group Chats`, | |
| 37 | + description: t`Chat files associated with deleted groups.`, | |
| 38 | + }, | |
| 39 | + avatarThumbnails: { | |
| 40 | + name: t`Avatar Thumbnails`, | |
| 41 | + description: t`Thumbnails for avatars of missing or deleted characters.`, | |
| 42 | + }, | |
| 43 | + backgroundThumbnails: { | |
| 44 | + name: t`Background Thumbnails`, | |
| 45 | + description: t`Thumbnails for missing or deleted backgrounds.`, | |
| 46 | + }, | |
| 47 | + chatBackups: { | |
| 48 | + name: t`Chat Backups`, | |
| 49 | + description: t`Automatically generated chat backups.`, | |
| 50 | + }, | |
| 51 | + settingsBackups: { | |
| 52 | + name: t`Settings Backups`, | |
| 53 | + description: t`Automatically generated settings backups.`, | |
| 54 | + }, | |
| 55 | + }; | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * Returns a promise that resolves to the Data Maid report. | |
| 60 | + * @returns {Promise<DataMaidReportResult>} | |
| 61 | + * @private | |
| 62 | + */ | |
| 63 | + async getReport() { | |
| 64 | + const response = await fetch('/api/data-maid/report', { | |
| 65 | + method: 'POST', | |
| 66 | + headers: getRequestHeaders(), | |
| 67 | + }); | |
| 68 | + | |
| 69 | + if (!response.ok) { | |
| 70 | + throw new Error(`Error fetching Data Maid report: ${response.statusText}`); | |
| 71 | + } | |
| 72 | + | |
| 73 | + return await response.json(); | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Finalizes the Data Maid process by sending a request to the server. | |
| 78 | + * @returns {Promise<void>} | |
| 79 | + * @private | |
| 80 | + */ | |
| 81 | + async finalize() { | |
| 82 | + const response = await fetch('/api/data-maid/finalize', { | |
| 83 | + method: 'POST', | |
| 84 | + headers: getRequestHeaders(), | |
| 85 | + body: JSON.stringify({ token: this.token }), | |
| 86 | + }); | |
| 87 | + | |
| 88 | + if (!response.ok) { | |
| 89 | + throw new Error(`Error finalizing Data Maid: ${response.statusText}`); | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * Sets up the dialog UI elements and event listeners. | |
| 95 | + * @private | |
| 96 | + */ | |
| 97 | + async setupDialogUI() { | |
| 98 | + const template = await renderTemplateAsync('dataMaidDialog'); | |
| 99 | + this.container = document.createElement('div'); | |
| 100 | + this.container.classList.add('dataMaidDialogContainer'); | |
| 101 | + this.container.innerHTML = template; | |
| 102 | + | |
| 103 | + const startButton = this.container.querySelector('.dataMaidStartButton'); | |
| 104 | + startButton.addEventListener('click', () => this.handleScanClick()); | |
| 105 | + } | |
| 106 | + | |
| 107 | + /** | |
| 108 | + * Handles the scan button click event. | |
| 109 | + * @private | |
| 110 | + */ | |
| 111 | + async handleScanClick() { | |
| 112 | + if (this.isScanning) { | |
| 113 | + toastr.warning(t`The scan is already running. Please wait for it to finish.`); | |
| 114 | + return; | |
| 115 | + } | |
| 116 | + | |
| 117 | + try { | |
| 118 | + const resultsList = this.container.querySelector('.dataMaidResultsList'); | |
| 119 | + resultsList.innerHTML = ''; | |
| 120 | + this.showSpinner(); | |
| 121 | + this.isScanning = true; | |
| 122 | + | |
| 123 | + const report = await this.getReport(); | |
| 124 | + | |
| 125 | + this.hideSpinner(); | |
| 126 | + await this.renderReport(report, resultsList); | |
| 127 | + this.token = report.token; | |
| 128 | + } catch (error) { | |
| 129 | + this.hideSpinner(); | |
| 130 | + toastr.error(t`An error has occurred. Check the console for details.`); | |
| 131 | + console.error('Error generating Data Maid report:', error); | |
| 132 | + } finally { | |
| 133 | + this.isScanning = false; | |
| 134 | + } | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * Shows the loading spinner and hides the placeholder. | |
| 139 | + * @private | |
| 140 | + */ | |
| 141 | + showSpinner() { | |
| 142 | + const spinner = this.container.querySelector('.dataMaidSpinner'); | |
| 143 | + const placeholder = this.container.querySelector('.dataMaidPlaceholder'); | |
| 144 | + placeholder.classList.add('displayNone'); | |
| 145 | + spinner.classList.remove('displayNone'); | |
| 146 | + } | |
| 147 | + | |
| 148 | + /** | |
| 149 | + * Hides the loading spinner. | |
| 150 | + * @private | |
| 151 | + */ | |
| 152 | + hideSpinner() { | |
| 153 | + const spinner = this.container.querySelector('.dataMaidSpinner'); | |
| 154 | + spinner.classList.add('displayNone'); | |
| 155 | + } | |
| 156 | + | |
| 157 | + /** | |
| 158 | + * Renders the Data Maid report into the results list. | |
| 159 | + * @param {DataMaidReportResult} report | |
| 160 | + * @param {Element} resultsList | |
| 161 | + * @private | |
| 162 | + */ | |
| 163 | + async renderReport(report, resultsList) { | |
| 164 | + for (const [prop, data] of Object.entries(this.DATA_MAID_CATEGORIES)) { | |
| 165 | + const category = await this.renderCategory(prop, data.name, data.description, report.report[prop]); | |
| 166 | + if (!category) { | |
| 167 | + continue; | |
| 168 | + } | |
| 169 | + resultsList.appendChild(category); | |
| 170 | + } | |
| 171 | + this.displayEmptyPlaceholder(); | |
| 172 | + } | |
| 173 | + | |
| 174 | + /** | |
| 175 | + * Displays a placeholder message if no items are found in the results list. | |
| 176 | + * @private | |
| 177 | + */ | |
| 178 | + displayEmptyPlaceholder() { | |
| 179 | + const resultsList = this.container.querySelector('.dataMaidResultsList'); | |
| 180 | + if (resultsList.children.length === 0) { | |
| 181 | + const placeholder = this.container.querySelector('.dataMaidPlaceholder'); | |
| 182 | + placeholder.classList.remove('displayNone'); | |
| 183 | + placeholder.textContent = t`No items found to clean up. Come back later!`; | |
| 184 | + } | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * Renders a single Data Maid category into a DOM element. | |
| 189 | + * @param {string} prop Property name for the category | |
| 190 | + * @param {string} name Name of the category | |
| 191 | + * @param {string} description Description of the category | |
| 192 | + * @param {import('../../src/endpoints/data-maid.js').DataMaidSanitizedRecord[]} items List of items in the category | |
| 193 | + * @return {Promise<Element|null>} A promise that resolves to a DOM element containing the rendered category | |
| 194 | + * @private | |
| 195 | + */ | |
| 196 | + async renderCategory(prop, name, description, items) { | |
| 197 | + if (!Array.isArray(items) || items.length === 0) { | |
| 198 | + return null; | |
| 199 | + } | |
| 200 | + | |
| 201 | + const viewModel = { | |
| 202 | + name: name, | |
| 203 | + description: description, | |
| 204 | + totalSize: humanFileSize(items.reduce((sum, item) => sum + item.size, 0)), | |
| 205 | + totalItems: items.length, | |
| 206 | + items: items.sort((a, b) => b.mtime - a.mtime).map(item => ({ | |
| 207 | + ...item, | |
| 208 | + size: humanFileSize(item.size), | |
| 209 | + date: timestampToMoment(item.mtime).format('L LT'), | |
| 210 | + })), | |
| 211 | + }; | |
| 212 | + | |
| 213 | + const template = await renderTemplateAsync('dataMaidCategory', viewModel); | |
| 214 | + const categoryElement = document.createElement('div'); | |
| 215 | + categoryElement.innerHTML = template; | |
| 216 | + categoryElement.querySelectorAll('.dataMaidItemView').forEach(button => { | |
| 217 | + button.addEventListener('click', async () => { | |
| 218 | + const item = button.closest('.dataMaidItem'); | |
| 219 | + const hash = item?.getAttribute('data-hash'); | |
| 220 | + if (hash) { | |
| 221 | + await this.view(prop, hash); | |
| 222 | + } | |
| 223 | + }); | |
| 224 | + }); | |
| 225 | + categoryElement.querySelectorAll('.dataMaidItemDownload').forEach(button => { | |
| 226 | + button.addEventListener('click', async () => { | |
| 227 | + const item = button.closest('.dataMaidItem'); | |
| 228 | + const hash = item?.getAttribute('data-hash'); | |
| 229 | + if (hash) { | |
| 230 | + await this.download(items, hash); | |
| 231 | + } | |
| 232 | + }); | |
| 233 | + }); | |
| 234 | + categoryElement.querySelectorAll('.dataMaidDeleteAll').forEach(button => { | |
| 235 | + button.addEventListener('click', async (event) => { | |
| 236 | + event.stopPropagation(); | |
| 237 | + const confirm = await Popup.show.confirm(t`Are you sure?`, t`This will permanently delete all files in this category. THIS CANNOT BE UNDONE!`); | |
| 238 | + if (!confirm) { | |
| 239 | + return; | |
| 240 | + } | |
| 241 | + | |
| 242 | + const hashes = items.map(item => item.hash).filter(hash => hash); | |
| 243 | + await this.delete(hashes); | |
| 244 | + | |
| 245 | + categoryElement.remove(); | |
| 246 | + this.displayEmptyPlaceholder(); | |
| 247 | + }); | |
| 248 | + | |
| 249 | + }); | |
| 250 | + categoryElement.querySelectorAll('.dataMaidItemDelete').forEach(button => { | |
| 251 | + button.addEventListener('click', async () => { | |
| 252 | + const item = button.closest('.dataMaidItem'); | |
| 253 | + const hash = item?.getAttribute('data-hash'); | |
| 254 | + if (hash) { | |
| 255 | + const confirm = await Popup.show.confirm(t`Are you sure?`, t`This will permanently delete the file. THIS CANNOT BE UNDONE!`); | |
| 256 | + if (!confirm) { | |
| 257 | + return; | |
| 258 | + } | |
| 259 | + if (await this.delete([hash])) { | |
| 260 | + item.remove(); | |
| 261 | + items.splice(items.findIndex(i => i.hash === hash), 1); | |
| 262 | + if (items.length === 0) { | |
| 263 | + categoryElement.remove(); | |
| 264 | + this.displayEmptyPlaceholder(); | |
| 265 | + } | |
| 266 | + } | |
| 267 | + } | |
| 268 | + }); | |
| 269 | + }); | |
| 270 | + return categoryElement; | |
| 271 | + } | |
| 272 | + | |
| 273 | + /** | |
| 274 | + * Constructs the URL for viewing an item by its hash. | |
| 275 | + * @param {string} hash Hash of the item to view | |
| 276 | + * @returns {string} URL to view the item | |
| 277 | + * @private | |
| 278 | + */ | |
| 279 | + getViewUrl(hash) { | |
| 280 | + return `/api/data-maid/view?hash=${encodeURIComponent(hash)}&token=${encodeURIComponent(this.token)}`; | |
| 281 | + } | |
| 282 | + | |
| 283 | + /** | |
| 284 | + * Downloads an item by its hash. | |
| 285 | + * @param {import('../../src/endpoints/data-maid.js').DataMaidSanitizedRecord[]} items List of items in the category | |
| 286 | + * @param {string} hash Hash of the item to download | |
| 287 | + * @private | |
| 288 | + */ | |
| 289 | + async download(items, hash) { | |
| 290 | + const item = items.find(i => i.hash === hash); | |
| 291 | + if (!item) { | |
| 292 | + return; | |
| 293 | + } | |
| 294 | + const url = this.getViewUrl(hash); | |
| 295 | + const a = document.createElement('a'); | |
| 296 | + a.href = url; | |
| 297 | + a.download = item?.name || hash; | |
| 298 | + document.body.appendChild(a); | |
| 299 | + a.click(); | |
| 300 | + document.body.removeChild(a); | |
| 301 | + } | |
| 302 | + | |
| 303 | + /** | |
| 304 | + * Opens the item view for a specific hash. | |
| 305 | + * @param {string} prop Property name for the category | |
| 306 | + * @param {string} hash Item hash to view | |
| 307 | + * @private | |
| 308 | + */ | |
| 309 | + async view(prop, hash) { | |
| 310 | + const url = this.getViewUrl(hash); | |
| 311 | + const isImage = ['images', 'avatarThumbnails', 'backgroundThumbnails'].includes(prop); | |
| 312 | + const element = isImage | |
| 313 | + ? await this.getViewElement(url) | |
| 314 | + : await this.getTextViewElement(url); | |
| 315 | + await callGenericPopup(element, POPUP_TYPE.DISPLAY, '', { large: true, wide: true }); | |
| 316 | + } | |
| 317 | + | |
| 318 | + /** | |
| 319 | + * Deletes an item by its file path hash. | |
| 320 | + * @param {string[]} hashes Hashes of items to delete | |
| 321 | + * @return {Promise<boolean>} True if the deletion was successful, false otherwise | |
| 322 | + * @private | |
| 323 | + */ | |
| 324 | + async delete(hashes) { | |
| 325 | + try { | |
| 326 | + const response = await fetch('/api/data-maid/delete', { | |
| 327 | + method: 'POST', | |
| 328 | + headers: getRequestHeaders(), | |
| 329 | + body: JSON.stringify({ hashes: hashes, token: this.token }), | |
| 330 | + }); | |
| 331 | + | |
| 332 | + if (!response.ok) { | |
| 333 | + throw new Error(`Error deleting item: ${response.statusText}`); | |
| 334 | + } | |
| 335 | + | |
| 336 | + return true; | |
| 337 | + } catch (error) { | |
| 338 | + console.error('Error deleting item:', error); | |
| 339 | + return false; | |
| 340 | + } | |
| 341 | + } | |
| 342 | + | |
| 343 | + /** | |
| 344 | + * Gets an image element for viewing images. | |
| 345 | + * @param {string} url View URL | |
| 346 | + * @returns {Promise<HTMLElement>} Image element | |
| 347 | + * @private | |
| 348 | + */ | |
| 349 | + async getViewElement(url) { | |
| 350 | + const img = document.createElement('img'); | |
| 351 | + img.src = url; | |
| 352 | + img.classList.add('dataMaidImageView'); | |
| 353 | + return img; | |
| 354 | + } | |
| 355 | + | |
| 356 | + /** | |
| 357 | + * Gets an iframe element for viewing text content. | |
| 358 | + * @param {string} url View URL | |
| 359 | + * @returns {Promise<HTMLTextAreaElement>} Frame element | |
| 360 | + * @private | |
| 361 | + */ | |
| 362 | + async getTextViewElement(url) { | |
| 363 | + const response = await fetch(url); | |
| 364 | + const text = await response.text(); | |
| 365 | + const element = document.createElement('textarea'); | |
| 366 | + element.classList.add('dataMaidTextView'); | |
| 367 | + element.readOnly = true; | |
| 368 | + element.textContent = text; | |
| 369 | + return element; | |
| 370 | + } | |
| 371 | + | |
| 372 | + /** | |
| 373 | + * Opens the Data Maid dialog and handles the interaction. | |
| 374 | + */ | |
| 375 | + async open() { | |
| 376 | + await this.setupDialogUI(); | |
| 377 | + await callGenericPopup(this.container, POPUP_TYPE.TEXT, '', { wide: true, large: true }); | |
| 378 | + | |
| 379 | + if (this.token) { | |
| 380 | + await this.finalize(); | |
| 381 | + } | |
| 382 | + } | |
| 383 | +} | |
| 384 | + | |
| 385 | +export function initDataMaid() { | |
| 386 | + const dataMaidButton = document.getElementById('data_maid_button'); | |
| 387 | + if (!dataMaidButton) { | |
| 388 | + console.warn('Data Maid button not found'); | |
| 389 | + return; | |
| 390 | + } | |
| 391 | + | |
| 392 | + dataMaidButton.addEventListener('click', () => new DataMaidDialog().open()); | |
| 393 | +} | |
| @@ -36,7 +36,13 @@ export let modules = []; | ||
| 36 | 36 | * A set of active extensions. |
| 37 | 37 | * @type {Set<string>} |
| 38 | 38 | */ |
| 39 | 39 | letconst activeExtensions = new Set(); |
| 40 | + | |
| 41 | +/** | |
| 42 | + * Errors that occurred while loading extensions. | |
| 43 | + * @type {Set<string>} | |
| 44 | + */ | |
| 45 | +const extensionLoadErrors = new Set(); | |
| 40 | 46 | |
| 41 | 47 | const getApiUrl = () => extension_settings.apiUrl; |
| 42 | 48 | const 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 | 178 | llmPrompt: undefined, |
| 173 | 179 | allowMultiple: true, |
| 174 | 180 | rerollIfSame: false, |
| 181 | + promptType: 'raw', | |
| 175 | 182 | }, |
| 176 | 183 | connectionManager: { |
| 177 | 184 | selectedProfile: '', |
| @@ -379,37 +386,90 @@ async function getManifests(names) { | ||
| 379 | 386 | * @returns {Promise<void>} |
| 380 | 387 | */ |
| 381 | 388 | async function activateExtensions() { |
| 389 | + extensionLoadErrors.clear(); | |
| 382 | 390 | const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1])); |
| 391 | + const extensionNames = extensions.map(x => x[0]); | |
| 383 | 392 | const promises = []; |
| 384 | 393 | |
| 385 | 394 | for (let entry of extensions) { |
| 386 | 395 | const name = entry[0]; |
| 387 | 396 | const manifest = entry[1]; |
| 397 | + const extrasRequirements = manifest.requires; | |
| 398 | + const extensionDependencies = manifest.dependencies; | |
| 399 | + const displayName = manifest.display_name || name; | |
| 388 | 400 | |
| 389 | 401 | if (activeExtensions.has(name)) { |
| 390 | 402 | continue; |
| 391 | 403 | } |
| 392 | 404 | |
| 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 | 439 | const isDisabled = extension_settings.disabledExtensions.includes(name); |
| 395 | 440 | |
| 396 | 441 | if (meetsModuleRequirements && meetsExtensionDeps && !isDisabled) { |
| 397 | 442 | try { |
| 398 | 443 | console.debug('Activating extension', name); |
| 399 | 444 | const promise = addExtensionLocale(name, manifest).finally(() => Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)])); |
| 445 | + Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]), | |
| 446 | + ); | |
| 400 | 447 | await promise |
| 401 | 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 | 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 | } |
| 411 | 470 | |
| 412 | 471 | await Promise.allSettled(promises); |
| 472 | + $('#extensions_details').toggleClass('warning', extensionLoadErrors.size > 0); | |
| 413 | 473 | } |
| 414 | 474 | |
| 415 | 475 | async function connectClickHandler() { |
| @@ -752,6 +812,27 @@ function getModuleInformation() { | ||
| 752 | 812 | } |
| 753 | 813 | |
| 754 | 814 | /** |
| 815 | + * Generates HTML for the extension load errors. | |
| 816 | + * @returns {string} HTML string containing the errors that occurred while loading extensions. | |
| 817 | + */ | |
| 818 | +function getExtensionLoadErrorsHtml() { | |
| 819 | + if (extensionLoadErrors.size === 0) { | |
| 820 | + return ''; | |
| 821 | + } | |
| 822 | + | |
| 823 | + const container = document.createElement('div'); | |
| 824 | + container.classList.add('info-block', 'error'); | |
| 825 | + | |
| 826 | + for (const error of extensionLoadErrors) { | |
| 827 | + const errorElement = document.createElement('div'); | |
| 828 | + errorElement.textContent = error; | |
| 829 | + container.appendChild(errorElement); | |
| 830 | + } | |
| 831 | + | |
| 832 | + return container.outerHTML; | |
| 833 | +} | |
| 834 | + | |
| 835 | +/** | |
| 755 | 836 | * Generates the HTML strings for all extensions and displays them in a popup. |
| 756 | 837 | */ |
| 757 | 838 | async function showExtensionsDetails() { |
| @@ -765,6 +846,7 @@ async function showExtensionsDetails() { | ||
| 765 | 846 | initialScrollTop = oldPopup.content.scrollTop; |
| 766 | 847 | await oldPopup.completeCancelled(); |
| 767 | 848 | } |
| 849 | + const htmlErrors = getExtensionLoadErrorsHtml(); | |
| 768 | 850 | const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>'); |
| 769 | 851 | const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>'); |
| 770 | 852 | const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5"> |
| @@ -787,6 +869,7 @@ async function showExtensionsDetails() { | ||
| 787 | 869 | |
| 788 | 870 | const html = $('<div></div>') |
| 789 | 871 | .addClass('extensions_info') |
| 872 | + .append(htmlErrors) | |
| 790 | 873 | .append(htmlDefault) |
| 791 | 874 | .append(htmlExternal) |
| 792 | 875 | .append(getModuleInformation()); |
| @@ -893,11 +976,14 @@ async function onUpdateClick() { | ||
| 893 | 976 | * Updates a third-party extension via the API. |
| 894 | 977 | * @param {string} extensionName Extension folder name |
| 895 | 978 | * @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 | */ |
| 897 | 981 | async function updateExtension(extensionName, quiet, timeout = null) { |
| 898 | 982 | try { |
| 983 | + const signal = timeout ? AbortSignal.timeout(timeout) : undefined; | |
| 899 | 984 | const response = await fetch('/api/extensions/update', { |
| 900 | 985 | method: 'POST', |
| 986 | + signal: signal, | |
| 901 | 987 | headers: getRequestHeaders(), |
| 902 | 988 | body: JSON.stringify({ |
| 903 | 989 | extensionName, |
| @@ -926,7 +1012,7 @@ async function updateExtension(extensionName, quiet) { | ||
| 926 | 1012 | toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`); |
| 927 | 1013 | } |
| 928 | 1014 | } catch (error) { |
| 929 | 1015 | console.error('ErrorExtension update error:', error); |
| 930 | 1016 | } |
| 931 | 1017 | } |
| 932 | 1018 | |
| @@ -1397,6 +1483,7 @@ async function autoUpdateExtensions(forceAll) { | ||
| 1397 | 1483 | const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 }); |
| 1398 | 1484 | const isCurrentUserAdmin = isAdmin(); |
| 1399 | 1485 | const promises = []; |
| 1486 | + const autoUpdateTimeout = 60 * 1000; | |
| 1400 | 1487 | for (const [id, manifest] of Object.entries(manifests)) { |
| 1401 | 1488 | const isDisabled = extension_settings.disabledExtensions.includes(id); |
| 1402 | 1489 | if (!forceAll && isDisabled) { |
| @@ -1410,7 +1497,7 @@ async function autoUpdateExtensions(forceAll) { | ||
| 1410 | 1497 | } |
| 1411 | 1498 | if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) { |
| 1412 | 1499 | console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`); |
| 1413 | 1500 | promises.push(updateExtension(id.replace('third-party', ''), true, autoUpdateTimeout)); |
| 1414 | 1501 | } |
| 1415 | 1502 | } |
| 1416 | 1503 | await Promise.allSettled(promises); |
| @@ -1453,7 +1540,7 @@ export async function runGenerationInterceptors(chat, contextSize, type) { | ||
| 1453 | 1540 | |
| 1454 | 1541 | /** |
| 1455 | 1542 | * Writes a field to the character's data extensions object. |
| 1456 | 1543 | * @param {number|string} characterId Index in the character array |
| 1457 | 1544 | * @param {string} key Field name |
| 1458 | 1545 | * @param {any} value Field value |
| 1459 | 1546 | * @returns {Promise<void>} When the field is written |
| @@ -53,11 +53,11 @@ function filterAssets() { | ||
| 53 | 53 | } |
| 54 | 54 | |
| 55 | 55 | const KNOWN_TYPES = { |
| 56 | 56 | 'extension': 't`Extensions'`, |
| 57 | 57 | 'character': 't`Characters'`, |
| 58 | 58 | 'ambient': 't`Ambient sounds'`, |
| 59 | 59 | 'bgm': 't`Background music'`, |
| 60 | 60 | 'blip': 't`Blip sounds'`, |
| 61 | 61 | }; |
| 62 | 62 | |
| 63 | 63 | async function downloadAssetsList(url) { |
| @@ -65,7 +65,7 @@ | ||
| 65 | 65 | .asset-download-button-text { |
| 66 | 66 | font: bold 20px "Quicksand", san-serif; |
| 67 | 67 | color: #ffffff; |
| 68 | 68 | transition: all 0.2svar(--animation-duration-2x); |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | 71 | .asset-download-button-loading .asset-download-button-text { |
| @@ -216,8 +216,37 @@ function cleanUpAttachments() { | ||
| 216 | 216 | } |
| 217 | 217 | } |
| 218 | 218 | |
| 219 | +/** | |
| 220 | + * Clean up character attachments when a character is deleted. | |
| 221 | + * @param {{character: import('../../char-data.js').v1CharData}} data Event data | |
| 222 | + */ | |
| 223 | +function cleanUpCharacterAttachments(data) { | |
| 224 | + const avatar = data?.character?.avatar; | |
| 225 | + if (!avatar) return; | |
| 226 | + if (Array.isArray(extension_settings?.character_attachments?.[avatar])) { | |
| 227 | + delete extension_settings.character_attachments[avatar]; | |
| 228 | + saveSettingsDebounced(); | |
| 229 | + } | |
| 230 | +} | |
| 231 | + | |
| 232 | +/** | |
| 233 | + * Handle character rename event to update character attachments. | |
| 234 | + * @param {string} oldAvatar Old avatar name | |
| 235 | + * @param {string} newAvatar New avatar name | |
| 236 | + */ | |
| 237 | +function handleCharacterRename(oldAvatar, newAvatar) { | |
| 238 | + if (!oldAvatar || !newAvatar) return; | |
| 239 | + if (Array.isArray(extension_settings?.character_attachments?.[oldAvatar])) { | |
| 240 | + extension_settings.character_attachments[newAvatar] = extension_settings.character_attachments[oldAvatar]; | |
| 241 | + delete extension_settings.character_attachments[oldAvatar]; | |
| 242 | + saveSettingsDebounced(); | |
| 243 | + } | |
| 244 | +} | |
| 245 | + | |
| 219 | 246 | jQuery(async () => { |
| 220 | 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 | 250 | const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {}); |
| 222 | 251 | const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {}); |
| 223 | 252 | $('#data_bank_wand_container').append(manageButton); |
| @@ -3,7 +3,7 @@ | ||
| 3 | 3 | File Name |
| 4 | 4 | </label> |
| 5 | 5 | <input type="text" class="text_pole" id="notepadFileName" name="notepadFileName" value="" /> |
| 6 | 6 | <labelslabel> |
| 7 | 7 | File Content |
| 8 | 8 | </label> |
| 9 | 9 | <textarea id="notepadFileContent" name="notepadFileContent" class="text_pole textarea_compact monospace flex1" placeholder="Enter your notes here."></textarea> |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | 3 | import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; |
| 4 | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | 6 | import { getMultimodalCaption } from '../shared.js'; |
| @@ -9,6 +9,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | ||
| 9 | 9 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 10 | 10 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 11 | 11 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 12 | +import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; | |
| 12 | 13 | export { MODULE_NAME }; |
| 13 | 14 | |
| 14 | 15 | const MODULE_NAME = 'caption'; |
| @@ -98,9 +99,9 @@ async function wrapCaptionTemplate(caption) { | ||
| 98 | 99 | let messageText = substituteParamsExtended(template, { caption: caption }); |
| 99 | 100 | |
| 100 | 101 | if (extension_settings.caption.refine_mode) { |
| 101 | 102 | messageText = await callPopupPopup.show.input( |
| 102 | 103 | '<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.', |
| 103 | - 'input', | |
| 104 | + 'Press "Cancel" to abort the caption sending.', | |
| 104 | 105 | messageText, |
| 105 | 106 | { rows: 5, okButton: 'Send' }); |
| 106 | 107 | |
| @@ -278,7 +279,7 @@ async function captionMultimodal(base64Img, externalPrompt) { | ||
| 278 | 279 | let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT; |
| 279 | 280 | |
| 280 | 281 | if (!externalPrompt && extension_settings.caption.prompt_ask) { |
| 281 | 282 | const customPrompt = await callPopupcallGenericPopup('<h3>Enter a comment or question:</h3>', 'input'POPUP_TYPE.INPUT, prompt, { rows: 2 }); |
| 282 | 283 | if (!customPrompt) { |
| 283 | 284 | throw new Error('User aborted the caption sending.'); |
| 284 | 285 | } |
| @@ -432,6 +433,7 @@ jQuery(async function () { | ||
| 432 | 433 | 'zerooneai': SECRET_KEYS.ZEROONEAI, |
| 433 | 434 | 'groq': SECRET_KEYS.GROQ, |
| 434 | 435 | 'cohere': SECRET_KEYS.COHERE, |
| 436 | + 'aimlapi': SECRET_KEYS.AIMLAPI, | |
| 435 | 437 | }; |
| 436 | 438 | |
| 437 | 439 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -18,6 +18,7 @@ | ||
| 18 | 18 | <label for="caption_multimodal_api" data-i18n="API">API</label> |
| 19 | 19 | <select id="caption_multimodal_api" class="flex1 text_pole"> |
| 20 | 20 | <option value="zerooneai">01.AI (Yi)</option> |
| 21 | + <option value="aimlapi">AI/ML API</option> | |
| 21 | 22 | <option value="anthropic">Anthropic</option> |
| 22 | 23 | <option value="cohere">Cohere</option> |
| 23 | 24 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> |
| @@ -85,11 +86,15 @@ | ||
| 85 | 86 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 86 | 87 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> |
| 87 | 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 | 91 | <option data-type="google" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> |
| 89 | 92 | <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option> |
| 90 | 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 | 95 | <option data-type="google" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> |
| 92 | 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 | 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 | 99 | <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option> |
| 95 | 100 | <option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option> |
| @@ -116,15 +121,37 @@ | ||
| 116 | 121 | <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option> |
| 117 | 122 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 118 | 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 | 126 | <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option> |
| 120 | 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 | 129 | <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option> |
| 122 | 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 | 132 | <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> |
| 124 | 133 | <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option> |
| 125 | 134 | <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> |
| 126 | 135 | <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> |
| 127 | 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 | 155 | <option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option> |
| 129 | 156 | <option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option> |
| 130 | 157 | <option data-type="openrouter" value="openai/gpt-4o-2024-05-13">openai/gpt-4o-2024-05-13</option> |
| @@ -13,6 +13,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | ||
| 13 | 13 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; |
| 14 | 14 | import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js'; |
| 15 | 15 | import { t } from '../../i18n.js'; |
| 16 | +import { getSecretLabelById } from '../../secrets.js'; | |
| 16 | 17 | |
| 17 | 18 | const MODULE_NAME = 'connection-manager'; |
| 18 | 19 | const NONE = '<None>'; |
| @@ -40,6 +41,8 @@ const CC_COMMANDS = [ | ||
| 40 | 41 | 'stop-strings', |
| 41 | 42 | 'start-reply-with', |
| 42 | 43 | 'reasoning-template', |
| 44 | + 'prompt-post-processing', | |
| 45 | + 'secret-id', | |
| 43 | 46 | ]; |
| 44 | 47 | |
| 45 | 48 | const TC_COMMANDS = [ |
| @@ -56,6 +59,7 @@ const TC_COMMANDS = [ | ||
| 56 | 59 | 'stop-strings', |
| 57 | 60 | 'start-reply-with', |
| 58 | 61 | 'reasoning-template', |
| 62 | + 'secret-id', | |
| 59 | 63 | ]; |
| 60 | 64 | |
| 61 | 65 | const FANCY_NAMES = { |
| @@ -73,6 +77,8 @@ const FANCY_NAMES = { | ||
| 73 | 77 | 'stop-strings': 'Custom Stopping Strings', |
| 74 | 78 | 'start-reply-with': 'Start Reply With', |
| 75 | 79 | 'reasoning-template': 'Reasoning Template', |
| 80 | + 'prompt-post-processing': 'Prompt Post-Processing', | |
| 81 | + 'secret-id': 'Secret', | |
| 76 | 82 | }; |
| 77 | 83 | |
| 78 | 84 | /** |
| @@ -342,6 +348,15 @@ function makeFancyProfile(profile) { | ||
| 342 | 348 | return acc; |
| 343 | 349 | } |
| 344 | 350 | |
| 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 | 360 | acc[value] = profile[key]; |
| 346 | 361 | return acc; |
| 347 | 362 | }, {}); |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { Fuse } from '../../../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js'; |
| 4 | 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; |
| 5 | 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 6 | 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; |
| @@ -86,6 +86,12 @@ const EXPRESSION_API = { | ||
| 86 | 86 | none: 99, |
| 87 | 87 | }; |
| 88 | 88 | |
| 89 | +/** @enum {string} */ | |
| 90 | +const PROMPT_TYPE = { | |
| 91 | + raw: 'raw', | |
| 92 | + full: 'full', | |
| 93 | +}; | |
| 94 | + | |
| 89 | 95 | let expressionsList = null; |
| 90 | 96 | let lastCharacter = undefined; |
| 91 | 97 | let lastMessage = null; |
| @@ -909,10 +915,6 @@ function sampleClassifyText(text) { | ||
| 909 | 915 | * @returns {Promise<string>} Prompt for the LLM API. |
| 910 | 916 | */ |
| 911 | 917 | async function getLlmPrompt(labels) { |
| 912 | - if (isJsonSchemaSupported()) { | |
| 913 | - return ''; | |
| 914 | - } | |
| 915 | - | |
| 916 | 918 | const labelsString = labels.map(x => `"${x}"`).join(', '); |
| 917 | 919 | const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString }); |
| 918 | 920 | return prompt; |
| @@ -976,6 +978,7 @@ function getJsonSchema(emotions) { | ||
| 976 | 978 | required: [ |
| 977 | 979 | 'emotion', |
| 978 | 980 | ], |
| 981 | + additionalProperties: false, | |
| 979 | 982 | }; |
| 980 | 983 | } |
| 981 | 984 | |
| @@ -1047,7 +1050,21 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | ||
| 1047 | 1050 | const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable }); |
| 1048 | 1051 | const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList); |
| 1049 | 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 | 1068 | return parseLlmResponse(emotionResponse, expressionsList); |
| 1052 | 1069 | } |
| 1053 | 1070 | // Using WebLLM |
| @@ -1702,6 +1719,7 @@ function onExpressionApiChanged() { | ||
| 1702 | 1719 | if (tempApi) { |
| 1703 | 1720 | extension_settings.expressions.api = Number(tempApi); |
| 1704 | 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 | 1723 | expressionsList = null; |
| 1706 | 1724 | spriteCache = {}; |
| 1707 | 1725 | moduleWorker(); |
| @@ -2102,6 +2120,11 @@ function migrateSettings() { | ||
| 2102 | 2120 | extension_settings.expressions.showDefault = false; |
| 2103 | 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 | } |
| 2106 | 2129 | |
| 2107 | 2130 | (async function () { |
| @@ -2169,6 +2192,16 @@ function migrateSettings() { | ||
| 2169 | 2192 | extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT; |
| 2170 | 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); | |
| 2172 | 2205 | |
| 2173 | 2206 | $('#expression_custom_add').on('click', onClickExpressionAddCustom); |
| 2174 | 2207 | $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); |
| @@ -40,8 +40,21 @@ | ||
| 40 | 40 | <i class="fa-solid fa-clock-rotate-left fa-sm"></i> |
| 41 | 41 | </div> |
| 42 | 42 | </label> |
| 43 | 43 | <small data-i18n="Will be used if theUsed APIin doesn'taddition supportto JSON schemas orand function calling.">Will be used if theUsed APIin doesn'taddition supportto JSON schemas orand function calling.</small> |
| 44 | 44 | <textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact autoSetHeight" rows="2" placeholder="Use {{labels}} 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 | 58 | </div> |
| 46 | 59 | <div class="expression_fallback_block m-b-1 m-t-1"> |
| 47 | 60 | <label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label> |
| @@ -52,7 +65,7 @@ | ||
| 52 | 65 | <label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label> |
| 53 | 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 | 67 | <div class="flex-container"> |
| 55 | 68 | <select id="expression_custom" class="flex1 margin0"></select> |
| 56 | 69 | <i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i> |
| 57 | 70 | <i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i> |
| 58 | 71 | </div> |
| @@ -4,6 +4,8 @@ import { | ||
| 4 | 4 | characters, |
| 5 | 5 | getRequestHeaders, |
| 6 | 6 | event_types, |
| 7 | + animation_duration, | |
| 8 | + animation_easing, | |
| 7 | 9 | } from '../../../script.js'; |
| 8 | 10 | import { groups, selected_group } from '../../group-chats.js'; |
| 9 | 11 | import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js'; |
| @@ -15,10 +17,12 @@ import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/S | ||
| 15 | 17 | import { DragAndDropHandler } from '../../dragdrop.js'; |
| 16 | 18 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 17 | 19 | import { t, translate } from '../../i18n.js'; |
| 20 | +import { Popup } from '../../popup.js'; | |
| 18 | 21 | |
| 19 | 22 | const extensionName = 'gallery'; |
| 20 | 23 | const extensionFolderPath = `scripts/extensions/${extensionName}/`; |
| 21 | 24 | let firstTime = true; |
| 25 | +let deleteModeActive = false; | |
| 22 | 26 | |
| 23 | 27 | // Exposed defaults for future tweaking |
| 24 | 28 | let thumbnailHeight = 150; |
| @@ -30,7 +34,15 @@ let galleryMaxRows = 3; | ||
| 30 | 34 | $('#movingDivs').on('click', '.dragClose', function () { |
| 31 | 35 | const relatedId = $(this).data('related-id'); |
| 32 | 36 | if (!relatedId) return; |
| 33 | 37 | const relatedElement = $(`#movingDivs > .draggable[id="${relatedId}"]`).remove(); |
| 38 | + relatedElement.transition({ | |
| 39 | + opacity: 0, | |
| 40 | + duration: animation_duration, | |
| 41 | + easing: animation_easing, | |
| 42 | + complete: () => { | |
| 43 | + relatedElement.remove(); | |
| 44 | + }, | |
| 45 | + }); | |
| 34 | 46 | }); |
| 35 | 47 | |
| 36 | 48 | const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved'; |
| @@ -147,6 +159,29 @@ async function getGalleryFolders() { | ||
| 147 | 159 | } |
| 148 | 160 | |
| 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 | + */ | |
| 165 | +async function deleteGalleryItem(url) { | |
| 166 | + try { | |
| 167 | + const response = await fetch('/api/images/delete', { | |
| 168 | + method: 'POST', | |
| 169 | + headers: getRequestHeaders(), | |
| 170 | + body: JSON.stringify({ path: url }), | |
| 171 | + }); | |
| 172 | + | |
| 173 | + if (!response.ok) { | |
| 174 | + throw new Error(`HTTP error. Status: ${response.status}`); | |
| 175 | + } | |
| 176 | + | |
| 177 | + toastr.success(t`Image deleted successfully.`); | |
| 178 | + } catch (error) { | |
| 179 | + console.error('Failed to delete the image:', error); | |
| 180 | + toastr.error(t`Failed to delete the image. Check the console for details.`); | |
| 181 | + } | |
| 182 | +} | |
| 183 | + | |
| 184 | +/** | |
| 150 | 185 | * Sets the sort order for the gallery. |
| 151 | 186 | * @param {string} order Sort order |
| 152 | 187 | */ |
| @@ -260,7 +295,7 @@ async function initGallery(items, url) { | ||
| 260 | 295 | * |
| 261 | 296 | * @returns {Promise<void>} - Promise representing the completion of the gallery display process. |
| 262 | 297 | */ |
| 263 | 298 | async function showCharGallery(deleteModeState = false) { |
| 264 | 299 | // Load necessary files if it's the first time calling the function |
| 265 | 300 | if (firstTime) { |
| 266 | 301 | await loadFileToDocument( |
| @@ -276,6 +311,7 @@ async function showCharGallery() { | ||
| 276 | 311 | } |
| 277 | 312 | |
| 278 | 313 | try { |
| 314 | + deleteModeActive = deleteModeState; | |
| 279 | 315 | let url = selected_group || this_chid; |
| 280 | 316 | if (!selected_group && this_chid !== undefined) { |
| 281 | 317 | url = getGalleryFolder(characters[this_chid]); |
| @@ -347,7 +383,7 @@ async function makeMovable(url) { | ||
| 347 | 383 | const id = 'gallery'; |
| 348 | 384 | const template = $('#generic_draggable_template').html(); |
| 349 | 385 | const newElement = $(template); |
| 350 | 386 | newElement.css({ 'background-color',: 'var(--SmartThemeBlurTintColor)', 'opacity': 0 }); |
| 351 | 387 | newElement.attr('forChar', id); |
| 352 | 388 | newElement.attr('id', id); |
| 353 | 389 | newElement.find('.drag-grabber').attr('id', `${id}header`); |
| @@ -429,6 +465,18 @@ async function makeMovable(url) { | ||
| 429 | 465 | galleryFolderAccept.title = t`Change gallery folder`; |
| 430 | 466 | galleryFolderAccept.addEventListener('click', onChangeFolder); |
| 431 | 467 | |
| 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 | 480 | const galleryFolderRestore = document.createElement('div'); |
| 433 | 481 | galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw'); |
| 434 | 482 | galleryFolderRestore.title = t`Restore gallery folder`; |
| @@ -436,6 +484,7 @@ async function makeMovable(url) { | ||
| 436 | 484 | |
| 437 | 485 | topBarElement.appendChild(galleryFolderInput); |
| 438 | 486 | topBarElement.appendChild(galleryFolderAccept); |
| 487 | + topBarElement.appendChild(galleryDeleteMode); | |
| 439 | 488 | topBarElement.appendChild(galleryFolderRestore); |
| 440 | 489 | newElement.append(topBarElement); |
| 441 | 490 | |
| @@ -466,6 +515,11 @@ async function makeMovable(url) { | ||
| 466 | 515 | loadMovingUIState(); |
| 467 | 516 | $(`.draggable[forChar="${id}"]`).css('display', 'block'); |
| 468 | 517 | dragElement(newElement); |
| 518 | + newElement.transition({ | |
| 519 | + opacity: 1, | |
| 520 | + duration: animation_duration, | |
| 521 | + easing: animation_easing, | |
| 522 | + }); | |
| 469 | 523 | |
| 470 | 524 | $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => { |
| 471 | 525 | console.log('saw drag on avatar!'); |
| @@ -635,11 +689,21 @@ function sanitizeHTMLId(id) { | ||
| 635 | 689 | function viewWithDragbox(items) { |
| 636 | 690 | if (items && items.length > 0) { |
| 637 | 691 | 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 | 701 | // ID should just be the last part of the URL, removing the extension |
| 639 | 702 | const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'))); |
| 640 | 703 | makeDragImg(id, url); |
| 641 | 704 | } |
| 642 | 705 | } |
| 706 | +} | |
| 643 | 707 | |
| 644 | 708 | |
| 645 | 709 | // Registers a simple command for opening the char gallery. |
| @@ -43,3 +43,8 @@ | ||
| 43 | 43 | #dragGallery { |
| 44 | 44 | min-height: 25dvh; |
| 45 | 45 | } |
| 46 | + | |
| 47 | +#gallery .right_menu_button.warning { | |
| 48 | + opacity: 1; | |
| 49 | + filter: unset; | |
| 50 | +} | |
| @@ -16,6 +16,7 @@ import { | ||
| 16 | 16 | getMaxContextSize, |
| 17 | 17 | setExtensionPrompt, |
| 18 | 18 | streamingProcessor, |
| 19 | + animation_easing, | |
| 19 | 20 | } from '../../../script.js'; |
| 20 | 21 | import { is_group_generating, selected_group } from '../../group-chats.js'; |
| 21 | 22 | import { loadMovingUIState } from '../../power-user.js'; |
| @@ -972,6 +973,7 @@ function doPopout(e) { | ||
| 972 | 973 | </div>`; |
| 973 | 974 | const newElement = $(template); |
| 974 | 975 | newElement.attr('id', 'summaryExtensionPopout') |
| 976 | + .css('opacity', 0) | |
| 975 | 977 | .removeClass('zoomed_avatar') |
| 976 | 978 | .addClass('draggable') |
| 977 | 979 | .empty(); |
| @@ -980,13 +982,13 @@ function doPopout(e) { | ||
| 980 | 982 | originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>'); |
| 981 | 983 | newElement.append(controlBarHtml).append(originalHTMLClone); |
| 982 | 984 | $('body').append(newElement); |
| 985 | + newElement.transition({ opacity: 1, duration: animation_duration, easing: animation_easing }); | |
| 983 | 986 | $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull'); |
| 984 | 987 | setMemoryContext(prevSummaryBoxContents, false); //paste prev summary box contents into popout box |
| 985 | 988 | setupListeners(); |
| 986 | 989 | loadSettings(); |
| 987 | 990 | loadMovingUIState(); |
| 988 | 991 | |
| 989 | - $('#summaryExtensionPopout').fadeIn(animation_duration); | |
| 990 | 992 | dragElement(newElement); |
| 991 | 993 | |
| 992 | 994 | //setup listener for close button to restore extensions menu |
| @@ -181,7 +181,7 @@ const init = async () => { | ||
| 181 | 181 | |
| 182 | 182 | eventSource.on(event_types.APP_READY, async()=>await finalizeInit()); |
| 183 | 183 | |
| 184 | 184 | window['globalThis.quickReplyApi'] = quickReplyApi; |
| 185 | 185 | }; |
| 186 | 186 | const finalizeInit = async () => { |
| 187 | 187 | debug('executing startup'); |
| @@ -18,7 +18,7 @@ | ||
| 18 | 18 | #qr--bar { |
| 19 | 19 | outline: none; |
| 20 | 20 | margin: 0; |
| 21 | - transition: 0.3s; | |
| 21 | + transition: var(--animation-duration-2x); | |
| 22 | 22 | opacity: 0.7; |
| 23 | 23 | display: flex; |
| 24 | 24 | align-items: center; |
| @@ -34,14 +34,12 @@ | ||
| 34 | 34 | right: 0.25em; |
| 35 | 35 | top: 0; |
| 36 | 36 | } |
| 37 | - | |
| 38 | 37 | /*hide QR popout for mobile*/ |
| 39 | 38 | @media screen and (max-width: 1000px) { |
| 40 | 39 | #qr--bar > #qr--popoutTrigger { |
| 41 | 40 | display: none; |
| 42 | 41 | } |
| 43 | 42 | } |
| 44 | - | |
| 45 | 43 | #qr--bar.popoutVisible { |
| 46 | 44 | padding-right: 2.5em; |
| 47 | 45 | } |
| @@ -61,9 +59,8 @@ | ||
| 61 | 59 | aspect-ratio: 1 / 1; |
| 62 | 60 | font-size: 20px; |
| 63 | 61 | opacity: 0.5; |
| 64 | 62 | transition: all 250msvar(--animation-duration-2x); |
| 65 | 63 | } |
| 66 | - | |
| 67 | 64 | #qr--popout > .qr--body { |
| 68 | 65 | overflow-y: auto; |
| 69 | 66 | } |
| @@ -124,7 +121,7 @@ | ||
| 124 | 121 | padding: 3px 5px; |
| 125 | 122 | margin: 3px 0; |
| 126 | 123 | cursor: pointer; |
| 127 | - transition: 0.3s; | |
| 124 | + transition: var(--animation-duration-2x); | |
| 128 | 125 | display: flex; |
| 129 | 126 | align-items: center; |
| 130 | 127 | justify-content: center; |
| @@ -248,7 +245,7 @@ | ||
| 248 | 245 | display: flex; |
| 249 | 246 | align-items: center; |
| 250 | 247 | opacity: 0; |
| 251 | 248 | transition: 100msvar(--animation-duration); |
| 252 | 249 | margin: -2px 0 -11px 0; |
| 253 | 250 | position: relative; |
| 254 | 251 | } |
| @@ -461,7 +458,7 @@ | ||
| 461 | 458 | cursor: not-allowed; |
| 462 | 459 | opacity: 0.5; |
| 463 | 460 | pointer-events: none; |
| 464 | - transition: 200ms; | |
| 461 | + transition: var(--animation-duration-2x); | |
| 465 | 462 | border-color: transparent; |
| 466 | 463 | } |
| 467 | 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 | 489 | width: 6px; |
| 493 | 490 | background-color: var(--SmartThemeBorderColor); |
| 494 | 491 | border: 2px solid var(--SmartThemeBlurTintColor); |
| 495 | 492 | transition: border-color 200msvar(--animation-duration-2x), background-color 200msvar(--animation-duration-2x); |
| 496 | 493 | cursor: w-resize; |
| 497 | 494 | } |
| 498 | 495 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover { |
| @@ -574,7 +571,7 @@ | ||
| 574 | 571 | gap: 1em; |
| 575 | 572 | text-align: left; |
| 576 | 573 | opacity: 0.75; |
| 577 | - transition: 200ms; | |
| 574 | + transition: var(--animation-duration-2x); | |
| 578 | 575 | cursor: pointer; |
| 579 | 576 | } |
| 580 | 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 | 733 | display: flex; |
| 737 | 734 | } |
| 738 | 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 | 737 | filter: grayscale(0); |
| 741 | 738 | } |
| 742 | 739 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy { |
| @@ -825,7 +822,7 @@ | ||
| 825 | 822 | position: absolute; |
| 826 | 823 | inset: 0; |
| 827 | 824 | right: calc(100% - var(--prog) * 1%); |
| 828 | - transition: 200ms; | |
| 825 | + transition: var(--animation-duration-2x); | |
| 829 | 826 | } |
| 830 | 827 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after { |
| 831 | 828 | animation-name: qr--progressPulse; |
| @@ -1053,7 +1050,7 @@ | ||
| 1053 | 1050 | white-space: pre; |
| 1054 | 1051 | font-weight: normal; |
| 1055 | 1052 | box-shadow: 0 0 0; |
| 1056 | - transition: 200ms; | |
| 1053 | + transition: var(--animation-duration-2x); | |
| 1057 | 1054 | } |
| 1058 | 1055 | .popup:has(.qr--transferModal) .popup-button-ok:after { |
| 1059 | 1056 | content: 'Transfer'; |
| @@ -1068,7 +1065,7 @@ | ||
| 1068 | 1065 | white-space: pre; |
| 1069 | 1066 | font-weight: normal; |
| 1070 | 1067 | box-shadow: 0 0 0; |
| 1071 | - transition: 200ms; | |
| 1068 | + transition: var(--animation-duration-2x); | |
| 1072 | 1069 | } |
| 1073 | 1070 | .popup:has(.qr--transferModal) .qr--copy:after { |
| 1074 | 1071 | content: 'Copy'; |
| @@ -1,11 +1,16 @@ | ||
| 1 | 1 | @keyframes qr--success { |
| 2 | - 0%, 100% { | |
| 2 | + | |
| 3 | + 0%, | |
| 4 | + 100% { | |
| 3 | 5 | color: var(--SmartThemeBodyColor); |
| 4 | 6 | } |
| 5 | - 25%, 75% { | |
| 7 | + | |
| 8 | + 25%, | |
| 9 | + 75% { | |
| 6 | 10 | color: rgb(81, 163, 81); |
| 7 | 11 | } |
| 8 | 12 | } |
| 13 | + | |
| 9 | 14 | &.qr--success { |
| 10 | 15 | animation-name: qr--success; |
| 11 | 16 | animation-duration: 3s; |
| @@ -13,10 +18,11 @@ | ||
| 13 | 18 | animation-delay: 0s; |
| 14 | 19 | animation-iteration-count: 1; |
| 15 | 20 | } |
| 21 | + | |
| 16 | 22 | #qr--bar { |
| 17 | 23 | outline: none; |
| 18 | 24 | margin: 0; |
| 19 | - transition: 0.3s; | |
| 25 | + transition: var(--animation-duration-2x); | |
| 20 | 26 | opacity: 0.7; |
| 21 | 27 | display: flex; |
| 22 | 28 | align-items: center; |
| @@ -33,6 +39,14 @@ | ||
| 33 | 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 | 50 | #qr--bar.popoutVisible { |
| 37 | 51 | padding-right: 2.5em; |
| 38 | 52 | } |
| @@ -54,7 +68,7 @@ | ||
| 54 | 68 | aspect-ratio: 1 / 1; |
| 55 | 69 | font-size: 20px; |
| 56 | 70 | opacity: 0.5; |
| 57 | 71 | transition: all 250msvar(--animation-duration-2x); |
| 58 | 72 | } |
| 59 | 73 | } |
| 60 | 74 | } |
| @@ -68,17 +82,21 @@ | ||
| 68 | 82 | #qr--popout>.qr--body { |
| 69 | 83 | >.qr--buttons { |
| 70 | 84 | --qr--color: transparent; |
| 85 | + | |
| 71 | 86 | &.qr--color { |
| 72 | 87 | background-color: var(--qr--color); |
| 73 | 88 | } |
| 89 | + | |
| 74 | 90 | &.qr--borderColor { |
| 75 | 91 | background-color: transparent; |
| 76 | 92 | border-left: 5px solid var(--qr--color); |
| 77 | 93 | border-right: 5px solid var(--qr--color); |
| 78 | 94 | } |
| 95 | + | |
| 79 | 96 | &:has(.qr--buttons.qr--color) { |
| 80 | 97 | margin: 5px; |
| 81 | 98 | } |
| 99 | + | |
| 82 | 100 | margin: 0; |
| 83 | 101 | padding: 0; |
| 84 | 102 | display: flex; |
| @@ -89,6 +107,7 @@ | ||
| 89 | 107 | |
| 90 | 108 | >.qr--buttons { |
| 91 | 109 | display: contents; |
| 110 | + | |
| 92 | 111 | &.qr--color { |
| 93 | 112 | .qr--button:before { |
| 94 | 113 | content: ''; |
| @@ -97,11 +116,14 @@ | ||
| 97 | 116 | inset: -5px; |
| 98 | 117 | z-index: -1; |
| 99 | 118 | } |
| 119 | + | |
| 100 | 120 | &.qr--borderColor { |
| 101 | 121 | .qr--button:before { |
| 102 | 122 | display: none; |
| 103 | 123 | } |
| 104 | - &:before, &:after { | |
| 124 | + | |
| 125 | + &:before, | |
| 126 | + &:after { | |
| 105 | 127 | content: ''; |
| 106 | 128 | width: 5px; |
| 107 | 129 | background-color: var(--qr--color); |
| @@ -118,7 +140,7 @@ | ||
| 118 | 140 | padding: 3px 5px; |
| 119 | 141 | margin: 3px 0; |
| 120 | 142 | cursor: pointer; |
| 121 | - transition: 0.3s; | |
| 143 | + transition: var(--animation-duration-2x); | |
| 122 | 144 | display: flex; |
| 123 | 145 | align-items: center; |
| 124 | 146 | justify-content: center; |
| @@ -132,6 +154,7 @@ | ||
| 132 | 154 | .qr--hidden { |
| 133 | 155 | display: none; |
| 134 | 156 | } |
| 157 | + | |
| 135 | 158 | .qr--button-icon { |
| 136 | 159 | margin: 0 0.5em; |
| 137 | 160 | } |
| @@ -274,18 +297,22 @@ | ||
| 274 | 297 | display: flex; |
| 275 | 298 | align-items: center; |
| 276 | 299 | opacity: 0; |
| 277 | 300 | transition: 100msvar(--animation-duration); |
| 278 | 301 | margin: -2px 0 -11px 0; |
| 279 | 302 | position: relative; |
| 303 | + | |
| 280 | 304 | .qr--actions { |
| 281 | 305 | display: flex; |
| 282 | 306 | gap: 0.25em; |
| 283 | 307 | flex: 0 0 auto; |
| 308 | + | |
| 284 | 309 | .qr--action { |
| 285 | 310 | margin: 0; |
| 286 | 311 | } |
| 287 | 312 | } |
| 288 | - &:before, &:after { | |
| 313 | + | |
| 314 | + &:before, | |
| 315 | + &:after { | |
| 289 | 316 | content: ""; |
| 290 | 317 | display: block; |
| 291 | 318 | flex: 1 1 auto; |
| @@ -293,10 +320,13 @@ | ||
| 293 | 320 | margin: 0 1em; |
| 294 | 321 | height: 0; |
| 295 | 322 | } |
| 296 | - &:hover, &:focus-within { | |
| 323 | + | |
| 324 | + &:hover, | |
| 325 | + &:focus-within { | |
| 297 | 326 | opacity: 1; |
| 298 | 327 | } |
| 299 | 328 | } |
| 329 | + | |
| 300 | 330 | >.qr--set-item .qr--content { |
| 301 | 331 | display: flex; |
| 302 | 332 | flex-direction: row; |
| @@ -336,9 +366,11 @@ | ||
| 336 | 366 | display: flex; |
| 337 | 367 | align-items: center; |
| 338 | 368 | gap: 0.5em; |
| 369 | + | |
| 339 | 370 | .qr--set-itemIcon:not(.fa-solid) { |
| 340 | 371 | display: none; |
| 341 | 372 | } |
| 373 | + | |
| 342 | 374 | .qr--set-itemLabel { |
| 343 | 375 | min-width: 24px; |
| 344 | 376 | } |
| @@ -384,6 +416,7 @@ | ||
| 384 | 416 | align-items: baseline; |
| 385 | 417 | } |
| 386 | 418 | } |
| 419 | + | |
| 387 | 420 | >#qr--autoExec { |
| 388 | 421 | .checkbox_label { |
| 389 | 422 | text-wrap: nowrap; |
| @@ -433,44 +466,52 @@ | ||
| 433 | 466 | bottom: unset; |
| 434 | 467 | margin: unset; |
| 435 | 468 | padding: 0; |
| 469 | + | |
| 436 | 470 | &::backdrop { |
| 437 | 471 | backdrop-filter: unset; |
| 438 | 472 | background-color: transparent; |
| 439 | 473 | } |
| 474 | + | |
| 440 | 475 | .popup-body { |
| 441 | 476 | flex: 0 0 auto; |
| 442 | 477 | height: min-content; |
| 443 | 478 | width: min-content; |
| 444 | 479 | } |
| 480 | + | |
| 445 | 481 | .popup-content { |
| 446 | 482 | flex: 0 0 auto; |
| 447 | 483 | margin-top: 0; |
| 448 | 484 | |
| 449 | 485 | >#qr--modalEditor { |
| 450 | 486 | max-height: 50vh; |
| 487 | + | |
| 451 | 488 | >#qr--main, |
| 452 | 489 | >#qr--resizeHandle, |
| 453 | 490 | >#qr--qrOptions>h3, |
| 454 | 491 | >#qr--qrOptions>#qr--modal-executeButtons, |
| 455 | 492 | > #qr--qrOptions > #qr--modal-executeProgress { |
| 456 | - { | |
| 457 | 493 | display: none; |
| 458 | 494 | } |
| 495 | + | |
| 459 | 496 | #qr--qrOptions { |
| 460 | 497 | width: auto; |
| 461 | 498 | } |
| 499 | + | |
| 462 | 500 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { |
| 463 | 501 | display: flex; |
| 464 | 502 | } |
| 503 | + | |
| 465 | 504 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize { |
| 466 | 505 | display: none; |
| 467 | 506 | } |
| 507 | + | |
| 468 | 508 | #qr--modal-debugState { |
| 469 | 509 | padding-top: 0; |
| 470 | 510 | } |
| 471 | 511 | } |
| 472 | 512 | } |
| 473 | 513 | } |
| 514 | + | |
| 474 | 515 | &:has(.qr--isExecuting) { |
| 475 | 516 | .popup-controls { |
| 476 | 517 | display: none; |
| @@ -481,10 +522,12 @@ | ||
| 481 | 522 | z-index: 50000; |
| 482 | 523 | pointer-events: none; |
| 483 | 524 | background-color: rgb(47 150 180 / 0.5); |
| 525 | + | |
| 484 | 526 | &.qr--unresolved { |
| 485 | 527 | background-color: rgb(255 255 0 / 0.5); |
| 486 | 528 | } |
| 487 | 529 | } |
| 530 | + | |
| 488 | 531 | .qr--highlight-secondary { |
| 489 | 532 | position: absolute; |
| 490 | 533 | z-index: 50000; |
| @@ -505,6 +548,7 @@ | ||
| 505 | 548 | overflow: hidden; |
| 506 | 549 | |
| 507 | 550 | &.qr--isExecuting { |
| 551 | + | |
| 508 | 552 | #qr--main>h3:first-child, |
| 509 | 553 | #qr--main>.qr--labels, |
| 510 | 554 | #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings, |
| @@ -512,28 +556,32 @@ | ||
| 512 | 556 | #qr--qrOptions>#qr--ctxEditor, |
| 513 | 557 | #qr--qrOptions>.qr--ctxEditorActions, |
| 514 | 558 | #qr--qrOptions>.qr--ctxEditorActions+h3, |
| 515 | 559 | #qr--qrOptions > .qr--ctxEditorActions + h3 + div { |
| 516 | - { | |
| 517 | 560 | display: none; |
| 518 | 561 | } |
| 562 | + | |
| 519 | 563 | #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message { |
| 520 | 564 | visibility: hidden; |
| 521 | 565 | } |
| 566 | + | |
| 522 | 567 | #qr--modal-debugButtons { |
| 523 | 568 | display: flex; |
| 569 | + | |
| 524 | 570 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { |
| 525 | 571 | cursor: not-allowed; |
| 526 | 572 | opacity: 0.5; |
| 527 | 573 | pointer-events: none; |
| 528 | - transition: 200ms; | |
| 574 | + transition: var(--animation-duration-2x); | |
| 529 | 575 | border-color: transparent; |
| 530 | 576 | } |
| 531 | 577 | } |
| 578 | + | |
| 532 | 579 | &.qr--isPaused #qr--modal-debugButtons { |
| 533 | 580 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { |
| 534 | 581 | cursor: pointer; |
| 535 | 582 | opacity: 1; |
| 536 | 583 | pointer-events: all; |
| 584 | + | |
| 537 | 585 | &#qr--modal-resume { |
| 538 | 586 | animation-name: qr--debugPulse; |
| 539 | 587 | animation-duration: 1500ms; |
| @@ -541,31 +589,38 @@ | ||
| 541 | 589 | animation-delay: 0s; |
| 542 | 590 | animation-iteration-count: infinite; |
| 543 | 591 | } |
| 592 | + | |
| 544 | 593 | &#qr--modal-resume { |
| 545 | 594 | border-color: rgb(81, 163, 81); |
| 546 | 595 | } |
| 596 | + | |
| 547 | 597 | &#qr--modal-step { |
| 548 | 598 | border-color: var(--SmartThemeQuoteColor); |
| 549 | 599 | } |
| 600 | + | |
| 550 | 601 | &#qr--modal-stepInto { |
| 551 | 602 | border-color: var(--SmartThemeQuoteColor); |
| 552 | 603 | } |
| 604 | + | |
| 553 | 605 | &#qr--modal-stepOut { |
| 554 | 606 | border-color: var(--SmartThemeQuoteColor); |
| 555 | 607 | } |
| 556 | 608 | } |
| 557 | 609 | } |
| 610 | + | |
| 558 | 611 | #qr--resizeHandle { |
| 559 | 612 | width: 6px; |
| 560 | 613 | background-color: var(--SmartThemeBorderColor); |
| 561 | 614 | border: 2px solid var(--SmartThemeBlurTintColor); |
| 562 | 615 | transition: border-color 200msvar(--animation-duration-2x), background-color 200msvar(--animation-duration-2x); |
| 563 | 616 | cursor: w-resize; |
| 617 | + | |
| 564 | 618 | &:hover { |
| 565 | 619 | background-color: var(--SmartThemeQuoteColor); |
| 566 | 620 | border-color: var(--SmartThemeQuoteColor); |
| 567 | 621 | } |
| 568 | 622 | } |
| 623 | + | |
| 569 | 624 | #qr--qrOptions { |
| 570 | 625 | width: var(--width, auto); |
| 571 | 626 | } |
| @@ -576,38 +631,48 @@ | ||
| 576 | 631 | display: flex; |
| 577 | 632 | flex-direction: column; |
| 578 | 633 | overflow: hidden; |
| 634 | + | |
| 579 | 635 | >.qr--labels { |
| 580 | 636 | flex: 0 0 auto; |
| 581 | 637 | display: flex; |
| 582 | 638 | flex-direction: row; |
| 583 | 639 | gap: 0.5em; |
| 584 | 640 | padding: 1px; |
| 585 | - > label, > .label { | |
| 641 | + | |
| 642 | + >label, | |
| 643 | + >.label { | |
| 586 | 644 | flex: 1 1 1px; |
| 587 | 645 | display: flex; |
| 588 | 646 | flex-direction: column; |
| 589 | 647 | position: relative; |
| 648 | + | |
| 590 | 649 | &.qr--fit { |
| 591 | 650 | flex: 0 0 auto; |
| 592 | 651 | justify-content: center; |
| 593 | 652 | } |
| 653 | + | |
| 594 | 654 | .qr--inputGroup { |
| 595 | 655 | display: flex; |
| 596 | 656 | align-items: baseline; |
| 597 | 657 | gap: 0.5em; |
| 658 | + | |
| 598 | 659 | input { |
| 599 | 660 | flex: 1 1 auto; |
| 600 | 661 | } |
| 601 | 662 | } |
| 663 | + | |
| 602 | 664 | .qr--labelText { |
| 603 | 665 | flex: 1 1 auto; |
| 604 | 666 | } |
| 667 | + | |
| 605 | 668 | .qr--labelHint { |
| 606 | 669 | flex: 1 1 auto; |
| 607 | 670 | } |
| 671 | + | |
| 608 | 672 | input { |
| 609 | 673 | flex: 0 0 auto; |
| 610 | 674 | } |
| 675 | + | |
| 611 | 676 | .qr--modal-switcherList { |
| 612 | 677 | background-color: var(--stcdx--bgColor); |
| 613 | 678 | border: 1px solid var(--SmartThemeBorderColor); |
| @@ -625,25 +690,32 @@ | ||
| 625 | 690 | list-style: none; |
| 626 | 691 | z-index: 40000; |
| 627 | 692 | max-width: 100%; |
| 693 | + | |
| 628 | 694 | .qr--modal-switcherItem { |
| 629 | 695 | display: flex; |
| 630 | 696 | gap: 1em; |
| 631 | 697 | text-align: left; |
| 632 | 698 | opacity: 0.75; |
| 633 | - transition: 200ms; | |
| 699 | + transition: var(--animation-duration-2x); | |
| 634 | 700 | cursor: pointer; |
| 701 | + | |
| 635 | 702 | &:hover { |
| 636 | 703 | opacity: 1; |
| 637 | 704 | } |
| 705 | + | |
| 638 | 706 | &.qr--current { |
| 639 | 707 | opacity: 1; |
| 640 | - .qr--label, .qr--id { | |
| 708 | + | |
| 709 | + .qr--label, | |
| 710 | + .qr--id { | |
| 641 | 711 | font-weight: bold; |
| 642 | 712 | } |
| 643 | 713 | } |
| 644 | 714 | } |
| 715 | + | |
| 645 | 716 | .qr--label { |
| 646 | 717 | white-space: nowrap; |
| 718 | + | |
| 647 | 719 | .menu_button { |
| 648 | 720 | display: inline-block; |
| 649 | 721 | height: min-content; |
| @@ -651,11 +723,19 @@ | ||
| 651 | 723 | margin: 0 0.5em 0 0; |
| 652 | 724 | } |
| 653 | 725 | } |
| 726 | + | |
| 654 | 727 | .qr--id { |
| 655 | 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 | 739 | .qr--message { |
| 660 | 740 | height: 1lh; |
| 661 | 741 | overflow: hidden; |
| @@ -666,11 +746,13 @@ | ||
| 666 | 746 | } |
| 667 | 747 | } |
| 668 | 748 | } |
| 749 | + | |
| 669 | 750 | >.qr--modal-messageContainer { |
| 670 | 751 | flex: 1 1 auto; |
| 671 | 752 | display: flex; |
| 672 | 753 | flex-direction: column; |
| 673 | 754 | overflow: hidden; |
| 755 | + | |
| 674 | 756 | >.qr--modal-editorSettings { |
| 675 | 757 | display: flex; |
| 676 | 758 | flex-wrap: wrap; |
| @@ -679,38 +761,48 @@ | ||
| 679 | 761 | color: var(--grey70); |
| 680 | 762 | font-size: smaller; |
| 681 | 763 | align-items: baseline; |
| 764 | + | |
| 682 | 765 | >.checkbox_label { |
| 683 | 766 | white-space: nowrap; |
| 767 | + | |
| 684 | 768 | >input { |
| 685 | 769 | font-size: inherit; |
| 686 | 770 | } |
| 687 | 771 | } |
| 688 | 772 | } |
| 773 | + | |
| 689 | 774 | >#qr--modal-messageHolder { |
| 690 | 775 | flex: 1 1 auto; |
| 691 | 776 | display: grid; |
| 692 | 777 | text-align: left; |
| 693 | 778 | overflow: hidden; |
| 779 | + | |
| 694 | 780 | &.qr--noSyntax { |
| 695 | 781 | >#qr--modal-messageSyntax { |
| 696 | 782 | display: none; |
| 697 | 783 | } |
| 784 | + | |
| 698 | 785 | >#qr--modal-message { |
| 699 | 786 | background-color: var(--ac-style-color-background); |
| 700 | 787 | color: var(--ac-style-color-text); |
| 701 | - &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | |
| 788 | + | |
| 789 | + &::-webkit-scrollbar, | |
| 790 | + &::-webkit-scrollbar-thumb { | |
| 702 | 791 | visibility: visible; |
| 703 | 792 | cursor: unset; |
| 704 | 793 | } |
| 794 | + | |
| 705 | 795 | &::selection { |
| 706 | 796 | color: unset; |
| 707 | 797 | background-color: rgba(108 171 251 / 0.25); |
| 798 | + | |
| 708 | 799 | @supports (color: rgb(from white r g b / 0.25)) { |
| 709 | 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 | 806 | >#qr--modal-messageSyntax { |
| 715 | 807 | grid-column: 1; |
| 716 | 808 | grid-row: 1; |
| @@ -720,10 +812,12 @@ | ||
| 720 | 812 | overflow: hidden; |
| 721 | 813 | min-width: 100%; |
| 722 | 814 | width: 0; |
| 815 | + | |
| 723 | 816 | >#qr--modal-messageSyntaxInner { |
| 724 | 817 | height: 100%; |
| 725 | 818 | } |
| 726 | 819 | } |
| 820 | + | |
| 727 | 821 | >#qr--modal-message { |
| 728 | 822 | background-color: transparent; |
| 729 | 823 | color: transparent; |
| @@ -731,19 +825,25 @@ | ||
| 731 | 825 | grid-row: 1; |
| 732 | 826 | caret-color: var(--ac-style-color-text); |
| 733 | 827 | overflow: auto; |
| 734 | - &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | |
| 828 | + | |
| 829 | + &::-webkit-scrollbar, | |
| 830 | + &::-webkit-scrollbar-thumb { | |
| 735 | 831 | visibility: hidden; |
| 736 | 832 | cursor: default; |
| 737 | 833 | } |
| 834 | + | |
| 738 | 835 | &::selection { |
| 739 | 836 | color: transparent; |
| 740 | 837 | background-color: rgba(108 171 251 / 0.25); |
| 838 | + | |
| 741 | 839 | @supports (color: rgb(from white r g b / 0.25)) { |
| 742 | 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 | 847 | font-family: var(--monoFontFamily); |
| 748 | 848 | padding: 0.75em; |
| 749 | 849 | margin: 0; |
| @@ -756,10 +856,12 @@ | ||
| 756 | 856 | } |
| 757 | 857 | } |
| 758 | 858 | } |
| 859 | + | |
| 759 | 860 | #qr--modal-icon { |
| 760 | 861 | height: 100%; |
| 761 | 862 | aspect-ratio: 1 / 1; |
| 762 | 863 | } |
| 864 | + | |
| 763 | 865 | #qr--modal-executeButtons { |
| 764 | 866 | display: flex; |
| 765 | 867 | gap: 1em; |
| @@ -778,7 +880,7 @@ | ||
| 778 | 880 | } |
| 779 | 881 | |
| 780 | 882 | #qr--modal-execute { |
| 781 | - transition: 200ms; | |
| 883 | + transition: var(--animation-duration-2x); | |
| 782 | 884 | filter: grayscale(0); |
| 783 | 885 | |
| 784 | 886 | &.qr--busy { |
| @@ -819,16 +921,20 @@ | ||
| 819 | 921 | border-color: rgb(215, 136, 114); |
| 820 | 922 | } |
| 821 | 923 | } |
| 924 | + | |
| 822 | 925 | #qr--modal-debugButtons { |
| 823 | 926 | display: none; |
| 824 | 927 | gap: 1em; |
| 928 | + | |
| 825 | 929 | .qr--modal-debugButton { |
| 826 | 930 | aspect-ratio: 1.25 / 1; |
| 827 | 931 | width: 2.25em; |
| 828 | 932 | position: relative; |
| 933 | + | |
| 829 | 934 | &:not(.fa-solid) { |
| 830 | 935 | border-width: 1px; |
| 831 | 936 | border-style: solid; |
| 937 | + | |
| 832 | 938 | &:after { |
| 833 | 939 | content: ''; |
| 834 | 940 | position: absolute; |
| @@ -839,18 +945,23 @@ | ||
| 839 | 945 | mask-repeat: no-repeat; |
| 840 | 946 | } |
| 841 | 947 | } |
| 948 | + | |
| 842 | 949 | &#qr--modal-resume:after { |
| 843 | 950 | mask-image: url('/img/step-resume.svg'); |
| 844 | 951 | } |
| 952 | + | |
| 845 | 953 | &#qr--modal-step:after { |
| 846 | 954 | mask-image: url('/img/step-over.svg'); |
| 847 | 955 | } |
| 956 | + | |
| 848 | 957 | &#qr--modal-stepInto:after { |
| 849 | 958 | mask-image: url('/img/step-into.svg'); |
| 850 | 959 | } |
| 960 | + | |
| 851 | 961 | &#qr--modal-stepOut:after { |
| 852 | 962 | mask-image: url('/img/step-out.svg'); |
| 853 | 963 | } |
| 964 | + | |
| 854 | 965 | &#qr--modal-maximize { |
| 855 | 966 | display: none; |
| 856 | 967 | } |
| @@ -879,7 +990,7 @@ | ||
| 879 | 990 | position: absolute; |
| 880 | 991 | inset: 0; |
| 881 | 992 | right: calc(100% - var(--prog) * 1%); |
| 882 | - transition: 200ms; | |
| 993 | + transition: var(--animation-duration-2x); | |
| 883 | 994 | } |
| 884 | 995 | |
| 885 | 996 | &.qr--paused:after { |
| @@ -941,11 +1052,14 @@ | ||
| 941 | 1052 | width: 0; |
| 942 | 1053 | white-space: pre-wrap; |
| 943 | 1054 | } |
| 1055 | + | |
| 944 | 1056 | #qr--modal-debugState { |
| 945 | 1057 | display: none; |
| 1058 | + | |
| 946 | 1059 | &.qr--active { |
| 947 | 1060 | display: block; |
| 948 | 1061 | } |
| 1062 | + | |
| 949 | 1063 | text-align: left; |
| 950 | 1064 | font-size: smaller; |
| 951 | 1065 | font-family: var(--monoFontFamily); |
| @@ -961,6 +1075,7 @@ | ||
| 961 | 1075 | display: grid; |
| 962 | 1076 | grid-template-columns: 0fr 1fr 1fr; |
| 963 | 1077 | column-gap: 0em; |
| 1078 | + | |
| 964 | 1079 | .qr--title { |
| 965 | 1080 | grid-column: 1 / 4; |
| 966 | 1081 | font-weight: bold; |
| @@ -969,47 +1084,66 @@ | ||
| 969 | 1084 | padding: 0.25em; |
| 970 | 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 | 1091 | display: contents; |
| 1092 | + | |
| 974 | 1093 | &:nth-child(2n + 1) { |
| 975 | - .qr--key, .qr--val { | |
| 1094 | + | |
| 1095 | + .qr--key, | |
| 1096 | + .qr--val { | |
| 976 | 1097 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); |
| 977 | 1098 | } |
| 1099 | + | |
| 978 | 1100 | .qr--val { |
| 979 | 1101 | &:nth-child(2n) { |
| 980 | 1102 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); |
| 981 | 1103 | } |
| 1104 | + | |
| 982 | 1105 | &:hover { |
| 983 | 1106 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); |
| 984 | 1107 | } |
| 985 | 1108 | } |
| 986 | 1109 | } |
| 1110 | + | |
| 987 | 1111 | &:nth-child(2n) { |
| 988 | 1112 | .qr--val { |
| 989 | 1113 | &:nth-child(2n) { |
| 990 | 1114 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); |
| 991 | 1115 | } |
| 1116 | + | |
| 992 | 1117 | &:hover { |
| 993 | 1118 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); |
| 994 | 1119 | } |
| 995 | 1120 | } |
| 996 | 1121 | } |
| 1122 | + | |
| 997 | 1123 | &.qr--isHidden { |
| 998 | - .qr--key, .qr--val { | |
| 1124 | + | |
| 1125 | + .qr--key, | |
| 1126 | + .qr--val { | |
| 999 | 1127 | opacity: 0.5; |
| 1000 | 1128 | } |
| 1001 | 1129 | } |
| 1130 | + | |
| 1002 | 1131 | .qr--val { |
| 1003 | 1132 | grid-column: 2 / 4; |
| 1133 | + | |
| 1004 | 1134 | &.qr--singleCol { |
| 1005 | 1135 | grid-column: unset; |
| 1006 | 1136 | } |
| 1137 | + | |
| 1007 | 1138 | &.qr--simple { |
| 1008 | - &:before, &:after { | |
| 1139 | + | |
| 1140 | + &:before, | |
| 1141 | + &:after { | |
| 1009 | 1142 | content: '"'; |
| 1010 | 1143 | color: var(--SmartThemeQuoteColor); |
| 1011 | 1144 | } |
| 1012 | 1145 | } |
| 1146 | + | |
| 1013 | 1147 | &.qr--unresolved { |
| 1014 | 1148 | &:after { |
| 1015 | 1149 | content: '-UNRESOLVED-'; |
| @@ -1019,21 +1153,36 @@ | ||
| 1019 | 1153 | } |
| 1020 | 1154 | } |
| 1021 | 1155 | } |
| 1156 | + | |
| 1022 | 1157 | .qr--key { |
| 1023 | 1158 | margin-left: 0.5em; |
| 1024 | 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 | 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 | 1179 | .qr--scope { |
| 1034 | 1180 | display: contents; |
| 1181 | + | |
| 1035 | 1182 | .qr--pipe { |
| 1036 | - .qr--key, .qr--val { | |
| 1183 | + | |
| 1184 | + .qr--key, | |
| 1185 | + .qr--val { | |
| 1037 | 1186 | opacity: 0.5; |
| 1038 | 1187 | } |
| 1039 | 1188 | } |
| @@ -1043,6 +1192,7 @@ | ||
| 1043 | 1192 | .qr--stack { |
| 1044 | 1193 | display: grid; |
| 1045 | 1194 | grid-template-columns: 1fr 0fr; |
| 1195 | + | |
| 1046 | 1196 | .qr--title { |
| 1047 | 1197 | grid-column: 1 / 3; |
| 1048 | 1198 | font-weight: bold; |
| @@ -1051,16 +1201,22 @@ | ||
| 1051 | 1201 | padding: 0.25em; |
| 1052 | 1202 | margin-top: 1em; |
| 1053 | 1203 | } |
| 1204 | + | |
| 1054 | 1205 | .qr--item { |
| 1055 | 1206 | display: contents; |
| 1207 | + | |
| 1056 | 1208 | &:nth-child(2n + 1) { |
| 1057 | - .qr--name, .qr--source { | |
| 1209 | + | |
| 1210 | + .qr--name, | |
| 1211 | + .qr--source { | |
| 1058 | 1212 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); |
| 1059 | 1213 | } |
| 1060 | 1214 | } |
| 1215 | + | |
| 1061 | 1216 | .qr--name { |
| 1062 | 1217 | margin-left: 0.5em; |
| 1063 | 1218 | } |
| 1219 | + | |
| 1064 | 1220 | .qr--source { |
| 1065 | 1221 | opacity: 0.5; |
| 1066 | 1222 | text-align: right; |
| @@ -1086,6 +1242,7 @@ | ||
| 1086 | 1242 | } |
| 1087 | 1243 | |
| 1088 | 1244 | @keyframes qr--debugPulse { |
| 1245 | + | |
| 1089 | 1246 | 0%, |
| 1090 | 1247 | 100% { |
| 1091 | 1248 | border-color: rgb(81, 163, 81); |
| @@ -1098,6 +1255,7 @@ | ||
| 1098 | 1255 | |
| 1099 | 1256 | .popup.qr--hide { |
| 1100 | 1257 | opacity: 0 !important; |
| 1258 | + | |
| 1101 | 1259 | &::backdrop { |
| 1102 | 1260 | opacity: 0 !important; |
| 1103 | 1261 | } |
| @@ -1117,14 +1275,16 @@ | ||
| 1117 | 1275 | overflow: hidden; |
| 1118 | 1276 | font-weight: bold; |
| 1119 | 1277 | } |
| 1278 | + | |
| 1120 | 1279 | display: flex; |
| 1121 | 1280 | align-items: center; |
| 1122 | 1281 | flex-direction: column; |
| 1123 | 1282 | white-space: pre; |
| 1124 | 1283 | font-weight: normal; |
| 1125 | 1284 | box-shadow: 0 0 0; |
| 1126 | - transition: 200ms; | |
| 1285 | + transition: var(--animation-duration-2x); | |
| 1127 | 1286 | } |
| 1287 | + | |
| 1128 | 1288 | .qr--copy { |
| 1129 | 1289 | &:after { |
| 1130 | 1290 | content: 'Copy'; |
| @@ -1132,24 +1292,28 @@ | ||
| 1132 | 1292 | overflow: hidden; |
| 1133 | 1293 | font-weight: bold; |
| 1134 | 1294 | } |
| 1295 | + | |
| 1135 | 1296 | display: flex; |
| 1136 | 1297 | align-items: center; |
| 1137 | 1298 | flex-direction: column; |
| 1138 | 1299 | white-space: pre; |
| 1139 | 1300 | font-weight: normal; |
| 1140 | 1301 | box-shadow: 0 0 0; |
| 1141 | - transition: 200ms; | |
| 1302 | + transition: var(--animation-duration-2x); | |
| 1142 | 1303 | } |
| 1304 | + | |
| 1143 | 1305 | &:has(.qr--transferSelect:focus) { |
| 1144 | 1306 | .popup-button-ok { |
| 1145 | 1307 | font-weight: bold; |
| 1146 | 1308 | box-shadow: 0 0 10px; |
| 1147 | 1309 | } |
| 1310 | + | |
| 1148 | 1311 | &.qr--isCopy { |
| 1149 | 1312 | .popup-button-ok { |
| 1150 | 1313 | font-weight: normal; |
| 1151 | 1314 | box-shadow: 0 0 0; |
| 1152 | 1315 | } |
| 1316 | + | |
| 1153 | 1317 | .qr--copy { |
| 1154 | 1318 | font-weight: bold; |
| 1155 | 1319 | box-shadow: 0 0 10px; |
| @@ -21,6 +21,32 @@ | ||
| 21 | 21 | <small data-i18n="ext_regex_import_script">Import</small> |
| 22 | 22 | </div> |
| 23 | 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 | 50 | </div> |
| 25 | 51 | <hr /> |
| 26 | 52 | <div id="global_scripts_block" class="padding5"> |
| @@ -30,7 +56,7 @@ | ||
| 30 | 56 | <small data-i18n="ext_regex_global_scripts_desc"> |
| 31 | 57 | Available for all characters. Saved to local settings. |
| 32 | 58 | </small> |
| 33 | 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 | 60 | </div> |
| 35 | 61 | <hr /> |
| 36 | 62 | <div id="scoped_scripts_block" class="padding5"> |
| @@ -45,7 +71,7 @@ | ||
| 45 | 71 | <small data-i18n="ext_regex_scoped_scripts_desc"> |
| 46 | 72 | Only available for this character. Saved to the card data. |
| 47 | 73 | </small> |
| 48 | 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 | 75 | </div> |
| 50 | 76 | </div> |
| 51 | 77 | </div> |
| @@ -146,7 +146,7 @@ | ||
| 146 | 146 | </select> |
| 147 | 147 | </label> |
| 148 | 148 | <span> |
| 149 | 149 | <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small> |
| 150 | 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. Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span> |
| 151 | 151 | </span> |
| 152 | 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."> |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | <div> |
| 2 | 2 | <h3 data-i18n="This character has embedded regex script(s).">This character has embedded regex script(s).</h3> |
| 3 | 3 | <h3 data-i18n="Would you like to allow using them?">Would you like to allow using them?</h3> |
| 4 | 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 | 5 | </div> |
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | import { callPopup, characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js'; |
| 2 | 2 | import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js'; |
| 3 | 3 | import { selected_group } from '../../group-chats.js'; |
| 4 | 4 | import { callGenericPopup, POPUP_TYPE } from '../../popup.js'; |
| @@ -12,6 +12,8 @@ import { regex_placement, runRegexScript, substitute_find_regex } from './engine | ||
| 12 | 12 | import { t } from '../../i18n.js'; |
| 13 | 13 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 14 | 14 | |
| 15 | +const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '_').toLowerCase(); | |
| 16 | + | |
| 15 | 17 | /** |
| 16 | 18 | * @typedef {import('../../char-data.js').RegexScriptData} RegexScript |
| 17 | 19 | */ |
| @@ -26,6 +28,18 @@ export function getRegexScripts() { | ||
| 26 | 28 | } |
| 27 | 29 | |
| 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 | + */ | |
| 36 | +function 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 | 43 | * Saves a regex script to the extension settings or character data. |
| 30 | 44 | * @param {import('../../char-data.js').RegexScriptData} regexScript |
| 31 | 45 | * @param {number} existingScriptIndex Index of the existing script |
| @@ -43,18 +57,18 @@ async function saveRegexScript(regexScript, existingScriptIndex, isScoped) { | ||
| 43 | 57 | |
| 44 | 58 | // Is the script name undefined or empty? |
| 45 | 59 | if (!regexScript.scriptName) { |
| 46 | 60 | toastr.error('t`Could not save regex script: The script name was undefined or empty!'`); |
| 47 | 61 | return; |
| 48 | 62 | } |
| 49 | 63 | |
| 50 | 64 | // Is a find regex present? |
| 51 | 65 | if (regexScript.findRegex.length === 0) { |
| 52 | 66 | toastr.warning('t`This regex script will not work, but was saved anyway: A find regex isn\'t present.'`); |
| 53 | 67 | } |
| 54 | 68 | |
| 55 | 69 | // Is there someplace to place results? |
| 56 | 70 | if (regexScript.placement.length === 0) { |
| 57 | 71 | toastr.warning('t`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!'`); |
| 58 | 72 | } |
| 59 | 73 | |
| 60 | 74 | if (existingScriptIndex !== -1) { |
| @@ -101,6 +115,7 @@ async function deleteRegexScript({ id, isScoped }) { | ||
| 101 | 115 | async function loadRegexScripts() { |
| 102 | 116 | $('#saved_regex_scripts').empty(); |
| 103 | 117 | $('#saved_scoped_scripts').empty(); |
| 118 | + setToggleAllIcon(false); | |
| 104 | 119 | |
| 105 | 120 | const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate')); |
| 106 | 121 | |
| @@ -137,7 +152,7 @@ async function loadRegexScripts() { | ||
| 137 | 152 | await onRegexEditorOpenClick(scriptHtml.attr('id'), isScoped); |
| 138 | 153 | }); |
| 139 | 154 | scriptHtml.find('.move_to_global').on('click', async function () { |
| 140 | 155 | const confirm = await callGenericPopup('t`Are you sure you want to move this regex script to global?'`, POPUP_TYPE.CONFIRM); |
| 141 | 156 | |
| 142 | 157 | if (!confirm) { |
| 143 | 158 | return; |
| @@ -148,16 +163,16 @@ async function loadRegexScripts() { | ||
| 148 | 163 | }); |
| 149 | 164 | scriptHtml.find('.move_to_scoped').on('click', async function () { |
| 150 | 165 | if (this_chid === undefined) { |
| 151 | 166 | toastr.error('t`No character selected.'`); |
| 152 | 167 | return; |
| 153 | 168 | } |
| 154 | 169 | |
| 155 | 170 | if (selected_group) { |
| 156 | 171 | toastr.error('t`Cannot edit scoped scripts in group chats.'`); |
| 157 | 172 | return; |
| 158 | 173 | } |
| 159 | 174 | |
| 160 | 175 | const confirm = await callGenericPopup('t`Are you sure you want to move this regex script to scoped?'`, POPUP_TYPE.CONFIRM); |
| 161 | 176 | |
| 162 | 177 | if (!confirm) { |
| 163 | 178 | return; |
| @@ -167,12 +182,12 @@ async function loadRegexScripts() { | ||
| 167 | 182 | await saveRegexScript(script, -1, true); |
| 168 | 183 | }); |
| 169 | 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 | 186 | const fileData = JSON.stringify(script, null, 4); |
| 172 | 187 | download(fileData, fileName, 'application/json'); |
| 173 | 188 | }); |
| 174 | 189 | scriptHtml.find('.delete_regex').on('click', async function () { |
| 175 | 190 | const confirm = await callGenericPopup('t`Are you sure you want to delete this regex script?'`, POPUP_TYPE.CONFIRM); |
| 176 | 191 | |
| 177 | 192 | if (!confirm) { |
| 178 | 193 | return; |
| @@ -181,12 +196,17 @@ async function loadRegexScripts() { | ||
| 181 | 196 | await deleteRegexScript({ id: script.id, isScoped }); |
| 182 | 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 | + }); | |
| 184 | 204 | |
| 185 | 205 | $(container).append(scriptHtml); |
| 186 | 206 | } |
| 187 | 207 | |
| 188 | 208 | extension_settings?.regex?.forEach((script, index, array) => renderScript('#saved_regex_scripts', script, false, index, array)); |
| 189 | 209 | characters[this_chid]?.data?.extensions?.regex_scripts?.forEach((script, index, array) => renderScript('#saved_scoped_scripts', script, true, index, array)); |
| 190 | 210 | |
| 191 | 211 | const isAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar); |
| 192 | 212 | $('#regex_scoped_toggle').prop('checked', isAllowed); |
| @@ -260,11 +280,18 @@ async function onRegexEditorOpenClick(existingId, isScoped) { | ||
| 260 | 280 | |
| 261 | 281 | const testScript = { |
| 262 | 282 | id: uuidv4(), |
| 263 | 283 | scriptName: editorHtml.find('.regex_script_name').val().toString(), |
| 264 | 284 | findRegex: editorHtml.find('.find_regex').val().toString(), |
| 265 | 285 | replaceString: editorHtml.find('.regex_replace_string').val().toString(), |
| 266 | 286 | trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [], |
| 267 | 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 | 296 | const rawTestString = String(editorHtml.find('#regex_test_input').val()); |
| 270 | 297 | const result = runRegexScript(testScript, rawTestString); |
| @@ -274,19 +301,19 @@ async function onRegexEditorOpenClick(existingId, isScoped) { | ||
| 274 | 301 | editorHtml.find('input, textarea, select').on('input', updateTestResult); |
| 275 | 302 | updateInfoBlock(editorHtml); |
| 276 | 303 | |
| 277 | 304 | const popupResult = await callPopupcallGenericPopup(editorHtml, 'confirm'POPUP_TYPE.CONFIRM, undefined'', { okButton: t`Save`, cancelButton: t`Cancel`, allowVerticalScrolling: true }); |
| 278 | 305 | if (popupResult) { |
| 279 | 306 | const newRegexScript = { |
| 280 | 307 | id: existingId ? String(existingId) : uuidv4(), |
| 281 | 308 | scriptName: String(editorHtml.find('.regex_script_name').val()), |
| 282 | 309 | findRegex: String(editorHtml.find('.find_regex').val()), |
| 283 | 310 | replaceString: String(editorHtml.find('.regex_replace_string').val()), |
| 284 | 311 | trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [], |
| 285 | 312 | placement: |
| 286 | 313 | editorHtml |
| 287 | 314 | .find('input[name="replace_position"]') |
| 288 | 315 | .filter(':checked') |
| 289 | 316 | .map(function () { return parseInt($(this).val().toString()); }) |
| 290 | 317 | .get() |
| 291 | 318 | .filter((e) => !isNaN(e)) || [], |
| 292 | 319 | disabled: editorHtml.find('input[name="disabled"]').prop('checked'), |
| @@ -459,19 +486,12 @@ async function toggleRegexCallback(args, scriptName) { | ||
| 459 | 486 | } |
| 460 | 487 | |
| 461 | 488 | /** |
| 462 | 489 | * Performs the import of the regex fileobject. |
| 463 | 490 | * @param {FileObject} fileregexScript Input fileobject |
| 464 | 491 | * @param {boolean} isScoped Is the script scoped to a character? |
| 465 | 492 | */ |
| 466 | 493 | async function onRegexImportFileChangeonRegexImportObjectChange(fileregexScript, isScoped) { |
| 467 | - if (!file) { | |
| 468 | - toastr.error('No file provided.'); | |
| 469 | - return; | |
| 470 | - } | |
| 471 | - | |
| 472 | 494 | try { |
| 473 | - const fileText = await getFileText(file); | |
| 474 | - const regexScript = JSON.parse(fileText); | |
| 475 | 495 | if (!regexScript.scriptName) { |
| 476 | 496 | throw new Error('No script name provided.'); |
| 477 | 497 | } |
| @@ -488,7 +508,34 @@ async function onRegexImportFileChange(file, isScoped) { | ||
| 488 | 508 | |
| 489 | 509 | saveSettingsDebounced(); |
| 490 | 510 | await loadRegexScripts(); |
| 491 | 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 | + */ | |
| 524 | +async 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 | 539 | } catch (error) { |
| 493 | 540 | console.log(error); |
| 494 | 541 | toastr.error('Invalid JSON file.'); |
| @@ -556,12 +603,12 @@ jQuery(async () => { | ||
| 556 | 603 | }); |
| 557 | 604 | $('#open_scoped_editor').on('click', function () { |
| 558 | 605 | if (this_chid === undefined) { |
| 559 | 606 | toastr.error('t`No character selected.'`); |
| 560 | 607 | return; |
| 561 | 608 | } |
| 562 | 609 | |
| 563 | 610 | if (selected_group) { |
| 564 | 611 | toastr.error('t`Cannot edit scoped scripts in group chats.'`); |
| 565 | 612 | return; |
| 566 | 613 | } |
| 567 | 614 | |
| @@ -585,6 +632,82 @@ jQuery(async () => { | ||
| 585 | 632 | $('#import_regex_file').trigger('click'); |
| 586 | 633 | }); |
| 587 | 634 | |
| 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 | 711 | let sortableDatas = [ |
| 589 | 712 | { |
| 590 | 713 | selector: '#saved_regex_scripts', |
| @@ -622,12 +745,12 @@ jQuery(async () => { | ||
| 622 | 745 | |
| 623 | 746 | $('#regex_scoped_toggle').on('input', function () { |
| 624 | 747 | if (this_chid === undefined) { |
| 625 | 748 | toastr.error('t`No character selected.'`); |
| 626 | 749 | return; |
| 627 | 750 | } |
| 628 | 751 | |
| 629 | 752 | if (selected_group) { |
| 630 | 753 | toastr.error('t`Cannot edit scoped scripts in group chats.'`); |
| 631 | 754 | return; |
| 632 | 755 | } |
| 633 | 756 | |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | <div class="regex-script-label flex-container flexnowrap"> |
| 2 | + <input type="checkbox" class="regex_bulk_checkbox" /> | |
| 2 | 3 | <span class="drag-handle menu-handle">☰</span> |
| 3 | 4 | <div class="regex_script_name flexGrow overflow-hidden"></div> |
| 4 | 5 | <div class="flex-container flexnowrap"> |
| @@ -15,7 +15,7 @@ | ||
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | .regex-script-container:empty::after { |
| 18 | - content: "No scripts found"; | |
| 18 | + content: attr(no-scripts-text); | |
| 19 | 19 | font-size: 0.95em; |
| 20 | 20 | opacity: 0.7; |
| 21 | 21 | display: block; |
| @@ -24,7 +24,7 @@ | ||
| 24 | 24 | |
| 25 | 25 | #scoped_scripts_block { |
| 26 | 26 | opacity: 1; |
| 27 | 27 | transition: opacity 0.2svar(--animation-duration-2x) ease-in-out; |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | #scoped_scripts_block .move_to_scoped { |
| @@ -56,7 +56,7 @@ | ||
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | .regex-script-label { |
| 59 | 59 | align-items: centerbaseline; |
| 60 | 60 | border: 1px solid var(--SmartThemeBorderColor); |
| 61 | 61 | border-radius: 10px; |
| 62 | 62 | padding: 0 5px; |
| @@ -78,7 +78,7 @@ input.enable_scoped { | ||
| 78 | 78 | cursor: pointer; |
| 79 | 79 | opacity: 0.5; |
| 80 | 80 | filter: grayscale(0.5); |
| 81 | 81 | transition: opacity 0.2svar(--animation-duration-2x) ease-in-out; |
| 82 | 82 | } |
| 83 | 83 | |
| 84 | 84 | .regex-toggle-off:hover { |
| @@ -126,3 +126,25 @@ input.enable_scoped { | ||
| 126 | 126 | right: 10px; |
| 127 | 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 | +} | |
| @@ -56,6 +56,13 @@ export async function getMultimodalCaption(base64Img, prompt) { | ||
| 56 | 56 | model: extension_settings.caption.multimodal_model || 'gpt-4-turbo', |
| 57 | 57 | }; |
| 58 | 58 | |
| 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 | 66 | if (isOllama) { |
| 60 | 67 | if (extension_settings.caption.multimodal_model === 'ollama_current') { |
| 61 | 68 | requestBody.model = textgenerationwebui_settings.ollama_model; |
| @@ -164,8 +171,24 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 164 | 171 | throw new Error('Google AI Studio API key is not set.'); |
| 165 | 172 | } |
| 166 | 173 | |
| 167 | 174 | if (multimodalApi === 'vertexai' && !secret_state[SECRET_KEYS.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 | } |
| 170 | 193 | |
| 171 | 194 | if (multimodalApi === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) { |
| @@ -211,6 +234,10 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 211 | 234 | if (multimodalApi === 'custom' && !oai_settings.custom_url) { |
| 212 | 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 | } |
| 215 | 242 | |
| 216 | 243 | /** |
| @@ -41,7 +41,7 @@ import { | ||
| 41 | 41 | stringFormat, |
| 42 | 42 | } from '../../utils.js'; |
| 43 | 43 | import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js'; |
| 44 | 44 | import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; |
| 45 | 45 | import { getNovelAnlas, getNovelUnlimitedImageGeneration, loadNovelSubscriptionData } from '../../nai-settings.js'; |
| 46 | 46 | import { getMultimodalCaption } from '../shared.js'; |
| 47 | 47 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -53,11 +53,12 @@ import { | ||
| 53 | 53 | } from '../../slash-commands/SlashCommandArgument.js'; |
| 54 | 54 | import { debounce_timeout } from '../../constants.js'; |
| 55 | 55 | import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 56 | 56 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 57 | 57 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 58 | 58 | import { ToolManager } from '../../tool-calling.js'; |
| 59 | 59 | import { MacrosParser } from '../../macros.js'; |
| 60 | 60 | import { t } from '../../i18n.js'; |
| 61 | +import { oai_settings } from '../../openai.js'; | |
| 61 | 62 | |
| 62 | 63 | export { MODULE_NAME }; |
| 63 | 64 | |
| @@ -74,6 +75,7 @@ const sources = { | ||
| 74 | 75 | novel: 'novel', |
| 75 | 76 | vlad: 'vlad', |
| 76 | 77 | openai: 'openai', |
| 78 | + aimlapi: 'aimlapi', | |
| 77 | 79 | comfy: 'comfy', |
| 78 | 80 | togetherai: 'togetherai', |
| 79 | 81 | drawthings: 'drawthings', |
| @@ -84,6 +86,7 @@ const sources = { | ||
| 84 | 86 | bfl: 'bfl', |
| 85 | 87 | falai: 'falai', |
| 86 | 88 | xai: 'xai', |
| 89 | + google: 'google', | |
| 87 | 90 | }; |
| 88 | 91 | |
| 89 | 92 | const initiators = { |
| @@ -329,6 +332,10 @@ const defaultSettings = { | ||
| 329 | 332 | |
| 330 | 333 | // BFL API settings |
| 331 | 334 | bfl_upsampling: false, |
| 335 | + | |
| 336 | + // Google settings | |
| 337 | + google_api: 'makersuite', | |
| 338 | + google_enhance: true, | |
| 332 | 339 | }; |
| 333 | 340 | |
| 334 | 341 | const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed); |
| @@ -508,6 +515,8 @@ async function loadSettings() { | ||
| 508 | 515 | $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id); |
| 509 | 516 | $('#sd_function_tool').prop('checked', extension_settings.sd.function_tool); |
| 510 | 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); | |
| 511 | 520 | |
| 512 | 521 | for (const style of extension_settings.sd.styles) { |
| 513 | 522 | const option = document.createElement('option'); |
| @@ -1144,42 +1153,6 @@ function onComfyWorkflowChange() { | ||
| 1144 | 1153 | saveSettingsDebounced(); |
| 1145 | 1154 | } |
| 1146 | 1155 | |
| 1147 | -async 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 | - | |
| 1171 | -async function onStabilityKeyClick() { | |
| 1172 | - return onApiKeyClick('Stability AI API Key:', SECRET_KEYS.STABILITY); | |
| 1173 | -} | |
| 1174 | - | |
| 1175 | -async function onBflKeyClick() { | |
| 1176 | - return onApiKeyClick('BFL API Key:', SECRET_KEYS.BFL); | |
| 1177 | -} | |
| 1178 | - | |
| 1179 | -async function onFalaiKeyClick() { | |
| 1180 | - return onApiKeyClick('FALAI API Key:', SECRET_KEYS.FALAI); | |
| 1181 | -} | |
| 1182 | - | |
| 1183 | 1156 | function onBflUpsamplingInput() { |
| 1184 | 1157 | extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked'); |
| 1185 | 1158 | saveSettingsDebounced(); |
| @@ -1303,6 +1276,7 @@ async function onModelChange() { | ||
| 1303 | 1276 | sources.horde, |
| 1304 | 1277 | sources.novel, |
| 1305 | 1278 | sources.openai, |
| 1279 | + sources.aimlapi, | |
| 1306 | 1280 | sources.togetherai, |
| 1307 | 1281 | sources.pollinations, |
| 1308 | 1282 | sources.stability, |
| @@ -1311,6 +1285,7 @@ async function onModelChange() { | ||
| 1311 | 1285 | sources.bfl, |
| 1312 | 1286 | sources.falai, |
| 1313 | 1287 | sources.xai, |
| 1288 | + sources.google, | |
| 1314 | 1289 | ]; |
| 1315 | 1290 | |
| 1316 | 1291 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1505,6 +1480,9 @@ async function loadSamplers() { | ||
| 1505 | 1480 | case sources.openai: |
| 1506 | 1481 | samplers = ['N/A']; |
| 1507 | 1482 | break; |
| 1483 | + case sources.aimlapi: | |
| 1484 | + samplers = ['N/A']; | |
| 1485 | + break; | |
| 1508 | 1486 | case sources.comfy: |
| 1509 | 1487 | samplers = await loadComfySamplers(); |
| 1510 | 1488 | break; |
| @@ -1529,6 +1507,9 @@ async function loadSamplers() { | ||
| 1529 | 1507 | case sources.xai: |
| 1530 | 1508 | samplers = ['N/A']; |
| 1531 | 1509 | break; |
| 1510 | + case sources.google: | |
| 1511 | + samplers = ['N/A']; | |
| 1512 | + break; | |
| 1532 | 1513 | } |
| 1533 | 1514 | |
| 1534 | 1515 | for (const sampler of samplers) { |
| @@ -1695,6 +1676,9 @@ async function loadModels() { | ||
| 1695 | 1676 | case sources.openai: |
| 1696 | 1677 | models = await loadOpenAiModels(); |
| 1697 | 1678 | break; |
| 1679 | + case sources.aimlapi: | |
| 1680 | + models = await loadAimlapiModels(); | |
| 1681 | + break; | |
| 1698 | 1682 | case sources.comfy: |
| 1699 | 1683 | models = await loadComfyModels(); |
| 1700 | 1684 | break; |
| @@ -1722,6 +1706,9 @@ async function loadModels() { | ||
| 1722 | 1706 | case sources.xai: |
| 1723 | 1707 | models = await loadXAIModels(); |
| 1724 | 1708 | break; |
| 1709 | + case sources.google: | |
| 1710 | + models = await loadGoogleModels(); | |
| 1711 | + break; | |
| 1725 | 1712 | } |
| 1726 | 1713 | |
| 1727 | 1714 | for (const model of models) { |
| @@ -1959,6 +1946,23 @@ async function loadOpenAiModels() { | ||
| 1959 | 1946 | ]; |
| 1960 | 1947 | } |
| 1961 | 1948 | |
| 1949 | +async 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 | + | |
| 1962 | 1966 | async function loadVladModels() { |
| 1963 | 1967 | if (!extension_settings.sd.vlad_url) { |
| 1964 | 1968 | return []; |
| @@ -2004,12 +2008,20 @@ async function loadVladModels() { | ||
| 2004 | 2008 | async function loadNovelModels() { |
| 2005 | 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 | 2019 | value: 'nai-diffusion-4-full', |
| 2008 | 2020 | text: 'NAI Diffusion Anime V4 (Full)', |
| 2009 | 2021 | }, |
| 2010 | 2022 | { |
| 2011 | 2023 | value: 'nai-diffusion-4-curated-preview', |
| 2012 | 2024 | text: 'NAI Diffusion Anime V4 (Curated Preview)', |
| 2013 | 2025 | }, |
| 2014 | 2026 | { |
| 2015 | 2027 | value: 'nai-diffusion-3', |
| @@ -2026,6 +2038,21 @@ async function loadNovelModels() { | ||
| 2026 | 2038 | ]; |
| 2027 | 2039 | } |
| 2028 | 2040 | |
| 2041 | +async 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 | + | |
| 2029 | 2056 | function loadNovelSchedulers() { |
| 2030 | 2057 | return ['karras', 'native', 'exponential', 'polyexponential']; |
| 2031 | 2058 | } |
| @@ -2078,6 +2105,9 @@ async function loadSchedulers() { | ||
| 2078 | 2105 | case sources.openai: |
| 2079 | 2106 | schedulers = ['N/A']; |
| 2080 | 2107 | break; |
| 2108 | + case sources.aimlapi: | |
| 2109 | + schedulers = ['N/A']; | |
| 2110 | + break; | |
| 2081 | 2111 | case sources.togetherai: |
| 2082 | 2112 | schedulers = ['N/A']; |
| 2083 | 2113 | break; |
| @@ -2105,6 +2135,9 @@ async function loadSchedulers() { | ||
| 2105 | 2135 | case sources.xai: |
| 2106 | 2136 | schedulers = ['N/A']; |
| 2107 | 2137 | break; |
| 2138 | + case sources.google: | |
| 2139 | + schedulers = ['N/A']; | |
| 2140 | + break; | |
| 2108 | 2141 | } |
| 2109 | 2142 | |
| 2110 | 2143 | for (const scheduler of schedulers) { |
| @@ -2169,6 +2202,9 @@ async function loadVaes() { | ||
| 2169 | 2202 | case sources.openai: |
| 2170 | 2203 | vaes = ['N/A']; |
| 2171 | 2204 | break; |
| 2205 | + case sources.aimlapi: | |
| 2206 | + vaes = ['N/A']; | |
| 2207 | + break; | |
| 2172 | 2208 | case sources.togetherai: |
| 2173 | 2209 | vaes = ['N/A']; |
| 2174 | 2210 | break; |
| @@ -2196,6 +2232,9 @@ async function loadVaes() { | ||
| 2196 | 2232 | case sources.xai: |
| 2197 | 2233 | vaes = ['N/A']; |
| 2198 | 2234 | break; |
| 2235 | + case sources.google: | |
| 2236 | + vaes = ['N/A']; | |
| 2237 | + break; | |
| 2199 | 2238 | } |
| 2200 | 2239 | |
| 2201 | 2240 | for (const vae of vaes) { |
| @@ -2465,9 +2504,14 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 2465 | 2504 | const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; |
| 2466 | 2505 | |
| 2467 | 2506 | // generate the text prompt for the image |
| 2468 | 2507 | constlet prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives); |
| 2469 | 2508 | console.log('Processed image prompt:', prompt); |
| 2470 | 2509 | |
| 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 | 2515 | $(stopButton).show(); |
| 2472 | 2516 | eventSource.once(CUSTOM_STOP_EVENT, stopListener); |
| 2473 | 2517 | |
| @@ -2741,6 +2785,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2741 | 2785 | case sources.openai: |
| 2742 | 2786 | result = await generateOpenAiImage(prefixedPrompt, signal); |
| 2743 | 2787 | break; |
| 2788 | + case sources.aimlapi: | |
| 2789 | + result = await generateAimlapiImage(prefixedPrompt, signal); | |
| 2790 | + break; | |
| 2744 | 2791 | case sources.comfy: |
| 2745 | 2792 | result = await generateComfyImage(prefixedPrompt, negativePrompt, signal); |
| 2746 | 2793 | break; |
| @@ -2768,6 +2815,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2768 | 2815 | case sources.xai: |
| 2769 | 2816 | result = await generateXAIImage(prefixedPrompt, negativePrompt, signal); |
| 2770 | 2817 | break; |
| 2818 | + case sources.google: | |
| 2819 | + result = await generateGoogleImage(prefixedPrompt, negativePrompt, signal); | |
| 2820 | + break; | |
| 2771 | 2821 | } |
| 2772 | 2822 | |
| 2773 | 2823 | if (!result.data) { |
| @@ -2905,10 +2955,14 @@ async function generateExtrasImage(prompt, negativePrompt, signal) { | ||
| 2905 | 2955 | * Gets an aspect ratio for Stability that is the closest to the given width and height. |
| 2906 | 2956 | * @param {number} width Target width |
| 2907 | 2957 | * @param {number} height Target height |
| 2958 | + * @param {'google'|'stability'} source Source of the request, used to determine aspect ratio | |
| 2908 | 2959 | * @returns {string} Closest aspect ratio as a string |
| 2909 | 2960 | */ |
| 2910 | 2961 | function getClosestAspectRatio(width, height, source) { |
| 2911 | 2962 | const aspectRatiosfunction =getAspectRatios() { |
| 2963 | + switch (source) { | |
| 2964 | + case 'stability': | |
| 2965 | + return { | |
| 2912 | 2966 | '16:9': 16 / 9, |
| 2913 | 2967 | '1:1': 1, |
| 2914 | 2968 | '21:9': 21 / 9, |
| @@ -2919,6 +2973,21 @@ function getClosestAspectRatio(width, height) { | ||
| 2919 | 2973 | '9:16': 9 / 16, |
| 2920 | 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 }; | |
| 2922 | 2991 | |
| 2923 | 2992 | const aspectRatio = width / height; |
| 2924 | 2993 | |
| @@ -2957,7 +3026,7 @@ async function generateStabilityImage(prompt, negativePrompt, signal) { | ||
| 2957 | 3026 | payload: { |
| 2958 | 3027 | prompt: prompt.slice(0, PROMPT_LIMIT), |
| 2959 | 3028 | negative_prompt: negativePrompt.slice(0, PROMPT_LIMIT), |
| 2960 | 3029 | aspect_ratio: getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height, 'stability'), |
| 2961 | 3030 | seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, |
| 2962 | 3031 | style_preset: extension_settings.sd.stability_style_preset, |
| 2963 | 3032 | output_format: IMAGE_FORMAT, |
| @@ -3326,6 +3395,47 @@ async function generateOpenAiImage(prompt, signal) { | ||
| 3326 | 3395 | } |
| 3327 | 3396 | |
| 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 | + */ | |
| 3404 | +async 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 | 3439 | * Generates an image in ComfyUI using the provided prompt and configuration settings. |
| 3330 | 3440 | * |
| 3331 | 3441 | * @param {string} prompt - The main instruction used to guide the image generation. |
| @@ -3567,7 +3677,41 @@ async function generateFalaiImage(prompt, negativePrompt, signal) { | ||
| 3567 | 3677 | return { format: 'jpg', data: data.image }; |
| 3568 | 3678 | } else { |
| 3569 | 3679 | const text = await result.text(); |
| 3570 | 3680 | console.logthrow 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 | + */ | |
| 3691 | +async 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 | 3715 | throw new Error(text); |
| 3572 | 3716 | } |
| 3573 | 3717 | } |
| @@ -3841,6 +3985,8 @@ function isValidState() { | ||
| 3841 | 3985 | return secret_state[SECRET_KEYS.NOVEL]; |
| 3842 | 3986 | case sources.openai: |
| 3843 | 3987 | return secret_state[SECRET_KEYS.OPENAI]; |
| 3988 | + case sources.aimlapi: | |
| 3989 | + return secret_state[SECRET_KEYS.AIMLAPI]; | |
| 3844 | 3990 | case sources.comfy: |
| 3845 | 3991 | return true; |
| 3846 | 3992 | case sources.togetherai: |
| @@ -3859,6 +4005,8 @@ function isValidState() { | ||
| 3859 | 4005 | return secret_state[SECRET_KEYS.FALAI]; |
| 3860 | 4006 | case sources.xai: |
| 3861 | 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 | } |
| 3864 | 4012 | |
| @@ -4040,6 +4188,7 @@ async function onImageSwiped({ message, element, direction }) { | ||
| 4040 | 4188 | const hasNegative = message.extra.negative; |
| 4041 | 4189 | const prompt = await refinePrompt(message.extra.title, false); |
| 4042 | 4190 | const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : ''; |
| 4191 | + message.extra.title = prompt; | |
| 4043 | 4192 | const characterName = context.groupId |
| 4044 | 4193 | ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString() |
| 4045 | 4194 | : context.characters[context.characterId]?.name; |
| @@ -4514,13 +4663,19 @@ jQuery(async () => { | ||
| 4514 | 4663 | $('#sd_interactive_visible').on('input', onInteractiveVisibleInput); |
| 4515 | 4664 | $('#sd_tool_visible').on('input', onToolVisibleInput); |
| 4516 | 4665 | $('#sd_swap_dimensions').on('click', onSwapDimensionsClick); |
| 4517 | - $('#sd_stability_key').on('click', onStabilityKeyClick); | |
| 4518 | 4666 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); |
| 4519 | 4667 | $('#sd_huggingface_model_id').on('input', onHFModelInput); |
| 4520 | 4668 | $('#sd_function_tool').on('input', onFunctionToolInput); |
| 4521 | - $('#sd_bfl_key').on('click', onBflKeyClick); | |
| 4522 | 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 | + }); | |
| 4524 | 4679 | |
| 4525 | 4680 | if (!CSS.supports('field-sizing', 'content')) { |
| 4526 | 4681 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| @@ -4548,6 +4703,19 @@ jQuery(async () => { | ||
| 4548 | 4703 | |
| 4549 | 4704 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| 4550 | 4705 | |
| 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 | 4719 | await loadSettings(); |
| 4552 | 4720 | $('body').addClass('sd'); |
| 4553 | 4721 | |
| @@ -37,11 +37,13 @@ | ||
| 37 | 37 | </label> |
| 38 | 38 | <label for="sd_source" data-i18n="Source">Source</label> |
| 39 | 39 | <select id="sd_source"> |
| 40 | + <option value="aimlapi">AI/ML API</option> | |
| 40 | 41 | <option value="bfl">BFL (Black Forest Labs)</option> |
| 41 | 42 | <option value="comfy">ComfyUI</option> |
| 42 | 43 | <option value="drawthings">DrawThings HTTP API</option> |
| 43 | 44 | <option value="extras">Extras API (deprecated)</option> |
| 44 | 45 | <option value="falai">FAL.AI</option> |
| 46 | + <option value="google">Google AI</option> | |
| 45 | 47 | <option value="huggingface">HuggingFace Inference API (serverless)</option> |
| 46 | 48 | <option value="nanogpt">NanoGPT</option> |
| 47 | 49 | <option value="novel">NovelAI Diffusion</option> |
| @@ -139,7 +141,16 @@ | ||
| 139 | 141 | </div> |
| 140 | 142 | <i>Hint: Save an API key in the NovelAI API settings to use it here.</i> |
| 141 | 143 | </div> |
| 142 | 144 | <div data-sd-source="openaiaimlapi"> |
| 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 | 154 | <small data-i18n="These settings only apply to DALL-E 3">These settings only apply to DALL-E 3</small> |
| 144 | 155 | <div class="flex-container"> |
| 145 | 156 | <div class="flex1"> |
| @@ -200,7 +211,7 @@ | ||
| 200 | 211 | <div data-sd-source="stability"> |
| 201 | 212 | <div class="flex-container flexnowrap alignItemsBaseline marginBot5"> |
| 202 | 213 | <strong class="flex1" data-i18n="API Key">API Key</strong> |
| 203 | 214 | <div id="sd_stability_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_stability"> |
| 204 | 215 | <i class="fa-fw fa-solid fa-key"></i> |
| 205 | 216 | <span data-i18n="Click to set">Click to set</span> |
| 206 | 217 | </div> |
| @@ -244,7 +255,7 @@ | ||
| 244 | 255 | <i class="fa-solid fa-share-from-square"></i> |
| 245 | 256 | </a> |
| 246 | 257 | <span class="expander"></span> |
| 247 | 258 | <div id="sd_bfl_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_bfl"> |
| 248 | 259 | <i class="fa-fw fa-solid fa-key"></i> |
| 249 | 260 | <span data-i18n="Click to set">Click to set</span> |
| 250 | 261 | </div> |
| @@ -264,13 +275,33 @@ | ||
| 264 | 275 | <i class="fa-solid fa-share-from-square"></i> |
| 265 | 276 | </a> |
| 266 | 277 | <span class="expander"></span> |
| 267 | 278 | <div id="sd_falai_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_falai"> |
| 268 | 279 | <i class="fa-fw fa-solid fa-key"></i> |
| 269 | 280 | <span data-i18n="Click to set">Click to set</span> |
| 270 | 281 | </div> |
| 271 | 282 | </div> |
| 272 | 283 | </div> |
| 273 | 284 | |
| 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 | 305 | <div class="flex-container"> |
| 275 | 306 | <div class="flex1"> |
| 276 | 307 | <label for="sd_model" data-i18n="Model">Model</label> |
| @@ -23,7 +23,7 @@ | ||
| 23 | 23 | <option value="bing">Bing</option> |
| 24 | 24 | <option value="oneringtranslator">OneRingTranslator</option> |
| 25 | 25 | <option value="yandex">Yandex</option> |
| 26 | 26 | </select> |
| 27 | 27 | <select id="deepl_api_endpoint" class="margin0 text_pole flex1" title="DeepL API Endpoint"> |
| 28 | 28 | <option value="free">Free</option> |
| 29 | 29 | <option value="pro">Pro</option> |
| @@ -0,0 +1,649 @@ | ||
| 1 | +import { saveTtsProviderSettings } from './index.js'; | |
| 2 | + | |
| 3 | +export { ChatterboxTtsProvider }; | |
| 4 | + | |
| 5 | +class 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) { | |
Diff truncated