Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging
| @@ -1,4 +1,6 @@ | ||
| 1 | 1 | .git |
| 2 | +.github | |
| 3 | +.vscode | |
| 2 | 4 | node_modules |
| 3 | 5 | npm-debug.log |
| 4 | 6 | readme* |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | name: Bug Report 🐛 |
| 2 | 2 | description: 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. |
| 3 | 3 | title: '[BUG] <title>' |
| 4 | 4 | labels: ['🐛 Bug'] |
| 5 | 5 | body: |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | name: Feature Request ✨ |
| 2 | 2 | description: Suggest an idea for future development of this project. Please use English only. |
| 3 | 3 | title: '[FEATURE_REQUEST] <title>' |
| 4 | 4 | labels: ['🦄 Feature Request'] |
| 5 | 5 | |
| @@ -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). | |
| @@ -8,3 +8,6 @@ secrets.json | ||
| 8 | 8 | /data |
| 9 | 9 | /cache |
| 10 | 10 | access.log |
| 11 | +.github | |
| 12 | +.vscode | |
| 13 | +.git | |
| @@ -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. | |
| @@ -20,6 +20,10 @@ | ||
| 20 | 20 | "type": "theme" |
| 21 | 21 | }, |
| 22 | 22 | { |
| 23 | + "filename": "themes/Azure.json", | |
| 24 | + "type": "theme" | |
| 25 | + }, | |
| 26 | + { | |
| 23 | 27 | "filename": "backgrounds/__transparent.png", |
| 24 | 28 | "type": "background" |
| 25 | 29 | }, |
| @@ -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 | +} | |
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "sillytavern", |
| 3 | 3 | "version": "1.12.23", |
| 4 | 4 | "lockfileVersion": 3, |
| 5 | 5 | "requires": true, |
| 6 | 6 | "packages": { |
| 7 | 7 | "": { |
| 8 | 8 | "name": "sillytavern", |
| 9 | 9 | "version": "1.12.23", |
| 10 | 10 | "hasInstallScript": true, |
| 11 | 11 | "license": "AGPL-3.0", |
| 12 | 12 | "dependencies": { |
| @@ -70,7 +70,7 @@ | ||
| 70 | 70 | "type": "git", |
| 71 | 71 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 72 | 72 | }, |
| 73 | 73 | "version": "1.12.23", |
| 74 | 74 | "scripts": { |
| 75 | 75 | "start": "node server.js", |
| 76 | 76 | "start:no-csrf": "node server.js --disableCsrf", |
| @@ -10,9 +10,9 @@ | ||
| 10 | 10 | width: 100svw; |
| 11 | 11 | height: 100svh; |
| 12 | 12 | background-color: var(--SmartThemeBlurTintColor); |
| 13 | + color: var(--SmartThemeBodyColor); | |
| 13 | 14 | /*for some reason the full screen blur does not work on iOS*/ |
| 14 | 15 | backdrop-filter: blur(30px); |
| 15 | - color: var(--SmartThemeBodyColor); | |
| 16 | 16 | opacity: 1; |
| 17 | 17 | } |
| 18 | 18 | |
| @@ -1,8 +1,10 @@ | ||
| 1 | 1 | /* 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 | - height: fit-content; | |
| 4 | +} | |
| 5 | - max-height: 90vh; | |
| 5 | + | |
| 6 | - max-height: 90svh; | |
| 6 | +body.safari .popup .popup-body { | |
| 7 | - } | |
| 7 | + height: fit-content; | |
| 8 | + max-height: 90vh; | |
| 9 | + max-height: 90svh; | |
| 8 | 10 | } |
| @@ -1969,7 +1969,7 @@ | ||
| 1969 | 1969 | <small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small> |
| 1970 | 1970 | <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"> |
| 1971 | 1971 | <div id="koboldcpp_hint" class="neutral_warning displayNone"> |
| 1972 | 1972 | WeKoboldCpp haveworks abetter dedicatedwhen KoboldCppyou supportselect underthe Text Completion ⇒API and then KoboldCpp. as a type! |
| 1973 | 1973 | </div> |
| 1974 | 1974 | <div class="flex-container"> |
| 1975 | 1975 | <div id="api_button" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="kobold">Connect</div> |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "Favorite": "お気に入り", |
| 3 | 3 | "Tag": "鬼ごっこタグ", |
| 4 | 4 | "Duplicate": "重複", |
| 5 | 5 | "Persona": "ペルソナ", |
| 6 | 6 | "Delete": "削除", |
| @@ -29,13 +29,13 @@ | ||
| 29 | 29 | "Text Adventure": "テキストアドベンチャー", |
| 30 | 30 | "response legth(tokens)": "応答の長さ(トークン数)", |
| 31 | 31 | "Streaming": "ストリーミング", |
| 32 | 32 | "Streaming_desc": "生成された応答をビット単位で表示します生成された応答を逐次表示します。", |
| 33 | 33 | "context size(tokens)": "コンテキストのサイズ(トークン数)", |
| 34 | 34 | "unlocked": "ロック解除", |
| 35 | 35 | "Only enable this if your model supports context sizes greater than 4096 tokens": "モデルが4096トークンを超えるコンテキストサイズをサポートしている場合にのみ有効にします", |
| 36 | 36 | "Max prompt cost:": "最大プロンプトコスト:", |
| 37 | 37 | "Display the response bit by bit as it is generated.": "生成されるたびに、応答をビットごとに表示します応答を逐次表示します。", |
| 38 | 38 | "When this is off, responses will be displayed all at once when they are complete.": "この機能がオフの場合、応答は完全になるとすぐにすべて一度に表示されます応答は完全に生成されたときに一度ですべて表示されます。", |
| 39 | 39 | "Temperature": "温度", |
| 40 | 40 | "rep.pen": "繰り返しペナルティ", |
| 41 | 41 | "Rep. Pen. Range.": "繰り返しペナルティの範囲", |
| @@ -46,10 +46,10 @@ | ||
| 46 | 46 | "Phrase Repetition Penalty": "フレーズの繰り返しペナルティ", |
| 47 | 47 | "Off": "オフ", |
| 48 | 48 | "Very light": "非常に軽い", |
| 49 | 49 | "Light": "ライト軽め", |
| 50 | 50 | "Medium": "ミディアム中程度", |
| 51 | 51 | "Aggressive": "攻撃的強め", |
| 52 | 52 | "Very aggressive": "非常に攻撃的非常に強い", |
| 53 | 53 | "Unlocked Context Size": "ロック解除されたコンテキストサイズ", |
| 54 | 54 | "Unrestricted maximum value for the context slider": "コンテキストスライダーの制限なしの最大値", |
| 55 | 55 | "Context Size (tokens)": "コンテキストサイズ(トークン数)", |
| @@ -132,7 +132,7 @@ | ||
| 132 | 132 | "CFG Scale": "CFGスケール", |
| 133 | 133 | "Negative Prompt": "ネガティブプロンプト", |
| 134 | 134 | "Add text here that would make the AI generate things you don't want in your outputs.": "出力に望ましくないものを生成させるAIを作成するテキストをここに追加します。", |
| 135 | 135 | "Used if CFG Scale is unset globally, per chat or character": "CFGスケールがグローバル、チャットごと、または文字ごとに設定されていない場合に使用されますまたはキャラクターごとに設定されていない場合に使用されます", |
| 136 | 136 | "Mirostat Tau": "Mirostat Tau", |
| 137 | 137 | "Mirostat LR": "ミロスタットLR", |
| 138 | 138 | "Min Length": "最小長", |
| @@ -214,7 +214,7 @@ | ||
| 214 | 214 | "Sampler Priority": "サンプラー優先度", |
| 215 | 215 | "Ooba only. Determines the order of samplers.": "Oobaのみ。サンプラーの順序を決定します。", |
| 216 | 216 | "Character Names Behavior": "キャラクター名の動作", |
| 217 | 217 | "Helps the model to associate messages with characters.": "モデルがメッセージを文字に関連付けるのに役立ちますモデルがメッセージをキャラクターに関連付けるのに役立ちます。", |
| 218 | 218 | "None": "なし", |
| 219 | 219 | "character_names_none": "グループと過去のペルソナを除きます。それ以外の場合は、プロンプトに名前を必ず入力してください。", |
| 220 | 220 | "Don't add character names.": "キャラクター名を追加しないでください。", |
| @@ -222,7 +222,7 @@ | ||
| 222 | 222 | "character_names_completion": "制限事項: ラテン英数字とアンダースコアのみ。すべてのソースで機能するわけではありません。特に、Claude、MistralAI、Google では機能しません。", |
| 223 | 223 | "Add character names to completion objects.": "完了オブジェクトにキャラクター名を追加します。", |
| 224 | 224 | "Message Content": "メッセージ内容", |
| 225 | 225 | "Prepend character names to message contents.": "メッセージの内容の先頭に文字名を追加しますメッセージの内容の先頭にキャラクター名を追加します。", |
| 226 | 226 | "Continue Postfix": "ポストフィックスの継続", |
| 227 | 227 | "The next chunk of the continued message will be appended using this as a separator.": "継続メッセージの次のチャンクは、これを区切り文字として使用して追加されます。", |
| 228 | 228 | "Space": "空間", |
| @@ -270,9 +270,9 @@ | ||
| 270 | 270 | "Text Completion": "テキスト補完", |
| 271 | 271 | "Chat Completion": "チャット完了", |
| 272 | 272 | "NovelAI": "NovelAI", |
| 273 | 273 | "KoboldAI Horde": "KoboldAIホルドKoboldAI Horde", |
| 274 | 274 | "KoboldAI": "KoboldAI", |
| 275 | 275 | "Avoid sending sensitive information to the Horde.": "ホルドに機密情報を送信しないでくださいHordeに機密情報を送信しないでください。", |
| 276 | 276 | "Review the Privacy statement": "プライバシー声明を確認する", |
| 277 | 277 | "Register a Horde account for faster queue times": "キュー待ち時間を短縮するためにHordeアカウントを登録する", |
| 278 | 278 | "Learn how to contribute your idle GPU cycles to the Horde": "アイドルのGPUサイクルをホルドに貢献する方法を学びます", |
| @@ -633,7 +633,7 @@ | ||
| 633 | 633 | "Tags_as_Folders_desc": "最近の変更: タグは、タグ管理メニューでフォルダーとしてマークされて初めてフォルダーとして表示されます。ここをクリックして表示します。", |
| 634 | 634 | "Character Handling": "キャラクター処理", |
| 635 | 635 | "If set in the advanced character definitions, this field will be displayed in the characters list.": "高度なキャラクター定義で設定されている場合、このフィールドがキャラクターリストに表示されます。", |
| 636 | 636 | "Char List Subheader": "文字リストサブヘッダーキャラクターリストサブヘッダー", |
| 637 | 637 | "Character Version": "キャラクターバージョン", |
| 638 | 638 | "Created by": "作成者", |
| 639 | 639 | "Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "曖昧な一致を使用し、名前の部分文字列ではなく、すべてのデータフィールドでリスト内のキャラクターを検索する", |
| @@ -729,8 +729,8 @@ | ||
| 729 | 729 | "Automatically hide details": "詳細を自動的に非表示にする", |
| 730 | 730 | "Determines how entries are found for autocomplete.": "オートコンプリートのエントリの検索方法を決定します。", |
| 731 | 731 | "Autocomplete Matching": "マッチング", |
| 732 | 732 | "Starts with": "始まりは前方一致", |
| 733 | 733 | "Includes": "含まれるもの部分一致", |
| 734 | 734 | "Fuzzy": "ファジー", |
| 735 | 735 | "Sets the style of the autocomplete.": "オートコンプリートのスタイルを設定します。", |
| 736 | 736 | "Autocomplete Style": "スタイル", |
| @@ -755,8 +755,8 @@ | ||
| 755 | 755 | "Auto-select": "自動選択", |
| 756 | 756 | "System Backgrounds": "システムの背景", |
| 757 | 757 | "Chat Backgrounds": "チャットの背景", |
| 758 | 758 | "bg_chat_hint_1": "チャットの背景は、", |
| 759 | 759 | "bg_chat_hint_2": "拡張子がここに表示されます拡張機能で生成したチャットの背景はここに表示されます。", |
| 760 | 760 | "Extensions": "拡張機能", |
| 761 | 761 | "Notify on extension updates": "拡張機能の更新時に通知", |
| 762 | 762 | "Manage extensions": "拡張機能を管理", |
| @@ -770,9 +770,9 @@ | ||
| 770 | 770 | "How do I use this?": "これをどのように使用しますか?", |
| 771 | 771 | "Click for stats!": "統計をクリック!", |
| 772 | 772 | "Usage Stats": "使用状況統計", |
| 773 | 773 | "Backup your personas to a file": "キャラクタをファイルにバックアップしますキャラクターをファイルにバックアップします", |
| 774 | 774 | "Backup": "バックアップ", |
| 775 | 775 | "Restore your personas from a file": "ファイルからキャラクタを復元しますファイルからキャラクターを復元します", |
| 776 | 776 | "Restore": "復元", |
| 777 | 777 | "Create a dummy persona": "ダミーのペルソナを作成", |
| 778 | 778 | "Create": "作成", |
| @@ -839,7 +839,7 @@ | ||
| 839 | 839 | "Describe your character's physical and mental traits here.": "ここにキャラクターの身体的および精神的特徴を説明します。", |
| 840 | 840 | "First message": "最初のメッセージ", |
| 841 | 841 | "Click to set additional greeting messages": "追加の挨拶メッセージを設定するにはクリック", |
| 842 | 842 | "Alt. Greetings": "挨拶他の挨拶", |
| 843 | 843 | "This will be the first message from the character that starts every chat.": "これはすべてのチャットを開始するキャラクターからの最初のメッセージになります。", |
| 844 | 844 | "Group Controls": "グループコントロール", |
| 845 | 845 | "Chat Name (Optional)": "チャット名(任意)", |
| @@ -889,7 +889,7 @@ | ||
| 889 | 889 | "popup-button-yes": "はい", |
| 890 | 890 | "popup-button-no": "いいえ", |
| 891 | 891 | "popup-button-cancel": "キャンセル", |
| 892 | 892 | "popup-button-import": "輸入インポート", |
| 893 | 893 | "Advanced Defininitions": "高度な定義", |
| 894 | 894 | "Prompt Overrides": "プロンプトのオーバーライド", |
| 895 | 895 | "(For Chat Completion and Instruct Mode)": "(チャット補完と指示モード用)", |
| @@ -937,7 +937,7 @@ | ||
| 937 | 937 | "Type here...": "ここに入力...", |
| 938 | 938 | "Chat Lorebook": "チャットロアブック", |
| 939 | 939 | "Chat Lorebook for": "チャットロアブック", |
| 940 | 940 | "chat_world_template_txt": "選択したワールド情報はこのチャットにバインドされます。AI の返信を生成する際、\nグローバルおよびキャラクターの伝承書のエントリと結合されますnグローバルおよびキャラクターのロアブックのエントリと結合されます。", |
| 941 | 941 | "Select a World Info file for": "次のためにワールド情報ファイルを選択", |
| 942 | 942 | "Primary Lorebook": "プライマリロアブック", |
| 943 | 943 | "A selected World Info will be bound to this character as its own Lorebook.": "選択したワールド情報は、このキャラクターにその独自のロアブックとしてバインドされます。", |
| @@ -1065,9 +1065,9 @@ | ||
| 1065 | 1065 | "Change it later in the 'User Settings' panel.": "後で「ユーザー設定」パネルで変更します。", |
| 1066 | 1066 | "Enable simple UI mode": "シンプルUIモードを有効にする", |
| 1067 | 1067 | "Looking for AI characters?": "AIキャラクターをお探しですか?", |
| 1068 | 1068 | "onboarding_import": "輸入インポート", |
| 1069 | 1069 | "from supported sources or view": "サポートされているソースからまたは表示", |
| 1070 | 1070 | "Sample characters": "サンプル文字サンプルキャラクター", |
| 1071 | 1071 | "Your Persona": "あなたのペルソナ", |
| 1072 | 1072 | "Before you get started, you must select a persona name.": "始める前に、ペルソナ名を選択する必要があります。", |
| 1073 | 1073 | "welcome_message_part_8": "これはいつでも変更可能です。", |
| @@ -1081,7 +1081,7 @@ | ||
| 1081 | 1081 | "View character card": "キャラクターカードを表示", |
| 1082 | 1082 | "Remove from group": "グループから削除", |
| 1083 | 1083 | "Add to group": "グループに追加", |
| 1084 | 1084 | "Alternate Greetings": "代わりの挨拶挨拶のバリエーション", |
| 1085 | 1085 | "Alternate_Greetings_desc": "これらは、新しいチャットを開始するときに最初のメッセージにスワイプとして表示されます。\nグループのメンバーは、そのうちの 1 つを選択して会話を開始できます。", |
| 1086 | 1086 | "Alternate Greetings Hint": "ボタンをクリックして始めましょう!", |
| 1087 | 1087 | "(This will be the first message from the character that starts every chat)": "(これはすべてのチャットを開始するキャラクターからの最初のメッセージになります)", |
| @@ -1118,11 +1118,11 @@ | ||
| 1118 | 1118 | "Will be used as the default CFG options for every chat unless overridden.": "上書きされない限り、すべてのチャットのデフォルトの CFG オプションとして使用されます。", |
| 1119 | 1119 | "CFG Prompt Cascading": "CFG プロンプト カスケード", |
| 1120 | 1120 | "Combine positive/negative prompts from other boxes.": "他のボックスからの肯定的/否定的なプロンプトを組み合わせます。", |
| 1121 | 1121 | "For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "たとえば、チャット、グローバル、および文字のボックスにチェックを入れるとおよびキャラクターのボックスにチェックを入れると、すべての否定プロンプトがコンマ区切りの文字列に結合されますすべてのネガティブプロンプトがコンマ区切りの文字列に結合されます。", |
| 1122 | 1122 | "Always Include": "常に含めます", |
| 1123 | 1123 | "Chat Negatives": "チャットのネガティブ", |
| 1124 | 1124 | "Character Negatives": "性格のマイナス面キャラクターのネガティブ", |
| 1125 | 1125 | "Global Negatives": "世界的なマイナスグローバルネガティブ", |
| 1126 | 1126 | "Custom Separator:": "カスタムセパレーター:", |
| 1127 | 1127 | "Insertion Depth:": "挿入深さ:", |
| 1128 | 1128 | "Token Probabilities": "トークン確率", |
| @@ -1236,7 +1236,7 @@ | ||
| 1236 | 1236 | "ext_regex_title": "正規表現", |
| 1237 | 1237 | "ext_regex_new_global_script": "+ グローバル", |
| 1238 | 1238 | "ext_regex_new_scoped_script": "+ スコープ付き", |
| 1239 | 1239 | "ext_regex_import_script": "輸入インポート", |
| 1240 | 1240 | "ext_regex_global_scripts": "グローバルスクリプト", |
| 1241 | 1241 | "ext_regex_global_scripts_desc": "すべてのキャラクターで使用可能。ローカル設定に保存されます。", |
| 1242 | 1242 | "ext_regex_scoped_scripts": "スコープ付きスクリプト", |
| @@ -1301,8 +1301,8 @@ | ||
| 1301 | 1301 | "Authentication (optional)": "認証(オプション)", |
| 1302 | 1302 | "Example: username:password": "例: ユーザー名:パスワード", |
| 1303 | 1303 | "Important:": "重要:", |
| 1304 | 1304 | "sd_auto_auth_warning_1": "SD Web UIを実行するUIを", |
| 1305 | 1305 | "sd_auto_auth_warning_2": "フラグフラグを指定して実行してください! サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。", |
| 1306 | 1306 | "sd_drawthings_url": "例: {{drawthings_url}}", |
| 1307 | 1307 | "sd_drawthings_auth_txt": "UI で HTTP API スイッチを有効にして DrawThings アプリを実行します。サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。", |
| 1308 | 1308 | "sd_vlad_url": "例: {{vlad_url}}", |
| @@ -1326,36 +1326,36 @@ | ||
| 1326 | 1326 | "Enhance": "強化する", |
| 1327 | 1327 | "Refine": "リファイン", |
| 1328 | 1328 | "Decrisper": "デクリスパー", |
| 1329 | 1329 | "Sampling steps": "サンプリング手順 ()サンプリングステップ数", |
| 1330 | 1330 | "Width": "幅 ()", |
| 1331 | 1331 | "Height": "身長 ()高さ", |
| 1332 | 1332 | "Resolution": "解決解像度", |
| 1333 | 1333 | "Model": "モデル", |
| 1334 | 1334 | "Sampling method": "サンプリング方法", |
| 1335 | 1335 | "Karras (not all samplers supported)": "Karras (すべてのサンプラーがサポートされているわけではありません)", |
| 1336 | 1336 | "SMEA versions of samplers are modified to perform better at high resolution.": "SMEA バージョンのサンプラーは、高解像度でより優れたパフォーマンスを発揮するように変更されています。", |
| 1337 | 1337 | "SMEA": "中小企業庁SMEA", |
| 1338 | 1338 | "DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.": "SMEA サンプラーの DYN バリアントは、多くの場合、より多様な出力をもたらしますが、非常に高い解像度では失敗する可能性があります。", |
| 1339 | 1339 | "DYN": "ダイナミック", |
| 1340 | 1340 | "Scheduler": "スケジューラスケジューラー", |
| 1341 | 1341 | "Restore Faces": "顔を復元する顔の修復", |
| 1342 | 1342 | "Hires. Fix": "雇用。修正高解像度補助", |
| 1343 | 1343 | "Upscaler": "アップスケーラー", |
| 1344 | 1344 | "Upscale by": "高級化アップスケール倍率", |
| 1345 | 1345 | "Denoising strength": "ノイズ除去の強さ", |
| 1346 | 1346 | "Hires steps (2nd pass)": "採用手順(2回目のパス)高解像度でのステップ数", |
| 1347 | 1347 | "Preset for prompt prefix and negative prompt": "プロンプトプレフィックスと否定プロンプトのプリセットプロンプトプレフィックスとネガティブプロンプトのプリセット", |
| 1348 | 1348 | "Style": "スタイル", |
| 1349 | 1349 | "Save style": "スタイルを保存", |
| 1350 | 1350 | "Delete style": "スタイルを削除", |
| 1351 | 1351 | "Common prompt prefix": "一般的なプロンプトプレフィックス共通のプロンプトプレフィックス", |
| 1352 | 1352 | "sd_prompt_prefix_placeholder": "生成されたプロンプトを挿入する場所を指定するには、{prompt}を使用します。", |
| 1353 | 1353 | "Negative common prompt prefix": "否定の共通プロンプト接頭辞共通のネガティブプロンプトプレフィックス", |
| 1354 | 1354 | "Character-specific prompt prefix": "文字固有のプロンプトプレフィックスキャラクター固有のプロンプトプレフィックス", |
| 1355 | 1355 | "Won't be used in groups.": "グループでは使用されません。", |
| 1356 | 1356 | "sd_character_prompt_placeholder": "現在選択されているキャラクターを説明する任意の特性現在選択されているキャラクターを説明する特徴。共通のプロンプト プレフィックスの後に追加されます共通のプロンプトプレフィックスの後に追加されます。\n例: 女性、緑の目、茶色の髪、ピンクのシャツ", |
| 1357 | 1357 | "Character-specific negative prompt prefix": "文字固有の否定プロンプト接頭辞キャラクター固有のネガティブプロンプトプレフィックス", |
| 1358 | 1358 | "sd_character_negative_prompt_placeholder": "選択したキャラクターに表示されるべきではない特性選択したキャラクターに表示されるべきではない特徴。否定の共通プロンプト接頭辞の後に追加されます共通のネガティブプロンプトプレフィックスの後に追加されます。\n例: ジュエリー、靴、メガネ", |
| 1359 | 1359 | "Shareable": "共有可能", |
| 1360 | 1360 | "Image Prompt Templates": "画像プロンプトテンプレート", |
| 1361 | 1361 | "Vectors Model Warning": "チャットの途中でモデルを変更する場合は、ベクトルを消去することをお勧めします。そうしないと、標準以下の結果になります。", |
| @@ -1380,22 +1380,22 @@ | ||
| 1380 | 1380 | "Warning:": "警告:", |
| 1381 | 1381 | "This action is irreversible.": "この操作は元に戻せません。", |
| 1382 | 1382 | "Type the user's handle below to confirm:": "確認するには、以下のユーザーのハンドルを入力してください。", |
| 1383 | 1383 | "Import Characters": "文字をインポートするキャラクターをインポートする", |
| 1384 | 1384 | "Enter the URL of the content to import": "インポートするコンテンツの URL を入力します", |
| 1385 | 1385 | "Supported sources:": "サポートされているソース:", |
| 1386 | 1386 | "char_import_1": "チャブキャラクターChub キャラクター (直接リンクまたはID)", |
| 1387 | 1387 | "char_import_example": "例:", |
| 1388 | 1388 | "char_import_2": "チャブの伝承集Chub ロアブック (直接リンクまたは ID)", |
| 1389 | 1389 | "char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)", |
| 1390 | 1390 | "char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)", |
| 1391 | 1391 | "char_import_5": "AICharacterCard.com キャラクター (直接リンクまたは ID)", |
| 1392 | 1392 | "char_import_6": "直接PNGリンク(参照", |
| 1393 | 1393 | "char_import_7": "許可されたホストの場合)", |
| 1394 | 1394 | "char_import_8": "RisuRealm キャラクター (直接リンク)", |
| 1395 | 1395 | "Supports importing multiple characters.": "複数の文字のインポートをサポートします複数のキャラクターのインポートをサポートします。", |
| 1396 | 1396 | "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。", |
| 1397 | 1397 | "Export for character": "文字のエクスポートキャラクターのエクスポート", |
| 1398 | 1398 | "Export prompts for this character, including their order.": "この文字のプロンプトを順序も含めてエクスポートしますこのキャラクターのプロンプトを順序も含めてエクスポートします。", |
| 1399 | 1399 | "Export all": "すべてをエクスポート", |
| 1400 | 1400 | "Export all your prompts to a file": "すべてのプロンプトをファイルにエクスポートする", |
| 1401 | 1401 | "Insert prompt": "プロンプトを挿入", |
| @@ -334,6 +334,9 @@ | ||
| 334 | 334 | "vLLM API key": "vLLM API 密钥", |
| 335 | 335 | "Example: 127.0.0.1:8000": "例如:http://127.0.0.1:8000", |
| 336 | 336 | "vLLM Model": "vLLM 模型", |
| 337 | + "HuggingFace Token": "HuggingFace 代币", | |
| 338 | + "Endpoint URL": "端点 URL", | |
| 339 | + "Example: https://****.endpoints.huggingface.cloud": "例如:https://****.endpoints.huggingface.cloud", | |
| 337 | 340 | "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(用于OpenAI API的包装器)", |
| 338 | 341 | "Aphrodite API key": "Aphrodite API 密钥", |
| 339 | 342 | "Aphrodite Model": "Aphrodite 模型", |
| @@ -419,6 +422,8 @@ | ||
| 419 | 422 | "Prompt Post-Processing": "提示词后处理", |
| 420 | 423 | "Applies additional processing to the prompt before sending it to the API.": "在将提示词发送到 API 之前对其进行额外处理。", |
| 421 | 424 | "prompt_post_processing_none": "未选择", |
| 425 | + "01.AI API Key": "01.AI API密钥", | |
| 426 | + "01.AI Model": "01.AI模型", | |
| 422 | 427 | "Additional Parameters": "附加参数", |
| 423 | 428 | "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "通过发送简短的测试消息验证您的API连接。请注意,您将因此而消耗额度!", |
| 424 | 429 | "Test Message": "发送测试消息", |
| @@ -1033,6 +1038,8 @@ | ||
| 1033 | 1038 | "Sticky": "粘性", |
| 1034 | 1039 | "Entries with a cooldown can't be activated N messages after being triggered.": "具有冷却时间的条目在触发后 N 条消息内无法被激活。", |
| 1035 | 1040 | "Cooldown": "冷却", |
| 1041 | + "Entries with a delay can't be activated until there are N messages present in the chat.": "直到聊天中出现 N 条消息时,延迟的条目才能被激活。", | |
| 1042 | + "Delay": "延迟", | |
| 1036 | 1043 | "Filter to Character(s)": "应用到角色", |
| 1037 | 1044 | "Character Exclusion": "反选角色", |
| 1038 | 1045 | "-- Characters not found --": "-- 未找到角色 --", |
| @@ -1077,6 +1084,7 @@ | ||
| 1077 | 1084 | "Move message up": "将消息上移", |
| 1078 | 1085 | "Move message down": "将消息下移", |
| 1079 | 1086 | "Enlarge": "放大", |
| 1087 | + "Caption": "标题", | |
| 1080 | 1088 | "Welcome to SillyTavern!": "欢迎来到 SillyTavern!", |
| 1081 | 1089 | "welcome_message_part_1": "阅读", |
| 1082 | 1090 | "welcome_message_part_2": "官方文档", |
| @@ -1113,10 +1121,6 @@ | ||
| 1113 | 1121 | "alternate_greetings_hint_2": "按钮即可开始!", |
| 1114 | 1122 | "Alternate Greeting #": "额外问候语 #", |
| 1115 | 1123 | "(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": "始终允许", | |
| 1120 | 1124 | "View contents": "查看内容", |
| 1121 | 1125 | "Remove the file": "删除文件", |
| 1122 | 1126 | "Unique to this chat": "此聊天独有", |
| @@ -1240,6 +1244,7 @@ | ||
| 1240 | 1244 | "Message Template": "消息模板", |
| 1241 | 1245 | "(use _space": "(使用", |
| 1242 | 1246 | "macro)": "宏指令)", |
| 1247 | + "Automatically caption images": "自动为图像添加标题", | |
| 1243 | 1248 | "Edit captions before saving": "保存前编辑标题", |
| 1244 | 1249 | "Character Expressions": "角色表情", |
| 1245 | 1250 | "Translate text to English before classification": "分类之前将文本翻译成英文", |
| @@ -1579,6 +1584,10 @@ | ||
| 1579 | 1584 | "Warning:": "警告:", |
| 1580 | 1585 | "This action is irreversible.": "此操作不可逆。", |
| 1581 | 1586 | "Type the user's handle below to confirm:": "在下面输入用户的名称以确认:", |
| 1587 | + "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。", | |
| 1588 | + "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。", | |
| 1589 | + "Always forbidden": "始终禁止", | |
| 1590 | + "Always allowed": "始终允许", | |
| 1582 | 1591 | "help_format_1": "文本格式化命令:", |
| 1583 | 1592 | "help_format_2": "*文本*", |
| 1584 | 1593 | "help_format_3": "显示为", |
| @@ -227,7 +227,7 @@ import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, de | ||
| 227 | 227 | import { initPresetManager } from './scripts/preset-manager.js'; |
| 228 | 228 | import { MacrosParser, evaluateMacros } from './scripts/macros.js'; |
| 229 | 229 | import { currentUser, setUserControls } from './scripts/user.js'; |
| 230 | 230 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; |
| 231 | 231 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; |
| 232 | 232 | import { ScraperManager } from './scripts/scrapers.js'; |
| 233 | 233 | import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js'; |
| @@ -520,6 +520,7 @@ const chatElement = $('#chat'); | ||
| 520 | 520 | let dialogueResolve = null; |
| 521 | 521 | let dialogueCloseStop = false; |
| 522 | 522 | export let chat_metadata = {}; |
| 523 | +/** @type {StreamingProcessor} */ | |
| 523 | 524 | export let streamingProcessor = null; |
| 524 | 525 | let crop_data = undefined; |
| 525 | 526 | let is_delete_mode = false; |
| @@ -837,6 +838,7 @@ export let main_api;// = "kobold"; | ||
| 837 | 838 | //novel settings |
| 838 | 839 | export let novelai_settings; |
| 839 | 840 | export let novelai_setting_names; |
| 841 | +/** @type {AbortController} */ | |
| 840 | 842 | let abortController; |
| 841 | 843 | |
| 842 | 844 | //css |
| @@ -4381,6 +4383,25 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4381 | 4383 | } |
| 4382 | 4384 | |
| 4383 | 4385 | /** |
| 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 | +/** | |
| 4384 | 4405 | * Injects extension prompts into chat messages. |
| 4385 | 4406 | * @param {object[]} messages Array of chat messages |
| 4386 | 4407 | * @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() { | ||
| 7163 | 7184 | * @param {string} inputValue - Value to set the input to. |
| 7164 | 7185 | * @param {PopupOptions} options - Options for the popup. |
| 7165 | 7186 | * @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. | |
| 7167 | 7189 | */ |
| 7168 | 7190 | export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) { |
| 7169 | 7191 | function getOkButtonText() { |
| @@ -7794,6 +7816,7 @@ window['SillyTavern'].getContext = function () { | ||
| 7794 | 7816 | eventTypes: event_types, |
| 7795 | 7817 | addOneMessage: addOneMessage, |
| 7796 | 7818 | generate: Generate, |
| 7819 | + stopGeneration: stopGeneration, | |
| 7797 | 7820 | getTokenCount: getTokenCount, |
| 7798 | 7821 | extensionPrompts: extension_prompts, |
| 7799 | 7822 | setExtensionPrompt: setExtensionPrompt, |
| @@ -7849,6 +7872,8 @@ window['SillyTavern'].getContext = function () { | ||
| 7849 | 7872 | * @deprecated Legacy snake-case naming, compatibility with old extensions |
| 7850 | 7873 | */ |
| 7851 | 7874 | event_types: event_types, |
| 7875 | + POPUP_TYPE: POPUP_TYPE, | |
| 7876 | + POPUP_RESULT: POPUP_RESULT, | |
| 7852 | 7877 | }; |
| 7853 | 7878 | }; |
| 7854 | 7879 | |
| @@ -10342,15 +10367,7 @@ jQuery(async function () { | ||
| 10342 | 10367 | }); |
| 10343 | 10368 | |
| 10344 | 10369 | $(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); | |
| 10354 | 10371 | }); |
| 10355 | 10372 | |
| 10356 | 10373 | $(document).on('click', '#form_sheld .stscript_continue', function () { |
| @@ -10839,3 +10856,4 @@ jQuery(async function () { | ||
| 10839 | 10856 | |
| 10840 | 10857 | initCustomSelectedSamplers(); |
| 10841 | 10858 | }); |
| 10859 | + | |
| @@ -725,8 +725,14 @@ export function initRossMods() { | ||
| 725 | 725 | RA_autoconnect(); |
| 726 | 726 | } |
| 727 | 727 | |
| 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'); | |
| 730 | 736 | } |
| 731 | 737 | |
| 732 | 738 | $('#main_api').change(function () { |
| @@ -1430,7 +1430,7 @@ jQuery(function () { | ||
| 1430 | 1430 | wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter'); |
| 1431 | 1431 | const textarea = document.createElement('textarea'); |
| 1432 | 1432 | textarea.value = String(bro.val()); |
| 1433 | 1433 | textarea.classList.add('height100p', 'wide100p', 'maximized_textarea'); |
| 1434 | 1434 | bro.hasClass('monospace') && textarea.classList.add('monospace'); |
| 1435 | 1435 | textarea.addEventListener('input', function () { |
| 1436 | 1436 | bro.val(textarea.value).trigger('input'); |
| @@ -154,7 +154,7 @@ export function initDynamicStyles() { | ||
| 154 | 154 | // Process all stylesheets on initial load |
| 155 | 155 | Array.from(document.styleSheets).forEach(sheet => { |
| 156 | 156 | try { |
| 157 | 157 | applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true }); |
| 158 | 158 | } catch (e) { |
| 159 | 159 | console.warn('Failed to process stylesheet on initial load:', e); |
| 160 | 160 | } |
| @@ -3017,25 +3017,25 @@ async function generateComfyImage(prompt, negativePrompt) { | ||
| 3017 | 3017 | const text = await workflowResponse.text(); |
| 3018 | 3018 | toastr.error(`Failed to load workflow.\n\n${text}`); |
| 3019 | 3019 | } |
| 3020 | 3020 | let workflow = (await workflowResponse.json()).replacereplaceAll('"%prompt%"', JSON.stringify(prompt)); |
| 3021 | 3021 | workflow = workflow.replacereplaceAll('"%negative_prompt%"', JSON.stringify(negativePrompt)); |
| 3022 | 3022 | |
| 3023 | 3023 | const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER); |
| 3024 | 3024 | workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed)); |
| 3025 | 3025 | placeholders.forEach(ph => { |
| 3026 | 3026 | workflow = workflow.replacereplaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph])); |
| 3027 | 3027 | }); |
| 3028 | 3028 | (extension_settings.sd.comfy_placeholders ?? []).forEach(ph => { |
| 3029 | 3029 | workflow = workflow.replacereplaceAll(`"%${ph.find}%"`, JSON.stringify(substituteParams(ph.replace))); |
| 3030 | 3030 | }); |
| 3031 | 3031 | if (/%user_avatar%/gi.test(workflow)) { |
| 3032 | 3032 | const response = await fetch(getUserAvatarUrl()); |
| 3033 | 3033 | if (response.ok) { |
| 3034 | 3034 | const avatarBlob = await response.blob(); |
| 3035 | 3035 | const avatarBase64 = await getBase64Async(avatarBlob); |
| 3036 | 3036 | workflow = workflow.replacereplaceAll('"%user_avatar%"', JSON.stringify(avatarBase64)); |
| 3037 | 3037 | } else { |
| 3038 | 3038 | workflow = workflow.replacereplaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL)); |
| 3039 | 3039 | } |
| 3040 | 3040 | } |
| 3041 | 3041 | if (/%char_avatar%/gi.test(workflow)) { |
| @@ -3043,9 +3043,9 @@ async function generateComfyImage(prompt, negativePrompt) { | ||
| 3043 | 3043 | if (response.ok) { |
| 3044 | 3044 | const avatarBlob = await response.blob(); |
| 3045 | 3045 | const avatarBase64 = await getBase64Async(avatarBlob); |
| 3046 | 3046 | workflow = workflow.replacereplaceAll('"%char_avatar%"', JSON.stringify(avatarBase64)); |
| 3047 | 3047 | } else { |
| 3048 | 3048 | workflow = workflow.replacereplaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL)); |
| 3049 | 3049 | } |
| 3050 | 3050 | } |
| 3051 | 3051 | console.log(`{ |
| @@ -178,8 +178,37 @@ async function loadGroupChat(chatId) { | ||
| 178 | 178 | return []; |
| 179 | 179 | } |
| 180 | 180 | |
| 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 | + | |
| 181 | 202 | export async function getGroupChat(groupId, reload = false) { |
| 182 | 203 | 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 | + | |
| 183 | 212 | const chat_id = group.chat_id; |
| 184 | 213 | const data = await loadGroupChat(chat_id); |
| 185 | 214 | let freshChat = false; |
| @@ -197,7 +226,6 @@ export async function getGroupChat(groupId, reload = false) { | ||
| 197 | 226 | if (group && Array.isArray(group.members)) { |
| 198 | 227 | for (let member of group.members) { |
| 199 | 228 | const character = characters.find(x => x.avatar === member || x.name === member); |
| 200 | - | |
| 201 | 229 | if (!character) { |
| 202 | 230 | continue; |
| 203 | 231 | } |
| @@ -219,10 +247,8 @@ export async function getGroupChat(groupId, reload = false) { | ||
| 219 | 247 | freshChat = true; |
| 220 | 248 | } |
| 221 | 249 | |
| 222 | - if (group) { | |
| 250 | + let metadata = group.chat_metadata ?? {}; | |
| 223 | - let metadata = group.chat_metadata ?? {}; | |
| 251 | + updateChatMetadata(metadata, true); | |
| 224 | - updateChatMetadata(metadata, true); | |
| 225 | - } | |
| 226 | 252 | |
| 227 | 253 | if (reload) { |
| 228 | 254 | select_group_chats(groupId, true); |
| @@ -1,7 +1,5 @@ | ||
| 1 | 1 | import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js'; |
| 2 | 2 | |
| 3 | -const ELEMENT_ID = 'loader'; | |
| 4 | - | |
| 5 | 3 | /** @type {Popup} */ |
| 6 | 4 | let loaderPopup; |
| 7 | 5 | |
| @@ -31,7 +29,7 @@ export async function hideLoader() { | ||
| 31 | 29 | return new Promise((resolve) => { |
| 32 | 30 | // Spinner blurs/fades out |
| 33 | 31 | $('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () { |
| 34 | 32 | $(`'#${ELEMENT_ID}`loader').remove(); |
| 35 | 33 | // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS |
| 36 | 34 | // If it's present, we remove it once and then it's gone. |
| 37 | 35 | yoinkPreloader(); |
| @@ -254,7 +254,7 @@ function getCurrentSwipeId() { | ||
| 254 | 254 | // For swipe macro, we are accepting using the message that is currently being swiped |
| 255 | 255 | const mid = getLastMessageId({ exclude_swipe_in_propress: false }); |
| 256 | 256 | const swipeId = chat[mid]?.swipe_id; |
| 257 | 257 | return swipeId !== null ? swipeId + 1 : null; |
| 258 | 258 | } |
| 259 | 259 | |
| 260 | 260 | /** |
| @@ -401,7 +401,7 @@ function timeDiffReplace(input) { | ||
| 401 | 401 | const time2 = moment(matchPart2); |
| 402 | 402 | |
| 403 | 403 | const timeDifference = moment.duration(time1.diff(time2)); |
| 404 | 404 | return timeDifference.humanize(true); |
| 405 | 405 | }); |
| 406 | 406 | |
| 407 | 407 | return output; |
| @@ -689,7 +689,7 @@ function formatWorldInfo(value) { | ||
| 689 | 689 | return ''; |
| 690 | 690 | } |
| 691 | 691 | |
| 692 | 692 | if (!oai_settings.wi_format.trim()) { |
| 693 | 693 | return value; |
| 694 | 694 | } |
| 695 | 695 | |
| @@ -40,7 +40,7 @@ import { tokenizers } from './tokenizers.js'; | ||
| 40 | 40 | import { BIAS_CACHE } from './logit-bias.js'; |
| 41 | 41 | import { renderTemplateAsync } from './templates.js'; |
| 42 | 42 | |
| 43 | 43 | import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js'; |
| 44 | 44 | import { FILTER_TYPES } from './filters.js'; |
| 45 | 45 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 46 | 46 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -335,6 +335,8 @@ const storage_keys = { | ||
| 335 | 335 | compact_input_area: 'compact_input_area', |
| 336 | 336 | auto_connect_legacy: 'AutoConnectEnabled', |
| 337 | 337 | auto_load_chat_legacy: 'AutoLoadChatEnabled', |
| 338 | + | |
| 339 | + storyStringValidationCache: 'StoryStringValidationCache', | |
| 338 | 340 | }; |
| 339 | 341 | |
| 340 | 342 | const contextControls = [ |
| @@ -2105,6 +2107,9 @@ export function fuzzySearchGroups(searchValue) { | ||
| 2105 | 2107 | */ |
| 2106 | 2108 | export function renderStoryString(params) { |
| 2107 | 2109 | try { |
| 2110 | + // Validate and log possible warnings/errors | |
| 2111 | + validateStoryString(power_user.context.story_string, params); | |
| 2112 | + | |
| 2108 | 2113 | // compile the story string template into a function, with no HTML escaping |
| 2109 | 2114 | const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true }); |
| 2110 | 2115 | |
| @@ -2132,6 +2137,55 @@ export function renderStoryString(params) { | ||
| 2132 | 2137 | } |
| 2133 | 2138 | } |
| 2134 | 2139 | |
| 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 | + | |
| 2135 | 2189 | const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a); |
| 2136 | 2190 | const compareFunc = (first, second) => { |
| 2137 | 2191 | const a = first[power_user.sort_field]; |
| @@ -33,6 +33,7 @@ import { | ||
| 33 | 33 | setCharacterName, |
| 34 | 34 | setExtensionPrompt, |
| 35 | 35 | setUserName, |
| 36 | + stopGeneration, | |
| 36 | 37 | substituteParams, |
| 37 | 38 | system_avatar, |
| 38 | 39 | system_message_types, |
| @@ -899,6 +900,24 @@ export function initDefaultSlashCommands() { | ||
| 899 | 900 | helpString: 'Adds a swipe to the last chat message.', |
| 900 | 901 | })); |
| 901 | 902 | 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({ | |
| 902 | 921 | name: 'abort', |
| 903 | 922 | callback: abortCallback, |
| 904 | 923 | namedArgumentList: [ |
| @@ -263,6 +263,8 @@ export const setting_names = [ | ||
| 263 | 263 | 'bypass_status_check', |
| 264 | 264 | ]; |
| 265 | 265 | |
| 266 | +const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); | |
| 267 | + | |
| 266 | 268 | export function validateTextGenUrl() { |
| 267 | 269 | const selector = SERVER_INPUTS[settings.type]; |
| 268 | 270 | |
| @@ -1045,6 +1047,10 @@ export function isJsonSchemaSupported() { | ||
| 1045 | 1047 | return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui'; |
| 1046 | 1048 | } |
| 1047 | 1049 | |
| 1050 | +function isDynamicTemperatureSupported() { | |
| 1051 | + return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type); | |
| 1052 | +} | |
| 1053 | + | |
| 1048 | 1054 | function getLogprobsNumber() { |
| 1049 | 1055 | if (settings.type === VLLM || settings.type === INFERMATICAI) { |
| 1050 | 1056 | return 5; |
| @@ -1055,6 +1061,7 @@ function getLogprobsNumber() { | ||
| 1055 | 1061 | |
| 1056 | 1062 | export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { |
| 1057 | 1063 | const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet'; |
| 1064 | + const dynatemp = isDynamicTemperatureSupported(); | |
| 1058 | 1065 | const { banned_tokens, banned_strings } = getCustomTokenBans(); |
| 1059 | 1066 | |
| 1060 | 1067 | let params = { |
| @@ -1063,7 +1070,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | ||
| 1063 | 1070 | 'max_new_tokens': maxTokens, |
| 1064 | 1071 | 'max_tokens': maxTokens, |
| 1065 | 1072 | 'logprobs': power_user.request_token_probabilities ? getLogprobsNumber() : undefined, |
| 1066 | 1073 | 'temperature': settings.dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp, |
| 1067 | 1074 | 'top_p': settings.top_p, |
| 1068 | 1075 | 'typical_p': settings.typical_p, |
| 1069 | 1076 | 'typical': settings.typical_p, |
| @@ -1081,11 +1088,11 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | ||
| 1081 | 1088 | 'length_penalty': settings.length_penalty, |
| 1082 | 1089 | 'early_stopping': settings.early_stopping, |
| 1083 | 1090 | 'add_bos_token': settings.add_bos_token, |
| 1084 | 1091 | 'dynamic_temperature': settings.dynatemp ? true : undefined, |
| 1085 | 1092 | 'dynatemp_low': settings.dynatemp ? settings.min_temp : undefined, |
| 1086 | 1093 | 'dynatemp_high': settings.dynatemp ? settings.max_temp : undefined, |
| 1087 | 1094 | 'dynatemp_range': settings.dynatemp ? (settings.max_temp - settings.min_temp) / 2 : undefined, |
| 1088 | 1095 | 'dynatemp_exponent': settings.dynatemp ? settings.dynatemp_exponent : undefined, |
| 1089 | 1096 | 'smoothing_factor': settings.smoothing_factor, |
| 1090 | 1097 | 'smoothing_curve': settings.smoothing_curve, |
| 1091 | 1098 | 'dry_allowed_length': settings.dry_allowed_length, |
| @@ -803,7 +803,7 @@ export function getImageSizeFromDataURL(dataUrl) { | ||
| 803 | 803 | |
| 804 | 804 | export function getCharaFilename(chid) { |
| 805 | 805 | const context = getContext(); |
| 806 | 806 | const fileName = context.characters[chid ?? context.characterId]?.avatar; |
| 807 | 807 | |
| 808 | 808 | if (fileName) { |
| 809 | 809 | return fileName.replace(/\.[^/.]+$/, ''); |
| @@ -609,10 +609,6 @@ const postSetupTasks = async function () { | ||
| 609 | 609 | console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!')); |
| 610 | 610 | } |
| 611 | 611 | } |
| 612 | - | |
| 613 | - if (listen && !basicAuthMode && enableAccounts) { | |
| 614 | - await userModule.checkAccountsProtection(); | |
| 615 | - } | |
| 616 | 612 | }; |
| 617 | 613 | |
| 618 | 614 | /** |
| @@ -631,16 +627,6 @@ async function loadPlugins() { | ||
| 631 | 627 | } |
| 632 | 628 | } |
| 633 | 629 | |
| 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 | - | |
| 644 | 630 | /** |
| 645 | 631 | * Set the title of the terminal window |
| 646 | 632 | * @param {string} title Desired title for the window |
| @@ -654,10 +640,53 @@ function setWindowTitle(title) { | ||
| 654 | 640 | } |
| 655 | 641 | } |
| 656 | 642 | |
| 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 | + | |
| 657 | 685 | // User storage module needs to be initialized before starting the server |
| 658 | 686 | userModule.initUserStorage(dataRoot) |
| 659 | 687 | .then(userModule.ensurePublicDirectoriesExist) |
| 660 | 688 | .then(userModule.migrateUserData) |
| 689 | + .then(verifySecuritySettings) | |
| 661 | 690 | .then(preSetupTasks) |
| 662 | 691 | .finally(() => { |
| 663 | 692 | if (cliArguments.ssl) { |
| @@ -681,27 +681,27 @@ async function createBackupArchive(handle, response) { | ||
| 681 | 681 | } |
| 682 | 682 | |
| 683 | 683 | /** |
| 684 | - * Checks if any admin users are not password protected. If so, logs a warning. | |
| 684 | + * Gets all of the users. | |
| 685 | 685 | * @returns {Promise<voidUser[]>} |
| 686 | 686 | */ |
| 687 | 687 | async function checkAccountsProtectiongetAllUsers() { |
| 688 | 688 | if (!ENABLE_ACCOUNTS) { |
| 689 | 689 | return []; |
| 690 | 690 | } |
| 691 | - | |
| 692 | 691 | /** |
| 693 | 692 | * @type {User[]} |
| 694 | 693 | */ |
| 695 | 694 | 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) { | |
| 696 | +} | |
| 698 | - console.warn(color.red('The following admin users are not password protected:')); | |
| 697 | + | |
| 699 | - unprotectedUsers.forEach(x => console.warn(color.yellow(x.handle))); | |
| 698 | +/** | |
| 700 | - console.log(); | |
| 699 | + * Gets all of the enabled users. | |
| 701 | - console.warn('Please disable them or set a password in the admin panel.'); | |
| 700 | + * @returns {Promise<User[]>} | |
| 702 | - console.log(); | |
| 701 | + */ | |
| 703 | - await delay(3000); | |
| 702 | +async function getAllEnabledUsers() { | |
| 704 | - } | |
| 703 | + const users = await getAllUsers(); | |
| 704 | + return users.filter(x => x.enabled); | |
| 705 | 705 | } |
| 706 | 706 | |
| 707 | 707 | /** |
| @@ -738,6 +738,7 @@ module.exports = { | ||
| 738 | 738 | shouldRedirectToLogin, |
| 739 | 739 | createBackupArchive, |
| 740 | 740 | tryAutoLogin, |
| 741 | 741 | checkAccountsProtectiongetAllUsers, |
| 742 | + getAllEnabledUsers, | |
| 742 | 743 | router, |
| 743 | 744 | }; |