mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-22 06:57:41 +01:00
commit
6c909acea6
@ -5,3 +5,4 @@ readme*
|
||||
Start.bat
|
||||
/dist
|
||||
/backups/
|
||||
cloudflared.exe
|
||||
|
16
.github/readme-zh_cn.md
vendored
16
.github/readme-zh_cn.md
vendored
@ -1,6 +1,8 @@
|
||||
[English](readme.md) | 中文
|
||||
|
||||

|
||||
|
||||
移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, WindowAI(Claude!)),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
|
||||
移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
|
||||
|
||||
基于 TavernAI 1.2.8 的分叉版本
|
||||
|
||||
@ -290,16 +292,18 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
|
||||
* Cohee's modifications and derived code: AGPL v3
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (https://github.com/bdashore3)
|
||||
* Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/)
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
|
||||
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
|
||||
* Noto Sans font by Google (OFL license)
|
||||
* Icon theme by Font Awesome https://fontawesome.com (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
|
||||
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>
|
||||
* Linux startup script by AlpinDale
|
||||
* Thanks paniphons for providing a FAQ document
|
||||
* 10K Discord Users Celebratory Background by @kallmeflocc
|
||||
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
|
||||
* Korean translation by @doloroushyeonse
|
||||
* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3
|
||||
|
8
.github/readme.md
vendored
8
.github/readme.md
vendored
@ -1,6 +1,8 @@
|
||||
English | [中文](readme-zh_cn.md)
|
||||
|
||||

