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