Merge pull request #900 from SillyTavern/staging

Staging
This commit is contained in:
Cohee 2023-08-08 22:54:20 +03:00 committed by GitHub
commit 6c909acea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 1650 additions and 461 deletions

View File

@ -5,3 +5,4 @@ readme*
Start.bat
/dist
/backups/
cloudflared.exe

View File

@ -1,6 +1,8 @@
[English](readme.md) | 中文
![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
移动设备界面友好多种人工智能服务或模型支持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
View File

@ -1,6 +1,8 @@
English | [中文](readme-zh_cn.md)
![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
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
View File

@ -28,3 +28,4 @@ secrets.json
public/movingUI/
public/QuickReplies/
content.log
cloudflared.exe

18
Remote-Link.cmd Normal file
View 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

View File

@ -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
View File

@ -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",

View File

@ -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": {

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.15,
"top_k": 0,
"top_p": 0.95,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 90,
"temp": 0.8,
"top_k": 28,
"top_p": 0.94,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.59,
"top_k": 0,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.8,
"top_k": 100,
"top_p": 0.9,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 1.0,
"top_p": 0.9,
"top_k": 40,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.43,
"top_p": 0.96,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.65,
"top_p": 0.9,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.51,
"top_p": 1,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 1600,
"genamt": 180,
"temp": 0.79,
"top_k": 0,
"top_p": 0.9,

View 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
]
}

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.79,
"top_p": 0.9,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 1400,
"genamt": 180,
"temp": 0.65,
"top_p": 0.9,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.63,
"top_k": 0,
"top_p": 0.98,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.7,
"top_k": 0,
"top_p": 0.5,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.7,
"top_k": 0,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 90,
"temp": 0.8,
"top_p": 0.94,
"top_k": 15,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.66,
"top_k": 0,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.94,
"top_k": 12,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.5,
"top_k": 85,
"top_p": 0.24,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.05,
"top_k": 0,
"top_p": 0.95,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.07,
"top_k": 100,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.44,
"top_k": 0,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.35,
"top_k": 0,
"top_p": 1,

View File

@ -1,6 +1,4 @@
{
"max_length": 1400,
"genamt": 80,
"temp": 1,
"top_p": 1,
"top_k": 0,

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 200,
"temp": 1,
"top_k": 0,
"top_p": 0.95,

View 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
]
}

View File

@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.72,
"tfs": 1,
"top_a": 0,

View 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
]
}

View File

@ -15,5 +15,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@ -15,5 +15,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.55,
"max_context": 8192
"max_context": 7800
}

View File

@ -16,5 +16,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"cfg_scale": 1.3,
"max_context": 8192
"max_context": 7800
}

View File

@ -17,5 +17,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@ -17,5 +17,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@ -16,5 +16,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "off",
"max_context": 8192
"max_context": 7800
}

View File

@ -15,5 +15,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.825,
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.35,
"max_context": 8192
"max_context": 7800
}

View 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
}

View File

@ -13,5 +13,5 @@
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View 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
}

View File

@ -14,5 +14,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.3,
"max_context": 8192
"max_context": 7800
}

View File

@ -18,5 +18,5 @@
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@ -15,5 +15,5 @@
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@ -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,

View 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
}

View File

@ -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",

View File

@ -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="&lt;START&gt;" maxlength="100" />
<textarea id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="&lt;START&gt;" 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="[&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></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>

View 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
}

View 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
}

View File

@ -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("<", "&lt;").replaceAll(">", "&gt;"); //for welcome message
//if (this_chid != undefined && !isSystem)
// mes = mes.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); //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");

View File

@ -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!");
}
}
});

View 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();
});

View 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"
}

View File

@ -0,0 +1,7 @@
.bulk_select_checkbox {
align-self: center;
}
#rm_print_characters_block.bulk_select .wide100pLess70px {
width: calc(100% - 85px);
}

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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);
})

View File

@ -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();
})

View 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;
}
}

View File

@ -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

View File

@ -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)) {

View File

@ -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 () {

View File

@ -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;
});

View File

@ -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',

View File

@ -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) {

View File

@ -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);

View File

@ -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) : [];
}

View File

@ -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;

View File

@ -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();
});

View File

@ -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
View File

@ -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}`,

View File

@ -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;