Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging

1af76af4d7d6f77f37fef981b1f3a3519651f9c5

WBlair1 <wolfgangblair1@gmail.com>

30 files changed, +387 -137Showing whitespace changes
.dockerignore+2 -0
@@ -1,4 +1,6 @@
11.git
2+.github
3+.vscode
24node_modules
35npm-debug.log
46readme*
.github/ISSUE_TEMPLATE/bug-report.yml+1 -1
@@ -1,5 +1,5 @@
11name: Bug Report 🐛
22description: Report something that's not working the intended way. Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused! Please use English only.
33title: '[BUG] <title>'
44labels: ['🐛 Bug']
55body:
.github/ISSUE_TEMPLATE/feature-request.yml+1 -1
@@ -1,5 +1,5 @@
11name: Feature Request ✨
22description: Suggest an idea for future development of this project. Please use English only.
33title: '[FEATURE_REQUEST] <title>'
44labels: ['🦄 Feature Request']
55
.github/pull_request_template.md+5 -0
@@ -0,0 +1,5 @@
1+<!-- Put X in the box below to confirm -->
2+
3+## Checklist:
4+
5+- [ ] I have read the [Contributing guidelines](https://github.com/SillyTavern/SillyTavern/blob/release/CONTRIBUTING.md).
.npmignore+3 -0
@@ -8,3 +8,6 @@ secrets.json
88/data
99/cache
1010access.log
11+.github
12+.vscode
13+.git
CONTRIBUTING.md+32 -0
@@ -0,0 +1,32 @@
1+# How to contribute to SillyTavern
2+
3+## Setting up the dev environment
4+
5+1. Required software: git and node.
6+2. Recommended editor: Visual Studio Code.
7+3. You can also use GitHub Codespaces which sets up everything for you.
8+
9+## Getting the code ready
10+
11+1. Register a GitHub account.
12+2. Fork this repository under your account.
13+3. Clone the fork onto your machine.
14+4. Open the cloned repository in the code editor.
15+5. Create a git branch (recommended).
16+6. Make your changes and test them locally.
17+7. Commit the changes and push the branch to the remote repo.
18+8. Go to GitHub, and open a pull request, targeting the upstream branch.
19+
20+## Contribution guidelines
21+
22+1. Our standards are pretty low, but make sure the code is not too ugly:
23+ - Run VS Code's autoformat when you're done.
24+ - Check with ESLint by running `npm run lint`, then fix the errors.
25+ - Use common sense and follow existing naming conventions.
26+2. Create pull requests for the staging branch, 99% of contributions should go there. That way people could test your code before the next stable release.
27+3. You can still send a pull request for release in the following scenarios:
28+ - Updating README.
29+ - Updating GitHub Actions.
30+ - Hotfixing a critical bug.
31+4. Project maintainers will test and can change your code before merging.
32+5. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
default/content/index.json+4 -0
@@ -20,6 +20,10 @@
2020 "type": "theme"
2121 },
2222 {
23+ "filename": "themes/Azure.json",
24+ "type": "theme"
25+ },
26+ {
2327 "filename": "backgrounds/__transparent.png",
2428 "type": "background"
2529 },
default/content/themes/Azure.json+35 -0
@@ -0,0 +1,35 @@
1+{
2+ "name": "Azure",
3+ "blur_strength": 11,
4+ "main_text_color": "rgba(171, 198, 223, 1)",
5+ "italics_text_color": "rgba(255, 255, 255, 1)",
6+ "underline_text_color": "rgba(188, 231, 207, 1)",
7+ "quote_text_color": "rgba(111, 133, 253, 1)",
8+ "blur_tint_color": "rgba(23, 30, 33, 0.61)",
9+ "chat_tint_color": "rgba(23, 23, 23, 0)",
10+ "user_mes_blur_tint_color": "rgba(0, 28, 174, 0.2)",
11+ "bot_mes_blur_tint_color": "rgba(0, 13, 57, 0.22)",
12+ "shadow_color": "rgba(0, 0, 0, 1)",
13+ "shadow_width": 5,
14+ "border_color": "rgba(0, 0, 0, 0.5)",
15+ "font_scale": 1,
16+ "fast_ui_mode": false,
17+ "waifuMode": false,
18+ "avatar_style": 1,
19+ "chat_display": 1,
20+ "noShadows": false,
21+ "chat_width": 50,
22+ "timer_enabled": true,
23+ "timestamps_enabled": true,
24+ "timestamp_model_icon": false,
25+ "mesIDDisplay_enabled": true,
26+ "message_token_count_enabled": false,
27+ "expand_message_actions": false,
28+ "enableZenSliders": false,
29+ "enableLabMode": false,
30+ "hotswap_enabled": true,
31+ "custom_css": "",
32+ "bogus_folders": false,
33+ "reduced_motion": false,
34+ "compact_input_area": false
35+}
package-lock.json+2 -2
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.23",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.23",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
package.json+1 -1
@@ -70,7 +70,7 @@
7070 "type": "git",
7171 "url": "https://github.com/SillyTavern/SillyTavern.git"
7272 },
7373 "version": "1.12.23",
7474 "scripts": {
7575 "start": "node server.js",
7676 "start:no-csrf": "node server.js --disableCsrf",
public/css/loader.css+1 -1
@@ -10,9 +10,9 @@
1010 width: 100svw;
1111 height: 100svh;
1212 background-color: var(--SmartThemeBlurTintColor);
13+ color: var(--SmartThemeBodyColor);
1314 /*for some reason the full screen blur does not work on iOS*/
1415 backdrop-filter: blur(30px);
15- color: var(--SmartThemeBodyColor);
1616 opacity: 1;
1717}
1818
public/css/popup-safari-fix.css+5 -3
@@ -1,8 +1,10 @@
11/* iPhone copium land */
2-@media screen and (max-width: 1000px) {
2+body.safari .popup .popup-body:has(.maximized_textarea) {
3- .ios .popup .popup-body {
3+ height: 100%;
4+}
5+
6+body.safari .popup .popup-body {
47 height: fit-content;
58 max-height: 90vh;
69 max-height: 90svh;
710}
8-}
public/index.html+1 -1
@@ -1969,7 +1969,7 @@
19691969 <small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
19701970 <input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off" data-server-history="kobold">
19711971 <div id="koboldcpp_hint" class="neutral_warning displayNone">
19721972 WeKoboldCpp haveworks abetter dedicatedwhen KoboldCppyou supportselect underthe Text Completion API and then KoboldCpp. as a type!
19731973 </div>
19741974 <div class="flex-container">
19751975 <div id="api_button" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="kobold">Connect</div>
public/locales/ja-jp.json+55 -55
@@ -1,6 +1,6 @@
11{
22 "Favorite": "お気に入り",
33 "Tag": "鬼ごっこタグ",
44 "Duplicate": "重複",
55 "Persona": "ペルソナ",
66 "Delete": "削除",
@@ -29,13 +29,13 @@
2929 "Text Adventure": "テキストアドベンチャー",
3030 "response legth(tokens)": "応答の長さ(トークン数)",
3131 "Streaming": "ストリーミング",
3232 "Streaming_desc": "生成された応答をビット単位で表示します生成された応答を逐次表示します。",
3333 "context size(tokens)": "コンテキストのサイズ(トークン数)",
3434 "unlocked": "ロック解除",
3535 "Only enable this if your model supports context sizes greater than 4096 tokens": "モデルが4096トークンを超えるコンテキストサイズをサポートしている場合にのみ有効にします",
3636 "Max prompt cost:": "最大プロンプトコスト:",
3737 "Display the response bit by bit as it is generated.": "生成されるたびに、応答をビットごとに表示します応答を逐次表示します。",
3838 "When this is off, responses will be displayed all at once when they are complete.": "この機能がオフの場合、応答は完全になるとすぐにすべて一度に表示されます応答は完全に生成されたときに一度ですべて表示されます。",
3939 "Temperature": "温度",
4040 "rep.pen": "繰り返しペナルティ",
4141 "Rep. Pen. Range.": "繰り返しペナルティの範囲",
@@ -46,10 +46,10 @@
4646 "Phrase Repetition Penalty": "フレーズの繰り返しペナルティ",
4747 "Off": "オフ",
4848 "Very light": "非常に軽い",
4949 "Light": "ライト軽め",
5050 "Medium": "ミディアム中程度",
5151 "Aggressive": "攻撃的強め",
5252 "Very aggressive": "非常に攻撃的非常に強い",
5353 "Unlocked Context Size": "ロック解除されたコンテキストサイズ",
5454 "Unrestricted maximum value for the context slider": "コンテキストスライダーの制限なしの最大値",
5555 "Context Size (tokens)": "コンテキストサイズ(トークン数)",
@@ -132,7 +132,7 @@
132132 "CFG Scale": "CFGスケール",
133133 "Negative Prompt": "ネガティブプロンプト",
134134 "Add text here that would make the AI generate things you don't want in your outputs.": "出力に望ましくないものを生成させるAIを作成するテキストをここに追加します。",
135135 "Used if CFG Scale is unset globally, per chat or character": "CFGスケールがグローバル、チャットごと、または文字ごとに設定されていない場合に使用されますまたはキャラクターごとに設定されていない場合に使用されます",
136136 "Mirostat Tau": "Mirostat Tau",
137137 "Mirostat LR": "ミロスタットLR",
138138 "Min Length": "最小長",
@@ -214,7 +214,7 @@
214214 "Sampler Priority": "サンプラー優先度",
215215 "Ooba only. Determines the order of samplers.": "Oobaのみ。サンプラーの順序を決定します。",
216216 "Character Names Behavior": "キャラクター名の動作",
217217 "Helps the model to associate messages with characters.": "モデルがメッセージを文字に関連付けるのに役立ちますモデルがメッセージをキャラクターに関連付けるのに役立ちます。",
218218 "None": "なし",
219219 "character_names_none": "グループと過去のペルソナを除きます。それ以外の場合は、プロンプトに名前を必ず入力してください。",
220220 "Don't add character names.": "キャラクター名を追加しないでください。",
@@ -222,7 +222,7 @@
222222 "character_names_completion": "制限事項: ラテン英数字とアンダースコアのみ。すべてのソースで機能するわけではありません。特に、Claude、MistralAI、Google では機能しません。",
223223 "Add character names to completion objects.": "完了オブジェクトにキャラクター名を追加します。",
224224 "Message Content": "メッセージ内容",
225225 "Prepend character names to message contents.": "メッセージの内容の先頭に文字名を追加しますメッセージの内容の先頭にキャラクター名を追加します。",
226226 "Continue Postfix": "ポストフィックスの継続",
227227 "The next chunk of the continued message will be appended using this as a separator.": "継続メッセージの次のチャンクは、これを区切り文字として使用して追加されます。",
228228 "Space": "空間",
@@ -270,9 +270,9 @@
270270 "Text Completion": "テキスト補完",
271271 "Chat Completion": "チャット完了",
272272 "NovelAI": "NovelAI",
273273 "KoboldAI Horde": "KoboldAIホルドKoboldAI Horde",
274274 "KoboldAI": "KoboldAI",
275275 "Avoid sending sensitive information to the Horde.": "ホルドに機密情報を送信しないでくださいHordeに機密情報を送信しないでください。",
276276 "Review the Privacy statement": "プライバシー声明を確認する",
277277 "Register a Horde account for faster queue times": "キュー待ち時間を短縮するためにHordeアカウントを登録する",
278278 "Learn how to contribute your idle GPU cycles to the Horde": "アイドルのGPUサイクルをホルドに貢献する方法を学びます",
@@ -633,7 +633,7 @@
633633 "Tags_as_Folders_desc": "最近の変更: タグは、タグ管理メニューでフォルダーとしてマークされて初めてフォルダーとして表示されます。ここをクリックして表示します。",
634634 "Character Handling": "キャラクター処理",
635635 "If set in the advanced character definitions, this field will be displayed in the characters list.": "高度なキャラクター定義で設定されている場合、このフィールドがキャラクターリストに表示されます。",
636636 "Char List Subheader": "文字リストサブヘッダーキャラクターリストサブヘッダー",
637637 "Character Version": "キャラクターバージョン",
638638 "Created by": "作成者",
639639 "Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "曖昧な一致を使用し、名前の部分文字列ではなく、すべてのデータフィールドでリスト内のキャラクターを検索する",
@@ -729,8 +729,8 @@
729729 "Automatically hide details": "詳細を自動的に非表示にする",
730730 "Determines how entries are found for autocomplete.": "オートコンプリートのエントリの検索方法を決定します。",
731731 "Autocomplete Matching": "マッチング",
732732 "Starts with": "始まりは前方一致",
733733 "Includes": "含まれるもの部分一致",
734734 "Fuzzy": "ファジー",
735735 "Sets the style of the autocomplete.": "オートコンプリートのスタイルを設定します。",
736736 "Autocomplete Style": "スタイル",
@@ -755,8 +755,8 @@
755755 "Auto-select": "自動選択",
756756 "System Backgrounds": "システムの背景",
757757 "Chat Backgrounds": "チャットの背景",
758758 "bg_chat_hint_1": "チャットの背景は、",
759759 "bg_chat_hint_2": "拡張子がここに表示されます拡張機能で生成したチャットの背景はここに表示されます。",
760760 "Extensions": "拡張機能",
761761 "Notify on extension updates": "拡張機能の更新時に通知",
762762 "Manage extensions": "拡張機能を管理",
@@ -770,9 +770,9 @@
770770 "How do I use this?": "これをどのように使用しますか?",
771771 "Click for stats!": "統計をクリック!",
772772 "Usage Stats": "使用状況統計",
773773 "Backup your personas to a file": "キャラクタをファイルにバックアップしますキャラクターをファイルにバックアップします",
774774 "Backup": "バックアップ",
775775 "Restore your personas from a file": "ファイルからキャラクタを復元しますファイルからキャラクターを復元します",
776776 "Restore": "復元",
777777 "Create a dummy persona": "ダミーのペルソナを作成",
778778 "Create": "作成",
@@ -839,7 +839,7 @@
839839 "Describe your character's physical and mental traits here.": "ここにキャラクターの身体的および精神的特徴を説明します。",
840840 "First message": "最初のメッセージ",
841841 "Click to set additional greeting messages": "追加の挨拶メッセージを設定するにはクリック",
842842 "Alt. Greetings": "挨拶他の挨拶",
843843 "This will be the first message from the character that starts every chat.": "これはすべてのチャットを開始するキャラクターからの最初のメッセージになります。",
844844 "Group Controls": "グループコントロール",
845845 "Chat Name (Optional)": "チャット名(任意)",
@@ -889,7 +889,7 @@
889889 "popup-button-yes": "はい",
890890 "popup-button-no": "いいえ",
891891 "popup-button-cancel": "キャンセル",
892892 "popup-button-import": "輸入インポート",
893893 "Advanced Defininitions": "高度な定義",
894894 "Prompt Overrides": "プロンプトのオーバーライド",
895895 "(For Chat Completion and Instruct Mode)": "(チャット補完と指示モード用)",
@@ -937,7 +937,7 @@
937937 "Type here...": "ここに入力...",
938938 "Chat Lorebook": "チャットロアブック",
939939 "Chat Lorebook for": "チャットロアブック",
940940 "chat_world_template_txt": "選択したワールド情報はこのチャットにバインドされます。AI の返信を生成する際、\nグローバルおよびキャラクターの伝承書のエントリと結合されますnグローバルおよびキャラクターのロアブックのエントリと結合されます。",
941941 "Select a World Info file for": "次のためにワールド情報ファイルを選択",
942942 "Primary Lorebook": "プライマリロアブック",
943943 "A selected World Info will be bound to this character as its own Lorebook.": "選択したワールド情報は、このキャラクターにその独自のロアブックとしてバインドされます。",
@@ -1065,9 +1065,9 @@
10651065 "Change it later in the 'User Settings' panel.": "後で「ユーザー設定」パネルで変更します。",
10661066 "Enable simple UI mode": "シンプルUIモードを有効にする",
10671067 "Looking for AI characters?": "AIキャラクターをお探しですか?",
10681068 "onboarding_import": "輸入インポート",
10691069 "from supported sources or view": "サポートされているソースからまたは表示",
10701070 "Sample characters": "サンプル文字サンプルキャラクター",
10711071 "Your Persona": "あなたのペルソナ",
10721072 "Before you get started, you must select a persona name.": "始める前に、ペルソナ名を選択する必要があります。",
10731073 "welcome_message_part_8": "これはいつでも変更可能です。",
@@ -1081,7 +1081,7 @@
10811081 "View character card": "キャラクターカードを表示",
10821082 "Remove from group": "グループから削除",
10831083 "Add to group": "グループに追加",
10841084 "Alternate Greetings": "代わりの挨拶挨拶のバリエーション",
10851085 "Alternate_Greetings_desc": "これらは、新しいチャットを開始するときに最初のメッセージにスワイプとして表示されます。\nグループのメンバーは、そのうちの 1 つを選択して会話を開始できます。",
10861086 "Alternate Greetings Hint": "ボタンをクリックして始めましょう!",
10871087 "(This will be the first message from the character that starts every chat)": "(これはすべてのチャットを開始するキャラクターからの最初のメッセージになります)",
@@ -1118,11 +1118,11 @@
11181118 "Will be used as the default CFG options for every chat unless overridden.": "上書きされない限り、すべてのチャットのデフォルトの CFG オプションとして使用されます。",
11191119 "CFG Prompt Cascading": "CFG プロンプト カスケード",
11201120 "Combine positive/negative prompts from other boxes.": "他のボックスからの肯定的/否定的なプロンプトを組み合わせます。",
11211121 "For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "たとえば、チャット、グローバル、および文字のボックスにチェックを入れるとおよびキャラクターのボックスにチェックを入れるとすべての否定プロンプトがコンマ区切りの文字列に結合されますすべてのネガティブプロンプトがコンマ区切りの文字列に結合されます。",
11221122 "Always Include": "常に含めます",
11231123 "Chat Negatives": "チャットのネガティブ",
11241124 "Character Negatives": "性格のマイナス面キャラクターのネガティブ",
11251125 "Global Negatives": "世界的なマイナスグローバルネガティブ",
11261126 "Custom Separator:": "カスタムセパレーター:",
11271127 "Insertion Depth:": "挿入深さ:",
11281128 "Token Probabilities": "トークン確率",
@@ -1236,7 +1236,7 @@
12361236 "ext_regex_title": "正規表現",
12371237 "ext_regex_new_global_script": "+ グローバル",
12381238 "ext_regex_new_scoped_script": "+ スコープ付き",
12391239 "ext_regex_import_script": "輸入インポート",
12401240 "ext_regex_global_scripts": "グローバルスクリプト",
12411241 "ext_regex_global_scripts_desc": "すべてのキャラクターで使用可能。ローカル設定に保存されます。",
12421242 "ext_regex_scoped_scripts": "スコープ付きスクリプト",
@@ -1301,8 +1301,8 @@
13011301 "Authentication (optional)": "認証(オプション)",
13021302 "Example: username:password": "例: ユーザー名:パスワード",
13031303 "Important:": "重要:",
13041304 "sd_auto_auth_warning_1": "SD Web UIを実行するUIを",
13051305 "sd_auto_auth_warning_2": "フラグフラグを指定して実行してください! サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
13061306 "sd_drawthings_url": "例: {{drawthings_url}}",
13071307 "sd_drawthings_auth_txt": "UI で HTTP API スイッチを有効にして DrawThings アプリを実行します。サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
13081308 "sd_vlad_url": "例: {{vlad_url}}",
@@ -1326,36 +1326,36 @@
13261326 "Enhance": "強化する",
13271327 "Refine": "リファイン",
13281328 "Decrisper": "デクリスパー",
13291329 "Sampling steps": "サンプリング手順 ()サンプリングステップ数",
13301330 "Width": "幅 ()",
13311331 "Height": "身長 ()高さ",
13321332 "Resolution": "解決解像度",
13331333 "Model": "モデル",
13341334 "Sampling method": "サンプリング方法",
13351335 "Karras (not all samplers supported)": "Karras (すべてのサンプラーがサポートされているわけではありません)",
13361336 "SMEA versions of samplers are modified to perform better at high resolution.": "SMEA バージョンのサンプラーは、高解像度でより優れたパフォーマンスを発揮するように変更されています。",
13371337 "SMEA": "中小企業庁SMEA",
13381338 "DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.": "SMEA サンプラーの DYN バリアントは、多くの場合、より多様な出力をもたらしますが、非常に高い解像度では失敗する可能性があります。",
13391339 "DYN": "ダイナミック",
13401340 "Scheduler": "スケジューラスケジューラー",
13411341 "Restore Faces": "顔を復元する顔の修復",
13421342 "Hires. Fix": "雇用。修正高解像度補助",
13431343 "Upscaler": "アップスケーラー",
13441344 "Upscale by": "高級化アップスケール倍率",
13451345 "Denoising strength": "ノイズ除去の強さ",
13461346 "Hires steps (2nd pass)": "採用手順(2回目のパス)高解像度でのステップ数",
13471347 "Preset for prompt prefix and negative prompt": "プロンプトプレフィックスと否定プロンプトのプリセットプロンプトプレフィックスとネガティブプロンプトのプリセット",
13481348 "Style": "スタイル",
13491349 "Save style": "スタイルを保存",
13501350 "Delete style": "スタイルを削除",
13511351 "Common prompt prefix": "一般的なプロンプトプレフィックス共通のプロンプトプレフィックス",
13521352 "sd_prompt_prefix_placeholder": "生成されたプロンプトを挿入する場所を指定するには、{prompt}を使用します。",
13531353 "Negative common prompt prefix": "否定の共通プロンプト接頭辞共通のネガティブプロンプトプレフィックス",
13541354 "Character-specific prompt prefix": "文字固有のプロンプトプレフィックスキャラクター固有のプロンプトプレフィックス",
13551355 "Won't be used in groups.": "グループでは使用されません。",
13561356 "sd_character_prompt_placeholder": "現在選択されているキャラクターを説明する任意の特性現在選択されているキャラクターを説明する特徴共通のプロンプト プレフィックスの後に追加されます共通のプロンプトプレフィックスの後に追加されます。\n例: 女性、緑の目、茶色の髪、ピンクのシャツ",
13571357 "Character-specific negative prompt prefix": "文字固有の否定プロンプト接頭辞キャラクター固有のネガティブプロンプトプレフィックス",
13581358 "sd_character_negative_prompt_placeholder": "選択したキャラクターに表示されるべきではない特性選択したキャラクターに表示されるべきではない特徴否定の共通プロンプト接頭辞の後に追加されます共通のネガティブプロンプトプレフィックスの後に追加されます。\n例: ジュエリー、靴、メガネ",
13591359 "Shareable": "共有可能",
13601360 "Image Prompt Templates": "画像プロンプトテンプレート",
13611361 "Vectors Model Warning": "チャットの途中でモデルを変更する場合は、ベクトルを消去することをお勧めします。そうしないと、標準以下の結果になります。",
@@ -1380,22 +1380,22 @@
13801380 "Warning:": "警告:",
13811381 "This action is irreversible.": "この操作は元に戻せません。",
13821382 "Type the user's handle below to confirm:": "確認するには、以下のユーザーのハンドルを入力してください。",
13831383 "Import Characters": "文字をインポートするキャラクターをインポートする",
13841384 "Enter the URL of the content to import": "インポートするコンテンツの URL を入力します",
13851385 "Supported sources:": "サポートされているソース:",
13861386 "char_import_1": "チャブキャラクターChub キャラクター (直接リンクまたはID)",
13871387 "char_import_example": "例:",
13881388 "char_import_2": "チャブの伝承集Chub ロアブック (直接リンクまたは ID)",
13891389 "char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)",
13901390 "char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)",
13911391 "char_import_5": "AICharacterCard.com キャラクター (直接リンクまたは ID)",
13921392 "char_import_6": "直接PNGリンク(参照",
13931393 "char_import_7": "許可されたホストの場合)",
13941394 "char_import_8": "RisuRealm キャラクター (直接リンク)",
13951395 "Supports importing multiple characters.": "複数の文字のインポートをサポートします複数のキャラクターのインポートをサポートします。",
13961396 "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",
13971397 "Export for character": "文字のエクスポートキャラクターのエクスポート",
13981398 "Export prompts for this character, including their order.": "この文字のプロンプトを順序も含めてエクスポートしますこのキャラクターのプロンプトを順序も含めてエクスポートします。",
13991399 "Export all": "すべてをエクスポート",
14001400 "Export all your prompts to a file": "すべてのプロンプトをファイルにエクスポートする",
14011401 "Insert prompt": "プロンプトを挿入",
public/locales/zh-cn.json+13 -4
@@ -334,6 +334,9 @@
334334 "vLLM API key": "vLLM API 密钥",
335335 "Example: 127.0.0.1:8000": "例如:http://127.0.0.1:8000",
336336 "vLLM Model": "vLLM 模型",
337+ "HuggingFace Token": "HuggingFace 代币",
338+ "Endpoint URL": "端点 URL",
339+ "Example: https://****.endpoints.huggingface.cloud": "例如:https://****.endpoints.huggingface.cloud",
337340 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(用于OpenAI API的包装器)",
338341 "Aphrodite API key": "Aphrodite API 密钥",
339342 "Aphrodite Model": "Aphrodite 模型",
@@ -419,6 +422,8 @@
419422 "Prompt Post-Processing": "提示词后处理",
420423 "Applies additional processing to the prompt before sending it to the API.": "在将提示词发送到 API 之前对其进行额外处理。",
421424 "prompt_post_processing_none": "未选择",
425+ "01.AI API Key": "01.AI API密钥",
426+ "01.AI Model": "01.AI模型",
422427 "Additional Parameters": "附加参数",
423428 "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "通过发送简短的测试消息验证您的API连接。请注意,您将因此而消耗额度!",
424429 "Test Message": "发送测试消息",
@@ -1033,6 +1038,8 @@
10331038 "Sticky": "粘性",
10341039 "Entries with a cooldown can't be activated N messages after being triggered.": "具有冷却时间的条目在触发后 N 条消息内无法被激活。",
10351040 "Cooldown": "冷却",
1041+ "Entries with a delay can't be activated until there are N messages present in the chat.": "直到聊天中出现 N 条消息时,延迟的条目才能被激活。",
1042+ "Delay": "延迟",
10361043 "Filter to Character(s)": "应用到角色",
10371044 "Character Exclusion": "反选角色",
10381045 "-- Characters not found --": "-- 未找到角色 --",
@@ -1077,6 +1084,7 @@
10771084 "Move message up": "将消息上移",
10781085 "Move message down": "将消息下移",
10791086 "Enlarge": "放大",
1087+ "Caption": "标题",
10801088 "Welcome to SillyTavern!": "欢迎来到 SillyTavern!",
10811089 "welcome_message_part_1": "阅读",
10821090 "welcome_message_part_2": "官方文档",
@@ -1113,10 +1121,6 @@
11131121 "alternate_greetings_hint_2": "按钮即可开始!",
11141122 "Alternate Greeting #": "额外问候语 #",
11151123 "(This will be the first message from the character that starts every chat)": "(这将是角色在每次聊天开始时发送的第一条消息)",
1116- "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
1117- "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
1118- "Always forbidden": "始终禁止",
1119- "Always allowed": "始终允许",
11201124 "View contents": "查看内容",
11211125 "Remove the file": "删除文件",
11221126 "Unique to this chat": "此聊天独有",
@@ -1240,6 +1244,7 @@
12401244 "Message Template": "消息模板",
12411245 "(use _space": "(使用",
12421246 "macro)": "宏指令)",
1247+ "Automatically caption images": "自动为图像添加标题",
12431248 "Edit captions before saving": "保存前编辑标题",
12441249 "Character Expressions": "角色表情",
12451250 "Translate text to English before classification": "分类之前将文本翻译成英文",
@@ -1579,6 +1584,10 @@
15791584 "Warning:": "警告:",
15801585 "This action is irreversible.": "此操作不可逆。",
15811586 "Type the user's handle below to confirm:": "在下面输入用户的名称以确认:",
1587+ "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
1588+ "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
1589+ "Always forbidden": "始终禁止",
1590+ "Always allowed": "始终允许",
15821591 "help_format_1": "文本格式化命令:",
15831592 "help_format_2": "*文本*",
15841593 "help_format_3": "显示为",
public/script.js+29 -11
@@ -227,7 +227,7 @@ import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, de
227227import { initPresetManager } from './scripts/preset-manager.js';
228228import { MacrosParser, evaluateMacros } from './scripts/macros.js';
229229import { currentUser, setUserControls } from './scripts/user.js';
230230import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
231231import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
232232import { ScraperManager } from './scripts/scrapers.js';
233233import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
@@ -520,6 +520,7 @@ const chatElement = $('#chat');
520520let dialogueResolve = null;
521521let dialogueCloseStop = false;
522522export let chat_metadata = {};
523+/** @type {StreamingProcessor} */
523524export let streamingProcessor = null;
524525let crop_data = undefined;
525526let is_delete_mode = false;
@@ -837,6 +838,7 @@ export let main_api;// = "kobold";
837838//novel settings
838839export let novelai_settings;
839840export let novelai_setting_names;
841+/** @type {AbortController} */
840842let abortController;
841843
842844//css
@@ -4381,6 +4383,25 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
43814383}
43824384
43834385/**
4386+ * Stops the generation and any streaming if it is currently running.
4387+ */
4388+export function stopGeneration() {
4389+ let stopped = false;
4390+ if (streamingProcessor) {
4391+ streamingProcessor.onStopStreaming();
4392+ streamingProcessor = null;
4393+ stopped = true;
4394+ }
4395+ if (abortController) {
4396+ abortController.abort('Clicked stop button');
4397+ hideStopButton();
4398+ stopped = true;
4399+ }
4400+ eventSource.emit(event_types.GENERATION_STOPPED);
4401+ return stopped;
4402+}
4403+
4404+/**
43844405 * Injects extension prompts into chat messages.
43854406 * @param {object[]} messages Array of chat messages
43864407 * @param {boolean} isContinue Whether the generation is a continuation. If true, the extension prompts of depth 0 are injected at position 1.
@@ -7163,7 +7184,8 @@ function onScenarioOverrideRemoveClick() {
71637184 * @param {string} inputValue - Value to set the input to.
71647185 * @param {PopupOptions} options - Options for the popup.
71657186 * @typedef {{okButton?: string, rows?: number, wide?: boolean, wider?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean, cropAspect?: number }} PopupOptions - Options for the popup.
7166- * @returns
7187+ * @returns {Promise<any>} A promise that resolves when the popup is closed.
7188+ * @deprecated Use `callGenericPopup` instead.
71677189 */
71687190export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
71697191 function getOkButtonText() {
@@ -7794,6 +7816,7 @@ window['SillyTavern'].getContext = function () {
77947816 eventTypes: event_types,
77957817 addOneMessage: addOneMessage,
77967818 generate: Generate,
7819+ stopGeneration: stopGeneration,
77977820 getTokenCount: getTokenCount,
77987821 extensionPrompts: extension_prompts,
77997822 setExtensionPrompt: setExtensionPrompt,
@@ -7849,6 +7872,8 @@ window['SillyTavern'].getContext = function () {
78497872 * @deprecated Legacy snake-case naming, compatibility with old extensions
78507873 */
78517874 event_types: event_types,
7875+ POPUP_TYPE: POPUP_TYPE,
7876+ POPUP_RESULT: POPUP_RESULT,
78527877 };
78537878};
78547879
@@ -10342,15 +10367,7 @@ jQuery(async function () {
1034210367 });
1034310368
1034410369 $(document).on('click', '.mes_stop', function () {
10345- if (streamingProcessor) {
10370+ stopGeneration();
10346- streamingProcessor.onStopStreaming();
10347- streamingProcessor = null;
10348- }
10349- if (abortController) {
10350- abortController.abort('Clicked stop button');
10351- hideStopButton();
10352- }
10353- eventSource.emit(event_types.GENERATION_STOPPED);
1035410371 });
1035510372
1035610373 $(document).on('click', '#form_sheld .stscript_continue', function () {
@@ -10839,3 +10856,4 @@ jQuery(async function () {
1083910856
1084010857 initCustomSelectedSamplers();
1084110858});
10859+
public/scripts/RossAscends-mods.js+8 -2
@@ -725,8 +725,14 @@ export function initRossMods() {
725725 RA_autoconnect();
726726 }
727727
728- if (getParsedUA()?.os?.name === 'iOS') {
728+ const userAgent = getParsedUA();
729- document.body.classList.add('ios');
729+ console.debug('User Agent', userAgent);
730+ const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
731+ const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
732+ const isIOS = userAgent?.os?.name === 'iOS';
733+
734+ if (isIOS || isMobileSafari || isDesktopSafari) {
735+ document.body.classList.add('safari');
730736 }
731737
732738 $('#main_api').change(function () {
public/scripts/chats.js+1 -1
@@ -1430,7 +1430,7 @@ jQuery(function () {
14301430 wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter');
14311431 const textarea = document.createElement('textarea');
14321432 textarea.value = String(bro.val());
14331433 textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');
14341434 bro.hasClass('monospace') && textarea.classList.add('monospace');
14351435 textarea.addEventListener('input', function () {
14361436 bro.val(textarea.value).trigger('input');
public/scripts/dynamic-styles.js+1 -1
@@ -154,7 +154,7 @@ export function initDynamicStyles() {
154154 // Process all stylesheets on initial load
155155 Array.from(document.styleSheets).forEach(sheet => {
156156 try {
157157 applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true });
158158 } catch (e) {
159159 console.warn('Failed to process stylesheet on initial load:', e);
160160 }
public/scripts/extensions/stable-diffusion/index.js+8 -8
@@ -3017,25 +3017,25 @@ async function generateComfyImage(prompt, negativePrompt) {
30173017 const text = await workflowResponse.text();
30183018 toastr.error(`Failed to load workflow.\n\n${text}`);
30193019 }
30203020 let workflow = (await workflowResponse.json()).replacereplaceAll('"%prompt%"', JSON.stringify(prompt));
30213021 workflow = workflow.replacereplaceAll('"%negative_prompt%"', JSON.stringify(negativePrompt));
30223022
30233023 const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
30243024 workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed));
30253025 placeholders.forEach(ph => {
30263026 workflow = workflow.replacereplaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
30273027 });
30283028 (extension_settings.sd.comfy_placeholders ?? []).forEach(ph => {
30293029 workflow = workflow.replacereplaceAll(`"%${ph.find}%"`, JSON.stringify(substituteParams(ph.replace)));
30303030 });
30313031 if (/%user_avatar%/gi.test(workflow)) {
30323032 const response = await fetch(getUserAvatarUrl());
30333033 if (response.ok) {
30343034 const avatarBlob = await response.blob();
30353035 const avatarBase64 = await getBase64Async(avatarBlob);
30363036 workflow = workflow.replacereplaceAll('"%user_avatar%"', JSON.stringify(avatarBase64));
30373037 } else {
30383038 workflow = workflow.replacereplaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
30393039 }
30403040 }
30413041 if (/%char_avatar%/gi.test(workflow)) {
@@ -3043,9 +3043,9 @@ async function generateComfyImage(prompt, negativePrompt) {
30433043 if (response.ok) {
30443044 const avatarBlob = await response.blob();
30453045 const avatarBase64 = await getBase64Async(avatarBlob);
30463046 workflow = workflow.replacereplaceAll('"%char_avatar%"', JSON.stringify(avatarBase64));
30473047 } else {
30483048 workflow = workflow.replacereplaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
30493049 }
30503050 }
30513051 console.log(`{
public/scripts/group-chats.js+29 -3
@@ -178,8 +178,37 @@ async function loadGroupChat(chatId) {
178178 return [];
179179}
180180
181+async function validateGroup(group) {
182+ if (!group) return;
183+
184+ // Validate that all members exist as characters
185+ let dirty = false;
186+ group.members = group.members.filter(member => {
187+ const character = characters.find(x => x.avatar === member || x.name === member);
188+ if (!character) {
189+ const msg = `Warning: Listed member ${member} does not exist as a character. It will be removed from the group.`;
190+ toastr.warning(msg, 'Group Validation');
191+ console.warn(msg);
192+ dirty = true;
193+ }
194+ return character;
195+ });
196+
197+ if (dirty) {
198+ await editGroup(group.id, true, false);
199+ }
200+}
201+
181202export async function getGroupChat(groupId, reload = false) {
182203 const group = groups.find((x) => x.id === groupId);
204+ if (!group) {
205+ console.warn('Group not found', groupId);
206+ return;
207+ }
208+
209+ // Run validation before any loading
210+ validateGroup(group);
211+
183212 const chat_id = group.chat_id;
184213 const data = await loadGroupChat(chat_id);
185214 let freshChat = false;
@@ -197,7 +226,6 @@ export async function getGroupChat(groupId, reload = false) {
197226 if (group && Array.isArray(group.members)) {
198227 for (let member of group.members) {
199228 const character = characters.find(x => x.avatar === member || x.name === member);
200-
201229 if (!character) {
202230 continue;
203231 }
@@ -219,10 +247,8 @@ export async function getGroupChat(groupId, reload = false) {
219247 freshChat = true;
220248 }
221249
222- if (group) {
223250 let metadata = group.chat_metadata ?? {};
224251 updateChatMetadata(metadata, true);
225- }
226252
227253 if (reload) {
228254 select_group_chats(groupId, true);
public/scripts/loader.js+1 -3
@@ -1,7 +1,5 @@
11import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
22
3-const ELEMENT_ID = 'loader';
4-
53/** @type {Popup} */
64let loaderPopup;
75
@@ -31,7 +29,7 @@ export async function hideLoader() {
3129 return new Promise((resolve) => {
3230 // Spinner blurs/fades out
3331 $('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
3432 $(`'#${ELEMENT_ID}`loader').remove();
3533 // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
3634 // If it's present, we remove it once and then it's gone.
3735 yoinkPreloader();
public/scripts/macros.js+2 -2
@@ -254,7 +254,7 @@ function getCurrentSwipeId() {
254254 // For swipe macro, we are accepting using the message that is currently being swiped
255255 const mid = getLastMessageId({ exclude_swipe_in_propress: false });
256256 const swipeId = chat[mid]?.swipe_id;
257257 return swipeId !== null ? swipeId + 1 : null;
258258}
259259
260260/**
@@ -401,7 +401,7 @@ function timeDiffReplace(input) {
401401 const time2 = moment(matchPart2);
402402
403403 const timeDifference = moment.duration(time1.diff(time2));
404404 return timeDifference.humanize(true);
405405 });
406406
407407 return output;
public/scripts/openai.js+1 -1
@@ -689,7 +689,7 @@ function formatWorldInfo(value) {
689689 return '';
690690 }
691691
692692 if (!oai_settings.wi_format.trim()) {
693693 return value;
694694 }
695695
public/scripts/power-user.js+55 -1
@@ -40,7 +40,7 @@ import { tokenizers } from './tokenizers.js';
4040import { BIAS_CACHE } from './logit-bias.js';
4141import { renderTemplateAsync } from './templates.js';
4242
4343import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
4444import { FILTER_TYPES } from './filters.js';
4545import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
4646import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -335,6 +335,8 @@ const storage_keys = {
335335 compact_input_area: 'compact_input_area',
336336 auto_connect_legacy: 'AutoConnectEnabled',
337337 auto_load_chat_legacy: 'AutoLoadChatEnabled',
338+
339+ storyStringValidationCache: 'StoryStringValidationCache',
338340};
339341
340342const contextControls = [
@@ -2105,6 +2107,9 @@ export function fuzzySearchGroups(searchValue) {
21052107 */
21062108export function renderStoryString(params) {
21072109 try {
2110+ // Validate and log possible warnings/errors
2111+ validateStoryString(power_user.context.story_string, params);
2112+
21082113 // compile the story string template into a function, with no HTML escaping
21092114 const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });
21102115
@@ -2132,6 +2137,55 @@ export function renderStoryString(params) {
21322137 }
21332138}
21342139
2140+/**
2141+ * Validate the story string for possible warnings or issues
2142+ *
2143+ * @param {string} storyString - The story string
2144+ * @param {Object} params - The story string parameters
2145+ */
2146+function validateStoryString(storyString, params) {
2147+ /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */
2148+ const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} };
2149+
2150+ const hash = getStringHash(storyString);
2151+
2152+ // Initialize the cache for the current hash if it doesn't exist
2153+ if (!cache.hashCache[hash]) {
2154+ cache.hashCache[hash] = { fieldsWarned: {} };
2155+ }
2156+
2157+ const currentCache = cache.hashCache[hash];
2158+ const fieldsToWarn = [];
2159+
2160+ function validateMissingField(field, fallbackLegacyField = null) {
2161+ const contains = storyString.includes(`{{${field}}}`) || (!!fallbackLegacyField && storyString.includes(`{{${fallbackLegacyField}}}`));
2162+ if (!contains && params[field]) {
2163+ const wasLogged = currentCache.fieldsWarned[field];
2164+ if (!wasLogged) {
2165+ fieldsToWarn.push(field);
2166+ currentCache.fieldsWarned[field] = true;
2167+ }
2168+ console.warn(`The story string does not contain {{${field}}}, but it would contain content:\n`, params[field]);
2169+ }
2170+ }
2171+
2172+ validateMissingField('description');
2173+ validateMissingField('personality');
2174+ validateMissingField('persona');
2175+ validateMissingField('scenario');
2176+ validateMissingField('system');
2177+ validateMissingField('wiBefore', 'loreBefore');
2178+ validateMissingField('wiAfter', 'loreAfter');
2179+
2180+ if (fieldsToWarn.length > 0) {
2181+ const fieldsList = fieldsToWarn.map(field => `{{${field}}}`).join(', ');
2182+ toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation');
2183+ }
2184+
2185+ localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
2186+}
2187+
2188+
21352189const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
21362190const compareFunc = (first, second) => {
21372191 const a = first[power_user.sort_field];
public/scripts/slash-commands.js+19 -0
@@ -33,6 +33,7 @@ import {
3333 setCharacterName,
3434 setExtensionPrompt,
3535 setUserName,
36+ stopGeneration,
3637 substituteParams,
3738 system_avatar,
3839 system_message_types,
@@ -899,6 +900,24 @@ export function initDefaultSlashCommands() {
899900 helpString: 'Adds a swipe to the last chat message.',
900901 }));
901902 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
903+ name: 'stop',
904+ callback: () => {
905+ const stopped = stopGeneration();
906+ return String(stopped);
907+ },
908+ returns: 'true/false, whether the generation was running and got stopped',
909+ helpString: `
910+ <div>
911+ Stops the generation and any streaming if it is currently running.
912+ </div>
913+ <div>
914+ Note: This command cannot be executed from the chat input, as sending any message or script from there is blocked during generation.
915+ But it can be executed via automations or QR scripts/buttons.
916+ </div>
917+ `,
918+ aliases: ['generate-stop'],
919+ }));
920+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
902921 name: 'abort',
903922 callback: abortCallback,
904923 namedArgumentList: [
public/scripts/textgen-settings.js+13 -6
@@ -263,6 +263,8 @@ export const setting_names = [
263263 'bypass_status_check',
264264];
265265
266+const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
267+
266268export function validateTextGenUrl() {
267269 const selector = SERVER_INPUTS[settings.type];
268270
@@ -1045,6 +1047,10 @@ export function isJsonSchemaSupported() {
10451047 return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui';
10461048}
10471049
1050+function isDynamicTemperatureSupported() {
1051+ return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
1052+}
1053+
10481054function getLogprobsNumber() {
10491055 if (settings.type === VLLM || settings.type === INFERMATICAI) {
10501056 return 5;
@@ -1055,6 +1061,7 @@ function getLogprobsNumber() {
10551061
10561062export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
10571063 const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
1064+ const dynatemp = isDynamicTemperatureSupported();
10581065 const { banned_tokens, banned_strings } = getCustomTokenBans();
10591066
10601067 let params = {
@@ -1063,7 +1070,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
10631070 'max_new_tokens': maxTokens,
10641071 'max_tokens': maxTokens,
10651072 'logprobs': power_user.request_token_probabilities ? getLogprobsNumber() : undefined,
10661073 'temperature': settings.dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
10671074 'top_p': settings.top_p,
10681075 'typical_p': settings.typical_p,
10691076 'typical': settings.typical_p,
@@ -1081,11 +1088,11 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
10811088 'length_penalty': settings.length_penalty,
10821089 'early_stopping': settings.early_stopping,
10831090 'add_bos_token': settings.add_bos_token,
10841091 'dynamic_temperature': settings.dynatemp ? true : undefined,
10851092 'dynatemp_low': settings.dynatemp ? settings.min_temp : undefined,
10861093 'dynatemp_high': settings.dynatemp ? settings.max_temp : undefined,
10871094 'dynatemp_range': settings.dynatemp ? (settings.max_temp - settings.min_temp) / 2 : undefined,
10881095 'dynatemp_exponent': settings.dynatemp ? settings.dynatemp_exponent : undefined,
10891096 'smoothing_factor': settings.smoothing_factor,
10901097 'smoothing_curve': settings.smoothing_curve,
10911098 'dry_allowed_length': settings.dry_allowed_length,
public/scripts/utils.js+1 -1
@@ -803,7 +803,7 @@ export function getImageSizeFromDataURL(dataUrl) {
803803
804804export function getCharaFilename(chid) {
805805 const context = getContext();
806806 const fileName = context.characters[chid ?? context.characterId]?.avatar;
807807
808808 if (fileName) {
809809 return fileName.replace(/\.[^/.]+$/, '');
server.js+43 -14
@@ -609,10 +609,6 @@ const postSetupTasks = async function () {
609609 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
610610 }
611611 }
612-
613- if (listen && !basicAuthMode && enableAccounts) {
614- await userModule.checkAccountsProtection();
615- }
616612};
617613
618614/**
@@ -631,16 +627,6 @@ async function loadPlugins() {
631627 }
632628}
633629
634-if (listen && !enableWhitelist && !basicAuthMode) {
635- if (getConfigValue('securityOverride', false)) {
636- console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
637- }
638- else {
639- console.error(color.red('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.'));
640- process.exit(1);
641- }
642-}
643-
644630/**
645631 * Set the title of the terminal window
646632 * @param {string} title Desired title for the window
@@ -654,10 +640,53 @@ function setWindowTitle(title) {
654640 }
655641}
656642
643+/**
644+ * Prints an error message and exits the process if necessary
645+ * @param {string} message The error message to print
646+ * @returns {void}
647+ */
648+function logSecurityAlert(message) {
649+ if (basicAuthMode || enableWhitelist) return; // safe!
650+ console.error(color.red(message));
651+ if (getConfigValue('securityOverride', false)) {
652+ console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
653+ return;
654+ }
655+ process.exit(1);
656+}
657+
658+async function verifySecuritySettings() {
659+ // Skip all security checks as listen is set to false
660+ if (!listen) {
661+ return;
662+ }
663+
664+ if (!enableAccounts) {
665+ logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.');
666+ }
667+
668+ const users = await userModule.getAllEnabledUsers();
669+ const unprotectedUsers = users.filter(x => !x.password);
670+ const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin);
671+
672+ if (unprotectedUsers.length > 0) {
673+ console.warn(color.blue('A friendly reminder that the following users are not password protected:'));
674+ unprotectedUsers.map(x => `${color.yellow(x.handle)} ${color.red(x.admin ? '(admin)' : '')}`).forEach(x => console.warn(x));
675+ console.log();
676+ console.warn(`Consider setting a password in the admin panel or by using the ${color.blue('recover.js')} script.`);
677+ console.log();
678+
679+ if (unprotectedAdminUsers.length > 0) {
680+ logSecurityAlert('If you are not using basic authentication or whitelisting, you should set a password for all admin users.');
681+ }
682+ }
683+}
684+
657685// User storage module needs to be initialized before starting the server
658686userModule.initUserStorage(dataRoot)
659687 .then(userModule.ensurePublicDirectoriesExist)
660688 .then(userModule.migrateUserData)
689+ .then(verifySecuritySettings)
661690 .then(preSetupTasks)
662691 .finally(() => {
663692 if (cliArguments.ssl) {
src/users.js+15 -14
@@ -681,27 +681,27 @@ async function createBackupArchive(handle, response) {
681681}
682682
683683/**
684- * Checks if any admin users are not password protected. If so, logs a warning.
684+ * Gets all of the users.
685685 * @returns {Promise<voidUser[]>}
686686 */
687687async function checkAccountsProtectiongetAllUsers() {
688688 if (!ENABLE_ACCOUNTS) {
689689 return [];
690690 }
691-
692691 /**
693692 * @type {User[]}
694693 */
695694 const users = await storage.values();
696- const unprotectedUsers = users.filter(x => x.enabled && x.admin && !x.password);
695+ return users;
697- if (unprotectedUsers.length > 0) {
698- console.warn(color.red('The following admin users are not password protected:'));
699- unprotectedUsers.forEach(x => console.warn(color.yellow(x.handle)));
700- console.log();
701- console.warn('Please disable them or set a password in the admin panel.');
702- console.log();
703- await delay(3000);
704696}
697+
698+/**
699+ * Gets all of the enabled users.
700+ * @returns {Promise<User[]>}
701+ */
702+async function getAllEnabledUsers() {
703+ const users = await getAllUsers();
704+ return users.filter(x => x.enabled);
705705}
706706
707707/**
@@ -738,6 +738,7 @@ module.exports = {
738738 shouldRedirectToLogin,
739739 createBackupArchive,
740740 tryAutoLogin,
741741 checkAccountsProtectiongetAllUsers,
742+ getAllEnabledUsers,
742743 router,
743744};