diff --git a/public/locales/ar-sa.json b/public/locales/ar-sa.json
index d722b70c0..a227372b2 100644
--- a/public/locales/ar-sa.json
+++ b/public/locales/ar-sa.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "موضع",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "موضع الحقن. بجوار المطالبات الأخرى (نسبية) أو داخل الدردشة (مطلقة).",
"prompt_manager_relative": "نسبي",
- "prompt_manager_absolute": "مطلق",
"prompt_manager_depth": "عمق",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "عمق الحقن. 0 = بعد الرسالة الأخيرة، 1 = قبل الرسالة الأخيرة، الخ.",
"Prompt": "موضوع",
diff --git a/public/locales/de-de.json b/public/locales/de-de.json
index 4551322da..fcadcbfbc 100644
--- a/public/locales/de-de.json
+++ b/public/locales/de-de.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Position",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Injektionsposition. Neben anderen Eingabeaufforderungen (relativ) oder im Chat (absolut).",
"prompt_manager_relative": "Relativ",
- "prompt_manager_absolute": "Absolut",
"prompt_manager_depth": "Tiefe",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Injektionstiefe. 0 = nach der letzten Nachricht, 1 = vor der letzten Nachricht usw.",
"Prompt": "Aufforderung",
diff --git a/public/locales/es-es.json b/public/locales/es-es.json
index d4b54fa7a..36cc002bc 100644
--- a/public/locales/es-es.json
+++ b/public/locales/es-es.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Posición",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Posición de inyección. Junto a otras indicaciones (relativa) o en el chat (absoluta).",
"prompt_manager_relative": "Relativo",
- "prompt_manager_absolute": "Absoluto",
"prompt_manager_depth": "Profundidad",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Profundidad de inyección. 0 = después del último mensaje, 1 = antes del último mensaje, etc.",
"Prompt": "Indicar",
diff --git a/public/locales/fr-fr.json b/public/locales/fr-fr.json
index 84f502e8c..1b30fda0e 100644
--- a/public/locales/fr-fr.json
+++ b/public/locales/fr-fr.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Position",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Position d'injection. À côté d’autres invites (relatives) ou dans le chat (absolues).",
"prompt_manager_relative": "Relatif",
- "prompt_manager_absolute": "Absolu",
"prompt_manager_depth": "Profondeur",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Profondeur d'injection. 0 = après le dernier message, 1 = avant le dernier message, etc.",
"Prompt": "Inciter",
diff --git a/public/locales/is-is.json b/public/locales/is-is.json
index 1d5587e0c..45e5a3095 100644
--- a/public/locales/is-is.json
+++ b/public/locales/is-is.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Staða",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Inndælingarstaða. Við hliðina á öðrum leiðbeiningum (afstætt) eða í spjalli (algert).",
"prompt_manager_relative": "Aðstandandi",
- "prompt_manager_absolute": "Algjört",
"prompt_manager_depth": "Dýpt",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Inndælingardýpt. 0 = eftir síðustu skilaboð, 1 = fyrir síðustu skilaboð o.s.frv.",
"Prompt": "Ábending",
diff --git a/public/locales/it-it.json b/public/locales/it-it.json
index 18f0a3fa9..ce0f9f03a 100644
--- a/public/locales/it-it.json
+++ b/public/locales/it-it.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Posizione",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Posizione di iniezione. Accanto ad altri suggerimenti (relativo) o in chat (assoluto).",
"prompt_manager_relative": "Parente",
- "prompt_manager_absolute": "Assoluto",
"prompt_manager_depth": "Profondità",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Profondità di iniezione. 0 = dopo l'ultimo messaggio, 1 = prima dell'ultimo messaggio, ecc.",
"Prompt": "Prompt",
diff --git a/public/locales/ja-jp.json b/public/locales/ja-jp.json
index 0af4081ee..2e18796f4 100644
--- a/public/locales/ja-jp.json
+++ b/public/locales/ja-jp.json
@@ -1,6 +1,6 @@
{
"Favorite": "お気に入り",
- "Tag": "鬼ごっこ",
+ "Tag": "タグ",
"Duplicate": "重複",
"Persona": "ペルソナ",
"Delete": "削除",
@@ -29,13 +29,13 @@
"Text Adventure": "テキストアドベンチャー",
"response legth(tokens)": "応答の長さ(トークン数)",
"Streaming": "ストリーミング",
- "Streaming_desc": "生成された応答をビット単位で表示します。",
+ "Streaming_desc": "生成された応答を逐次表示します。",
"context size(tokens)": "コンテキストのサイズ(トークン数)",
"unlocked": "ロック解除",
"Only enable this if your model supports context sizes greater than 4096 tokens": "モデルが4096トークンを超えるコンテキストサイズをサポートしている場合にのみ有効にします",
"Max prompt cost:": "最大プロンプトコスト:",
- "Display the response bit by bit as it is generated.": "生成されるたびに、応答をビットごとに表示します。",
- "When this is off, responses will be displayed all at once when they are complete.": "この機能がオフの場合、応答は完全になるとすぐにすべて一度に表示されます。",
+ "Display the response bit by bit as it is generated.": "生成されるたびに、応答を逐次表示します。",
+ "When this is off, responses will be displayed all at once when they are complete.": "この機能がオフの場合、応答は完全に生成されたときに一度ですべて表示されます。",
"Temperature": "温度",
"rep.pen": "繰り返しペナルティ",
"Rep. Pen. Range.": "繰り返しペナルティの範囲",
@@ -46,10 +46,10 @@
"Phrase Repetition Penalty": "フレーズの繰り返しペナルティ",
"Off": "オフ",
"Very light": "非常に軽い",
- "Light": "ライト",
- "Medium": "ミディアム",
- "Aggressive": "攻撃的",
- "Very aggressive": "非常に攻撃的",
+ "Light": "軽め",
+ "Medium": "中程度",
+ "Aggressive": "強め",
+ "Very aggressive": "非常に強い",
"Unlocked Context Size": "ロック解除されたコンテキストサイズ",
"Unrestricted maximum value for the context slider": "コンテキストスライダーの制限なしの最大値",
"Context Size (tokens)": "コンテキストサイズ(トークン数)",
@@ -132,7 +132,7 @@
"CFG Scale": "CFGスケール",
"Negative Prompt": "ネガティブプロンプト",
"Add text here that would make the AI generate things you don't want in your outputs.": "出力に望ましくないものを生成させるAIを作成するテキストをここに追加します。",
- "Used if CFG Scale is unset globally, per chat or character": "CFGスケールがグローバル、チャットごと、または文字ごとに設定されていない場合に使用されます",
+ "Used if CFG Scale is unset globally, per chat or character": "CFGスケールがグローバル、チャットごと、またはキャラクターごとに設定されていない場合に使用されます",
"Mirostat Tau": "Mirostat Tau",
"Mirostat LR": "ミロスタットLR",
"Min Length": "最小長",
@@ -214,7 +214,7 @@
"Sampler Priority": "サンプラー優先度",
"Ooba only. Determines the order of samplers.": "Oobaのみ。サンプラーの順序を決定します。",
"Character Names Behavior": "キャラクター名の動作",
- "Helps the model to associate messages with characters.": "モデルがメッセージを文字に関連付けるのに役立ちます。",
+ "Helps the model to associate messages with characters.": "モデルがメッセージをキャラクターに関連付けるのに役立ちます。",
"None": "なし",
"character_names_none": "グループと過去のペルソナを除きます。それ以外の場合は、プロンプトに名前を必ず入力してください。",
"Don't add character names.": "キャラクター名を追加しないでください。",
@@ -222,7 +222,7 @@
"character_names_completion": "制限事項: ラテン英数字とアンダースコアのみ。すべてのソースで機能するわけではありません。特に、Claude、MistralAI、Google では機能しません。",
"Add character names to completion objects.": "完了オブジェクトにキャラクター名を追加します。",
"Message Content": "メッセージ内容",
- "Prepend character names to message contents.": "メッセージの内容の先頭に文字名を追加します。",
+ "Prepend character names to message contents.": "メッセージの内容の先頭にキャラクター名を追加します。",
"Continue Postfix": "ポストフィックスの継続",
"The next chunk of the continued message will be appended using this as a separator.": "継続メッセージの次のチャンクは、これを区切り文字として使用して追加されます。",
"Space": "空間",
@@ -270,9 +270,9 @@
"Text Completion": "テキスト補完",
"Chat Completion": "チャット完了",
"NovelAI": "NovelAI",
- "KoboldAI Horde": "KoboldAIホルド",
+ "KoboldAI Horde": "KoboldAI Horde",
"KoboldAI": "KoboldAI",
- "Avoid sending sensitive information to the Horde.": "ホルドに機密情報を送信しないでください。",
+ "Avoid sending sensitive information to the Horde.": "Hordeに機密情報を送信しないでください。",
"Review the Privacy statement": "プライバシー声明を確認する",
"Register a Horde account for faster queue times": "キュー待ち時間を短縮するためにHordeアカウントを登録する",
"Learn how to contribute your idle GPU cycles to the Horde": "アイドルのGPUサイクルをホルドに貢献する方法を学びます",
@@ -633,7 +633,7 @@
"Tags_as_Folders_desc": "最近の変更: タグは、タグ管理メニューでフォルダーとしてマークされて初めてフォルダーとして表示されます。ここをクリックして表示します。",
"Character Handling": "キャラクター処理",
"If set in the advanced character definitions, this field will be displayed in the characters list.": "高度なキャラクター定義で設定されている場合、このフィールドがキャラクターリストに表示されます。",
- "Char List Subheader": "文字リストサブヘッダー",
+ "Char List Subheader": "キャラクターリストサブヘッダー",
"Character Version": "キャラクターバージョン",
"Created by": "作成者",
"Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "曖昧な一致を使用し、名前の部分文字列ではなく、すべてのデータフィールドでリスト内のキャラクターを検索する",
@@ -729,8 +729,8 @@
"Automatically hide details": "詳細を自動的に非表示にする",
"Determines how entries are found for autocomplete.": "オートコンプリートのエントリの検索方法を決定します。",
"Autocomplete Matching": "マッチング",
- "Starts with": "始まりは",
- "Includes": "含まれるもの",
+ "Starts with": "前方一致",
+ "Includes": "部分一致",
"Fuzzy": "ファジー",
"Sets the style of the autocomplete.": "オートコンプリートのスタイルを設定します。",
"Autocomplete Style": "スタイル",
@@ -755,8 +755,8 @@
"Auto-select": "自動選択",
"System Backgrounds": "システムの背景",
"Chat Backgrounds": "チャットの背景",
- "bg_chat_hint_1": "チャットの背景は、",
- "bg_chat_hint_2": "拡張子がここに表示されます。",
+ "bg_chat_hint_1": "",
+ "bg_chat_hint_2": "拡張機能で生成したチャットの背景はここに表示されます。",
"Extensions": "拡張機能",
"Notify on extension updates": "拡張機能の更新時に通知",
"Manage extensions": "拡張機能を管理",
@@ -770,9 +770,9 @@
"How do I use this?": "これをどのように使用しますか?",
"Click for stats!": "統計をクリック!",
"Usage Stats": "使用状況統計",
- "Backup your personas to a file": "キャラクタをファイルにバックアップします",
+ "Backup your personas to a file": "キャラクターをファイルにバックアップします",
"Backup": "バックアップ",
- "Restore your personas from a file": "ファイルからキャラクタを復元します",
+ "Restore your personas from a file": "ファイルからキャラクターを復元します",
"Restore": "復元",
"Create a dummy persona": "ダミーのペルソナを作成",
"Create": "作成",
@@ -839,7 +839,7 @@
"Describe your character's physical and mental traits here.": "ここにキャラクターの身体的および精神的特徴を説明します。",
"First message": "最初のメッセージ",
"Click to set additional greeting messages": "追加の挨拶メッセージを設定するにはクリック",
- "Alt. Greetings": "挨拶",
+ "Alt. Greetings": "他の挨拶",
"This will be the first message from the character that starts every chat.": "これはすべてのチャットを開始するキャラクターからの最初のメッセージになります。",
"Group Controls": "グループコントロール",
"Chat Name (Optional)": "チャット名(任意)",
@@ -889,7 +889,7 @@
"popup-button-yes": "はい",
"popup-button-no": "いいえ",
"popup-button-cancel": "キャンセル",
- "popup-button-import": "輸入",
+ "popup-button-import": "インポート",
"Advanced Defininitions": "高度な定義",
"Prompt Overrides": "プロンプトのオーバーライド",
"(For Chat Completion and Instruct Mode)": "(チャット補完と指示モード用)",
@@ -937,7 +937,7 @@
"Type here...": "ここに入力...",
"Chat Lorebook": "チャットロアブック",
"Chat Lorebook for": "チャットロアブック",
- "chat_world_template_txt": "選択したワールド情報はこのチャットにバインドされます。AI の返信を生成する際、\nグローバルおよびキャラクターの伝承書のエントリと結合されます。",
+ "chat_world_template_txt": "選択したワールド情報はこのチャットにバインドされます。AI の返信を生成する際、\nグローバルおよびキャラクターのロアブックのエントリと結合されます。",
"Select a World Info file for": "次のためにワールド情報ファイルを選択",
"Primary Lorebook": "プライマリロアブック",
"A selected World Info will be bound to this character as its own Lorebook.": "選択したワールド情報は、このキャラクターにその独自のロアブックとしてバインドされます。",
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "位置",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "挿入位置。他のプロンプトの隣 (相対) またはチャット内 (絶対)。",
"prompt_manager_relative": "相対的",
- "prompt_manager_absolute": "絶対",
"prompt_manager_depth": "深さ",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "注入の深さ。0 = 最後のメッセージの後、1 = 最後のメッセージの前など。",
"Prompt": "プロンプト",
@@ -1065,9 +1064,9 @@
"Change it later in the 'User Settings' panel.": "後で「ユーザー設定」パネルで変更します。",
"Enable simple UI mode": "シンプルUIモードを有効にする",
"Looking for AI characters?": "AIキャラクターをお探しですか?",
- "onboarding_import": "輸入",
+ "onboarding_import": "インポート",
"from supported sources or view": "サポートされているソースからまたは表示",
- "Sample characters": "サンプル文字",
+ "Sample characters": "サンプルキャラクター",
"Your Persona": "あなたのペルソナ",
"Before you get started, you must select a persona name.": "始める前に、ペルソナ名を選択する必要があります。",
"welcome_message_part_8": "これはいつでも変更可能です。",
@@ -1081,7 +1080,7 @@
"View character card": "キャラクターカードを表示",
"Remove from group": "グループから削除",
"Add to group": "グループに追加",
- "Alternate Greetings": "代わりの挨拶",
+ "Alternate Greetings": "挨拶のバリエーション",
"Alternate_Greetings_desc": "これらは、新しいチャットを開始するときに最初のメッセージにスワイプとして表示されます。\nグループのメンバーは、そのうちの 1 つを選択して会話を開始できます。",
"Alternate Greetings Hint": "ボタンをクリックして始めましょう!",
"(This will be the first message from the character that starts every chat)": "(これはすべてのチャットを開始するキャラクターからの最初のメッセージになります)",
@@ -1118,11 +1117,11 @@
"Will be used as the default CFG options for every chat unless overridden.": "上書きされない限り、すべてのチャットのデフォルトの CFG オプションとして使用されます。",
"CFG Prompt Cascading": "CFG プロンプト カスケード",
"Combine positive/negative prompts from other boxes.": "他のボックスからの肯定的/否定的なプロンプトを組み合わせます。",
- "For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "たとえば、チャット、グローバル、および文字のボックスにチェックを入れると、すべての否定プロンプトがコンマ区切りの文字列に結合されます。",
+ "For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "たとえば、チャット、グローバル、およびキャラクターのボックスにチェックを入れると、すべてのネガティブプロンプトがコンマ区切りの文字列に結合されます。",
"Always Include": "常に含めます",
"Chat Negatives": "チャットのネガティブ",
- "Character Negatives": "性格のマイナス面",
- "Global Negatives": "世界的なマイナス",
+ "Character Negatives": "キャラクターのネガティブ",
+ "Global Negatives": "グローバルネガティブ",
"Custom Separator:": "カスタムセパレーター:",
"Insertion Depth:": "挿入深さ:",
"Token Probabilities": "トークン確率",
@@ -1236,7 +1235,7 @@
"ext_regex_title": "正規表現",
"ext_regex_new_global_script": "+ グローバル",
"ext_regex_new_scoped_script": "+ スコープ付き",
- "ext_regex_import_script": "輸入",
+ "ext_regex_import_script": "インポート",
"ext_regex_global_scripts": "グローバルスクリプト",
"ext_regex_global_scripts_desc": "すべてのキャラクターで使用可能。ローカル設定に保存されます。",
"ext_regex_scoped_scripts": "スコープ付きスクリプト",
@@ -1301,8 +1300,8 @@
"Authentication (optional)": "認証(オプション)",
"Example: username:password": "例: ユーザー名:パスワード",
"Important:": "重要:",
- "sd_auto_auth_warning_1": "SD Web UIを実行する",
- "sd_auto_auth_warning_2": "フラグ! サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
+ "sd_auto_auth_warning_1": "SD Web UIを",
+ "sd_auto_auth_warning_2": "フラグを指定して実行してください! サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
"sd_drawthings_url": "例: {{drawthings_url}}",
"sd_drawthings_auth_txt": "UI で HTTP API スイッチを有効にして DrawThings アプリを実行します。サーバーは SillyTavern ホスト マシンからアクセスできる必要があります。",
"sd_vlad_url": "例: {{vlad_url}}",
@@ -1326,36 +1325,36 @@
"Enhance": "強化する",
"Refine": "リファイン",
"Decrisper": "デクリスパー",
- "Sampling steps": "サンプリング手順 ()",
- "Width": "幅 ()",
- "Height": "身長 ()",
- "Resolution": "解決",
+ "Sampling steps": "サンプリングステップ数",
+ "Width": "幅",
+ "Height": "高さ",
+ "Resolution": "解像度",
"Model": "モデル",
"Sampling method": "サンプリング方法",
"Karras (not all samplers supported)": "Karras (すべてのサンプラーがサポートされているわけではありません)",
"SMEA versions of samplers are modified to perform better at high resolution.": "SMEA バージョンのサンプラーは、高解像度でより優れたパフォーマンスを発揮するように変更されています。",
- "SMEA": "中小企業庁",
+ "SMEA": "SMEA",
"DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.": "SMEA サンプラーの DYN バリアントは、多くの場合、より多様な出力をもたらしますが、非常に高い解像度では失敗する可能性があります。",
"DYN": "ダイナミック",
- "Scheduler": "スケジューラ",
- "Restore Faces": "顔を復元する",
- "Hires. Fix": "雇用。修正",
+ "Scheduler": "スケジューラー",
+ "Restore Faces": "顔の修復",
+ "Hires. Fix": "高解像度補助",
"Upscaler": "アップスケーラー",
- "Upscale by": "高級化",
+ "Upscale by": "アップスケール倍率",
"Denoising strength": "ノイズ除去の強さ",
- "Hires steps (2nd pass)": "採用手順(2回目のパス)",
- "Preset for prompt prefix and negative prompt": "プロンプトプレフィックスと否定プロンプトのプリセット",
+ "Hires steps (2nd pass)": "高解像度でのステップ数",
+ "Preset for prompt prefix and negative prompt": "プロンプトプレフィックスとネガティブプロンプトのプリセット",
"Style": "スタイル",
"Save style": "スタイルを保存",
"Delete style": "スタイルを削除",
- "Common prompt prefix": "一般的なプロンプトプレフィックス",
+ "Common prompt prefix": "共通のプロンプトプレフィックス",
"sd_prompt_prefix_placeholder": "生成されたプロンプトを挿入する場所を指定するには、{prompt}を使用します。",
- "Negative common prompt prefix": "否定の共通プロンプト接頭辞",
- "Character-specific prompt prefix": "文字固有のプロンプトプレフィックス",
+ "Negative common prompt prefix": "共通のネガティブプロンプトプレフィックス",
+ "Character-specific prompt prefix": "キャラクター固有のプロンプトプレフィックス",
"Won't be used in groups.": "グループでは使用されません。",
- "sd_character_prompt_placeholder": "現在選択されているキャラクターを説明する任意の特性。共通のプロンプト プレフィックスの後に追加されます。\n例: 女性、緑の目、茶色の髪、ピンクのシャツ",
- "Character-specific negative prompt prefix": "文字固有の否定プロンプト接頭辞",
- "sd_character_negative_prompt_placeholder": "選択したキャラクターに表示されるべきではない特性。否定の共通プロンプト接頭辞の後に追加されます。\n例: ジュエリー、靴、メガネ",
+ "sd_character_prompt_placeholder": "現在選択されているキャラクターを説明する特徴。共通のプロンプトプレフィックスの後に追加されます。\n例: 女性、緑の目、茶色の髪、ピンクのシャツ",
+ "Character-specific negative prompt prefix": "キャラクター固有のネガティブプロンプトプレフィックス",
+ "sd_character_negative_prompt_placeholder": "選択したキャラクターに表示されるべきではない特徴。共通のネガティブプロンプトプレフィックスの後に追加されます。\n例: ジュエリー、靴、メガネ",
"Shareable": "共有可能",
"Image Prompt Templates": "画像プロンプトテンプレート",
"Vectors Model Warning": "チャットの途中でモデルを変更する場合は、ベクトルを消去することをお勧めします。そうしないと、標準以下の結果になります。",
@@ -1380,22 +1379,22 @@
"Warning:": "警告:",
"This action is irreversible.": "この操作は元に戻せません。",
"Type the user's handle below to confirm:": "確認するには、以下のユーザーのハンドルを入力してください。",
- "Import Characters": "文字をインポートする",
+ "Import Characters": "キャラクターをインポートする",
"Enter the URL of the content to import": "インポートするコンテンツの URL を入力します",
"Supported sources:": "サポートされているソース:",
- "char_import_1": "チャブキャラクター(直接リンクまたはID)",
+ "char_import_1": "Chub キャラクター (直接リンクまたはID)",
"char_import_example": "例:",
- "char_import_2": "チャブの伝承集 (直接リンクまたは ID)",
+ "char_import_2": "Chub ロアブック (直接リンクまたは ID)",
"char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)",
"char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)",
"char_import_5": "AICharacterCard.com キャラクター (直接リンクまたは ID)",
"char_import_6": "直接PNGリンク(参照",
"char_import_7": "許可されたホストの場合)",
"char_import_8": "RisuRealm キャラクター (直接リンク)",
- "Supports importing multiple characters.": "複数の文字のインポートをサポートします。",
+ "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。",
"Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。",
- "Export for character": "文字のエクスポート",
- "Export prompts for this character, including their order.": "この文字のプロンプトを順序も含めてエクスポートします。",
+ "Export for character": "キャラクターのエクスポート",
+ "Export prompts for this character, including their order.": "このキャラクターのプロンプトを順序も含めてエクスポートします。",
"Export all": "すべてをエクスポート",
"Export all your prompts to a file": "すべてのプロンプトをファイルにエクスポートする",
"Insert prompt": "プロンプトを挿入",
diff --git a/public/locales/ko-kr.json b/public/locales/ko-kr.json
index 1c56ff3cc..cf04e38ae 100644
--- a/public/locales/ko-kr.json
+++ b/public/locales/ko-kr.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "위치",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "주입 위치. 다른 프롬프트 옆(상대적) 또는 채팅 내(절대적).",
"prompt_manager_relative": "상대적인",
- "prompt_manager_absolute": "순수한",
"prompt_manager_depth": "깊이",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "주입 깊이. 0 = 마지막 메시지 뒤, 1 = 마지막 메시지 앞 등",
"Prompt": "프롬프트",
diff --git a/public/locales/nl-nl.json b/public/locales/nl-nl.json
index f102ee93e..256eda091 100644
--- a/public/locales/nl-nl.json
+++ b/public/locales/nl-nl.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Positie",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Injectiepositie. Naast andere prompts (relatief) of in-chat (absoluut).",
"prompt_manager_relative": "Familielid",
- "prompt_manager_absolute": "Absoluut",
"prompt_manager_depth": "Diepte",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Injectiediepte. 0 = na het laatste bericht, 1 = voor het laatste bericht, etc.",
"Prompt": "Prompt",
diff --git a/public/locales/pt-pt.json b/public/locales/pt-pt.json
index caf918890..77bbdbafd 100644
--- a/public/locales/pt-pt.json
+++ b/public/locales/pt-pt.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Posição",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Posição de injeção. Ao lado de outras solicitações (relativas) ou no chat (absolutas).",
"prompt_manager_relative": "Relativo",
- "prompt_manager_absolute": "Absoluto",
"prompt_manager_depth": "Profundidade",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Profundidade de injeção. 0 = após a última mensagem, 1 = antes da última mensagem, etc.",
"Prompt": "Prompt",
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json
index 878433ca0..02d19207a 100644
--- a/public/locales/ru-ru.json
+++ b/public/locales/ru-ru.json
@@ -163,9 +163,9 @@
"View hidden API keys": "Посмотреть скрытые API-ключи",
"Advanced Formatting": "Расширенное форматирование",
"Context Template": "Шаблон контекста",
- "Replace Macro in Custom Stopping Strings": "Заменить макросы в пользовательских стоп-строках",
+ "Replace Macro in Custom Stopping Strings": "Заменять макросы в пользовательских стоп-строках",
"Story String": "Строка истории",
- "Example Separator": "Пример разделителя",
+ "Example Separator": "Разделитель примеров сообщений",
"Chat Start": "Начало чата",
"Activation Regex": "Regex для активации",
"Instruct Mode": "Режим Instruct",
@@ -187,7 +187,7 @@
"Misc. Settings": "Доп. настройки",
"Auto-Continue": "Авто-продолжение",
"Collapse Consecutive Newlines": "Сворачивать последовательные новые строки",
- "Allow for Chat Completion APIs": "Разрешить для API Chat Completion",
+ "Allow for Chat Completion APIs": "Разрешить для Chat Completion API",
"Target length (tokens)": "Целевая длина (в токенах)",
"World Info": "Информация о мире",
"Scan Depth": "Глубина сканирования",
@@ -377,7 +377,7 @@
"Log prompts to console": "Выводить промпты в консоль",
"Never resize avatars": "Не менять размер аватарок",
"Show avatar filenames": "Показывать названия файлов аватарок",
- "Import Card Tags": "Импорт тегов карточки",
+ "Import Card Tags": "Импортировать теги карточки",
"Confirm message deletion": "Подтверждение удаления сообщений",
"Spoiler Free Mode": "Режим без спойлеров",
"Auto-swipe": "Автоматические свайпы",
@@ -1025,7 +1025,6 @@
"prompt_manager_position": "Точка инжекта",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Как рассчитывать позицию для инжекта. Она может располагаться по отношению к другим промптам (относительная) либо по отношению к чату (абсолютная).",
"prompt_manager_relative": "Относительная",
- "prompt_manager_absolute": "Абсолютная",
"prompt_manager_depth": "Глубина",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.",
"The prompt to be sent.": "Отправляемый ИИ промпт.",
@@ -1274,7 +1273,7 @@
"openrouter_force_instruct": "This option is outdated and will be removed in the future. To use instruct formatting, please switch to OpenRouter under Text Completion API instead.",
"Force_Instruct_Mode_formatting_Description": "If both Instruct Mode and this are enabled, the prompt will be formatted by SillyTavern using the current\n advanced formatting settings (except instruct System Prompt). If disabled, the prompt will be formatted by OpenRouter.",
"Clear your cookie": "Clear your cookie",
- "Add Chat Start and Example Separator to a list of stopping strings.": "Add Chat Start and Example Separator to a list of stopping strings.",
+ "Add Chat Start and Example Separator to a list of stopping strings.": "Использовать Начало чата и Разделитель примеров сообщений в качестве стоп-строк.",
"context_allow_jailbreak": "Если в карточке есть джейлбрейк И ПРИ ЭТОМ включена опция \"Приоритет джейлбрейку из карточки персонажа\", то этот джейлбрейк добавляется в конец промпта.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.",
"Context Order": "Context Order",
"Summary": "Summary",
@@ -1636,5 +1635,10 @@
"Change Password": "Сменить пароль",
"Reset Code:": "Reset Code:",
"Click _space": "Нажмите ",
- "Alternate Greeting #": "Вариант #"
+ "Alternate Greeting #": "Вариант #",
+ "Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.": "Выберите, какие действия следует предпринять по отношению к тегам импортируемой карточки. При выборе опции \"Спрашивать\" вы будете решать это индивидуально для каждой карточки.",
+ "Ask": "Спрашивать",
+ "tag_import_all": "Все",
+ "Existing": "Только существующие",
+ "tag_import_none": "Не импортировать"
}
diff --git a/public/locales/uk-ua.json b/public/locales/uk-ua.json
index ec9c933fa..bd2ae43f1 100644
--- a/public/locales/uk-ua.json
+++ b/public/locales/uk-ua.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Позиція",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Позиція ін'єкції. Поруч з іншими підказками (відносні) або в чаті (абсолютні).",
"prompt_manager_relative": "Відносна",
- "prompt_manager_absolute": "Абсолютний",
"prompt_manager_depth": "Глибина",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глибина ін'єкції. 0 = після останнього повідомлення, 1 = перед останнім повідомленням тощо.",
"Prompt": "Запит",
diff --git a/public/locales/vi-vn.json b/public/locales/vi-vn.json
index bfcfb4dd3..d7399ea45 100644
--- a/public/locales/vi-vn.json
+++ b/public/locales/vi-vn.json
@@ -1023,7 +1023,6 @@
"prompt_manager_position": "Chức vụ",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "Vị trí tiêm. Bên cạnh các lời nhắc khác (tương đối) hoặc trong trò chuyện (tuyệt đối).",
"prompt_manager_relative": "Liên quan đến",
- "prompt_manager_absolute": "tuyệt đối",
"prompt_manager_depth": "Chiều sâu",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Độ sâu phun. 0 = sau tin nhắn cuối cùng, 1 = trước tin nhắn cuối cùng, v.v.",
"Prompt": "Đề xuất",
diff --git a/public/locales/zh-cn.json b/public/locales/zh-cn.json
index a7f7ae597..1e236dcf6 100644
--- a/public/locales/zh-cn.json
+++ b/public/locales/zh-cn.json
@@ -217,6 +217,7 @@
"Character Names Behavior": "角色名称行为",
"Helps the model to associate messages with characters.": "有助于模型将消息与角色关联起来。",
"None": "无",
+ "tag_import_none": "无",
"character_names_none": "群聊和过去的角色除外。否则,请确保在提示词中提供了姓名。",
"Don't add character names.": "不添加角色名称。",
"Completion": "补全对象",
@@ -333,6 +334,9 @@
"vLLM API key": "vLLM API 密钥",
"Example: 127.0.0.1:8000": "例如:http://127.0.0.1:8000",
"vLLM Model": "vLLM 模型",
+ "HuggingFace Token": "HuggingFace 代币",
+ "Endpoint URL": "端点 URL",
+ "Example: https://****.endpoints.huggingface.cloud": "例如:https://****.endpoints.huggingface.cloud",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(用于OpenAI API的包装器)",
"Aphrodite API key": "Aphrodite API 密钥",
"Aphrodite Model": "Aphrodite 模型",
@@ -418,6 +422,8 @@
"Prompt Post-Processing": "提示词后处理",
"Applies additional processing to the prompt before sending it to the API.": "在将提示词发送到 API 之前对其进行额外处理。",
"prompt_post_processing_none": "未选择",
+ "01.AI API Key": "01.AI API密钥",
+ "01.AI Model": "01.AI模型",
"Additional Parameters": "附加参数",
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "通过发送简短的测试消息验证您的API连接。请注意,您将因此而消耗额度!",
"Test Message": "发送测试消息",
@@ -653,7 +659,7 @@
"Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.": "定义在导入卡片时应选择哪种操作来导入其列出的标签。“询问”将始终显示对话框。",
"Import Card Tags": "导入卡片标签",
"Ask": "询问",
- "All": "全部",
+ "tag_import_all": "全部",
"Existing": "现存的",
"Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "使用模糊匹配,在列表中通过所有数据字段搜索角色,而不仅仅是名称子字符串",
"Advanced Character Search": "高级角色搜索",
@@ -1032,6 +1038,8 @@
"Sticky": "粘性",
"Entries with a cooldown can't be activated N messages after being triggered.": "具有冷却时间的条目在触发后 N 条消息内无法被激活。",
"Cooldown": "冷却",
+ "Entries with a delay can't be activated until there are N messages present in the chat.": "直到聊天中出现 N 条消息时,延迟的条目才能被激活。",
+ "Delay": "延迟",
"Filter to Character(s)": "应用到角色",
"Character Exclusion": "反选角色",
"-- Characters not found --": "-- 未找到角色 --",
@@ -1048,7 +1056,6 @@
"prompt_manager_position": "位置",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "注入位置。其他提示词旁边(相对)或在聊天中(绝对)。",
"prompt_manager_relative": "相对",
- "prompt_manager_absolute": "绝对",
"prompt_manager_depth": "深度",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "注入深度。0 = 在最后一条消息之后,1 = 在最后一条消息之前,等等。",
"Prompt": "提示词",
@@ -1076,6 +1083,7 @@
"Move message up": "将消息上移",
"Move message down": "将消息下移",
"Enlarge": "放大",
+ "Caption": "标题",
"Welcome to SillyTavern!": "欢迎来到 SillyTavern!",
"welcome_message_part_1": "阅读",
"welcome_message_part_2": "官方文档",
@@ -1112,10 +1120,6 @@
"alternate_greetings_hint_2": "按钮即可开始!",
"Alternate Greeting #": "额外问候语 #",
"(This will be the first message from the character that starts every chat)": "(这将是角色在每次聊天开始时发送的第一条消息)",
- "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
- "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
- "Always forbidden": "始终禁止",
- "Always allowed": "始终允许",
"View contents": "查看内容",
"Remove the file": "删除文件",
"Unique to this chat": "此聊天独有",
@@ -1239,6 +1243,7 @@
"Message Template": "消息模板",
"(use _space": "(使用",
"macro)": "宏指令)",
+ "Automatically caption images": "自动为图像添加标题",
"Edit captions before saving": "保存前编辑标题",
"Character Expressions": "角色表情",
"Translate text to English before classification": "分类之前将文本翻译成英文",
@@ -1578,6 +1583,10 @@
"Warning:": "警告:",
"This action is irreversible.": "此操作不可逆。",
"Type the user's handle below to confirm:": "在下面输入用户的名称以确认:",
+ "Forbid Media Override explanation": "当前角色/群组在聊天中使用外部媒体的能力。",
+ "Forbid Media Override subtitle": "媒体:图像、视频、音频。外部:不在本地服务器上托管。",
+ "Always forbidden": "始终禁止",
+ "Always allowed": "始终允许",
"help_format_1": "文本格式化命令:",
"help_format_2": "*文本*",
"help_format_3": "显示为",
diff --git a/public/locales/zh-tw.json b/public/locales/zh-tw.json
index cb5cb0965..e8418ef56 100644
--- a/public/locales/zh-tw.json
+++ b/public/locales/zh-tw.json
@@ -1025,7 +1025,6 @@
"prompt_manager_position": "位置",
"Injection position. Next to other prompts (relative) or in-chat (absolute).": "注入位置。與其他提示詞相鄰(相對位置)或在聊天中(絕對位置)。",
"prompt_manager_relative": "相對位置",
- "prompt_manager_absolute": "絕對位置",
"prompt_manager_depth": "深度",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "注入深度。0 = 在最後一條訊息之後,1 = 在最後一條訊息之前,以此類推。",
"Prompt": "提示詞",
diff --git a/public/script.js b/public/script.js
index 67335b544..79eae6303 100644
--- a/public/script.js
+++ b/public/script.js
@@ -22,7 +22,7 @@ import {
parseTabbyLogprobs,
} from './scripts/textgen-settings.js';
-const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types;
+const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
import {
world_info,
@@ -35,6 +35,7 @@ import {
setWorldInfoButtonClass,
importWorldInfo,
wi_anchor_position,
+ world_info_include_names,
} from './scripts/world-info.js';
import {
@@ -43,7 +44,6 @@ import {
saveGroupChat,
getGroups,
generateGroupWrapper,
- deleteGroup,
is_group_generating,
resetSelectedGroup,
select_group_chats,
@@ -159,7 +159,7 @@ import {
import { debounce_timeout } from './scripts/constants.js';
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
-import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, getSlashCommandsHelp, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
+import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
import {
tag_map,
tags,
@@ -209,7 +209,7 @@ import {
instruct_presets,
selectContextPreset,
} from './scripts/instruct-mode.js';
-import { initLocales } from './scripts/i18n.js';
+import { initLocales, t, translate } from './scripts/i18n.js';
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache } from './scripts/tokenizers.js';
import {
user_avatar,
@@ -223,10 +223,10 @@ import {
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
import { hideLoader, showLoader } from './scripts/loader.js';
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
-import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
+import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js';
-import { evaluateMacros } from './scripts/macros.js';
+import { MacrosParser, evaluateMacros } from './scripts/macros.js';
import { currentUser, setUserControls } from './scripts/user.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
@@ -266,8 +266,6 @@ await new Promise((resolve) => {
});
showLoader();
-// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
-document.getElementById('preloader').remove();
// Configure toast library:
toastr.options.escapeHtml = true; // Prevent raw HTML inserts
@@ -408,6 +406,7 @@ export const event_types = {
MESSAGE_EDITED: 'message_edited',
MESSAGE_DELETED: 'message_deleted',
MESSAGE_UPDATED: 'message_updated',
+ MESSAGE_FILE_EMBEDDED: 'message_file_embedded',
IMPERSONATE_READY: 'impersonate_ready',
CHAT_CHANGED: 'chat_id_changed',
GENERATION_STARTED: 'generation_started',
@@ -454,6 +453,7 @@ export const event_types = {
OPEN_CHARACTER_LIBRARY: 'open_character_library',
LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register',
LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call',
+ ONLINE_STATUS_CHANGED: 'online_status_changed',
};
export const eventSource = new EventEmitter();
@@ -521,6 +521,7 @@ const chatElement = $('#chat');
let dialogueResolve = null;
let dialogueCloseStop = false;
export let chat_metadata = {};
+/** @type {StreamingProcessor} */
export let streamingProcessor = null;
let crop_data = undefined;
let is_delete_mode = false;
@@ -838,6 +839,7 @@ export let main_api;// = "kobold";
//novel settings
export let novelai_settings;
export let novelai_setting_names;
+/** @type {AbortController} */
let abortController;
//css
@@ -910,6 +912,7 @@ async function firstLoadInit() {
initKeyboard();
initDynamicStyles();
initTags();
+ initDefaultSlashCommands();
await getUserAvatars(true, user_avatar);
await getCharacters();
await getBackgrounds();
@@ -1050,10 +1053,10 @@ export async function clearItemizedPrompts() {
async function getStatusHorde() {
try {
const hordeStatus = await checkHordeStatus();
- online_status = hordeStatus ? 'Connected' : 'no_connection';
+ setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');
}
catch {
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
}
return resultCheckStatus();
@@ -1064,7 +1067,7 @@ async function getStatusKobold() {
if (!endpoint) {
console.warn('No endpoint for status check');
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
return resultCheckStatus();
}
@@ -1081,7 +1084,7 @@ async function getStatusKobold() {
const data = await response.json();
- online_status = data?.model ?? 'no_connection';
+ setOnlineStatus(data?.model ?? 'no_connection');
if (!data.koboldUnitedVersion) {
throw new Error('Missing mandatory Kobold version in data:', data);
@@ -1099,7 +1102,7 @@ async function getStatusKobold() {
}
} catch (err) {
console.error('Error getting status', err);
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
}
return resultCheckStatus();
@@ -1112,12 +1115,12 @@ async function getStatusTextgen() {
if (!endpoint) {
console.warn('No endpoint for status check');
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
return resultCheckStatus();
}
if (textgen_settings.type == OOBA && textgen_settings.bypass_status_check) {
- online_status = 'Status check bypassed';
+ setOnlineStatus('Status check bypassed');
return resultCheckStatus();
}
@@ -1137,34 +1140,37 @@ async function getStatusTextgen() {
if (textgen_settings.type === MANCER) {
loadMancerModels(data?.data);
- online_status = textgen_settings.mancer_model;
+ setOnlineStatus(textgen_settings.mancer_model);
} else if (textgen_settings.type === TOGETHERAI) {
loadTogetherAIModels(data?.data);
- online_status = textgen_settings.togetherai_model;
+ setOnlineStatus(textgen_settings.togetherai_model);
} else if (textgen_settings.type === OLLAMA) {
loadOllamaModels(data?.data);
- online_status = textgen_settings.ollama_model || 'Connected';
+ setOnlineStatus(textgen_settings.ollama_model || 'Connected');
} else if (textgen_settings.type === INFERMATICAI) {
loadInfermaticAIModels(data?.data);
- online_status = textgen_settings.infermaticai_model;
+ setOnlineStatus(textgen_settings.infermaticai_model);
} else if (textgen_settings.type === DREAMGEN) {
loadDreamGenModels(data?.data);
- online_status = textgen_settings.dreamgen_model;
+ setOnlineStatus(textgen_settings.dreamgen_model);
} else if (textgen_settings.type === OPENROUTER) {
loadOpenRouterModels(data?.data);
- online_status = textgen_settings.openrouter_model;
+ setOnlineStatus(textgen_settings.openrouter_model);
} else if (textgen_settings.type === VLLM) {
loadVllmModels(data?.data);
- online_status = textgen_settings.vllm_model;
+ setOnlineStatus(textgen_settings.vllm_model);
} else if (textgen_settings.type === APHRODITE) {
loadAphroditeModels(data?.data);
- online_status = textgen_settings.aphrodite_model;
+ setOnlineStatus(textgen_settings.aphrodite_model);
+ } else if (textgen_settings.type === FEATHERLESS) {
+ loadFeatherlessModels(data?.data);
+ setOnlineStatus(textgen_settings.featherless_model);
} else {
- online_status = data?.result;
+ setOnlineStatus(data?.result);
}
if (!online_status) {
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
}
// Determine instruct mode preset
@@ -1176,7 +1182,7 @@ async function getStatusTextgen() {
}
} catch (err) {
console.error('Error getting status', err);
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
}
return resultCheckStatus();
@@ -1190,9 +1196,9 @@ async function getStatusNovel() {
throw new Error('Could not load subscription data');
}
- online_status = getNovelTier();
+ setOnlineStatus(getNovelTier());
} catch {
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
}
resultCheckStatus();
@@ -2073,14 +2079,23 @@ export function updateMessageBlock(messageId, message) {
appendMediaToMessage(message, messageElement);
}
-export function appendMediaToMessage(mes, messageElement) {
+/**
+ * Appends image or file to the message element.
+ * @param {object} mes Message object
+ * @param {JQuery
} messageElement Message element
+ * @param {boolean} [adjustScroll=true] Whether to adjust the scroll position after appending the media
+ */
+export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
// Add image to message
if (mes.extra?.image) {
const chatHeight = $('#chat').prop('scrollHeight');
const image = messageElement.find('.mes_img');
const text = messageElement.find('.mes_text');
const isInline = !!mes.extra?.inline_image;
- image.on('load', function () {
+ image.off('load').on('load', function () {
+ if (!adjustScroll) {
+ return;
+ }
const scrollPosition = $('#chat').scrollTop();
const newChatHeight = $('#chat').prop('scrollHeight');
const diff = newChatHeight - chatHeight;
@@ -3399,6 +3414,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
let regexedMessage = getRegexedString(message, regexType, options);
regexedMessage = await appendFileContent(chatItem, regexedMessage);
+ if (chatItem?.extra?.append_title && chatItem?.extra?.title) {
+ regexedMessage = `${regexedMessage}\n\n${chatItem.extra.title}`;
+ }
+
return {
...chatItem,
mes: regexedMessage,
@@ -3482,7 +3501,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
// Add WI to prompt (and also inject WI to AN value via hijack)
// Make quiet prompt available for WIAN
setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);
- const chatForWI = coreChat.map(x => `${x.name}: ${x.mes}`).reverse();
+ const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse();
const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun);
setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true);
@@ -4364,6 +4383,25 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
}
}
+/**
+ * Stops the generation and any streaming if it is currently running.
+ */
+export function stopGeneration() {
+ let stopped = false;
+ if (streamingProcessor) {
+ streamingProcessor.onStopStreaming();
+ streamingProcessor = null;
+ stopped = true;
+ }
+ if (abortController) {
+ abortController.abort('Clicked stop button');
+ hideStopButton();
+ stopped = true;
+ }
+ eventSource.emit(event_types.GENERATION_STOPPED);
+ return stopped;
+}
+
/**
* Injects extension prompts into chat messages.
* @param {object[]} messages Array of chat messages
@@ -5526,9 +5564,17 @@ export function setCharacterName(value) {
name2 = value;
}
+/**
+ * Sets the API connection status of the application
+ * @param {string|'no_connection'} value Connection status value
+ */
export function setOnlineStatus(value) {
+ const previousStatus = online_status;
online_status = value;
displayOnlineStatus();
+ if (previousStatus !== online_status) {
+ eventSource.emitAndWait(event_types.ONLINE_STATUS_CHANGED, online_status);
+ }
}
export function setEditedMessageId(value) {
@@ -5618,7 +5664,7 @@ export async function renameCharacter(name = null, { silent = false, renameChats
}
}
catch (error) {
- // Reloading to prevent data corruption
+ // Reloading to prevent data corruption
if (!silent) await callPopup('Something went wrong. The page will be reloaded.', 'text');
else toastr.error('Something went wrong. The page will be reloaded.', 'Rename Character');
@@ -5766,6 +5812,7 @@ export async function saveChat(chat_name, withMetadata, mesId) {
contentType: 'application/json',
success: function (data) { },
error: function (jqXHR, exception) {
+ toastr.error('Check the server connection and reload the page to prevent data loss.', 'Chat could not be saved');
console.log(exception);
console.log(jqXHR);
},
@@ -6096,7 +6143,7 @@ export function changeMainAPI() {
}
main_api = selectedVal;
- online_status = 'no_connection';
+ setOnlineStatus('no_connection');
if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
$('#api_button_openai').trigger('click');
@@ -6129,7 +6176,7 @@ async function doOnboarding(avatarId) {
template.find('input[name="enable_simple_mode"]').on('input', function () {
simpleUiMode = $(this).is(':checked');
});
- let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2 });
+ let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2, wide: true, large: true });
if (userName) {
userName = userName.replace('\n', ' ');
@@ -7138,7 +7185,8 @@ function onScenarioOverrideRemoveClick() {
* @param {string} inputValue - Value to set the input to.
* @param {PopupOptions} options - Options for the popup.
* @typedef {{okButton?: string, rows?: number, wide?: boolean, wider?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean, cropAspect?: number }} PopupOptions - Options for the popup.
- * @returns
+ * @returns {Promise} A promise that resolves when the popup is closed.
+ * @deprecated Use `callGenericPopup` instead.
*/
export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
function getOkButtonText() {
@@ -7769,6 +7817,7 @@ window['SillyTavern'].getContext = function () {
eventTypes: event_types,
addOneMessage: addOneMessage,
generate: Generate,
+ stopGeneration: stopGeneration,
getTokenCount: getTokenCount,
extensionPrompts: extension_prompts,
setExtensionPrompt: setExtensionPrompt,
@@ -7790,6 +7839,8 @@ window['SillyTavern'].getContext = function () {
* @deprecated Handlebars for extensions are no longer supported.
*/
registerHelper: () => { },
+ registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
+ unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
registedDebugFunction: registerDebugFunction,
/**
* @deprecated Use renderExtensionTemplateAsync instead.
@@ -7799,6 +7850,8 @@ window['SillyTavern'].getContext = function () {
registerDataBankScraper: ScraperManager.registerDataBankScraper,
callPopup: callPopup,
callGenericPopup: callGenericPopup,
+ showLoader: showLoader,
+ hideLoader: hideLoader,
mainApi: main_api,
extensionSettings: extension_settings,
ModuleWorkerWrapper: ModuleWorkerWrapper,
@@ -7810,6 +7863,8 @@ window['SillyTavern'].getContext = function () {
messageFormatting: messageFormatting,
shouldSendOnEnter: shouldSendOnEnter,
isMobile: isMobile,
+ t: t,
+ translate: translate,
tags: tags,
tagMap: tag_map,
menuType: menu_type,
@@ -7818,6 +7873,8 @@ window['SillyTavern'].getContext = function () {
* @deprecated Legacy snake-case naming, compatibility with old extensions
*/
event_types: event_types,
+ POPUP_TYPE: POPUP_TYPE,
+ POPUP_RESULT: POPUP_RESULT,
};
};
@@ -7867,7 +7924,7 @@ function swipe_left() { // when we swipe left..but no generation.
}
$(this).parent().children('.mes_block').transition({
x: swipe_range,
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: async function () {
@@ -7910,7 +7967,7 @@ function swipe_left() { // when we swipe left..but no generation.
complete: function () {
$(this).parent().children('.mes_block').transition({
x: '0px',
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: async function () {
@@ -7925,7 +7982,7 @@ function swipe_left() { // when we swipe left..but no generation.
$(this).parent().children('.avatar').transition({
x: swipe_range,
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: function () {
@@ -7937,7 +7994,7 @@ function swipe_left() { // when we swipe left..but no generation.
complete: function () {
$(this).parent().children('.avatar').transition({
x: '0px',
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: function () {
@@ -8044,7 +8101,7 @@ const swipe_right = () => {
this_mes_div.children('.swipe_left').css('display', 'flex');
this_mes_div.children('.mes_block').transition({ // this moves the div back and forth
x: '-' + swipe_range,
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: async function () {
@@ -8103,7 +8160,7 @@ const swipe_right = () => {
complete: function () {
this_mes_div.children('.mes_block').transition({
x: '0px',
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: async function () {
@@ -8126,7 +8183,7 @@ const swipe_right = () => {
});
this_mes_div.children('.avatar').transition({ // moves avatar along with swipe
x: '-' + swipe_range,
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: function () {
@@ -8138,7 +8195,7 @@ const swipe_right = () => {
complete: function () {
this_mes_div.children('.avatar').transition({
x: '0px',
- duration: swipe_duration,
+ duration: animation_duration > 0 ? swipe_duration : 0,
easing: animation_easing,
queue: false,
complete: function () {
@@ -8279,6 +8336,11 @@ const CONNECT_API_MAP = {
button: '#api_button_openai',
source: chat_completion_sources.GROQ,
},
+ '01ai': {
+ selected: 'openai',
+ button: '#api_button_openai',
+ source: chat_completion_sources.ZEROONEAI,
+ },
'infermaticai': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
@@ -8294,6 +8356,11 @@ const CONNECT_API_MAP = {
button: '#api_button_textgenerationwebui',
type: textgen_types.OPENROUTER,
},
+ 'huggingface': {
+ selected: 'textgenerationwebui',
+ button: '#api_button_textgenerationwebui',
+ type: textgen_types.HUGGINGFACE,
+ },
};
async function selectContextCallback(_, name) {
@@ -8408,10 +8475,10 @@ async function connectAPISlash(_, text) {
/**
* Imports supported files dropped into the app window.
* @param {File[]} files Array of files to process
- * @param {boolean?} preserveFileNames Whether to preserve original file names
+ * @param {Map} [data] Extra data to pass to the import function
* @returns {Promise}
*/
-export async function processDroppedFiles(files, preserveFileNames = false) {
+export async function processDroppedFiles(files, data = new Map()) {
const allowedMimeTypes = [
'application/json',
'image/png',
@@ -8428,7 +8495,8 @@ export async function processDroppedFiles(files, preserveFileNames = false) {
for (const file of files) {
const extension = file.name.split('.').pop().toLowerCase();
if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) {
- await importCharacter(file, preserveFileNames);
+ const preservedName = data instanceof Map && data.get(file);
+ await importCharacter(file, preservedName);
} else {
toastr.warning('Unsupported file type: ' + file.name);
}
@@ -8438,10 +8506,10 @@ export async function processDroppedFiles(files, preserveFileNames = false) {
/**
* Imports a character from a file.
* @param {File} file File to import
- * @param {boolean?} preserveFileName Whether to preserve original file name
+ * @param {string?} preserveFileName Whether to preserve original file name
* @returns {Promise}
*/
-async function importCharacter(file, preserveFileName = false) {
+async function importCharacter(file, preserveFileName = '') {
if (is_group_generating || is_send_press) {
toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted');
throw new Error('Cannot import character while generating');
@@ -8457,7 +8525,7 @@ async function importCharacter(file, preserveFileName = false) {
const formData = new FormData();
formData.append('avatar', file);
formData.append('file_type', format);
- formData.append('preserve_file_name', String(preserveFileName));
+ if (preserveFileName) formData.append('preserved_name', preserveFileName);
const data = await jQuery.ajax({
type: 'POST',
@@ -8541,6 +8609,39 @@ async function doImpersonate(args, prompt) {
return '';
}
+export async function doNewChat({ deleteCurrentChat = false } = {}) {
+ //Make a new chat for selected character
+ if ((!selected_group && this_chid == undefined) || menu_type == 'create') {
+ return;
+ }
+
+ //Fix it; New chat doesn't create while open create character menu
+ await clearChat();
+ chat.length = 0;
+
+ chat_file_for_del = getCurrentChatDetails()?.sessionName;
+
+ // Make it easier to find in backups
+ if (deleteCurrentChat) {
+ await saveChatConditional();
+ }
+
+ if (selected_group) {
+ await createNewGroupChat(selected_group);
+ if (deleteCurrentChat) await deleteGroupChat(selected_group, chat_file_for_del);
+ }
+ else {
+ //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
+ chat_metadata = {};
+ characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
+ $('#selected_chat_pole').val(characters[this_chid].chat);
+ await getChat();
+ await createOrEditCharacter(new CustomEvent('newChat'));
+ if (deleteCurrentChat) await delChat(chat_file_for_del + '.jsonl');
+ }
+
+}
+
async function doDeleteChat() {
await displayPastChats();
let currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
@@ -8649,13 +8750,11 @@ function doCloseChat() {
* it proceeds to delete character from UI and saves settings.
* In case of error during the fetch request, it logs the error details.
*
- * @param {string} popup_type - The type of popup currently active.
* @param {string} this_chid - The character ID to be deleted.
* @param {boolean} delete_chats - Whether to delete chats or not.
*/
-export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
- if (popup_type !== 'del_ch' ||
- !characters[this_chid]) {
+export async function handleDeleteCharacter(this_chid, delete_chats) {
+ if (!characters[this_chid]) {
return;
}
@@ -8701,6 +8800,8 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
await eventSource.emit(event_types.CHAT_DELETED, name);
}
}
+
+ eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
}
/**
@@ -9095,14 +9196,26 @@ jQuery(async function () {
chooseBogusFolder($(this), tagId);
});
- $(document).on('input', '.edit_textarea', function () {
- scroll_holder = $('#chat').scrollTop();
- $(this).height(0).height(this.scrollHeight);
+ /**
+ * Sets the scroll height of the edit textarea to fit the content.
+ * @param {HTMLTextAreaElement} e Textarea element to auto-fit
+ */
+ function autoFitEditTextArea(e) {
+ scroll_holder = chatElement[0].scrollTop;
+ e.style.height = '0';
+ e.style.height = `${e.scrollHeight + 4}px`;
is_use_scroll_holder = true;
+ }
+ const autoFitEditTextAreaDebounced = debounce(autoFitEditTextArea, debounce_timeout.short);
+ document.addEventListener('input', e => {
+ if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) {
+ const immediately = e.target.scrollHeight > e.target.offsetHeight || e.target.value === '';
+ immediately ? autoFitEditTextArea(e.target) : autoFitEditTextAreaDebounced(e.target);
+ }
});
- $('#chat').on('scroll', function () {
+ document.getElementById('chat').addEventListener('scroll', function () {
if (is_use_scroll_holder) {
- $('#chat').scrollTop(scroll_holder);
+ this.scrollTop = scroll_holder;
is_use_scroll_holder = false;
}
});
@@ -9202,46 +9315,9 @@ jQuery(async function () {
}, 2000);
}
}
- if (popup_type == 'del_ch') {
- const deleteChats = !!$('#del_char_checkbox').prop('checked');
- eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
- await handleDeleteCharacter(popup_type, this_chid, deleteChats);
- }
if (popup_type == 'alternate_greeting' && menu_type !== 'create') {
createOrEditCharacter();
}
- //Make a new chat for selected character
- if (
- popup_type == 'new_chat' &&
- (selected_group || this_chid !== undefined) &&
- menu_type != 'create'
- ) {
- //Fix it; New chat doesn't create while open create character menu
- await clearChat();
- chat.length = 0;
-
- chat_file_for_del = getCurrentChatDetails()?.sessionName;
- const isDelChatCheckbox = document.getElementById('del_chat_checkbox')?.checked;
-
- // Make it easier to find in backups
- if (isDelChatCheckbox) {
- await saveChatConditional();
- }
-
- if (selected_group) {
- await createNewGroupChat(selected_group);
- if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del);
- }
- else {
- //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
- chat_metadata = {};
- characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
- $('#selected_chat_pole').val(characters[this_chid].chat);
- await getChat();
- await createOrEditCharacter(new CustomEvent('newChat'));
- if (isDelChatCheckbox) await delChat(chat_file_for_del + '.jsonl');
- }
- }
if (dialogueResolve) {
if (popup_type == 'input') {
@@ -9287,15 +9363,27 @@ jQuery(async function () {
$('#form_create').submit(createOrEditCharacter);
- $('#delete_button').on('click', function () {
- callPopup(`
- Delete the character?
- THIS IS PERMANENT!
+ $('#delete_button').on('click', async function () {
+ if (!this_chid) {
+ toastr.warning('No character selected.');
+ return;
+ }
+
+ let deleteChats = false;
+
+ const confirm = await Popup.show.confirm('Delete the character?', `
+ THIS IS PERMANENT!
Also delete the chat files
- `, 'del_ch', '',
- );
+ `, {
+ onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),
+ });
+ if (!confirm) {
+ return;
+ }
+
+ await deleteCharacter(characters[this_chid].avatar, { deleteChats: deleteChats });
});
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
@@ -9446,6 +9534,8 @@ jQuery(async function () {
{ id: 'api_key_openrouter-tg', secret: SECRET_KEYS.OPENROUTER },
{ id: 'api_key_koboldcpp', secret: SECRET_KEYS.KOBOLDCPP },
{ id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP },
+ { id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS },
+ { id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE },
];
for (const key of keys) {
@@ -9557,14 +9647,20 @@ jQuery(async function () {
else if (id == 'option_start_new_chat') {
if ((selected_group || this_chid !== undefined) && !is_send_press) {
- callPopup(`
- Start new chat?
+ let deleteCurrentChat = false;
+ const result = await Popup.show.confirm('Start new chat?', `
Also delete the current chat file
-
- `, 'new_chat', '');
+ `, {
+ onClose: () => deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'),
+ });
+ if (!result) {
+ return;
+ }
+
+ await doNewChat({ deleteCurrentChat: deleteCurrentChat });
}
}
@@ -9651,14 +9747,8 @@ jQuery(async function () {
});
$('#newChatFromManageScreenButton').on('click', function () {
- setTimeout(() => {
- $('#option_start_new_chat').trigger('click');
- }, 1);
- setTimeout(() => {
- $('#dialogue_popup_ok').trigger('click');
- }, 1);
+ doNewChat({ deleteCurrentChat: false });
$('#select_chat_cross').trigger('click');
-
});
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -10278,15 +10368,7 @@ jQuery(async function () {
});
$(document).on('click', '.mes_stop', function () {
- if (streamingProcessor) {
- streamingProcessor.onStopStreaming();
- streamingProcessor = null;
- }
- if (abortController) {
- abortController.abort('Clicked stop button');
- hideStopButton();
- }
- eventSource.emit(event_types.GENERATION_STOPPED);
+ stopGeneration();
});
$(document).on('click', '#form_sheld .stscript_continue', function () {
@@ -10574,10 +10656,12 @@ jQuery(async function () {
}
try {
- const cloneFile = new File([file], characters[this_chid].avatar, { type: file.type });
const chatFile = characters[this_chid]['chat'];
- await processDroppedFiles([cloneFile], true);
+ const data = new Map();
+ data.set(file, characters[this_chid].avatar);
+ await processDroppedFiles([file], data);
await openCharacterChat(chatFile);
+ await fetch(getThumbnailUrl('avatar', characters[this_chid].avatar), { cache: 'no-cache' });
} catch {
toastr.error('Failed to replace the character card.', 'Something went wrong');
}
@@ -10773,3 +10857,4 @@ jQuery(async function () {
initCustomSelectedSamplers();
});
+
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index b2d97ff97..dceba9063 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -360,6 +360,7 @@ function RA_autoconnect(PrevApi) {
|| (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI])
|| (textgen_settings.type === textgen_types.DREAMGEN && secret_state[SECRET_KEYS.DREAMGEN])
|| (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER])
+ || (textgen_settings.type === textgen_types.FEATHERLESS && secret_state[SECRET_KEYS.FEATHERLESS])
) {
$('#api_button_textgenerationwebui').trigger('click');
}
@@ -379,6 +380,7 @@ function RA_autoconnect(PrevApi) {
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
+ || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
) {
$('#api_button_openai').trigger('click');
@@ -476,8 +478,8 @@ export function dragElement(elmnt) {
}
const style = getComputedStyle(target);
- height = parseInt(style.height)
- width = parseInt(style.width)
+ height = parseInt(style.height);
+ width = parseInt(style.width);
top = parseInt(style.top);
left = parseInt(style.left);
right = parseInt(style.right);
@@ -723,6 +725,16 @@ export function initRossMods() {
RA_autoconnect();
}
+ const userAgent = getParsedUA();
+ console.debug('User Agent', userAgent);
+ const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
+ const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
+ const isIOS = userAgent?.os?.name === 'iOS';
+
+ if (isIOS || isMobileSafari || isDesktopSafari) {
+ document.body.classList.add('safari');
+ }
+
$('#main_api').change(function () {
var PrevAPI = main_api;
setTimeout(() => RA_autoconnect(PrevAPI), 100);
@@ -926,8 +938,8 @@ export function initRossMods() {
return false;
}
- $(document).on('keydown', function (event) {
- processHotkeys(event.originalEvent);
+ $(document).on('keydown', async function (event) {
+ await processHotkeys(event.originalEvent);
});
const hotkeyTargets = {
@@ -939,7 +951,7 @@ export function initRossMods() {
/**
* @param {KeyboardEvent} event
*/
- function processHotkeys(event) {
+ async function processHotkeys(event) {
//Enter to send when send_textarea in focus
if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter();
@@ -1003,21 +1015,17 @@ export function initRossMods() {
if (skipConfirm) {
doRegenerate();
} else {
- Popup.show.confirm('Regenerate Message', `
- Are you sure you want to regenerate the latest message?
-
-
- Don't ask again
- `, {
- onClose: (popup) => {
- if (!popup.result) {
- return;
- }
- const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked');
- SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
- doRegenerate();
- },
- })
+ let regenerateWithCtrlEnter = false;
+ const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
+ customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
+ onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
+ });
+ if (!result) {
+ return;
+ }
+
+ SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
+ doRegenerate();
}
return;
} else {
diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js
index f099ba047..14ba977f8 100644
--- a/public/scripts/bookmarks.js
+++ b/public/scripts/bookmarks.js
@@ -24,6 +24,7 @@ import {
saveGroupBookmarkChat,
selected_group,
} from './group-chats.js';
+import { Popup } from './popup.js';
import { createTagMapFromList } from './tags.js';
import {
@@ -239,8 +240,7 @@ async function convertSoloToGroupChat() {
return;
}
- const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm');
-
+ const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat? This cannot be reverted.');
if (!confirm) {
return;
}
@@ -336,6 +336,7 @@ async function convertSoloToGroupChat() {
if (!createChatResponse.ok) {
console.error('Group chat creation unsuccessful');
+ toastr.error('Group chat creation unsuccessful');
return;
}
diff --git a/public/scripts/chats.js b/public/scripts/chats.js
index 2402dd2db..6d1e8d694 100644
--- a/public/scripts/chats.js
+++ b/public/scripts/chats.js
@@ -185,18 +185,19 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
const file = fileInput.files[0];
if (!file) return;
+ const slug = getStringHash(file.name);
+ const fileNamePrefix = `${Date.now()}_${slug}`;
const fileBase64 = await getBase64Async(file);
let base64Data = fileBase64.split(',')[1];
// If file is image
if (file.type.startsWith('image/')) {
const extension = file.type.split('/')[1];
- const imageUrl = await saveBase64AsFile(base64Data, name2, file.name, extension);
+ const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
message.extra.image = imageUrl;
message.extra.inline_image = true;
} else {
- const slug = getStringHash(file.name);
- const uniqueFileName = `${Date.now()}_${slug}.txt`;
+ const uniqueFileName = `${fileNamePrefix}.txt`;
if (isConvertible(file.type)) {
try {
@@ -319,12 +320,10 @@ export function hasPendingFileAttachment() {
/**
* Displays file information in the message sending form.
+ * @param {File} file File object
* @returns {Promise}
*/
-async function onFileAttach() {
- const fileInput = document.getElementById('file_form_input');
- if (!(fileInput instanceof HTMLInputElement)) return;
- const file = fileInput.files[0];
+async function onFileAttach(file) {
if (!file) return;
const isValid = await validateFile(file);
@@ -419,6 +418,7 @@ function embedMessageFile(messageId, messageBlock) {
}
await populateFileAttachment(message, 'embed_file_input');
+ await eventSource.emit(event_types.MESSAGE_FILE_EMBEDDED, messageId);
appendMediaToMessage(message, messageBlock);
await saveChatConditional();
}
@@ -616,6 +616,8 @@ async function deleteMessageImage() {
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
+ delete message.extra.title;
+ delete message.extra.append_title;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
await saveChatConditional();
@@ -1119,7 +1121,7 @@ async function openAttachmentManager() {
const cleanupFn = await renderButtons();
await verifyAttachments();
await renderAttachments();
- await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
+ await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close', allowVerticalScrolling: true });
cleanupFn();
dragDropHandler.destroy();
@@ -1428,7 +1430,7 @@ jQuery(function () {
wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter');
const textarea = document.createElement('textarea');
textarea.value = String(bro.val());
- textarea.classList.add('height100p', 'wide100p');
+ textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');
bro.hasClass('monospace') && textarea.classList.add('monospace');
textarea.addEventListener('input', function () {
bro.val(textarea.value).trigger('input');
@@ -1503,8 +1505,34 @@ jQuery(function () {
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
- $('#file_form_input').on('change', onFileAttach);
+ $('#file_form_input').on('change', async () => {
+ const fileInput = document.getElementById('file_form_input');
+ if (!(fileInput instanceof HTMLInputElement)) return;
+ const file = fileInput.files[0];
+ await onFileAttach(file);
+ });
$('#file_form').on('reset', function () {
$('#file_form').addClass('displayNone');
});
+
+ document.getElementById('send_textarea').addEventListener('paste', async function (event) {
+ if (event.clipboardData.files.length === 0) {
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const fileInput = document.getElementById('file_form_input');
+ if (!(fileInput instanceof HTMLInputElement)) return;
+
+ // Workaround for Firefox: Use a DataTransfer object to indirectly set fileInput.files
+ const dataTransfer = new DataTransfer();
+ for (let i = 0; i < event.clipboardData.files.length; i++) {
+ dataTransfer.items.add(event.clipboardData.files[i]);
+ }
+
+ fileInput.files = dataTransfer.files;
+ await onFileAttach(fileInput.files[0]);
+ });
});
diff --git a/public/scripts/constants.js b/public/scripts/constants.js
index c70b3af63..f95a8e146 100644
--- a/public/scripts/constants.js
+++ b/public/scripts/constants.js
@@ -5,6 +5,8 @@
export const debounce_timeout = {
/** [100 ms] For ultra-fast responses, typically for keypresses or executions that might happen multiple times in a loop or recursion. */
quick: 100,
+ /** [200 ms] Slightly slower than quick, but still very responsive. */
+ short: 200,
/** [300 ms] Default time for general use, good balance between responsiveness and performance. */
standard: 300,
/** [1.000 ms] For situations where the function triggers more intensive tasks. */
diff --git a/public/scripts/dynamic-styles.js b/public/scripts/dynamic-styles.js
index b70c33e6b..dd5462501 100644
--- a/public/scripts/dynamic-styles.js
+++ b/public/scripts/dynamic-styles.js
@@ -154,7 +154,7 @@ export function initDynamicStyles() {
// Process all stylesheets on initial load
Array.from(document.styleSheets).forEach(sheet => {
try {
- applyDynamicFocusStyles(sheet, { fromExtension: sheet.href.toLowerCase().includes('scripts/extensions') });
+ applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true });
} catch (e) {
console.warn('Failed to process stylesheet on initial load:', e);
}
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 3ecf7c893..57e512ea8 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -374,7 +374,7 @@ async function addExtensionsButtonAndMenu() {
$('html').on('click', function (e) {
const clickTarget = $(e.target);
- const noCloseTargets = ['#sd_gen', '#extensionsMenuButton'];
+ const noCloseTargets = ['#sd_gen', '#extensionsMenuButton', '#roll_dice'];
if (dropdown.is(':visible') && !noCloseTargets.some(id => clickTarget.closest(id).length > 0)) {
$(dropdown).fadeOut(animation_duration);
}
@@ -641,9 +641,16 @@ async function showExtensionsDetails() {
action: async () => {
requiresReload = true;
await autoUpdateExtensions(true);
- popup.complete(POPUP_RESULT.AFFIRMATIVE);
+ await popup.complete(POPUP_RESULT.AFFIRMATIVE);
},
};
+
+ // If we are updating an extension, the "old" popup is still active. We should close that.
+ const oldPopup = Popup.util.popups.find(popup => popup.content.querySelector('.extensions_info'));
+ if (oldPopup) {
+ await oldPopup.complete(POPUP_RESULT.CANCELLED);
+ }
+
const popup = new Popup(`${html}
`, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
popupPromise = popup.show();
} catch (error) {
diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js
index e06449f88..b47f73a2f 100644
--- a/public/scripts/extensions/caption/index.js
+++ b/public/scripts/extensions/caption/index.js
@@ -1,6 +1,6 @@
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
-import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
+import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
import { SECRET_KEYS, secret_state } from '../../secrets.js';
import { getMultimodalCaption } from '../shared.js';
@@ -84,12 +84,11 @@ async function setSpinnerIcon() {
}
/**
- * Sends a captioned message to the chat.
- * @param {string} caption Caption text
- * @param {string} image Image URL
+ * Wraps a caption with a message template.
+ * @param {string} caption Raw caption
+ * @returns {Promise} Wrapped caption
*/
-async function sendCaptionedMessage(caption, image) {
- const context = getContext();
+async function wrapCaptionTemplate(caption) {
let template = extension_settings.caption.template || TEMPLATE_DEFAULT;
if (!/{{caption}}/i.test(template)) {
@@ -101,7 +100,7 @@ async function sendCaptionedMessage(caption, image) {
if (extension_settings.caption.refine_mode) {
messageText = await callPopup(
- 'Review and edit the generated message: Press "Cancel" to abort the caption sending.',
+ 'Review and edit the generated caption: Press "Cancel" to abort the caption sending.',
'input',
messageText,
{ rows: 5, okButton: 'Send' });
@@ -111,6 +110,55 @@ async function sendCaptionedMessage(caption, image) {
}
}
+ return messageText;
+}
+
+/**
+ * Appends caption to an existing message.
+ * @param {Object} data Message data
+ * @returns {Promise}
+ */
+async function captionExistingMessage(data) {
+ if (!(data?.extra?.image)) {
+ return;
+ }
+
+ const imageData = await fetch(data.extra.image);
+ const blob = await imageData.blob();
+ const type = imageData.headers.get('Content-Type');
+ const file = new File([blob], 'image.png', { type });
+ const caption = await getCaptionForFile(file, null, true);
+
+ if (!caption) {
+ console.warn('Failed to generate a caption for the image.');
+ return;
+ }
+
+ const wrappedCaption = await wrapCaptionTemplate(caption);
+
+ const messageText = String(data.mes).trim();
+
+ if (!messageText) {
+ data.extra.inline_image = false;
+ data.mes = wrappedCaption;
+ data.extra.title = wrappedCaption;
+ }
+ else {
+ data.extra.inline_image = true;
+ data.extra.append_title = true;
+ data.extra.title = wrappedCaption;
+ }
+}
+
+/**
+ * Sends a captioned message to the chat.
+ * @param {string} caption Caption text
+ * @param {string} image Image URL
+ */
+async function sendCaptionedMessage(caption, image) {
+ const messageText = await wrapCaptionTemplate(caption);
+
+ const context = getContext();
const message = {
name: context.name1,
is_user: true,
@@ -356,6 +404,7 @@ jQuery(async function () {
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||
+ (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'vllm' && textgenerationwebui_settings.server_urls[textgen_types.VLLM]) ||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||
extension_settings.caption.source === 'local' ||
extension_settings.caption.source === 'horde';
@@ -408,7 +457,7 @@ jQuery(async function () {
});
}
async function addSettings() {
- const html = await renderExtensionTemplateAsync('caption', 'settings');
+ const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });
$('#caption_container').append(html);
}
@@ -422,6 +471,7 @@ jQuery(async function () {
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
$('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));
$('#caption_prompt_ask').prop('checked', !!(extension_settings.caption.prompt_ask));
+ $('#caption_auto_mode').prop('checked', !!(extension_settings.caption.auto_mode));
$('#caption_source').val(extension_settings.caption.source);
$('#caption_prompt').val(extension_settings.caption.prompt);
$('#caption_template').val(extension_settings.caption.template);
@@ -447,6 +497,41 @@ jQuery(async function () {
extension_settings.caption.prompt_ask = $('#caption_prompt_ask').prop('checked');
saveSettingsDebounced();
});
+ $('#caption_auto_mode').on('input', () => {
+ extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');
+ saveSettingsDebounced();
+ });
+
+ const onMessageEvent = async (index) => {
+ if (!extension_settings.caption.auto_mode) {
+ return;
+ }
+
+ const data = getContext().chat[index];
+ await captionExistingMessage(data);
+ };
+
+ eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);
+ eventSource.on(event_types.MESSAGE_FILE_EMBEDDED, onMessageEvent);
+
+ $(document).on('click', '.mes_img_caption', async function () {
+ const animationClass = 'fa-fade';
+ const messageBlock = $(this).closest('.mes');
+ const messageImg = messageBlock.find('.mes_img');
+ if (messageImg.hasClass(animationClass)) return;
+ messageImg.addClass(animationClass);
+ try {
+ const index = Number(messageBlock.attr('mesid'));
+ const data = getContext().chat[index];
+ await captionExistingMessage(data);
+ appendMediaToMessage(data, messageBlock, false);
+ await saveChatConditional();
+ } catch(e) {
+ console.error('Message image recaption failed', e);
+ } finally {
+ messageImg.removeClass(animationClass);
+ }
+ });
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',
callback: captionCommandCallback,
@@ -482,4 +567,6 @@ jQuery(async function () {
`,
}));
+
+ document.body.classList.add('caption');
});
diff --git a/public/scripts/extensions/caption/settings.html b/public/scripts/extensions/caption/settings.html
index 3e23cfbd5..ccbdd67c0 100644
--- a/public/scripts/extensions/caption/settings.html
+++ b/public/scripts/extensions/caption/settings.html
@@ -26,6 +26,7 @@