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

1af76af4d7d6f77f37fef981b1f3a3519651f9c5

WBlair1 <wolfgangblair1@gmail.com>

30 files changed, +387 -137Showing whitespace changes
.dockerignore+2 -0
@@ -1,4 +1,6 @@
1.git1.git
2.github
3.vscode
2node_modules4node_modules
3npm-debug.log5npm-debug.log
4readme*6readme*
.github/ISSUE_TEMPLATE/bug-report.yml+1 -1
@@ -1,5 +1,5 @@
1name: Bug Report 🐛1name: Bug Report 🐛
2description: 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!2description: 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.
3title: '[BUG] <title>'3title: '[BUG] <title>'
4labels: ['🐛 Bug']4labels: ['🐛 Bug']
5body:5body:
.github/ISSUE_TEMPLATE/feature-request.yml+1 -1
@@ -1,5 +1,5 @@
1name: Feature Request ✨1name: Feature Request ✨
2description: Suggest an idea for future development of this project2description: Suggest an idea for future development of this project. Please use English only.
3title: '[FEATURE_REQUEST] <title>'3title: '[FEATURE_REQUEST] <title>'
4labels: ['🦄 Feature Request']4labels: ['🦄 Feature Request']
55
.github/pull_request_template.md+5 -0
@@ -0,0 +1,5 @@
1<!-- Put X in the box below to confirm -->
2
3## Checklist:
4
5- [ ] I have read the [Contributing guidelines](https://github.com/SillyTavern/SillyTavern/blob/release/CONTRIBUTING.md).
.npmignore+3 -0
@@ -8,3 +8,6 @@ secrets.json
8/data8/data
9/cache9/cache
10access.log10access.log
11.github
12.vscode
13.git
CONTRIBUTING.md+32 -0
@@ -0,0 +1,32 @@
1# How to contribute to SillyTavern
2
3## Setting up the dev environment
4
51. Required software: git and node.
62. Recommended editor: Visual Studio Code.
73. You can also use GitHub Codespaces which sets up everything for you.
8
9## Getting the code ready
10
111. Register a GitHub account.
122. Fork this repository under your account.
133. Clone the fork onto your machine.
144. Open the cloned repository in the code editor.
155. Create a git branch (recommended).
166. Make your changes and test them locally.
177. Commit the changes and push the branch to the remote repo.
188. Go to GitHub, and open a pull request, targeting the upstream branch.
19
20## Contribution guidelines
21
221. 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.
262. 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.
273. 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.
314. Project maintainers will test and can change your code before merging.
325. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
default/content/index.json+4 -0
@@ -20,6 +20,10 @@
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 },
default/content/themes/Azure.json+35 -0
@@ -0,0 +1,35 @@
1{
2 "name": "Azure",
3 "blur_strength": 11,
4 "main_text_color": "rgba(171, 198, 223, 1)",
5 "italics_text_color": "rgba(255, 255, 255, 1)",
6 "underline_text_color": "rgba(188, 231, 207, 1)",
7 "quote_text_color": "rgba(111, 133, 253, 1)",
8 "blur_tint_color": "rgba(23, 30, 33, 0.61)",
9 "chat_tint_color": "rgba(23, 23, 23, 0)",
10 "user_mes_blur_tint_color": "rgba(0, 28, 174, 0.2)",
11 "bot_mes_blur_tint_color": "rgba(0, 13, 57, 0.22)",
12 "shadow_color": "rgba(0, 0, 0, 1)",
13 "shadow_width": 5,
14 "border_color": "rgba(0, 0, 0, 0.5)",
15 "font_scale": 1,
16 "fast_ui_mode": false,
17 "waifuMode": false,
18 "avatar_style": 1,
19 "chat_display": 1,
20 "noShadows": false,
21 "chat_width": 50,
22 "timer_enabled": true,
23 "timestamps_enabled": true,
24 "timestamp_model_icon": false,
25 "mesIDDisplay_enabled": true,
26 "message_token_count_enabled": false,
27 "expand_message_actions": false,
28 "enableZenSliders": false,
29 "enableLabMode": false,
30 "hotswap_enabled": true,
31 "custom_css": "",
32 "bogus_folders": false,
33 "reduced_motion": false,
34 "compact_input_area": false
35}
package-lock.json+2 -2
@@ -1,12 +1,12 @@
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": {
package.json+1 -1
@@ -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",
public/css/loader.css+1 -1
@@ -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}
1818
public/css/popup-safari-fix.css+5 -3
@@ -1,8 +1,10 @@
1/* iPhone copium land */1/* iPhone copium land */
2@media screen and (max-width: 1000px) {2body.safari .popup .popup-body:has(.maximized_textarea) {
3 .ios .popup .popup-body {3 height: 100%;
4}
5
6body.safari .popup .popup-body {
4 height: fit-content;7 height: fit-content;
5 max-height: 90vh;8 max-height: 90vh;
6 max-height: 90svh;9 max-height: 90svh;
7}10}
8}
public/index.html+1 -1
@@ -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>
public/locales/ja-jp.json+55 -55
@@ -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": "プロンプトを挿入",
public/locales/zh-cn.json+13 -4
@@ -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": "显示为",
public/script.js+29 -11
@@ -227,7 +227,7 @@ import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, de
227import { initPresetManager } from './scripts/preset-manager.js';227import { initPresetManager } from './scripts/preset-manager.js';
228import { MacrosParser, evaluateMacros } from './scripts/macros.js';228import { MacrosParser, evaluateMacros } from './scripts/macros.js';
229import { currentUser, setUserControls } from './scripts/user.js';229import { currentUser, setUserControls } from './scripts/user.js';
230import { POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';230import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
231import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';231import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
232import { ScraperManager } from './scripts/scrapers.js';232import { ScraperManager } from './scripts/scrapers.js';
233import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';233import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
@@ -520,6 +520,7 @@ const chatElement = $('#chat');
520let dialogueResolve = null;520let dialogueResolve = null;
521let dialogueCloseStop = false;521let dialogueCloseStop = false;
522export let chat_metadata = {};522export let chat_metadata = {};
523/** @type {StreamingProcessor} */
523export let streamingProcessor = null;524export let streamingProcessor = null;
524let crop_data = undefined;525let crop_data = undefined;
525let is_delete_mode = false;526let is_delete_mode = false;
@@ -837,6 +838,7 @@ export let main_api;// = "kobold";
837//novel settings838//novel settings
838export let novelai_settings;839export let novelai_settings;
839export let novelai_setting_names;840export let novelai_setting_names;
841/** @type {AbortController} */
840let abortController;842let abortController;
841843
842//css844//css
@@ -4381,6 +4383,25 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4381}4383}
43824384
4383/**4385/**
4386 * Stops the generation and any streaming if it is currently running.
4387 */
4388export 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 messages4406 * @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 * @returns7187 * @returns {Promise<any>} A promise that resolves when the popup is closed.
7188 * @deprecated Use `callGenericPopup` instead.
7167 */7189 */
7168export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {7190export 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 extensions7872 * @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};
78547879
@@ -10342,15 +10367,7 @@ jQuery(async function () {
10342 });10367 });
1034310368
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 });
1035510372
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 () {
1083910856
10840 initCustomSelectedSamplers();10857 initCustomSelectedSamplers();
10841});10858});
10859
public/scripts/RossAscends-mods.js+8 -2
@@ -725,8 +725,14 @@ export function initRossMods() {
725 RA_autoconnect();725 RA_autoconnect();
726 }726 }
727727
728 if (getParsedUA()?.os?.name === 'iOS') {728 const userAgent = getParsedUA();
729 document.body.classList.add('ios');729 console.debug('User Agent', userAgent);
730 const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
731 const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
732 const isIOS = userAgent?.os?.name === 'iOS';
733
734 if (isIOS || isMobileSafari || isDesktopSafari) {
735 document.body.classList.add('safari');
730 }736 }
731737
732 $('#main_api').change(function () {738 $('#main_api').change(function () {
public/scripts/chats.js+1 -1
@@ -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');
public/scripts/dynamic-styles.js+1 -1
@@ -154,7 +154,7 @@ export function initDynamicStyles() {
154 // Process all stylesheets on initial load154 // 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 }
public/scripts/extensions/stable-diffusion/index.js+8 -8
@@ -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));
30223022
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(`{
public/scripts/group-chats.js+29 -3
@@ -178,8 +178,37 @@ async function loadGroupChat(chatId) {
178 return [];178 return [];
179}179}
180180
181async 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
181export async function getGroupChat(groupId, reload = false) {202export 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 }
221249
222 if (group) {
223 let metadata = group.chat_metadata ?? {};250 let metadata = group.chat_metadata ?? {};
224 updateChatMetadata(metadata, true);251 updateChatMetadata(metadata, true);
225 }
226252
227 if (reload) {253 if (reload) {
228 select_group_chats(groupId, true);254 select_group_chats(groupId, true);
public/scripts/loader.js+1 -3
@@ -1,7 +1,5 @@
1import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';1import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
22
3const ELEMENT_ID = 'loader';
4
5/** @type {Popup} */3/** @type {Popup} */
6let loaderPopup;4let loaderPopup;
75
@@ -31,7 +29,7 @@ export async function hideLoader() {
31 return new Promise((resolve) => {29 return new Promise((resolve) => {
32 // Spinner blurs/fades out30 // 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 JS33 // 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();
public/scripts/macros.js+2 -2
@@ -254,7 +254,7 @@ function getCurrentSwipeId() {
254 // For swipe macro, we are accepting using the message that is currently being swiped254 // 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}
259259
260/**260/**
@@ -401,7 +401,7 @@ function timeDiffReplace(input) {
401 const time2 = moment(matchPart2);401 const time2 = moment(matchPart2);
402402
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 });
406406
407 return output;407 return output;
public/scripts/openai.js+1 -1
@@ -689,7 +689,7 @@ function formatWorldInfo(value) {
689 return '';689 return '';
690 }690 }
691691
692 if (!oai_settings.wi_format) {692 if (!oai_settings.wi_format.trim()) {
693 return value;693 return value;
694 }694 }
695695
public/scripts/power-user.js+55 -1
@@ -40,7 +40,7 @@ import { tokenizers } from './tokenizers.js';
40import { BIAS_CACHE } from './logit-bias.js';40import { BIAS_CACHE } from './logit-bias.js';
41import { renderTemplateAsync } from './templates.js';41import { renderTemplateAsync } from './templates.js';
4242
43import { countOccurrences, debounce, delay, download, getFileText, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';43import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
44import { FILTER_TYPES } from './filters.js';44import { FILTER_TYPES } from './filters.js';
45import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';45import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
46import { SlashCommand } from './slash-commands/SlashCommand.js';46import { 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};
339341
340const contextControls = [342const contextControls = [
@@ -2105,6 +2107,9 @@ export function fuzzySearchGroups(searchValue) {
2105 */2107 */
2106export function renderStoryString(params) {2108export 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 escaping2113 // 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 });
21102115
@@ -2132,6 +2137,55 @@ export function renderStoryString(params) {
2132 }2137 }
2133}2138}
21342139
2140/**
2141 * Validate the story string for possible warnings or issues
2142 *
2143 * @param {string} storyString - The story string
2144 * @param {Object} params - The story string parameters
2145 */
2146function 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
2135const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);2189const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
2136const compareFunc = (first, second) => {2190const compareFunc = (first, second) => {
2137 const a = first[power_user.sort_field];2191 const a = first[power_user.sort_field];
public/scripts/slash-commands.js+19 -0
@@ -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: [
public/scripts/textgen-settings.js+13 -6
@@ -263,6 +263,8 @@ export const setting_names = [
263 'bypass_status_check',263 'bypass_status_check',
264];264];
265265
266const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
267
266export function validateTextGenUrl() {268export function validateTextGenUrl() {
267 const selector = SERVER_INPUTS[settings.type];269 const selector = SERVER_INPUTS[settings.type];
268270
@@ -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}
10471049
1050function isDynamicTemperatureSupported() {
1051 return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
1052}
1053
1048function getLogprobsNumber() {1054function 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() {
10551061
1056export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {1062export 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();
10591066
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,
public/scripts/utils.js+1 -1
@@ -803,7 +803,7 @@ export function getImageSizeFromDataURL(dataUrl) {
803803
804export function getCharaFilename(chid) {804export 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;
807807
808 if (fileName) {808 if (fileName) {
809 return fileName.replace(/\.[^/.]+$/, '');809 return fileName.replace(/\.[^/.]+$/, '');
server.js+43 -14
@@ -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};
617613
618/**614/**
@@ -631,16 +627,6 @@ async function loadPlugins() {
631 }627 }
632}628}
633629
634if (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 window631 * Set the title of the terminal window
646 * @param {string} title Desired title for the window632 * @param {string} title Desired title for the window
@@ -654,10 +640,53 @@ function setWindowTitle(title) {
654 }640 }
655}641}
656642
643/**
644 * Prints an error message and exits the process if necessary
645 * @param {string} message The error message to print
646 * @returns {void}
647 */
648function 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
658async 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 server685// User storage module needs to be initialized before starting the server
658userModule.initUserStorage(dataRoot)686userModule.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) {
src/users.js+15 -14
@@ -681,27 +681,27 @@ async function createBackupArchive(handle, response) {
681}681}
682682
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 */
687async function checkAccountsProtection() {687async 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) {
698 console.warn(color.red('The following admin users are not password protected:'));
699 unprotectedUsers.forEach(x => console.warn(color.yellow(x.handle)));
700 console.log();
701 console.warn('Please disable them or set a password in the admin panel.');
702 console.log();
703 await delay(3000);
704}696}
697
698/**
699 * Gets all of the enabled users.
700 * @returns {Promise<User[]>}
701 */
702async function getAllEnabledUsers() {
703 const users = await getAllUsers();
704 return users.filter(x => x.enabled);
705}705}
706706
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};