|
||||
|
||||
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, WindowAI(Claude!)), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
|
||||
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
|
||||
|
||||
Based on a fork of TavernAI 1.2.8
|
||||
|
||||
@ -65,7 +67,7 @@ Get in touch with the developers directly:
|
||||
* Chat bookmarks / branching (duplicates the dialogue in its current state)
|
||||
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
|
||||
* World Info support: create rich lore or save tokens on your character card
|
||||
* Window AI browser extension support (run models like Claude, GPT 4): <https://windowai.io/>
|
||||
* [OpenRouter](https://openrouter.ai) connection for various APIs (Claude, GPT-4/3.5 and more)
|
||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
||||
* [AI Horde](https://horde.koboldai.net/) connection
|
||||
* Prompt generation formatting tweaking
|
||||
@ -293,6 +295,7 @@ GNU Affero General Public License for more details.**
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
@ -306,3 +309,4 @@ GNU Affero General Public License for more details.**
|
||||
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
|
||||
* Korean translation by @doloroushyeonse
|
||||
* k_euler_a support for Horde by <https://github.com/Teashrock>
|
||||
* Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,3 +28,4 @@ secrets.json
|
||||
public/movingUI/
|
||||
public/QuickReplies/
|
||||
content.log
|
||||
cloudflared.exe
|
||||
|
18
Remote-Link.cmd
Normal file
18
Remote-Link.cmd
Normal file
@ -0,0 +1,18 @@
|
||||
@echo off
|
||||
echo ========================================================================================================================
|
||||
echo WARNING: Cloudflare Tunnel!
|
||||
echo ========================================================================================================================
|
||||
echo This script downloads and runs the latest cloudflared.exe from Cloudflare to set up an HTTPS tunnel to your SillyTavern!
|
||||
echo Using the randomly generated temporary tunnel URL, anyone can access your SillyTavern over the Internet while the tunnel
|
||||
echo is active. Keep the URL safe and secure your SillyTavern installation by setting a username and password in config.conf!
|
||||
echo.
|
||||
echo See https://docs.sillytavern.app/usage/remoteconnections/ for more details about how to secure your SillyTavern install.
|
||||
echo.
|
||||
echo By continuing you confirm that you're aware of the potential dangers of having a tunnel open and take all responsibility
|
||||
echo to properly use and secure it!
|
||||
echo.
|
||||
echo To abort, press Ctrl+C or close this window now!
|
||||
echo.
|
||||
pause
|
||||
if not exist cloudflared.exe curl -Lo cloudflared.exe https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe
|
||||
cloudflared.exe tunnel --url localhost:8000
|
@ -59,7 +59,7 @@
|
||||
"trusted_workers_only": false
|
||||
},
|
||||
"power_user": {
|
||||
"tokenizer": 3,
|
||||
"tokenizer": 99,
|
||||
"token_padding": 64,
|
||||
"collapse_newlines": false,
|
||||
"pygmalion_formatting": 0,
|
||||
@ -147,6 +147,7 @@
|
||||
"persona_description": "",
|
||||
"persona_description_position": 0,
|
||||
"custom_stopping_strings": "",
|
||||
"custom_stopping_strings_macro": true,
|
||||
"fuzzy_search": false
|
||||
},
|
||||
"extension_settings": {
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.9.4",
|
||||
"version": "1.9.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.9.4",
|
||||
"version": "1.9.5",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
|
@ -50,9 +50,10 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.9.4",
|
||||
"version": "1.9.5",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start-multi": "node server.js --disableCsrf",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
},
|
||||
"bin": {
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 1.15,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 90,
|
||||
"temp": 0.8,
|
||||
"top_k": 28,
|
||||
"top_p": 0.94,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.59,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.8,
|
||||
"top_k": 100,
|
||||
"top_p": 0.9,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 180,
|
||||
"temp": 1.0,
|
||||
"top_p": 0.9,
|
||||
"top_k": 40,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 180,
|
||||
"temp": 0.43,
|
||||
"top_p": 0.96,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 180,
|
||||
"temp": 0.65,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.51,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 1600,
|
||||
"genamt": 180,
|
||||
"temp": 0.79,
|
||||
"top_k": 0,
|
||||
"top_p": 0.9,
|
||||
|
22
public/KoboldAI Settings/Deterministic.settings
Normal file
22
public/KoboldAI Settings/Deterministic.settings
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"temp": 0,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 2048,
|
||||
"streaming_kobold": true,
|
||||
"top_p": 0,
|
||||
"top_a": 0,
|
||||
"top_k": 1,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.2,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
]
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 180,
|
||||
"temp": 0.79,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 1400,
|
||||
"genamt": 180,
|
||||
"temp": 0.65,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.63,
|
||||
"top_k": 0,
|
||||
"top_p": 0.98,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.7,
|
||||
"top_k": 0,
|
||||
"top_p": 0.5,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.7,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 90,
|
||||
"temp": 0.8,
|
||||
"top_p": 0.94,
|
||||
"top_k": 15,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.66,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.94,
|
||||
"top_k": 12,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 1.5,
|
||||
"top_k": 85,
|
||||
"top_p": 0.24,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 1.05,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 1.07,
|
||||
"top_k": 100,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.44,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 1.35,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 1400,
|
||||
"genamt": 80,
|
||||
"temp": 1,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 200,
|
||||
"temp": 1,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
|
22
public/KoboldAI Settings/Storywriter-Llama2.settings
Normal file
22
public/KoboldAI Settings/Storywriter-Llama2.settings
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"temp": 0.72,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 4096,
|
||||
"streaming_kobold": true,
|
||||
"top_p": 0.73,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.2,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
]
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"max_length": 2048,
|
||||
"genamt": 100,
|
||||
"temp": 0.72,
|
||||
"tfs": 1,
|
||||
"top_a": 0,
|
||||
|
22
public/KoboldAI Settings/simple-proxy-for-tavern.settings
Normal file
22
public/KoboldAI Settings/simple-proxy-for-tavern.settings
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"temp": 0.65,
|
||||
"rep_pen": 1.18,
|
||||
"rep_pen_range": 2048,
|
||||
"streaming_kobold": true,
|
||||
"top_p": 0.47,
|
||||
"top_a": 0,
|
||||
"top_k": 42,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
]
|
||||
}
|
@ -15,5 +15,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -15,5 +15,5 @@
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "medium",
|
||||
"cfg_scale": 1.55,
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -16,5 +16,5 @@
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "very_aggressive",
|
||||
"cfg_scale": 1.3,
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -17,5 +17,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -17,5 +17,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -16,5 +16,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "off",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -15,5 +15,5 @@
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"cfg_scale": 1.825,
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "medium",
|
||||
"cfg_scale": 1.35,
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
19
public/NovelAI Settings/Pro_Writer-Kayra.settings
Normal file
19
public/NovelAI Settings/Pro_Writer-Kayra.settings
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [3, 4, 0],
|
||||
"temperature": 1.19,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_a": 0.116,
|
||||
"tail_free_sampling": 0.958,
|
||||
"repetition_penalty": 1.64,
|
||||
"repetition_penalty_slope": 2.12,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"repetition_penalty_range": 2048,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "medium",
|
||||
"cfg_scale": 1.0,
|
||||
"max_context": 7800
|
||||
}
|
@ -13,5 +13,5 @@
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
19
public/NovelAI Settings/Tea_Time-Kayra.settings
Normal file
19
public/NovelAI Settings/Tea_Time-Kayra.settings
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [5, 0, 4],
|
||||
"temperature": 1,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_a": 0.017,
|
||||
"typical_p": 0.975,
|
||||
"repetition_penalty": 3,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"repetition_penalty_range": 7680,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"cfg_scale": 1.0,
|
||||
"max_context": 7800
|
||||
}
|
@ -14,5 +14,5 @@
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"cfg_scale": 1.3,
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -18,5 +18,5 @@
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -15,5 +15,5 @@
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "very_aggressive",
|
||||
"max_context": 8192
|
||||
"max_context": 7800
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"top_p": 0.9,
|
||||
"top_k": 20,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.75,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
|
23
public/TextGen Settings/simple-proxy-for-tavern.settings
Normal file
23
public/TextGen Settings/simple-proxy-for-tavern.settings
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"temp": 0.65,
|
||||
"top_p": 0.47,
|
||||
"top_k": 42,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.18,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 0,
|
||||
"mirostat_tau": 5,
|
||||
"mirostat_eta": 0.1
|
||||
}
|
@ -1523,7 +1523,7 @@
|
||||
"Continue": "이어서 계속",
|
||||
"Editing:": "편집:",
|
||||
"AI reply prefix": "AI 답변 접두사",
|
||||
"Custom Stopping Strings": "문장출력 중단 문자열 (KoboldAI/TextGen)",
|
||||
"Custom Stopping Strings": "문장출력 중단 문자열 (KoboldAI/TextGen/NovelAI)",
|
||||
"JSON serialized array of strings": "JSON 연속 문자배열",
|
||||
"words you dont want generated separated by comma ','": "답변에 포함을 막으려는 단어 (쉼표','로 구분)",
|
||||
"Extensions URL": "확장기능 URL",
|
||||
|
@ -328,7 +328,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="temp_novel" name="volume" min="0.1" max="2.0" step="0.01">
|
||||
<input type="range" id="temp_novel" name="volume" min="0.1" max="2.50" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="temp_novel" id="temp_counter_novel">
|
||||
@ -342,7 +342,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_novel" name="volume" min="1" max="1.5" step="0.01">
|
||||
<input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.05">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="rep_pen_novel" id="rep_pen_counter_novel">
|
||||
@ -387,7 +387,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_freq_novel" name="volume" min="0" max="1" step="0.00001">
|
||||
<input type="range" id="rep_pen_freq_novel" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="rep_pen_freq_novel" id="rep_pen_freq_counter_novel">
|
||||
@ -402,7 +402,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_presence_novel" name="volume" min="0" max="1" step="0.001">
|
||||
<input type="range" id="rep_pen_presence_novel" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="rep_pen_presence_novel" id="rep_pen_presence_counter_novel">
|
||||
@ -807,6 +807,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="novel_api-settings">
|
||||
<div class="range-block">
|
||||
<div class="range-block-title openai_restorable">
|
||||
<span data-i18n="Preamble">Preamble</span>
|
||||
<div id="nai_preamble_restore" title="Restore default prompt" data-i18n="[title]Restore default prompt"
|
||||
class="right_menu_button">
|
||||
<div class="fa-solid fa-clock-rotate-left "></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output">
|
||||
Use style tags to modify the writing style of the output
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<textarea id="nai_preamble_textarea" class="text_pole textarea_compact" name="nai_preamble" rows="2"
|
||||
placeholder=""></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Top P">
|
||||
Top P
|
||||
@ -1320,12 +1336,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<div class="range-block-title" data-i18n="Assistant Prefill">
|
||||
Assistant Prefill
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<input type="text" id="claude_assistant_prefill" class="text_pole" placeholder="Start Claude's answer with...">
|
||||
</div>
|
||||
<span data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="3" maxlength="5000" placeholder="Start Claude's answer with..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer wide100p">
|
||||
@ -1539,7 +1551,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="textgenerationwebui_api" style="display: none;position: relative;">
|
||||
<div class="flex-container">
|
||||
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
If you are using:
|
||||
<div class="flex-container indent20p">
|
||||
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
|
||||
oobabooga/text-generation-webui
|
||||
</a>
|
||||
@ -1547,8 +1561,30 @@
|
||||
Make sure you run it with <tt>--api</tt> flag
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-container indent20p">
|
||||
<a href="https://mancer.tech/" target="_blank">
|
||||
Mancer AI
|
||||
</a>
|
||||
<label class="checkbox_label" for="use-mancer-api-checkbox">
|
||||
<span data-i18n="Use API key (Only required for Mancer)">
|
||||
Click this box (and add your API key!):
|
||||
</span>
|
||||
<input id="use-mancer-api-checkbox" type="checkbox" />
|
||||
</label>
|
||||
</div>
|
||||
<div id="mancer-api-ui" style="display:none;">
|
||||
<h4 data-i18n="Mancer API key">Mancer API key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Blocking API url">Blocking API url</h4>
|
||||
<small>Example: http://127.0.0.1:5000/</small>
|
||||
@ -1563,6 +1599,7 @@
|
||||
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
|
||||
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="online_status4">
|
||||
<div class="online_status_indicator4"></div>
|
||||
<div class="online_status_text4" data-i18n="Not connected">Not connected</div>
|
||||
@ -1809,7 +1846,7 @@
|
||||
<span data-i18n="Trim Incomplete Sentences">Trim Incomplete Sentences</span>
|
||||
</label>
|
||||
<!-- Add margin since this is a child of above -->
|
||||
<label style="margin-left: 1em;" class="checkbox_label" for="include_newline_checkbox">
|
||||
<label class="checkbox_label indent20p" for="include_newline_checkbox">
|
||||
<input id="include_newline_checkbox" type="checkbox" />
|
||||
<span data-i18n="Include Newline">Include Newline</span>
|
||||
</label>
|
||||
@ -1818,7 +1855,7 @@
|
||||
Custom Chat Separator
|
||||
</h4>
|
||||
<div>
|
||||
<input id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="<START>" maxlength="100" />
|
||||
<textarea id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="<START>" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -1853,6 +1890,10 @@
|
||||
<input id="instruct_names" type="checkbox" />
|
||||
<span data-i18n="Include Names">Include Names</span>
|
||||
</label>
|
||||
<label for="instruct_names_force_groups" class="checkbox_label indent20p">
|
||||
<input id="instruct_names_force_groups" type="checkbox" />
|
||||
<span data-i18n="Force for Groups and Personas">Force for Groups and Personas</span>
|
||||
</label>
|
||||
</div>
|
||||
<label for="instruct_presets">
|
||||
<span data-i18n="Presets">Presets</span>
|
||||
@ -1871,7 +1912,7 @@
|
||||
<span data-i18n="Input Sequence">Input Sequence</span>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="500" />
|
||||
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -1879,7 +1920,7 @@
|
||||
<span data-i18n="Output Sequence">Output Sequence</span>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1889,7 +1930,7 @@
|
||||
<small data-i18n="System Sequence">System Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="500" />
|
||||
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -1897,7 +1938,7 @@
|
||||
<small data-i18n="Stop Sequence">Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@ -1905,7 +1946,7 @@
|
||||
<small data-i18n="Separator">Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1920,6 +1961,7 @@
|
||||
</a>
|
||||
</h4>
|
||||
<select id="tokenizer">
|
||||
<option value="99">Best match (recommended)</option>
|
||||
<option value="0">None / Estimated</option>
|
||||
<option value="1">GPT-3 (OpenAI)</option>
|
||||
<option value="2">GPT-3 (Alternative / Classic)</option>
|
||||
@ -1984,7 +2026,7 @@
|
||||
</label>
|
||||
<h4>
|
||||
<span data-i18n="Custom Stopping Strings">
|
||||
Custom Stopping Strings (KoboldAI/TextGen)
|
||||
Custom Stopping Strings (KoboldAI/TextGen/NovelAI)
|
||||
</span>
|
||||
<div>
|
||||
<small>
|
||||
@ -1996,6 +2038,12 @@
|
||||
<div>
|
||||
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact" placeholder="["Ford", "BMW", "Fiat"]"></textarea>
|
||||
</div>
|
||||
<label class="checkbox_label" for="custom_stopping_strings_macro">
|
||||
<input id="custom_stopping_strings_macro" type="checkbox" checked>
|
||||
<span data-i18n="Replace Macro in Custom Stopping Strings">
|
||||
Replace Macro in Custom Stopping Strings
|
||||
</span>
|
||||
</label>
|
||||
<h4>
|
||||
<span data-i18n="Pygmalion Formatting">
|
||||
Pygmalion Formatting
|
||||
@ -2145,9 +2193,7 @@
|
||||
Match whole words
|
||||
</small>
|
||||
</label>
|
||||
<label title="Alert if your world info is greater than the allocated budget."
|
||||
data-i18n="[title]Alert if your world info is greater than the allocated budget."
|
||||
class="checkbox_label">
|
||||
<label title="Alert if your world info is greater than the allocated budget." data-i18n="[title]Alert if your world info is greater than the allocated budget." class="checkbox_label">
|
||||
<input id="world_info_overflow_alert" type="checkbox" />
|
||||
<small data-i18n="Alert On Overflow">
|
||||
Alert On Overflow
|
||||
@ -2503,6 +2549,9 @@
|
||||
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
|
||||
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
|
||||
</label>
|
||||
<label for="relaxed_api_urls" title="Reduce the formatting requirements on API URLS"><input id="relaxed_api_urls" type="checkbox" />
|
||||
<span data-i18n="Relaxed API URLS">Relaxed API URLS</span>
|
||||
</label>
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
@ -2625,16 +2674,24 @@
|
||||
<option value="3" data-i18n="Bottom of Author's Note">Bottom of Author's Note</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<label for="persona_show_notifications" class="checkbox_label">
|
||||
<input id="persona_show_notifications" type="checkbox" />
|
||||
<span data-i18n="Show Notifications Show notifications on switching personas">
|
||||
Show notifications on switching personas
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Your Avatar" class="title_restorable">
|
||||
<span>Your Persona</span>
|
||||
<h4 class="title_restorable">
|
||||
<span data-i18n="Your Persona">Your Persona</span>
|
||||
<button class="menu_button menu_button_icon user_stats_button" title="Click for stats!">
|
||||
<i class="fa-solid fa-circle-info"></i>Usage Stats
|
||||
<i class="fa-solid fa-circle-info"></i><span data-i18n="Usage Stats">Usage Stats</span>
|
||||
</button>
|
||||
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
|
||||
<i class="fa-solid fa-person-circle-question fa-fw"></i>
|
||||
<span>Blank</span>
|
||||
<span data-i18n="Blank">Blank</span>
|
||||
</div>
|
||||
</h4>
|
||||
<div id="user_avatar_block">
|
||||
@ -3262,7 +3319,7 @@
|
||||
<div title="Rename chat file" class="renameChatButton fa-solid fa-pen" data-i18n="[title]Rename chat file"></div>
|
||||
<div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div>
|
||||
<div title="Download chat as plain text document" data-format="txt" class="exportChatButton fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div>
|
||||
<div title="Delete chat file" file_name="" class="PastChat_cross fa-solid fa-circle-xmark" data-i18n="[title]Delete chat file"></div>
|
||||
<div title="Delete chat file" file_name="" class="PastChat_cross fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -3624,7 +3681,7 @@
|
||||
</div>
|
||||
|
||||
<div id="hotswap_template" class="template_element">
|
||||
<div class="hotswapAvatar">
|
||||
<div class="hotswapAvatar" title="Add a character/group to favorites to display it here!">
|
||||
<img src="/img/ai4.png">
|
||||
</div>
|
||||
</div>
|
||||
@ -3782,11 +3839,11 @@
|
||||
<!-- popups live outside sheld to avoid blur conflicts -->
|
||||
<div id="options" class="font-family-reset" style="display: none;">
|
||||
<div class="options-content">
|
||||
<a id="option_close_chat">
|
||||
<a id="option_close_chat" class="displayNone">
|
||||
<i class="fa-lg fa-solid fa-times"></i>
|
||||
<span data-i18n="Close chat">Close chat</span>
|
||||
</a>
|
||||
<a id="option_settings">
|
||||
<a id="option_settings" class="displayNone">
|
||||
<i class="fa-lg fa-solid fa-cog"></i>
|
||||
<span data-i18n="Toggle Panels">Toggle Panels</span>
|
||||
</a>
|
||||
|
12
public/instruct/OpenOrca-OpenChat.json
Normal file
12
public/instruct/OpenOrca-OpenChat.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"input_sequence": "User: ",
|
||||
"macro": true,
|
||||
"name": "OpenOrca/OpenChat",
|
||||
"names": true,
|
||||
"output_sequence": "<|end_of_turn|>\nAssistant: ",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
|
||||
"system_sequence": "",
|
||||
"wrap": false
|
||||
}
|
12
public/instruct/Roleplay.json
Normal file
12
public/instruct/Roleplay.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:",
|
||||
"macro": true,
|
||||
"name": "Roleplay",
|
||||
"names": false,
|
||||
"output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
}
|
277
public/script.js
277
public/script.js
@ -17,6 +17,7 @@ import {
|
||||
loadTextGenSettings,
|
||||
generateTextGenWithStreaming,
|
||||
getTextGenGenerationData,
|
||||
formatTextGenURL,
|
||||
} from "./scripts/textgen-settings.js";
|
||||
|
||||
import {
|
||||
@ -135,6 +136,7 @@ import {
|
||||
download,
|
||||
isDataURL,
|
||||
getCharaFilename,
|
||||
isDigitsOnly,
|
||||
} from "./scripts/utils.js";
|
||||
|
||||
import { extension_settings, getContext, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
||||
@ -303,7 +305,6 @@ export const comment_avatar = "img/quill.png";
|
||||
export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header
|
||||
let is_colab = false;
|
||||
let is_checked_colab = false;
|
||||
let is_mes_reload_avatar = false;
|
||||
let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
|
||||
placement: 'top-start'
|
||||
});
|
||||
@ -363,10 +364,10 @@ const system_messages = {
|
||||
mes:
|
||||
`Hello there! Please select the help topic you would like to learn more about:
|
||||
<ul>
|
||||
<li><a href="javascript:displayHelp('1')">Slash Commands</a> (or <tt>/help slash</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('2')">Formatting</a> (or <tt>/help format</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('3')">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('4')">{{Macros}}</a> (or <tt>/help macros</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="1">Slash Commands</a> (or <tt>/help slash</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="2">Formatting</a> (or <tt>/help format</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="3">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="4">{{Macros}}</a> (or <tt>/help macros</tt>)</li>
|
||||
</ul>
|
||||
<br><b>Still got questions left? The <a target="_blank" href="https://docs.sillytavern.app/">Official SillyTavern Documentation Website</a> has much more information!</b>`
|
||||
},
|
||||
@ -408,12 +409,25 @@ const system_messages = {
|
||||
mes:
|
||||
`Text formatting commands:
|
||||
<ul>
|
||||
<li><tt>{{text}}</tt> - sets a one-time behavioral bias for the AI. Resets when you send the next message.</li>
|
||||
<li><tt>*text*</tt> - displays as <i>italics</i></li>
|
||||
<li><tt>**text**</tt> - displays as <b>bold</b></li>
|
||||
<li><tt>***text***</tt> - displays as <b><i>bold italics</i></b></li>
|
||||
<li><tt>` + "```" + `text` + "```" + `</tt> - displays as a code block</li>
|
||||
<li><tt>` + "`" + `text` + "`" + `</tt> - displays as inline code</li>
|
||||
<li><tt>` + "```" + `text` + "```" + `</tt> - displays as a code block (new lines allowed between the backticks)</li>
|
||||
<pre>
|
||||
<code>
|
||||
like
|
||||
this
|
||||
</code>
|
||||
</pre>
|
||||
<li><tt>` + "`" + `text` + "`" + `</tt> - displays as <code>inline code</code></li>
|
||||
<li><tt>` + "> " + `text` + `</tt> - displays as a blockquote (note the space after >)</li>
|
||||
<blockquote>like this</blockquote>
|
||||
<li><tt>` + "# " + `text` + `</tt> - displays as a large header (note the space)</li>
|
||||
<h1>like this</h1>
|
||||
<li><tt>` + "## " + `text` + `</tt> - displays as a medium header (note the space)</li>
|
||||
<h2>like this</h2>
|
||||
<li><tt>` + "### " + `text` + `</tt> - displays as a small header (note the space)</li>
|
||||
<h3>like this</h3>
|
||||
<li><tt>$$ text $$</tt> - renders a LaTeX formula (if enabled)</li>
|
||||
<li><tt>$ text $</tt> - renders an AsciiMath formula (if enabled)</li>
|
||||
</ul>`
|
||||
@ -523,7 +537,11 @@ const system_messages = {
|
||||
|
||||
$(document).ajaxError(function myErrorHandler(_, xhr) {
|
||||
if (xhr.status == 403) {
|
||||
toastr.warning("doubleCsrf errors in console are NORMAL in this case. Just reload the page or close this tab.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
|
||||
toastr.warning(
|
||||
"doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.",
|
||||
"Looks like you've opened SillyTavern in another browser tab",
|
||||
{ timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -545,8 +563,27 @@ async function getClientVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenizerBestMatch() {
|
||||
if (main_api === 'novel') {
|
||||
if (nai_settings.model_novel.includes('krake') || nai_settings.model_novel.includes('euterpe')) {
|
||||
return tokenizers.CLASSIC;
|
||||
}
|
||||
if (nai_settings.model_novel.includes('clio')) {
|
||||
return tokenizers.NERD;
|
||||
}
|
||||
if (nai_settings.model_novel.includes('kayra')) {
|
||||
return tokenizers.NERD2;
|
||||
}
|
||||
}
|
||||
if (main_api === 'kobold' || main_api === 'textgenerationwebui' || main_api === 'koboldhorde') {
|
||||
return tokenizers.LLAMA;
|
||||
}
|
||||
|
||||
return power_user.NONE;
|
||||
}
|
||||
|
||||
function getTokenCount(str, padding = undefined) {
|
||||
if (typeof str !== 'string') {
|
||||
if (typeof str !== 'string' || !str?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -562,6 +599,10 @@ function getTokenCount(str, padding = undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenizerType === tokenizers.BEST_MATCH) {
|
||||
tokenizerType = getTokenizerBestMatch();
|
||||
}
|
||||
|
||||
if (padding === undefined) {
|
||||
padding = 0;
|
||||
}
|
||||
@ -603,11 +644,42 @@ function countTokensRemote(endpoint, str, padding) {
|
||||
return tokenCount + padding;
|
||||
}
|
||||
|
||||
function getTextTokensRemote(endpoint, str) {
|
||||
let ids = [];
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST',
|
||||
url: endpoint,
|
||||
data: JSON.stringify({ text: str }),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
ids = data.ids;
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function getTextTokens(tokenizerType, str) {
|
||||
switch (tokenizerType) {
|
||||
case tokenizers.LLAMA:
|
||||
return getTextTokensRemote('/tokenize_llama', str);
|
||||
case tokenizers.NERD:
|
||||
return getTextTokensRemote('/tokenize_nerdstash', str);
|
||||
case tokenizers.NERD2:
|
||||
return getTextTokensRemote('/tokenize_nerdstash_v2', str);
|
||||
default:
|
||||
console.warn("Calling getTextTokens with unsupported tokenizer type", tokenizerType);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function reloadMarkdownProcessor(render_formulas = false) {
|
||||
if (render_formulas) {
|
||||
converter = new showdown.Converter({
|
||||
emoji: "true",
|
||||
underline: "true",
|
||||
parseImgDimensions: "true",
|
||||
extensions: [
|
||||
showdownKatex(
|
||||
{
|
||||
@ -623,6 +695,7 @@ function reloadMarkdownProcessor(render_formulas = false) {
|
||||
converter = new showdown.Converter({
|
||||
emoji: "true",
|
||||
literalMidWordUnderscores: "true",
|
||||
parseImgDimensions: "true",
|
||||
});
|
||||
}
|
||||
|
||||
@ -689,6 +762,7 @@ let is_get_status = false;
|
||||
let is_get_status_novel = false;
|
||||
let is_api_button_press = false;
|
||||
let is_api_button_press_novel = false;
|
||||
let api_use_mancer_webui = false;
|
||||
|
||||
let is_send_press = false; //Send generation
|
||||
let add_mes_without_animation = false;
|
||||
@ -822,9 +896,9 @@ async function getStatus() {
|
||||
type: "POST", //
|
||||
url: "/getstatus", //
|
||||
data: JSON.stringify({
|
||||
api_server:
|
||||
main_api == "kobold" ? api_server : api_server_textgenerationwebui,
|
||||
api_server: main_api == "kobold" ? api_server : api_server_textgenerationwebui,
|
||||
main_api: main_api,
|
||||
use_mancer: main_api == "textgenerationwebui" ? api_use_mancer_webui : false,
|
||||
}),
|
||||
beforeSend: function () { },
|
||||
cache: false,
|
||||
@ -851,6 +925,11 @@ async function getStatus() {
|
||||
kai_settings.can_use_streaming = canUseKoboldStreaming(data.koboldVersion);
|
||||
}
|
||||
|
||||
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
|
||||
if (online_status == "no_connection" && data.response) {
|
||||
toastr.error(data.response, "API Error", { timeOut: 5000, preventDuplicates: true })
|
||||
}
|
||||
|
||||
//console.log(online_status);
|
||||
resultCheckStatus();
|
||||
if (online_status !== "no_connection") {
|
||||
@ -1190,8 +1269,8 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
|
||||
mes = fixMarkdown(mes);
|
||||
}
|
||||
|
||||
if (this_chid != undefined && !isSystem)
|
||||
mes = mes.replaceAll("<", "<").replaceAll(">", ">"); //for welcome message
|
||||
//if (this_chid != undefined && !isSystem)
|
||||
// mes = mes.replaceAll("<", "<").replaceAll(">", ">"); //for welcome message
|
||||
if ((this_chid === undefined || this_chid === "invalid-safety-id") && !selected_group) {
|
||||
mes = mes
|
||||
.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
|
||||
@ -1233,7 +1312,7 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
|
||||
*/
|
||||
|
||||
if (!power_user.allow_name2_display && ch_name && !isUser && !isSystem) {
|
||||
mes = mes.replaceAll(`${ch_name}:`, "");
|
||||
mes = mes.replace(new RegExp(`(^|\n)${ch_name}:`, 'g'), "$1");
|
||||
}
|
||||
|
||||
//function to hide any <tags> from AI response output
|
||||
@ -1332,9 +1411,6 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
|
||||
const momentDate = timestampToMoment(mes.send_date);
|
||||
const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
|
||||
|
||||
|
||||
|
||||
|
||||
if (mes?.extra?.display_text) {
|
||||
messageText = mes.extra.display_text;
|
||||
}
|
||||
@ -1364,9 +1440,6 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
|
||||
} else {
|
||||
if (characters[this_chid].avatar != "none") {
|
||||
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
if (is_mes_reload_avatar !== false) {
|
||||
avatarImg += "&" + is_mes_reload_avatar;
|
||||
}
|
||||
} else {
|
||||
avatarImg = default_avatar;
|
||||
}
|
||||
@ -1614,7 +1687,7 @@ function getTimeSinceLastMessage() {
|
||||
}
|
||||
|
||||
function randomReplace(input, emptyListPlaceholder = '') {
|
||||
const randomPattern = /{{random:([^}]+)}}/gi;
|
||||
const randomPattern = /{{random[ : ]([^}]+)}}/gi;
|
||||
|
||||
return input.replace(randomPattern, (match, listString) => {
|
||||
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
||||
@ -1632,10 +1705,15 @@ function randomReplace(input, emptyListPlaceholder = '') {
|
||||
}
|
||||
|
||||
function diceRollReplace(input, invalidRollPlaceholder = '') {
|
||||
const randomPattern = /{{roll:([^}]+)}}/gi;
|
||||
const rollPattern = /{{roll[ : ]([^}]+)}}/gi;
|
||||
|
||||
return input.replace(rollPattern, (match, matchValue) => {
|
||||
let formula = matchValue.trim();
|
||||
|
||||
if (isDigitsOnly(formula)) {
|
||||
formula = `1d${formula}`;
|
||||
}
|
||||
|
||||
return input.replace(randomPattern, (match, matchValue) => {
|
||||
const formula = matchValue.trim();
|
||||
const isValid = droll.validate(formula);
|
||||
|
||||
if (!isValid) {
|
||||
@ -1688,8 +1766,12 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
|
||||
if (power_user.custom_stopping_strings) {
|
||||
const customStoppingStrings = getCustomStoppingStrings();
|
||||
if (power_user.custom_stopping_strings_macro) {
|
||||
result.push(...customStoppingStrings.map(x => substituteParams(x, name1, name2)));
|
||||
} else {
|
||||
result.push(...customStoppingStrings);
|
||||
}
|
||||
}
|
||||
|
||||
return addSpace ? result.map(x => `${x} `) : result;
|
||||
}
|
||||
@ -1768,7 +1850,7 @@ export function extractMessageBias(message) {
|
||||
const match = curMatch[1].trim();
|
||||
|
||||
// Ignore random/roll pattern matches
|
||||
if (/^random:.+/i.test(match) || /^roll:.+/i.test(match)) {
|
||||
if (/^random[ : ].+/i.test(match) || /^roll[ : ].+/i.test(match)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1824,7 +1906,7 @@ function getPersonaDescription(storyString) {
|
||||
case persona_description_positions.BEFORE_CHAR:
|
||||
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
|
||||
case persona_description_positions.AFTER_CHAR:
|
||||
return `${storyString}\n${substituteParams(power_user.persona_description)}`;
|
||||
return `${storyString}${substituteParams(power_user.persona_description)}\n`;
|
||||
default:
|
||||
if (shouldWIAddPrompt) {
|
||||
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
|
||||
@ -2487,7 +2569,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
|
||||
if (i === arrMes.length - 1 && !item.trim().startsWith(name1 + ":")) {
|
||||
if (textareaText == "") {
|
||||
//if (textareaText == "") {
|
||||
// Cohee: I think this was added to allow the model to continue
|
||||
// where it left off by removing the trailing newline at the end
|
||||
// that was added by chat2 generator. This causes problems with
|
||||
@ -2495,7 +2577,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
// removing a newline ONLY at the end of the string if it exists.
|
||||
item = item.replace(/\n?$/, '');
|
||||
//item = item.substr(0, item.length - 1);
|
||||
}
|
||||
//}
|
||||
}
|
||||
if (is_pygmalion && !isInstruct) {
|
||||
if (item.trim().startsWith(name1)) {
|
||||
@ -2630,8 +2712,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
setPromtString();
|
||||
}
|
||||
|
||||
// add chat preamble
|
||||
mesSendString = addChatsPreamble(mesSendString);
|
||||
|
||||
// add a custom dingus (if defined)
|
||||
mesSendString = adjustChatsSeparator(mesSendString);
|
||||
mesSendString = addChatsSeparator(mesSendString);
|
||||
|
||||
let finalPromt =
|
||||
storyString +
|
||||
@ -2684,10 +2769,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
else if (main_api == 'textgenerationwebui') {
|
||||
generate_data = getTextGenGenerationData(finalPromt, this_amount_gen, isImpersonate);
|
||||
generate_data.use_mancer = api_use_mancer_webui;
|
||||
}
|
||||
else if (main_api == 'novel') {
|
||||
const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
|
||||
generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen);
|
||||
generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate);
|
||||
}
|
||||
else if (main_api == 'openai') {
|
||||
let [prompt, counts] = await prepareOpenAIMessages({
|
||||
@ -2789,7 +2875,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status);
|
||||
const error = await response.json();
|
||||
throw error;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@ -2945,6 +3032,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
activateSendButtons();
|
||||
//console.log('runGenerate calling showSwipeBtns');
|
||||
showSwipeButtons();
|
||||
|
||||
if (main_api == 'textgenerationwebui' && api_use_mancer_webui) {
|
||||
const errorText = `<h3>Inferencer endpoint is unhappy!</h3>
|
||||
Returned status <tt>${data.status}</tt> with the reason:<br/>
|
||||
${data.response}`;
|
||||
callPopup(errorText, 'text');
|
||||
}
|
||||
}
|
||||
console.debug('/savechat called by /Generate');
|
||||
|
||||
@ -2959,6 +3053,10 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
};
|
||||
|
||||
function onError(exception) {
|
||||
if (typeof exception?.error?.message === 'string') {
|
||||
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
|
||||
}
|
||||
|
||||
reject(exception);
|
||||
$("#send_textarea").removeAttr('disabled');
|
||||
is_send_press = false;
|
||||
@ -3104,7 +3202,12 @@ function parseTokenCounts(counts, thisPromptBits) {
|
||||
});
|
||||
}
|
||||
|
||||
function adjustChatsSeparator(mesSendString) {
|
||||
function addChatsPreamble(mesSendString) {
|
||||
const preamble = main_api === 'novel' ? nai_settings.preamble : "";
|
||||
return preamble + '\n' + mesSendString;
|
||||
}
|
||||
|
||||
function addChatsSeparator(mesSendString) {
|
||||
if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) {
|
||||
mesSendString = power_user.custom_chat_separator + '\n' + mesSendString;
|
||||
}
|
||||
@ -3114,6 +3217,10 @@ function adjustChatsSeparator(mesSendString) {
|
||||
mesSendString = mesSendString;
|
||||
}
|
||||
|
||||
else if (main_api === 'novel') {
|
||||
mesSendString = '\n***\n' + mesSendString;
|
||||
}
|
||||
|
||||
// add non-pygma dingus
|
||||
else if (!is_pygmalion) {
|
||||
mesSendString = '\nThen the roleplay chat between ' + name1 + ' and ' + name2 + ' begins.\n' + mesSendString;
|
||||
@ -3227,21 +3334,20 @@ function promptItemize(itemizedPrompts, requestedMesId) {
|
||||
}
|
||||
|
||||
//these happen regardless of API
|
||||
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
|
||||
var charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription);
|
||||
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
|
||||
var scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText);
|
||||
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
|
||||
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
|
||||
var allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors);
|
||||
var summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString);
|
||||
var authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString);
|
||||
var smartContextStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].smartContextString);
|
||||
var afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
|
||||
var zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].zeroDepthAnchor);
|
||||
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
|
||||
var thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context;
|
||||
var thisPrompt_padding = itemizedPrompts[thisPromptSet].padding;
|
||||
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
|
||||
var this_main_api = itemizedPrompts[thisPromptSet].main_api;
|
||||
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
|
||||
|
||||
if (this_main_api == 'openai') {
|
||||
//for OAI API
|
||||
@ -3283,6 +3389,7 @@ function promptItemize(itemizedPrompts, requestedMesId) {
|
||||
var mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString)
|
||||
var ActualChatHistoryTokens = mesSendStringTokens - (allAnchorsTokens - afterScenarioAnchorTokens) + power_user.token_padding;
|
||||
var instructionTokens = getTokenCount(itemizedPrompts[thisPromptSet].instruction);
|
||||
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
|
||||
|
||||
var totalTokensInPrompt =
|
||||
storyStringTokens + //chardefs total
|
||||
@ -3720,13 +3827,15 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete
|
||||
getMessage = cleanGroupMessage(getMessage);
|
||||
}
|
||||
|
||||
if (!power_user.allow_name2_display) {
|
||||
getMessage = getMessage.replace(new RegExp(`(^|\n)${name2}:`, 'g'), "$1");
|
||||
}
|
||||
|
||||
if (isImpersonate) {
|
||||
getMessage = getMessage.trim();
|
||||
}
|
||||
|
||||
const stoppingStrings = getStoppingStrings(isImpersonate, false);
|
||||
//console.log('stopping on these strings: ');
|
||||
//console.log(stoppingStrings);
|
||||
|
||||
for (const stoppingString of stoppingStrings) {
|
||||
if (stoppingString.length) {
|
||||
@ -4161,9 +4270,18 @@ async function read_avatar_load(input) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#create_button").trigger('click');
|
||||
await createOrEditCharacter();
|
||||
await delay(durationSaveEdit);
|
||||
|
||||
const formData = new FormData($("#form_create").get(0));
|
||||
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'pragma': 'no-cache',
|
||||
'cache-control': 'no-cache',
|
||||
}
|
||||
});
|
||||
|
||||
$(".mes").each(async function () {
|
||||
if ($(this).attr("is_system") == 'true') {
|
||||
@ -4181,22 +4299,12 @@ async function read_avatar_load(input) {
|
||||
}
|
||||
});
|
||||
|
||||
await delay(durationSaveEdit);
|
||||
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'pragma': 'no-cache',
|
||||
'cache-control': 'no-cache',
|
||||
}
|
||||
});
|
||||
console.log('Avatar refreshed');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function getCropPopup(src) {
|
||||
return `<h3>Set the crop position of the avatar image and click Ok to confirm.</h3>
|
||||
return `<h3>Set the crop position of the avatar image and click Accept to confirm.</h3>
|
||||
<div id='avatarCropWrap'>
|
||||
<img id='avatarToCrop' src='${src}'>
|
||||
</div>`;
|
||||
@ -4509,7 +4617,9 @@ export function setUserName(value) {
|
||||
name1 = default_user_name;
|
||||
console.log(`User name changed to ${name1}`);
|
||||
$("#your_name").val(name1);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success(`Your messages will now be sent as ${name1}`, 'Current persona updated');
|
||||
}
|
||||
saveSettings("change_name");
|
||||
}
|
||||
|
||||
@ -4608,7 +4718,7 @@ function setUserAvatar() {
|
||||
const personaName = power_user.personas[user_avatar];
|
||||
if (personaName && name1 !== personaName) {
|
||||
const lockedPersona = chat_metadata['persona'];
|
||||
if (lockedPersona && lockedPersona !== user_avatar) {
|
||||
if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
`To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,
|
||||
`This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,
|
||||
@ -4715,7 +4825,9 @@ async function setDefaultPersona() {
|
||||
}
|
||||
|
||||
console.log(`Removing default persona ${avatarId}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('This persona will no longer be used by default when you open a new chat.', `Default persona removed`);
|
||||
}
|
||||
delete power_user.default_persona;
|
||||
} else {
|
||||
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
|
||||
@ -4727,8 +4839,10 @@ async function setDefaultPersona() {
|
||||
}
|
||||
|
||||
power_user.default_persona = avatarId;
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);
|
||||
}
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
@ -4790,18 +4904,22 @@ function lockUserNameToChat() {
|
||||
console.log(`Unlocking persona for this chat ${chat_metadata['persona']}`);
|
||||
delete chat_metadata['persona'];
|
||||
saveMetadata();
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');
|
||||
}
|
||||
updateUserLockIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(user_avatar in power_user.personas)) {
|
||||
console.log(`Creating a new persona ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
'Creating a new persona for currently selected user name and avatar...',
|
||||
'Persona not set for this avatar',
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, },
|
||||
);
|
||||
}
|
||||
power_user.personas[user_avatar] = name1;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
|
||||
}
|
||||
@ -4810,7 +4928,9 @@ function lockUserNameToChat() {
|
||||
saveMetadata();
|
||||
saveSettingsDebounced();
|
||||
console.log(`Locking persona for this chat ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success(`User persona is locked to ${name1} in this chat`);
|
||||
}
|
||||
updateUserLockIcon();
|
||||
}
|
||||
|
||||
@ -5031,11 +5151,13 @@ async function getSettings(type) {
|
||||
|
||||
setWorldInfoSettings(settings, data);
|
||||
|
||||
api_server_textgenerationwebui =
|
||||
settings.api_server_textgenerationwebui;
|
||||
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
|
||||
$("#textgenerationwebui_api_url_text").val(
|
||||
api_server_textgenerationwebui
|
||||
);
|
||||
api_use_mancer_webui = settings.api_use_mancer_webui
|
||||
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
|
||||
$('#use-mancer-api-checkbox').trigger("change");
|
||||
|
||||
selected_button = settings.selected_button;
|
||||
|
||||
@ -5069,6 +5191,7 @@ async function saveSettings(type) {
|
||||
active_group: active_group,
|
||||
api_server: api_server,
|
||||
api_server_textgenerationwebui: api_server_textgenerationwebui,
|
||||
api_use_mancer_webui: api_use_mancer_webui,
|
||||
preset_settings: preset_settings,
|
||||
user_avatar: user_avatar,
|
||||
amount_gen: amount_gen,
|
||||
@ -6831,6 +6954,11 @@ function importCharacter(file) {
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.error) {
|
||||
toastr.error('The file is likely invalid or corrupted.', 'Could not import character');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.file_name !== undefined) {
|
||||
$('#character_search_bar').val('').trigger('input');
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
@ -6912,6 +7040,10 @@ function doCharListDisplaySwitch() {
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
function doCloseChat() {
|
||||
$("#option_close_chat").trigger('click')
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to handle the deletion of a character, given a specific popup type and character ID.
|
||||
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
|
||||
@ -6921,13 +7053,13 @@ function doCharListDisplaySwitch() {
|
||||
*
|
||||
* @param {string} popup_type - The type of popup currently active.
|
||||
* @param {string} this_chid - The character ID to be deleted.
|
||||
* @param {boolean} delete_chats - Whether to delete chats or not.
|
||||
*/
|
||||
export async function handleDeleteCharacter(popup_type, this_chid) {
|
||||
export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
|
||||
if (popup_type !== "del_ch") {
|
||||
return;
|
||||
}
|
||||
|
||||
const delete_chats = !!$("#del_char_checkbox").prop("checked");
|
||||
const avatar = characters[this_chid].avatar;
|
||||
const name = characters[this_chid].name;
|
||||
|
||||
@ -6978,6 +7110,10 @@ export async function deleteCharacter(name, avatar) {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function doTogglePanels() {
|
||||
$("#option_settings").trigger('click')
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
@ -6992,6 +7128,9 @@ $(document).ready(function () {
|
||||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai) – connect to an API", true, true);
|
||||
registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true);
|
||||
registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true);
|
||||
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
|
||||
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], "- toggle UI panels on/off", true, true);
|
||||
|
||||
|
||||
|
||||
setTimeout(function () {
|
||||
@ -7358,7 +7497,8 @@ $(document).ready(function () {
|
||||
}, 200);
|
||||
}
|
||||
if (popup_type == "del_ch") {
|
||||
handleDeleteCharacter(popup_type, this_chid, characters);
|
||||
const deleteChats = !!$("#del_char_checkbox").prop("checked");
|
||||
await handleDeleteCharacter(popup_type, this_chid, deleteChats);
|
||||
}
|
||||
if (popup_type == "alternate_greeting" && menu_type !== "create") {
|
||||
createOrEditCharacter();
|
||||
@ -7435,7 +7575,6 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
$("#add_avatar_button").change(function () {
|
||||
is_mes_reload_avatar = Date.now();
|
||||
read_avatar_load(this);
|
||||
});
|
||||
|
||||
@ -7447,7 +7586,7 @@ $(document).ready(function () {
|
||||
<h3>Delete the character?</h3>
|
||||
<b>THIS IS PERMANENT!<br><br>
|
||||
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
|
||||
<input type="checkbox" id="del_char_checkbox" checked />
|
||||
<input type="checkbox" id="del_char_checkbox" />
|
||||
<span>Also delete the chat files</span>
|
||||
</label><br></b>`
|
||||
);
|
||||
@ -7630,16 +7769,28 @@ $(document).ready(function () {
|
||||
}
|
||||
});
|
||||
|
||||
$("#api_button_textgenerationwebui").click(function (e) {
|
||||
$("#use-mancer-api-checkbox").on("change", function (e) {
|
||||
const enabled = $("#use-mancer-api-checkbox").prop("checked");
|
||||
$("#mancer-api-ui").toggle(enabled);
|
||||
api_use_mancer_webui = enabled;
|
||||
saveSettingsDebounced();
|
||||
getStatus();
|
||||
});
|
||||
|
||||
$("#api_button_textgenerationwebui").click(async function (e) {
|
||||
e.stopPropagation();
|
||||
if ($("#textgenerationwebui_api_url_text").val() != "") {
|
||||
let value = formatKoboldUrl($("#textgenerationwebui_api_url_text").val().trim());
|
||||
|
||||
let value = formatTextGenURL($("#textgenerationwebui_api_url_text").val().trim())
|
||||
if (!value) {
|
||||
callPopup('Please enter a valid URL.', 'text');
|
||||
callPopup('Please enter a valid URL.<br/>WebUI URLs should end with <tt>/api</tt>', 'text');
|
||||
return;
|
||||
}
|
||||
|
||||
const mancer_key = $("#api_key_mancer").val().trim();
|
||||
if (mancer_key.length) {
|
||||
await writeSecret(SECRET_KEYS.MANCER, mancer_key);
|
||||
}
|
||||
|
||||
$("#textgenerationwebui_api_url_text").val(value);
|
||||
$("#api_loading_textgenerationwebui").css("display", "inline-block");
|
||||
$("#api_button_textgenerationwebui").css("display", "none");
|
||||
|
@ -380,6 +380,7 @@ export async function favsToHotswap() {
|
||||
thisHotSwapSlot.attr('grid', isGroup ? grid : '');
|
||||
thisHotSwapSlot.attr('chid', isCharacter ? chid : '');
|
||||
thisHotSwapSlot.data('id', isGroup ? grid : chid);
|
||||
thisHotSwapSlot.attr('title', '');
|
||||
|
||||
if (isGroup) {
|
||||
const group = groups.find(x => x.id === grid);
|
||||
@ -534,12 +535,14 @@ export function dragElement(elmnt) {
|
||||
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
|
||||
hasBeenDraggedByUser = true
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
dragMouseDown(e);
|
||||
});
|
||||
$(elmnt).off('mousedown').on('mousedown', () => { isMouseDown = true })
|
||||
} else {
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
$(elmnt).off('mousedown').on('mousedown', () => {
|
||||
isMouseDown = true
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
})
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
@ -618,7 +621,7 @@ export function dragElement(elmnt) {
|
||||
}
|
||||
|
||||
//prevent resizing from top left into the top bar
|
||||
if (top <= 40 && maxX >= topBarFirstX && left <= topBarFirstX
|
||||
if (top < 40 && maxX >= topBarFirstX && left <= topBarFirstX
|
||||
) {
|
||||
console.debug('prevent topbar underlap resize')
|
||||
elmnt.css('width', width - 1 + "px");
|
||||
@ -674,8 +677,6 @@ export function dragElement(elmnt) {
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
|
||||
function dragMouseDown(e) {
|
||||
|
||||
if (e) {
|
||||
@ -745,6 +746,7 @@ export function dragElement(elmnt) {
|
||||
$("body").css("overflow", "");
|
||||
// Clear the "data-dragged" attribute
|
||||
elmnt.attr('data-dragged', 'false');
|
||||
observer.disconnect()
|
||||
console.debug(`Saving ${elmntName} UI position`)
|
||||
saveSettingsDebounced();
|
||||
|
||||
@ -987,6 +989,12 @@ $("document").ready(function () {
|
||||
Generate();
|
||||
}
|
||||
}
|
||||
if ($(':focus').attr('id') === 'dialogue_popup_input' && !isMobile()) {
|
||||
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter") {
|
||||
event.preventDefault();
|
||||
$('#dialogue_popup_ok').trigger('click');
|
||||
}
|
||||
}
|
||||
//ctrl+shift+up to scroll to context line
|
||||
if (event.shiftKey && event.ctrlKey && event.key == "ArrowUp") {
|
||||
event.preventDefault();
|
||||
@ -1134,5 +1142,11 @@ $("document").ready(function () {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {
|
||||
// Your code here
|
||||
event.preventDefault();
|
||||
console.log("Ctrl +" + event.key + " pressed!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
117
public/scripts/extensions/bulk-edit/index.js
Normal file
117
public/scripts/extensions/bulk-edit/index.js
Normal file
@ -0,0 +1,117 @@
|
||||
import { characters, getCharacters, handleDeleteCharacter, callPopup } from "../../../script.js";
|
||||
|
||||
let is_bulk_edit = false;
|
||||
|
||||
/**
|
||||
* Toggles bulk edit mode on/off when the edit button is clicked.
|
||||
*/
|
||||
function onEditButtonClick() {
|
||||
console.log("Edit button clicked");
|
||||
// toggle bulk edit mode
|
||||
if (is_bulk_edit) {
|
||||
disableBulkSelect();
|
||||
// hide the delete button
|
||||
$("#bulkDeleteButton").hide();
|
||||
is_bulk_edit = false;
|
||||
} else {
|
||||
enableBulkSelect();
|
||||
// show the delete button
|
||||
$("#bulkDeleteButton").show();
|
||||
is_bulk_edit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the character with the given chid.
|
||||
*
|
||||
* @param {string} this_chid - The chid of the character to delete.
|
||||
*/
|
||||
async function deleteCharacter(this_chid) {
|
||||
await handleDeleteCharacter("del_ch", this_chid, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all characters that have been selected via the bulk checkboxes.
|
||||
*/
|
||||
async function onDeleteButtonClick() {
|
||||
console.log("Delete button clicked");
|
||||
|
||||
// Create a mapping of chid to avatar
|
||||
let toDelete = [];
|
||||
$(".bulk_select_checkbox:checked").each((i, el) => {
|
||||
const chid = $(el).parent().attr("chid");
|
||||
const avatar = characters[chid].avatar;
|
||||
// Add the avatar to the list of avatars to delete
|
||||
toDelete.push(avatar);
|
||||
});
|
||||
|
||||
const confirm = await callPopup('<h3>Are you sure you want to delete these characters?</h3>You would need to delete the chat files manually.<br>', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.log('User cancelled delete');
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the characters
|
||||
for (const avatar of toDelete) {
|
||||
console.log(`Deleting character with avatar ${avatar}`);
|
||||
await getCharacters();
|
||||
|
||||
//chid should be the key of the character with the given avatar
|
||||
const chid = Object.keys(characters).find((key) => characters[key].avatar === avatar);
|
||||
console.log(`Deleting character with chid ${chid}`);
|
||||
await deleteCharacter(chid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the bulk edit and delete buttons to the UI.
|
||||
*/
|
||||
function addButtons() {
|
||||
const editButton = $(
|
||||
"<i id='bulkEditButton' class='fa-solid fa-edit menu_button bulkEditButton' title='Bulk edit characters'></i>"
|
||||
);
|
||||
const deleteButton = $(
|
||||
"<i id='bulkDeleteButton' class='fa-solid fa-trash menu_button bulkDeleteButton' title='Bulk delete characters' style='display: none;'></i>"
|
||||
);
|
||||
|
||||
$("#charListGridToggle").after(editButton, deleteButton);
|
||||
|
||||
$("#bulkEditButton").on("click", onEditButtonClick);
|
||||
$("#bulkDeleteButton").on("click", onDeleteButtonClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables bulk selection by adding a checkbox next to each character.
|
||||
*/
|
||||
function enableBulkSelect() {
|
||||
$("#rm_print_characters_block .character_select").each((i, el) => {
|
||||
const character = $(el).text();
|
||||
const checkbox = $("<input type='checkbox' class='bulk_select_checkbox'>");
|
||||
checkbox.on("change", () => {
|
||||
// Do something when the checkbox is changed
|
||||
});
|
||||
$(el).prepend(checkbox);
|
||||
});
|
||||
$("#rm_print_characters_block").addClass("bulk_select");
|
||||
// We also need to disable the default click event for the character_select divs
|
||||
$(document).on("click", ".bulk_select_checkbox", function (event) {
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables bulk selection by removing the checkboxes.
|
||||
*/
|
||||
function disableBulkSelect() {
|
||||
$(".bulk_select_checkbox").remove();
|
||||
$("#rm_print_characters_block").removeClass("bulk_select");
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point that runs on page load.
|
||||
*/
|
||||
jQuery(async () => {
|
||||
addButtons();
|
||||
// loadSettings();
|
||||
});
|
11
public/scripts/extensions/bulk-edit/manifest.json
Normal file
11
public/scripts/extensions/bulk-edit/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Bulk Card Editor",
|
||||
"loading_order": 9,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "city-unit",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/city-unit"
|
||||
}
|
7
public/scripts/extensions/bulk-edit/style.css
Normal file
7
public/scripts/extensions/bulk-edit/style.css
Normal file
@ -0,0 +1,7 @@
|
||||
.bulk_select_checkbox {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#rm_print_characters_block.bulk_select .wide100pLess70px {
|
||||
width: calc(100% - 85px);
|
||||
}
|
@ -9,6 +9,7 @@ const MODULE_NAME = 'expressions';
|
||||
const UPDATE_INTERVAL = 2000;
|
||||
const FALLBACK_EXPRESSION = 'joy';
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
"live2d",
|
||||
"admiration",
|
||||
"amusement",
|
||||
"anger",
|
||||
@ -392,6 +393,108 @@ function onExpressionsShowDefaultInput() {
|
||||
}
|
||||
}
|
||||
|
||||
async function unloadLiveChar() {
|
||||
try {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/live2d/unload';
|
||||
const loadResponse = await doExtrasFetch(url);
|
||||
if (!loadResponse.ok) {
|
||||
throw new Error(loadResponse.statusText);
|
||||
}
|
||||
const loadResponseText = await loadResponse.text();
|
||||
//console.log(`Response: ${loadResponseText}`);
|
||||
} catch (error) {
|
||||
//console.error(`Error unloading - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLiveChar() {
|
||||
if (!modules.includes('live2d')) {
|
||||
console.debug('live2d module is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
const live2dPath = `/characters/${encodeURIComponent(spriteFolderName)}/live2d.png`;
|
||||
|
||||
try {
|
||||
const spriteResponse = await fetch(live2dPath);
|
||||
|
||||
if (!spriteResponse.ok) {
|
||||
throw new Error(spriteResponse.statusText);
|
||||
}
|
||||
|
||||
const spriteBlob = await spriteResponse.blob();
|
||||
const spriteFile = new File([spriteBlob], 'live2d.png', { type: 'image/png' });
|
||||
const formData = new FormData();
|
||||
formData.append('file', spriteFile);
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/live2d/load';
|
||||
|
||||
const loadResponse = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!loadResponse.ok) {
|
||||
throw new Error(loadResponse.statusText);
|
||||
}
|
||||
|
||||
const loadResponseText = await loadResponse.text();
|
||||
console.log(`Load live2d response: ${loadResponseText}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error loading live2d image: ${live2dPath} - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleImageChange() {
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
|
||||
if (!imgElement) {
|
||||
console.log("Cannot find addExpressionImage()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension_settings.expressions.live2d) {
|
||||
// Method get IP of endpoint
|
||||
const live2dResultFeedSrc = `${getApiUrl()}/api/live2d/result_feed`;
|
||||
$('#expression-holder').css({ display: '' });
|
||||
if (imgElement.src !== live2dResultFeedSrc) {
|
||||
const expressionImageElement = document.querySelector('.expression_list_image');
|
||||
|
||||
if (expressionImageElement) {
|
||||
doExtrasFetch(expressionImageElement.src, {
|
||||
method: 'HEAD',
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
imgElement.src = live2dResultFeedSrc;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error); // Log the error if necessary
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgElement.src = ""; //remove incase char doesnt have expressions
|
||||
setExpression(getContext().name2, FALLBACK_EXPRESSION, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
@ -405,6 +508,16 @@ async function moduleWorker() {
|
||||
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
|
||||
removeExpression();
|
||||
spriteCache = {};
|
||||
|
||||
//clear expression
|
||||
let imgElement = document.getElementById('expression-image');
|
||||
imgElement.src = "";
|
||||
|
||||
//set checkbox to global var
|
||||
$('#image_type_toggle').prop('checked', extension_settings.expressions.live2d);
|
||||
if(extension_settings.expressions.live2d == true){
|
||||
setLive2dState(extension_settings.expressions.live2d);
|
||||
}
|
||||
}
|
||||
|
||||
const vnMode = isVisualNovelMode();
|
||||
@ -507,6 +620,64 @@ async function moduleWorker() {
|
||||
lastCharacter = context.groupId || context.characterId;
|
||||
lastMessage = currentLastMessage.mes;
|
||||
}
|
||||
}
|
||||
|
||||
async function live2dcheck() {
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
try {
|
||||
await validateImages(spriteFolderName);
|
||||
|
||||
let live2dObj = spriteCache[spriteFolderName].find(obj => obj.label === 'live2d');
|
||||
let live2dPath_f = live2dObj ? live2dObj.path : null;
|
||||
|
||||
if(live2dPath_f != null){
|
||||
//console.log("live2dPath_f " + live2dPath_f);
|
||||
return true;
|
||||
} else {
|
||||
//console.log("live2dPath_f is null");
|
||||
unloadLiveChar();
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
function setLive2dState(switch_var){
|
||||
extension_settings.expressions.live2d = switch_var; // Store setting
|
||||
saveSettingsDebounced();
|
||||
|
||||
live2dcheck().then(result => {
|
||||
if (result) {
|
||||
//console.log("Live2d exists!");
|
||||
|
||||
if (extension_settings.expressions.live2d) {
|
||||
loadLiveChar();
|
||||
} else {
|
||||
unloadLiveChar();
|
||||
}
|
||||
handleImageChange(switch_var); // Change image as needed
|
||||
|
||||
|
||||
} else {
|
||||
//console.log("Live2d does not exist.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function getSpriteFolderName(message) {
|
||||
@ -654,7 +825,6 @@ async function getSpritesList(name) {
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
|
||||
|
||||
let sprites = result.ok ? (await result.json()) : [];
|
||||
return sprites;
|
||||
}
|
||||
@ -697,6 +867,8 @@ async function getExpressionsList() {
|
||||
}
|
||||
|
||||
async function setExpression(character, expression, force) {
|
||||
if (extension_settings.expressions.live2d == false) {
|
||||
|
||||
console.debug('entered setExpressions');
|
||||
await validateImages(character);
|
||||
const img = $('img.expression');
|
||||
@ -789,22 +961,43 @@ async function setExpression(character, expression, force) {
|
||||
setDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
if (extension_settings.expressions.showDefault) {
|
||||
setDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setDefault() {
|
||||
console.debug('setting default');
|
||||
const defImgUrl = `/img/default-expressions/${expression}.png`;
|
||||
console.log(defImgUrl);
|
||||
//console.log(defImgUrl);
|
||||
img.attr('src', defImgUrl);
|
||||
img.addClass('default');
|
||||
}
|
||||
document.getElementById("expression-holder").style.display = '';
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
live2dcheck().then(result => {
|
||||
if (result) {
|
||||
// Find the <img> element with id="expression-image" and class="expression"
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
//console.log("searching");
|
||||
if (imgElement) {
|
||||
//console.log("setting value");
|
||||
imgElement.src = getApiUrl() + '/api/live2d/result_feed';
|
||||
}
|
||||
|
||||
} else {
|
||||
//console.log("The fetch failed!");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onClickExpressionImage() {
|
||||
@ -1052,7 +1245,6 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
$('body').append(element);
|
||||
}
|
||||
function addSettings() {
|
||||
|
||||
const html = `
|
||||
<div class="expression_settings">
|
||||
<div class="inline-drawer">
|
||||
@ -1060,7 +1252,15 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
<b>Character Expressions</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<!-- Toggle button for aituber/static images -->
|
||||
<div class="toggle_button">
|
||||
<label class="switch">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<label for="image_type_toggle">Image Type - Live2d (extras)</label>
|
||||
</div>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
@ -1090,6 +1290,7 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#extensions_settings').append(html);
|
||||
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
|
||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
@ -1105,6 +1306,10 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
|
||||
$(window).on("resize", updateVisualNovelModeDebounced);
|
||||
$('.expression_settings').hide();
|
||||
|
||||
$('#image_type_toggle').on('click', function () {
|
||||
setLive2dState(this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
addExpressionImage();
|
||||
|
@ -544,6 +544,30 @@ async function onSelectInjectFile(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the length of character description in the current context
|
||||
function getCharacterDataLength() {
|
||||
const context = getContext();
|
||||
const character = context.characters[context.characterId];
|
||||
|
||||
if (typeof character?.data !== 'object') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let characterDataLength = 0;
|
||||
|
||||
for (const [key, value] of Object.entries(character.data)) {
|
||||
if (typeof value !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (['description', 'personality', 'scenario'].includes(key)) {
|
||||
characterDataLength += character.data[key].length;
|
||||
}
|
||||
}
|
||||
|
||||
return characterDataLength;
|
||||
}
|
||||
|
||||
/*
|
||||
* Automatically adjusts the extension settings for the optimal number of messages to keep and query based
|
||||
* on the chat history and a specified maximum context length.
|
||||
@ -558,6 +582,10 @@ function doAutoAdjust(chat, maxContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust max context for character defs length
|
||||
maxContext = Math.floor(maxContext - (getCharacterDataLength() / CHARACTERS_PER_TOKEN_RATIO));
|
||||
console.debug('CHROMADB: Max context adjusted for character defs: %o', maxContext);
|
||||
|
||||
console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength);
|
||||
// Convert to number of "tokens"
|
||||
const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getStringHash, debounce, waitUntilCondition, extractAllWords } from "../../utils.js";
|
||||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
|
||||
import { is_group_generating, selected_group } from "../../group-chats.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
@ -333,8 +334,12 @@ async function summarizeChat(context) {
|
||||
|
||||
async function summarizeChatMain(context, force) {
|
||||
try {
|
||||
// Wait for group to finish generating
|
||||
if (selected_group) {
|
||||
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
|
||||
}
|
||||
// Wait for the send button to be released
|
||||
waitUntilCondition(() => is_send_press === false, 10000, 100);
|
||||
waitUntilCondition(() => is_send_press === false, 30000, 100);
|
||||
} catch {
|
||||
console.debug('Timeout waiting for is_send_press');
|
||||
return;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { chat_metadata, callPopup, saveSettingsDebounced, getCurrentChatId } from "../../../script.js";
|
||||
import { chat_metadata, callPopup, saveSettingsDebounced, is_send_press } from "../../../script.js";
|
||||
import { getContext, extension_settings, saveMetadataDebounced } from "../../extensions.js";
|
||||
import {
|
||||
substituteParams,
|
||||
@ -7,6 +7,8 @@ import {
|
||||
generateQuietPrompt,
|
||||
} from "../../../script.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { waitUntilCondition } from "../../utils.js";
|
||||
import { is_group_generating, selected_group } from "../../group-chats.js";
|
||||
|
||||
const MODULE_NAME = "Objective"
|
||||
|
||||
@ -17,6 +19,7 @@ let currentChatId = ""
|
||||
let currentObjective = null
|
||||
let currentTask = null
|
||||
let checkCounter = 0
|
||||
let lastMessageWasSwipe = false
|
||||
|
||||
|
||||
const defaultPrompts = {
|
||||
@ -72,6 +75,9 @@ function getTaskByIdRecurse(taskId, task) {
|
||||
function substituteParamsPrompts(content) {
|
||||
content = content.replace(/{{objective}}/gi, currentObjective.description)
|
||||
content = content.replace(/{{task}}/gi, currentTask.description)
|
||||
if (currentTask.parent){
|
||||
content = content.replace(/{{parent}}/gi, currentTask.parent.description)
|
||||
}
|
||||
content = substituteParams(content)
|
||||
return content
|
||||
}
|
||||
@ -96,8 +102,8 @@ async function generateTasks() {
|
||||
}
|
||||
updateUiTaskList();
|
||||
setCurrentTask();
|
||||
console.info(`Response for Objective: '${taskTree.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
|
||||
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!');
|
||||
console.info(`Response for Objective: '${currentObjective.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(currentObjective.children.map(v => {return v.toSaveState()}), null, 2)} `)
|
||||
toastr.success(`Generated ${currentObjective.children.length} tasks`, 'Done!');
|
||||
}
|
||||
|
||||
// Call Quiet Generate to check if a task is completed
|
||||
@ -106,6 +112,19 @@ async function checkTaskCompleted() {
|
||||
if (jQuery.isEmptyObject(currentTask)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Wait for group to finish generating
|
||||
if (selected_group) {
|
||||
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
|
||||
}
|
||||
// Another extension might be doing something with the chat, so wait for it to finish
|
||||
await waitUntilCondition(() => is_send_press === false, 30000, 10);
|
||||
} catch {
|
||||
console.debug("Failed to wait for group to finish generating")
|
||||
return;
|
||||
}
|
||||
|
||||
checkCounter = $('#objective-check-frequency').val()
|
||||
toastr.info("Checking for task completion.")
|
||||
|
||||
@ -126,7 +145,7 @@ async function checkTaskCompleted() {
|
||||
function getNextIncompleteTaskRecurse(task){
|
||||
if (task.completed === false // Return task if incomplete
|
||||
&& task.children.length === 0 // Ensure task has no children, it's subtasks will determine completeness
|
||||
&& task.parentId // Must have parent id. Only root task will be missing this and we dont want that
|
||||
&& task.parentId !== "" // Must have parent id. Only root task will be missing this and we dont want that
|
||||
){
|
||||
return task
|
||||
}
|
||||
@ -513,6 +532,7 @@ const defaultSettings = {
|
||||
|
||||
// Convenient single call. Not much at the moment.
|
||||
function resetState() {
|
||||
lastMessageWasSwipe = false
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
@ -780,9 +800,12 @@ jQuery(() => {
|
||||
eventSource.on(event_types.CHAT_CHANGED, () => {
|
||||
resetState()
|
||||
});
|
||||
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, () => {
|
||||
lastMessageWasSwipe = true
|
||||
})
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, () => {
|
||||
if (currentChatId == undefined || currentTask == undefined) {
|
||||
if (currentChatId == undefined || jQuery.isEmptyObject(currentTask) || lastMessageWasSwipe) {
|
||||
lastMessageWasSwipe = false
|
||||
return
|
||||
}
|
||||
if ($("#objective-check-frequency").val() > 0) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
|
||||
import { getContext, extension_settings } from "../../extensions.js";
|
||||
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
|
||||
|
||||
|
||||
export { MODULE_NAME };
|
||||
@ -256,7 +257,7 @@ async function applyQuickReplyPreset(name) {
|
||||
const quickReplyPreset = presets.find(x => x.name == name);
|
||||
|
||||
if (!quickReplyPreset) {
|
||||
console.log(`error, QR preset '${name}' not found`)
|
||||
toastr.warning(`error, QR preset '${name}' not found. Confirm you are using proper case sensitivity!`)
|
||||
return;
|
||||
}
|
||||
|
||||
@ -268,9 +269,27 @@ async function applyQuickReplyPreset(name) {
|
||||
moduleWorker();
|
||||
|
||||
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
|
||||
|
||||
console.debug('QR Preset applied: ' + name);
|
||||
//loadMovingUIState()
|
||||
}
|
||||
|
||||
async function doQRPresetSwitch(_, text) {
|
||||
text = String(text)
|
||||
applyQuickReplyPreset(text)
|
||||
}
|
||||
|
||||
async function doQR(_, text) {
|
||||
if (!text) {
|
||||
toastr.warning('must specify which QR # to use')
|
||||
return
|
||||
}
|
||||
|
||||
text = Number(text)
|
||||
//use scale starting with 0
|
||||
//ex: user inputs "/qr 2" >> qr with data-index 1 (but 2nd item displayed) gets triggered
|
||||
let QRnum = Number(text - 1)
|
||||
if (QRnum <= 0) { QRnum = 0 }
|
||||
const whichQR = $("#quickReplies").find(`[data-index='${QRnum}']`);
|
||||
whichQR.trigger('click')
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
@ -326,5 +345,11 @@ jQuery(async () => {
|
||||
|
||||
await loadSettings('init');
|
||||
addQuickReplyBar();
|
||||
|
||||
});
|
||||
|
||||
$(document).ready(() => {
|
||||
registerSlashCommand('qr', doQR, [], "- requires number argument, activates the specified QuickReply", true, true);
|
||||
registerSlashCommand('qrset', doQRPresetSwitch, [], "- arg: QuickReply Preset Name, swaps to that QR preset", true, true);
|
||||
|
||||
})
|
||||
|
@ -8,16 +8,21 @@ import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper
|
||||
import { VoskSttProvider } from './vosk.js'
|
||||
import { WhisperSttProvider } from './whisper.js'
|
||||
import { BrowserSttProvider } from './browser.js'
|
||||
import { StreamingSttProvider } from './streaming.js'
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'Speech Recognition';
|
||||
const DEBUG_PREFIX = "<Speech Recognition module> "
|
||||
const UPDATE_INTERVAL = 100;
|
||||
|
||||
let inApiCall = false;
|
||||
|
||||
let sttProviders = {
|
||||
None: null,
|
||||
Browser: BrowserSttProvider,
|
||||
Whisper: WhisperSttProvider,
|
||||
Vosk: VoskSttProvider,
|
||||
Streaming: StreamingSttProvider,
|
||||
}
|
||||
|
||||
let sttProvider = null
|
||||
@ -27,6 +32,82 @@ let audioRecording = false
|
||||
const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } };
|
||||
let audioChunks = [];
|
||||
|
||||
async function moduleWorker() {
|
||||
if (sttProviderName != "Streaming") {
|
||||
return;
|
||||
}
|
||||
|
||||
// API is busy
|
||||
if (inApiCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
inApiCall = true;
|
||||
const userMessageOriginal = await sttProvider.getUserMessage();
|
||||
let userMessageFormatted = userMessageOriginal.trim();
|
||||
|
||||
if (userMessageFormatted.length > 0)
|
||||
{
|
||||
console.debug(DEBUG_PREFIX+"recorded transcript: \""+userMessageFormatted+"\"");
|
||||
|
||||
let userMessageLower = userMessageFormatted.toLowerCase();
|
||||
// remove punctuation
|
||||
let userMessageRaw = userMessageLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
|
||||
|
||||
console.debug(DEBUG_PREFIX+"raw transcript:",userMessageRaw);
|
||||
|
||||
// Detect trigger words
|
||||
let messageStart = -1;
|
||||
|
||||
if (extension_settings.speech_recognition.Streaming.triggerWordsEnabled) {
|
||||
|
||||
for (const triggerWord of extension_settings.speech_recognition.Streaming.triggerWords) {
|
||||
const triggerPos = userMessageRaw.indexOf(triggerWord.toLowerCase());
|
||||
|
||||
// Trigger word not found or not starting message and just a substring
|
||||
if (triggerPos == -1){ // | (triggerPos > 0 & userMessageFormatted[triggerPos-1] != " ")) {
|
||||
console.debug(DEBUG_PREFIX+"trigger word not found: ", triggerWord);
|
||||
}
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX+"Found trigger word: ", triggerWord, " at index ", triggerPos);
|
||||
if (triggerPos < messageStart | messageStart == -1) { // & (triggerPos + triggerWord.length) < userMessageFormatted.length)) {
|
||||
messageStart = triggerPos; // + triggerWord.length + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messageStart = 0;
|
||||
}
|
||||
|
||||
if (messageStart == -1) {
|
||||
console.debug(DEBUG_PREFIX+"message ignored, no trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"");
|
||||
if (extension_settings.speech_recognition.Streaming.debug) {
|
||||
toastr.info(
|
||||
"No trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"",
|
||||
DEBUG_PREFIX+"message ignored.",
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
else{
|
||||
userMessageFormatted = userMessageFormatted.substring(messageStart);
|
||||
processTranscript(userMessageFormatted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.debug(DEBUG_PREFIX+"Received empty transcript, ignored");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(error);
|
||||
}
|
||||
finally {
|
||||
inApiCall = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function processTranscript(transcript) {
|
||||
try {
|
||||
const transcriptOriginal = transcript;
|
||||
@ -198,13 +279,21 @@ function loadSttProvider(provider) {
|
||||
if (sttProviderName == "Browser") {
|
||||
sttProvider.processTranscriptFunction = processTranscript;
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
}
|
||||
else {
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
loadNavigatorAudioRecording();
|
||||
|
||||
$("#microphone_button").show();
|
||||
}
|
||||
|
||||
if (sttProviderName == "Vosk" | sttProviderName == "Whisper") {
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
loadNavigatorAudioRecording();
|
||||
$("#microphone_button").show();
|
||||
}
|
||||
|
||||
if (sttProviderName == "Streaming") {
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
$("#microphone_button").off('click');
|
||||
$("#microphone_button").hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onSttProviderChange() {
|
||||
@ -231,7 +320,7 @@ const defaultSettings = {
|
||||
messageMode: "append",
|
||||
messageMappingText: "",
|
||||
messageMapping: [],
|
||||
messageMappingEnabled: false
|
||||
messageMappingEnabled: false,
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
@ -344,8 +433,7 @@ $(document).ready(function () {
|
||||
addExtensionControls(); // No init dependencies
|
||||
loadSettings(); // Depends on Extension Controls and loadTtsProvider
|
||||
loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies
|
||||
|
||||
//const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
//setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
|
||||
//moduleWorker();
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
|
||||
moduleWorker();
|
||||
})
|
||||
|
102
public/scripts/extensions/speech-recognition/streaming.js
Normal file
102
public/scripts/extensions/speech-recognition/streaming.js
Normal file
@ -0,0 +1,102 @@
|
||||
import { getApiUrl, doExtrasFetch, modules } from "../../extensions.js";
|
||||
export { StreamingSttProvider }
|
||||
|
||||
const DEBUG_PREFIX = "<Speech Recognition module (streaming)> "
|
||||
|
||||
class StreamingSttProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings
|
||||
|
||||
defaultSettings = {
|
||||
triggerWordsText: "",
|
||||
triggerWords : [],
|
||||
triggerWordsEnabled : false,
|
||||
debug : false,
|
||||
}
|
||||
|
||||
get settingsHtml() {
|
||||
let html = '\
|
||||
<div id="speech_recognition_streaming_trigger_words_div">\
|
||||
<span>Trigger words</span>\
|
||||
<textarea id="speech_recognition_streaming_trigger_words" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated words that triggers new message, example:\nhey, hey aqua, record, listen"></textarea>\
|
||||
<label class="checkbox_label" for="speech_recognition_streaming_trigger_words_enabled">\
|
||||
<input type="checkbox" id="speech_recognition_streaming_trigger_words_enabled" name="speech_recognition_trigger_words_enabled">\
|
||||
<small>Enable trigger words</small>\
|
||||
</label>\
|
||||
<label class="checkbox_label" for="speech_recognition_streaming_debug">\
|
||||
<input type="checkbox" id="speech_recognition_streaming_debug" name="speech_recognition_streaming_debug">\
|
||||
<small>Enable debug pop ups</small>\
|
||||
</label>\
|
||||
</div>\
|
||||
'
|
||||
return html
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
this.settings.triggerWordsText = $('#speech_recognition_streaming_trigger_words').val();
|
||||
let array = $('#speech_recognition_streaming_trigger_words').val().split(",");
|
||||
array = array.map(element => {return element.trim().toLowerCase();});
|
||||
array = array.filter((str) => str !== '');
|
||||
this.settings.triggerWords = array;
|
||||
this.settings.triggerWordsEnabled = $("#speech_recognition_streaming_trigger_words_enabled").is(':checked');
|
||||
this.settings.debug = $("#speech_recognition_streaming_debug").is(':checked');
|
||||
console.debug(DEBUG_PREFIX+" Updated settings: ", this.settings);
|
||||
this.loadSettings(this.settings);
|
||||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
// Populate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
|
||||
}
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
|
||||
for (const key in settings){
|
||||
if (key in this.settings){
|
||||
this.settings[key] = settings[key]
|
||||
} else {
|
||||
throw `Invalid setting passed to STT extension: ${key}`
|
||||
}
|
||||
}
|
||||
|
||||
$("#speech_recognition_streaming_trigger_words").val(this.settings.triggerWordsText);
|
||||
$("#speech_recognition_streaming_trigger_words_enabled").prop('checked',this.settings.triggerWordsEnabled);
|
||||
$("#speech_recognition_streaming_debug").prop('checked',this.settings.debug);
|
||||
|
||||
console.debug(DEBUG_PREFIX+"streaming STT settings loaded")
|
||||
}
|
||||
|
||||
async getUserMessage() {
|
||||
// Return if module is not loaded
|
||||
if (!modules.includes('streaming-stt')) {
|
||||
console.debug(DEBUG_PREFIX+"Module streaming-stt must be activated in Sillytavern Extras for streaming user voice.")
|
||||
return "";
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/speech-recognition/streaming/record-and-transcript';
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ text: "" }),
|
||||
});
|
||||
|
||||
if (!apiResult.ok) {
|
||||
toastr.error(apiResult.statusText, DEBUG_PREFIX+'STT Generation Failed (streaming)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
||||
}
|
||||
|
||||
const data = await apiResult.json();
|
||||
return data.transcript;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
|
||||
import { ModuleWorkerWrapper, extension_settings, getContext } from '../../extensions.js'
|
||||
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext } from '../../extensions.js'
|
||||
import { escapeRegex, getStringHash } from '../../utils.js'
|
||||
import { EdgeTtsProvider } from './edge.js'
|
||||
import { ElevenLabsTtsProvider } from './elevenlabs.js'
|
||||
@ -7,14 +7,13 @@ import { SileroTtsProvider } from './silerotts.js'
|
||||
import { CoquiTtsProvider } from './coquitts.js'
|
||||
import { SystemTtsProvider } from './system.js'
|
||||
import { NovelTtsProvider } from './novel.js'
|
||||
import { isMobile } from '../../RossAscends-mods.js'
|
||||
import { power_user } from '../../power-user.js'
|
||||
|
||||
const UPDATE_INTERVAL = 1000
|
||||
|
||||
let voiceMap = {} // {charName:voiceid, charName2:voiceid2}
|
||||
let audioControl
|
||||
|
||||
let storedvalue = false;
|
||||
let lastCharacterId = null
|
||||
let lastGroupId = null
|
||||
let lastChatId = null
|
||||
@ -164,6 +163,20 @@ async function moduleWorker() {
|
||||
ttsJobQueue.push(message)
|
||||
}
|
||||
|
||||
function talkingAnimation(switchValue) {
|
||||
const apiUrl = getApiUrl();
|
||||
const animationType = switchValue ? "start" : "stop";
|
||||
|
||||
if (switchValue !== storedvalue) {
|
||||
try {
|
||||
console.log(animationType + " Talking Animation");
|
||||
doExtrasFetch(`${apiUrl}/api/live2d/${animationType}_talking`);
|
||||
storedvalue = switchValue; // Update the storedvalue to the current switchValue
|
||||
} catch (error) {
|
||||
// Handle the error here or simply ignore it to prevent logging
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetTtsPlayback() {
|
||||
// Stop system TTS utterance
|
||||
@ -291,8 +304,10 @@ function updateUiAudioPlayState() {
|
||||
// Give user feedback that TTS is active by setting the stop icon if processing or playing
|
||||
if (!audioElement.paused || isTtsProcessing()) {
|
||||
img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton'
|
||||
talkingAnimation(true)
|
||||
} else {
|
||||
img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'
|
||||
talkingAnimation(false)
|
||||
}
|
||||
$('#tts_media_control').attr('class', img);
|
||||
} else {
|
||||
@ -354,6 +369,7 @@ async function processAudioJobQueue() {
|
||||
audioQueueProcessorReady = false
|
||||
currentAudioJob = audioJobQueue.pop()
|
||||
playAudioData(currentAudioJob)
|
||||
talkingAnimation(true)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
audioQueueProcessorReady = true
|
||||
|
@ -4,6 +4,10 @@ import {
|
||||
getStoppingStrings,
|
||||
} from "../script.js";
|
||||
|
||||
import {
|
||||
power_user,
|
||||
} from "./power-user.js";
|
||||
|
||||
export {
|
||||
kai_settings,
|
||||
loadKoboldSettings,
|
||||
@ -35,13 +39,13 @@ const MIN_STREAMING_KCPPVERSION = '1.30';
|
||||
function formatKoboldUrl(value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (!power_user.relaxed_api_urls) {
|
||||
url.pathname = '/api';
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
catch {
|
||||
} catch { } // Just using URL as a validation check
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadKoboldSettings(preset) {
|
||||
for (const name of Object.keys(kai_settings)) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import {
|
||||
getRequestHeaders,
|
||||
saveSettingsDebounced,
|
||||
getStoppingStrings,
|
||||
getTextTokens
|
||||
} from "../script.js";
|
||||
import { tokenizers } from "./power-user.js";
|
||||
|
||||
export {
|
||||
nai_settings,
|
||||
@ -10,6 +13,8 @@ export {
|
||||
getNovelTier,
|
||||
};
|
||||
|
||||
const default_preamble = "[ Style: chat, complex, sensory, visceral ]";
|
||||
|
||||
const nai_settings = {
|
||||
temperature: 0.5,
|
||||
repetition_penalty: 1,
|
||||
@ -26,6 +31,7 @@ const nai_settings = {
|
||||
model_novel: "euterpe-v2",
|
||||
preset_settings_novel: "Classic-Euterpe",
|
||||
streaming_novel: false,
|
||||
nai_preamble: default_preamble,
|
||||
};
|
||||
|
||||
const nai_tiers = {
|
||||
@ -73,6 +79,7 @@ function loadNovelSettings(settings) {
|
||||
$(`#model_novel_select option[value=${nai_settings.model_novel}]`).attr("selected", true);
|
||||
$('#model_novel_select').val(nai_settings.model_novel);
|
||||
|
||||
if (settings.nai_preamble !== undefined) nai_settings.preamble = settings.nai_preamble;
|
||||
nai_settings.preset_settings_novel = settings.preset_settings_novel;
|
||||
nai_settings.temperature = settings.temperature;
|
||||
nai_settings.repetition_penalty = settings.repetition_penalty;
|
||||
@ -154,6 +161,7 @@ function loadNovelSettingsUi(ui_settings) {
|
||||
$("#phrase_rep_pen_counter_novel").text(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
|
||||
$("#min_length_novel").val(ui_settings.min_length);
|
||||
$("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0));
|
||||
$('#nai_preamble_textarea').val(ui_settings.nai_preamble);
|
||||
|
||||
$("#streaming_novel").prop('checked', ui_settings.streaming_novel);
|
||||
}
|
||||
@ -245,8 +253,24 @@ const sliders = [
|
||||
},
|
||||
];
|
||||
|
||||
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen) {
|
||||
const isNewModel = (nai_settings.model_novel.includes('clio') || nai_settings.model_novel.includes('kayra'));
|
||||
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate) {
|
||||
const clio = nai_settings.model_novel.includes('clio');
|
||||
const kayra = nai_settings.model_novel.includes('kayra');
|
||||
const isNewModel = clio || kayra;
|
||||
|
||||
const tokenizerType = kayra ? tokenizers.NERD2 : (clio ? tokenizers.NERD : tokenizers.NONE);
|
||||
const stopSequences = (tokenizerType !== tokenizers.NONE)
|
||||
? getStoppingStrings(isImpersonate, false)
|
||||
.map(t => getTextTokens(tokenizerType, t))
|
||||
: undefined;
|
||||
|
||||
let useInstruct = false;
|
||||
if (isNewModel) {
|
||||
// NovelAI claims they scan backwards 1000 characters (not tokens!) to look for instruct brackets. That's really short.
|
||||
const tail = finalPromt.slice(-1500);
|
||||
useInstruct = tail.includes("}");
|
||||
}
|
||||
|
||||
return {
|
||||
"input": finalPromt,
|
||||
"model": nai_settings.model_novel,
|
||||
@ -268,12 +292,13 @@ export function getNovelGenerationData(finalPromt, this_settings, this_amount_ge
|
||||
"cfg_uc": "",
|
||||
"phrase_rep_pen": nai_settings.phrase_rep_pen,
|
||||
//"stop_sequences": {{187}},
|
||||
"stop_sequences": stopSequences,
|
||||
//bad_words_ids = {{50256}, {0}, {1}};
|
||||
"generate_until_sentence": true,
|
||||
"use_cache": false,
|
||||
"use_string": true,
|
||||
"return_full_text": false,
|
||||
"prefix": isNewModel ? "special_instruct" : "vanilla",
|
||||
"prefix": useInstruct ? "special_instruct" : (isNewModel ? "special_proseaugmenter" : "vanilla"),
|
||||
"order": this_settings.order,
|
||||
"streaming": nai_settings.streaming_novel,
|
||||
};
|
||||
@ -321,6 +346,17 @@ export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
$("#nai_preamble_textarea").on('input', function () {
|
||||
nai_settings.preamble = $('#nai_preamble_textarea').val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#nai_preamble_restore").on('click', function () {
|
||||
nai_settings.preamble = default_preamble;
|
||||
$('#nai_preamble_textarea').val(nai_settings.preamble);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
sliders.forEach(slider => {
|
||||
$(document).on("input", slider.sliderId, function () {
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
|
||||
import { delay, debounce } from "./utils.js";
|
||||
import { delay } from "./utils.js";
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
@ -71,6 +71,7 @@ const tokenizers = {
|
||||
NERD: 4,
|
||||
NERD2: 5,
|
||||
API: 6,
|
||||
BEST_MATCH: 99,
|
||||
}
|
||||
|
||||
const send_on_enter_options = {
|
||||
@ -87,7 +88,7 @@ export const persona_description_positions = {
|
||||
}
|
||||
|
||||
let power_user = {
|
||||
tokenizer: tokenizers.CLASSIC,
|
||||
tokenizer: tokenizers.BEST_MATCH,
|
||||
token_padding: 64,
|
||||
collapse_newlines: false,
|
||||
pygmalion_formatting: pygmalion_options.AUTO,
|
||||
@ -163,6 +164,7 @@ let power_user = {
|
||||
prefer_character_jailbreak: true,
|
||||
continue_on_send: false,
|
||||
trim_spaces: true,
|
||||
relaxed_api_urls: false,
|
||||
|
||||
instruct: {
|
||||
enabled: false,
|
||||
@ -176,6 +178,7 @@ let power_user = {
|
||||
preset: 'Alpaca',
|
||||
separator_sequence: '',
|
||||
macro: false,
|
||||
names_force_groups: true,
|
||||
},
|
||||
|
||||
personas: {},
|
||||
@ -184,8 +187,10 @@ let power_user = {
|
||||
|
||||
persona_description: '',
|
||||
persona_description_position: persona_description_positions.BEFORE_CHAR,
|
||||
persona_show_notifications: true,
|
||||
|
||||
custom_stopping_strings: '',
|
||||
custom_stopping_strings_macro: true,
|
||||
fuzzy_search: false,
|
||||
};
|
||||
|
||||
@ -670,6 +675,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
power_user.chat_width = 50;
|
||||
}
|
||||
|
||||
$('#relaxed_api_urls').prop("checked", power_user.relaxed_api_urls);
|
||||
$('#trim_spaces').prop("checked", power_user.trim_spaces);
|
||||
$('#continue_on_send').prop("checked", power_user.continue_on_send);
|
||||
$('#auto_swipe').prop("checked", power_user.auto_swipe);
|
||||
@ -677,7 +683,9 @@ function loadPowerUserSettings(settings, data) {
|
||||
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
|
||||
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
|
||||
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
|
||||
$("#custom_stopping_strings_macro").prop("checked", power_user.custom_stopping_strings_macro);
|
||||
$('#fuzzy_search_checkbox').prop("checked", power_user.fuzzy_search);
|
||||
$('#persona_show_notifications').prop("checked", power_user.persona_show_notifications);
|
||||
|
||||
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
|
||||
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
|
||||
@ -848,8 +856,13 @@ function loadInstructMode() {
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ id: "instruct_names", property: "names", isCheckbox: true },
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
|
||||
];
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
@ -860,7 +873,7 @@ function loadInstructMode() {
|
||||
}
|
||||
|
||||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? $(this).prop('checked') : $(this).val();
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
@ -924,7 +937,12 @@ export function fuzzySearchCharacters(searchValue) {
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
@ -952,7 +970,7 @@ export function formatInstructStoryString(story, systemPrompt) {
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
@ -1977,6 +1995,12 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#relaxed_api_urls").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.relaxed_api_urls = value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#spoiler_free_mode').on('input', function () {
|
||||
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
||||
switchSpoilerMode();
|
||||
@ -1993,11 +2017,21 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#custom_stopping_strings_macro").change(function () {
|
||||
power_user.custom_stopping_strings_macro = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#fuzzy_search_checkbox').on('input', function () {
|
||||
power_user.fuzzy_search = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#persona_show_notifications').on('input', function () {
|
||||
power_user.persona_show_notifications = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(window).on('focus', function () {
|
||||
browser_has_focus = true;
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import { callPopup, getRequestHeaders } from "../script.js";
|
||||
|
||||
export const SECRET_KEYS = {
|
||||
HORDE: 'api_key_horde',
|
||||
MANCER: 'api_key_mancer',
|
||||
OPENAI: 'api_key_openai',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
@ -11,6 +12,7 @@ export const SECRET_KEYS = {
|
||||
|
||||
const INPUT_MAP = {
|
||||
[SECRET_KEYS.HORDE]: '#horde_api_key',
|
||||
[SECRET_KEYS.MANCER]: '#api_key_mancer',
|
||||
[SECRET_KEYS.OPENAI]: '#api_key_openai',
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
|
@ -423,7 +423,11 @@ function helpCommandCallback(_, type) {
|
||||
}
|
||||
}
|
||||
|
||||
window['displayHelp'] = (page) => helpCommandCallback(null, page);
|
||||
$(document).on('click', '[data-displayHelp]', function (e) {
|
||||
e.preventDefault();
|
||||
const page = String($(this).data('displayhelp'));
|
||||
helpCommandCallback(null, page);
|
||||
});
|
||||
|
||||
function setBackgroundCallback(_, bg) {
|
||||
if (!bg) {
|
||||
|
@ -499,7 +499,7 @@ function onViewTagsListClick() {
|
||||
$(list).append('<h3>Tags</h3><i>Click on the tag name to edit it.</i><br>');
|
||||
$(list).append('<i>Click on color box to assign new color.</i><br><br>');
|
||||
|
||||
for (const tag of tags) {
|
||||
for (const tag of tags.slice().sort((a, b) => a?.name?.localeCompare(b?.name))) {
|
||||
const count = everything.filter(x => x == tag.id).length;
|
||||
const template = $('#tag_view_template .tag_view_item').clone();
|
||||
template.attr('id', tag.id);
|
||||
|
@ -6,10 +6,15 @@ import {
|
||||
setGenerationParamsFromPreset,
|
||||
} from "../script.js";
|
||||
|
||||
import {
|
||||
power_user,
|
||||
} from "./power-user.js";
|
||||
|
||||
export {
|
||||
textgenerationwebui_settings,
|
||||
loadTextGenSettings,
|
||||
generateTextGenWithStreaming,
|
||||
formatTextGenURL,
|
||||
}
|
||||
|
||||
const textgenerationwebui_settings = {
|
||||
@ -94,6 +99,17 @@ function selectPreset(name) {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function formatTextGenURL(value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (!power_user.relaxed_api_urls) {
|
||||
url.pathname = '/api';
|
||||
}
|
||||
return url.toString();
|
||||
} catch { } // Just using URL as a validation check
|
||||
return null;
|
||||
}
|
||||
|
||||
function convertPresets(presets) {
|
||||
return Array.isArray(presets) ? presets.map(JSON.parse) : [];
|
||||
}
|
||||
|
@ -4,6 +4,10 @@ export function onlyUnique(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export function isDigitsOnly(str) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
export function shuffle(array) {
|
||||
let currentIndex = array.length,
|
||||
randomIndex;
|
||||
|
@ -1511,7 +1511,7 @@ jQuery(() => {
|
||||
});
|
||||
|
||||
$('#world_info_overflow_alert').on('change', function () {
|
||||
world_info_overflow_alert = $(this).val();
|
||||
world_info_overflow_alert = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -217,12 +217,18 @@ table.responsiveTable {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mes_text q,
|
||||
.mes_text blockquote {
|
||||
.mes_text q {
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mes_text blockquote {
|
||||
border-left: 3px solid var(--SmartThemeQuoteColor);
|
||||
padding-left: 10px;
|
||||
background-color: var(--black30a);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mes_text strong em,
|
||||
.mes_text strong,
|
||||
.mes_text h2,
|
||||
@ -948,10 +954,6 @@ select {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#rm_ch_create_block textarea {
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
.margin-bot-10px,
|
||||
.marginBot10 {
|
||||
margin-bottom: 10px;
|
||||
@ -1293,7 +1295,8 @@ body.charListGrid #rm_print_characters_block .tags_inline {
|
||||
|
||||
}
|
||||
|
||||
.floating_prompt_radio_group, .radio_group {
|
||||
.floating_prompt_radio_group,
|
||||
.radio_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -1717,11 +1720,10 @@ body.big-avatars .ch_description {
|
||||
|
||||
|
||||
#form_create {
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
grid-template-rows:
|
||||
[avatar] min-content [hr] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto [hidden] min-content;
|
||||
}
|
||||
|
||||
.avatar_div {
|
||||
@ -1752,7 +1754,6 @@ body.big-avatars #avatar_div_div.avatar img {
|
||||
#avatar-and-name-block {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
flex-wrap: wrap;
|
||||
/* margin-bottom: 4px; */
|
||||
}
|
||||
@ -3040,9 +3041,23 @@ h5 {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
|
||||
.PastChat_cross:hover {
|
||||
color: red;
|
||||
filter: drop-shadow(0 0 2px red);
|
||||
-webkit-animation: infinite-spinning 1s ease-out 0s infinite normal;
|
||||
animation: infinite-spinning 1s ease-out 0s infinite normal;
|
||||
}
|
||||
|
||||
/* HEINOUS */
|
||||
@keyframes infinite-spinning {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#export_character_div {
|
||||
@ -4409,6 +4424,10 @@ toolcool-color-picker {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.indent20p {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.wi-enter-footer-text {
|
||||
font-size: calc(var(--mainFontSize) * 0.8);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
@ -5083,11 +5102,6 @@ body.waifuMode .zoomed_avatar {
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
#form_create {
|
||||
grid-template-rows:
|
||||
[avatar] min-content [hr] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto;
|
||||
}
|
||||
|
||||
#result_info {
|
||||
font-size: calc(var(--mainFontSize) - .1rem);
|
||||
}
|
||||
@ -5103,10 +5117,6 @@ body.waifuMode .zoomed_avatar {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
#rm_ch_create_block textarea {
|
||||
max-height: 190px;
|
||||
}
|
||||
|
||||
.drawer25pWidth {
|
||||
flex-basis: max(calc(100% / 4 - 10px), 190px);
|
||||
}
|
||||
|
172
server.js
172
server.js
@ -34,7 +34,11 @@ if (net.setDefaultAutoSelectFamily) {
|
||||
}
|
||||
|
||||
const cliArguments = yargs(hideBin(process.argv))
|
||||
.option('ssl', {
|
||||
.option('disableCsrf', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Disables CSRF protection'
|
||||
}).option('ssl', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Enables SSL'
|
||||
@ -119,10 +123,15 @@ const allowKeysExposure = config.allowKeysExposure;
|
||||
const axios = require('axios');
|
||||
const tiktoken = require('@dqbd/tiktoken');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
function getHordeClient() {
|
||||
const AIHorde = require("./src/horde");
|
||||
const ai_horde = new AIHorde({
|
||||
client_agent: getVersion()?.agent || 'SillyTavern:UNKNOWN:Cohee#1207',
|
||||
});
|
||||
return ai_horde;
|
||||
}
|
||||
|
||||
const ipMatching = require('ip-matching');
|
||||
const yauzl = require('yauzl');
|
||||
|
||||
@ -146,6 +155,14 @@ let response_getstatus;
|
||||
let first_run = true;
|
||||
|
||||
|
||||
|
||||
function get_mancer_headers() {
|
||||
const api_key_mancer = readSecret(SECRET_KEYS.MANCER);
|
||||
return api_key_mancer ? { "X-API-KEY": api_key_mancer } : {};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
|
||||
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
|
||||
//During testing, this performs the same as previous date.now() structure.
|
||||
@ -178,13 +195,19 @@ async function loadSentencepieceTokenizer(modelPath) {
|
||||
async function countSentencepieceTokens(spp, text) {
|
||||
// Fallback to strlen estimation
|
||||
if (!spp) {
|
||||
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
||||
return {
|
||||
ids: [],
|
||||
count: Math.ceil(text.length / CHARS_PER_TOKEN)
|
||||
};
|
||||
}
|
||||
|
||||
let cleaned = cleanText(text);
|
||||
let cleaned = text; // cleanText(text); <-- cleaning text can result in an incorrect tokenization
|
||||
|
||||
let ids = spp.encodeIds(cleaned);
|
||||
return ids.length;
|
||||
return {
|
||||
ids,
|
||||
count: ids.length
|
||||
};
|
||||
}
|
||||
|
||||
async function loadClaudeTokenizer(modelPath) {
|
||||
@ -294,6 +317,7 @@ const directories = {
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
if (cliArguments.disableCsrf === false) {
|
||||
const doubleCsrf = require('csrf-csrf').doubleCsrf;
|
||||
|
||||
const CSRF_SECRET = crypto.randomBytes(8).toString('hex');
|
||||
@ -319,6 +343,14 @@ app.get("/csrf-token", (req, res) => {
|
||||
|
||||
app.use(cookieParser(COOKIES_SECRET));
|
||||
app.use(doubleCsrfProtection);
|
||||
} else {
|
||||
console.warn("\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n");
|
||||
app.get("/csrf-token", (req, res) => {
|
||||
res.json({
|
||||
"token": 'disabled'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// CORS Settings //
|
||||
const cors = require('cors');
|
||||
@ -506,8 +538,16 @@ app.post("/generate", jsonParser, async function (request, response_generate = r
|
||||
return response.body.pipe(response_generate);
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
console.log(`Kobold returned error: ${response.status} ${response.statusText} ${await response.text()}`);
|
||||
return response.status(response.status).send({ error: true });
|
||||
const errorText = await response.text();
|
||||
console.log(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`);
|
||||
|
||||
try {
|
||||
const errorJson = JSON.parse(errorText);
|
||||
const message = errorJson?.detail?.msg || errorText;
|
||||
return response_generate.status(400).send({ error: { message } });
|
||||
} catch {
|
||||
return response_generate.status(400).send({ error: { message: errorText } });
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@ -626,13 +666,22 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (request.body.use_mancer) {
|
||||
args.headers = Object.assign(args.headers, get_mancer_headers());
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await postAsync(api_server + "/v1/generate", args);
|
||||
console.log(data);
|
||||
return response_generate.send(data);
|
||||
} catch (error) {
|
||||
retval = { error: true, status: error.status, response: error.statusText };
|
||||
console.log(error);
|
||||
return response_generate.send({ error: true });
|
||||
try {
|
||||
retval.response = await error.json();
|
||||
retval.response = retval.response.result;
|
||||
} catch { }
|
||||
return response_generate.send(retval);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -696,6 +745,11 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
|
||||
var args = {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
};
|
||||
|
||||
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
|
||||
args.headers = Object.assign(args.headers, get_mancer_headers());
|
||||
}
|
||||
|
||||
var url = api_server + "/v1/model";
|
||||
let version = '';
|
||||
let koboldVersion = {};
|
||||
@ -716,18 +770,18 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
|
||||
};
|
||||
}
|
||||
}
|
||||
client.get(url, args, function (data, response) {
|
||||
client.get(url, args, async function (data, response) {
|
||||
if (typeof data !== 'object') {
|
||||
data = {};
|
||||
}
|
||||
if (response.statusCode == 200) {
|
||||
data.version = version;
|
||||
data.koboldVersion = koboldVersion;
|
||||
if (data.result != "ReadOnly") {
|
||||
} else {
|
||||
if (data.result == "ReadOnly") {
|
||||
data.result = "no_connection";
|
||||
}
|
||||
} else {
|
||||
data.response = data.result;
|
||||
data.result = "no_connection";
|
||||
}
|
||||
response_getstatus.send(data);
|
||||
@ -1133,7 +1187,7 @@ app.post("/deletecharacter", jsonParser, async function (request, response) {
|
||||
return response.sendStatus(403);
|
||||
}
|
||||
|
||||
if (request.body.delete_chats == 'true') {
|
||||
if (request.body.delete_chats == true) {
|
||||
try {
|
||||
await fs.promises.rm(path.join(chatsPath, sanitize(dir_name)), { recursive: true, force: true })
|
||||
} catch (err) {
|
||||
@ -1755,13 +1809,12 @@ app.post("/getstatus_novelai", jsonParser, function (request, response_getstatus
|
||||
const api_key_novel = readSecret(SECRET_KEYS.NOVEL);
|
||||
|
||||
if (!api_key_novel) {
|
||||
return response_generate_novel.sendStatus(401);
|
||||
return response_getstatus_novel.sendStatus(401);
|
||||
}
|
||||
|
||||
var data = {};
|
||||
var args = {
|
||||
data: data,
|
||||
|
||||
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + api_key_novel }
|
||||
};
|
||||
client.get(api_novelai + "/user/subscription", args, function (data, response) {
|
||||
@ -1769,17 +1822,15 @@ app.post("/getstatus_novelai", jsonParser, function (request, response_getstatus
|
||||
//console.log(data);
|
||||
response_getstatus_novel.send(data);//data);
|
||||
}
|
||||
else {
|
||||
if (response.statusCode == 401) {
|
||||
console.log('Access Token is incorrect.');
|
||||
response_getstatus_novel.send({ error: true });
|
||||
}
|
||||
if (response.statusCode == 500 || response.statusCode == 501 || response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
|
||||
|
||||
console.log(data);
|
||||
response_getstatus_novel.send({ error: true });
|
||||
}
|
||||
}).on('error', function () {
|
||||
//console.log('');
|
||||
//console.log('something went wrong on the request', err.request.options);
|
||||
response_getstatus_novel.send({ error: true });
|
||||
});
|
||||
});
|
||||
@ -1824,6 +1875,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
"cfg_scale": request.body.cfg_scale,
|
||||
"cfg_uc": request.body.cfg_uc,
|
||||
"phrase_rep_pen": request.body.phrase_rep_pen,
|
||||
"stop_sequences": request.body.stop_sequences,
|
||||
//"stop_sequences": {{187}},
|
||||
"bad_words_ids": isNewModel ? novelai.badWordsList : (isKrake ? novelai.krakeBadWordsList : novelai.euterpeBadWordsList),
|
||||
"logit_bias_exp": isNewModel ? novelai.logitBiasExp : null,
|
||||
@ -1864,8 +1916,19 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
});
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
console.log(`Novel API returned error: ${response.status} ${response.statusText} ${await response.text()}`);
|
||||
return response.status(response.status).send({ error: true });
|
||||
const text = await response.text();
|
||||
let message = text;
|
||||
console.log(`Novel API returned error: ${response.status} ${response.statusText} ${text}`);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
message = data.message;
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return response_generate_novel.status(response.status).send({ error: { message } });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@ -1923,14 +1986,16 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) {
|
||||
ii--;
|
||||
if (lastLine) {
|
||||
|
||||
let jsonData = json5.parse(lastLine);
|
||||
if (jsonData.name !== undefined || jsonData.character_name !== undefined) {
|
||||
let jsonData = tryParse(lastLine);
|
||||
if (jsonData && (jsonData.name !== undefined || jsonData.character_name !== undefined)) {
|
||||
chatData[i] = {};
|
||||
chatData[i]['file_name'] = file;
|
||||
chatData[i]['file_size'] = fileSizeInKB;
|
||||
chatData[i]['chat_items'] = itemCounter - 1;
|
||||
chatData[i]['mes'] = jsonData['mes'] || '[The chat is empty]';
|
||||
chatData[i]['last_mes'] = jsonData['send_date'] || Date.now();
|
||||
} else {
|
||||
console.log('Found an invalid or corrupted chat file: ' + fullPathAndFile);
|
||||
}
|
||||
}
|
||||
if (ii === 0) {
|
||||
@ -3174,16 +3239,19 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
let bodyParams;
|
||||
|
||||
if (!request.body.use_openrouter) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
bodyParams = {};
|
||||
} else {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
bodyParams = { 'transforms': ["middle-out"] };
|
||||
}
|
||||
|
||||
if (!api_key_openai && !request.body.reverse_proxy) {
|
||||
@ -3220,7 +3288,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
"top_p": request.body.top_p,
|
||||
"top_k": request.body.top_k,
|
||||
"stop": request.body.stop,
|
||||
"logit_bias": request.body.logit_bias
|
||||
"logit_bias": request.body.logit_bias,
|
||||
...bodyParams,
|
||||
},
|
||||
signal: controller.signal,
|
||||
};
|
||||
@ -3258,7 +3327,22 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
makeRequest(config, response_generate_openai, request, retries - 1);
|
||||
}, timeout);
|
||||
} else {
|
||||
handleError(error, response_generate_openai, request);
|
||||
let errorData = error.response.data;
|
||||
|
||||
if (request.body.stream) {
|
||||
try {
|
||||
const chunks = await readAllChunks(errorData);
|
||||
const blob = new Blob(chunks, { type: 'application/json' });
|
||||
const text = await blob.text();
|
||||
errorData = JSON.parse(text);
|
||||
} catch {
|
||||
console.warn('Error parsing streaming response');
|
||||
}
|
||||
} else {
|
||||
errorData = typeof errorData === 'string' ? tryParse(errorData) : errorData;
|
||||
}
|
||||
|
||||
handleError(error, response_generate_openai, errorData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3270,27 +3354,28 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error, response_generate_openai, request) {
|
||||
function handleError(error, response_generate_openai, errorData) {
|
||||
console.error('Error:', error.message);
|
||||
|
||||
let message = error?.response?.statusText;
|
||||
|
||||
switch (error?.response?.status) {
|
||||
case 402:
|
||||
message = error?.response?.data?.error?.message || 'Credit limit reached';
|
||||
const statusMessages = {
|
||||
400: 'Bad request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Credit limit reached',
|
||||
403: 'Forbidden',
|
||||
404: 'Not found',
|
||||
429: 'Too many requests',
|
||||
451: 'Unavailable for legal reasons',
|
||||
};
|
||||
|
||||
const status = error?.response?.status;
|
||||
if (statusMessages.hasOwnProperty(status)) {
|
||||
message = errorData?.error?.message || statusMessages[status];
|
||||
console.log(message);
|
||||
break;
|
||||
case 403:
|
||||
message = error?.response?.data?.error?.message || 'API key disabled or exhausted';
|
||||
console.log(message);
|
||||
break;
|
||||
case 451:
|
||||
message = error?.response?.data?.error?.message || 'Unavailable for legal reasons';
|
||||
console.log(message);
|
||||
break;
|
||||
}
|
||||
|
||||
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';
|
||||
const quota_error = error?.response?.status === 429 && errorData?.error?.type === 'insufficient_quota';
|
||||
const response = { error: { message }, quota_error: quota_error }
|
||||
if (!response_generate_openai.headersSent) {
|
||||
response_generate_openai.send(response);
|
||||
@ -3415,8 +3500,8 @@ function createTokenizationHandler(getTokenizerFn) {
|
||||
|
||||
const text = request.body.text || '';
|
||||
const tokenizer = getTokenizerFn();
|
||||
const count = await countSentencepieceTokens(tokenizer, text);
|
||||
return response.send({ count });
|
||||
const { ids, count } = await countSentencepieceTokens(tokenizer, text);
|
||||
return response.send({ ids, count });
|
||||
};
|
||||
}
|
||||
|
||||
@ -3435,6 +3520,10 @@ app.post("/tokenize_via_api", jsonParser, async function (request, response) {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
};
|
||||
|
||||
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
|
||||
args.headers = Object.assign(args.headers, get_mancer_headers());
|
||||
}
|
||||
|
||||
const data = await postAsync(api_server + "/v1/token-count", args);
|
||||
console.log(data);
|
||||
return response.send({ count: data['results'][0]['tokens'] });
|
||||
@ -3640,6 +3729,7 @@ const SECRETS_FILE = './secrets.json';
|
||||
const SETTINGS_FILE = './public/settings.json';
|
||||
const SECRET_KEYS = {
|
||||
HORDE: 'api_key_horde',
|
||||
MANCER: 'api_key_mancer',
|
||||
OPENAI: 'api_key_openai',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
@ -3771,6 +3861,7 @@ app.post('/viewsecrets', jsonParser, async (_, response) => {
|
||||
|
||||
app.post('/horde_samplers', jsonParser, async (_, response) => {
|
||||
try {
|
||||
const ai_horde = getHordeClient();
|
||||
const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers);
|
||||
response.send(samplers);
|
||||
} catch (error) {
|
||||
@ -3781,6 +3872,7 @@ app.post('/horde_samplers', jsonParser, async (_, response) => {
|
||||
|
||||
app.post('/horde_models', jsonParser, async (_, response) => {
|
||||
try {
|
||||
const ai_horde = getHordeClient();
|
||||
const models = await ai_horde.getModels();
|
||||
response.send(models);
|
||||
} catch (error) {
|
||||
@ -3797,6 +3889,7 @@ app.post('/horde_userinfo', jsonParser, async (_, response) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const ai_horde = getHordeClient();
|
||||
const user = await ai_horde.findUser({ token: api_key_horde });
|
||||
return response.send(user);
|
||||
} catch (error) {
|
||||
@ -3812,6 +3905,7 @@ app.post('/horde_generateimage', jsonParser, async (request, response) => {
|
||||
console.log('Stable Horde request:', request.body);
|
||||
|
||||
try {
|
||||
const ai_horde = getHordeClient();
|
||||
const generation = await ai_horde.postAsyncImageGenerate(
|
||||
{
|
||||
prompt: `${request.body.prompt_prefix} ${request.body.prompt} ### ${request.body.negative_prompt}`,
|
||||
|
@ -61,6 +61,11 @@ const parse = async (cardUrl, format) => {
|
||||
return PNGtext.decode(chunk.data);
|
||||
});
|
||||
|
||||
if (textChunks.length === 0) {
|
||||
console.error('PNG metadata does not contain any character data.');
|
||||
throw new Error('No PNG metadata.');
|
||||
}
|
||||
|
||||
return Buffer.from(textChunks[0].text, 'base64').toString('utf8');
|
||||
default:
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user