Merge branch 'staging' into smol-tag-improvements

This commit is contained in:
Cohee 2024-06-16 16:04:37 +03:00
commit e3a46df010
65 changed files with 2907 additions and 1969 deletions

View File

@ -1,7 +1,6 @@
name: Update i18n data
on:
workflow_dispatch:
on: workflow_dispatch
jobs:
build:

View File

@ -1,7 +1,7 @@
{
"name": "Cappuccino",
"blur_strength": 3,
"main_text_color": "rgba(255, 255, 255, 1)",
"main_text_color": "rgba(235, 235, 235, 1)",
"italics_text_color": "rgba(230, 210, 190, 1)",
"underline_text_color": "rgba(205, 180, 160, 1)",
"quote_text_color": "rgba(165, 140, 115, 1)",

View File

@ -23,6 +23,14 @@
opacity: 0.8;
}
.select2-selection--single .select2-selection__placeholder {
color: var(--SmartThemeEmColor);
}
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: var(--SmartThemeEmColor);
}
.select2-container .select2-selection--single .select2-selection__rendered {
color: var(--SmartThemeBodyColor);
line-height: revert;
@ -195,6 +203,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
.select2_choice_clickable+span.select2-container .select2-selection__choice__display {
cursor: pointer;
}
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
cursor: pointer;
transition: background-color 0.3s;
@ -212,7 +221,9 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
.select2_multi_sameline+span.select2-container .select2-selection--multiple {
display: flex;
flex-wrap: wrap;
}.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
}
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
/* Allow search placeholder to take up all space if needed */
flex-grow: 1;
}
@ -256,4 +267,4 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
.select2_multi_sameline+span.select2-container.select2-container--focus .select2-selection--multiple .select2-search--inline {
height: unset;
}
}

View File

@ -1753,8 +1753,8 @@
<input id="openai_function_calling" type="checkbox" />
<span data-i18n="Enable function calling">Enable function calling</span>
<div class="flexBasis100p toggle-description justifyLeft">
Allows using <a href="https://platform.openai.com/docs/guides/function-calling" target="_blank">function tools</a>.
Can be utilized by various extensions to provide additional functionality.
<span data-i18n="enable_functions_desc_1">Allows using </span><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank" data-i18n="enable_functions_desc_2">function tools</a>.
<span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
</div>
</label>
</div>
@ -1863,7 +1863,7 @@
<div id="openai_logit_bias_new_entry" class="menu_button wide100p flex-container justifyCenter" data-i18n="Add bias entry">
Add bias entry
</div>
<div class="openai_logit_bias_list"></div>
<div class="openai_logit_bias_list" no_items_text="No items" data-i18n="[no_items_text]openai_logit_bias_no_items"></div>
<div class="m-t-1">
<small>
<i class="fa-solid fa-lightbulb"></i>
@ -1887,7 +1887,7 @@
</div>
<div id="sys-settings-button" class="drawer">
<div class="drawer-toggle drawer-header">
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections"></div>
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections;[no_connection_text]api_no_connection" no_connection_text="No connection..."></div>
</div>
<div id="rm_api_block" class="drawer-content closedDrawer">
<h3 class="margin0" id="title_api">API</h3>
@ -2357,7 +2357,7 @@
<select id="chat_completion_source">
<optgroup>
<option value="openai">OpenAI</option>
<option value="custom">Custom (OpenAI-compatible)</option>
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
</optgroup>
<optgroup>
<option value="ai21">AI21</option>
@ -2856,16 +2856,16 @@
<form id="custom_form" data-source="custom">
<h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
<div class="flex-container">
<input id="custom_api_url_text" class="text_pole wide100p" maxlength="5000" value="" autocomplete="off" placeholder="Example: http://localhost:1234/v1">
<input id="custom_api_url_text" class="text_pole wide100p" maxlength="5000" value="" autocomplete="off" data-i18n="[placeholder]Example: http://localhost:1234/v1" placeholder="Example: http://localhost:1234/v1">
</div>
<div>
<small>
Doesn't work? Try adding <code>/v1</code> at the end of the URL!
<span data-i18n="Doesn't work? Try adding">Doesn't work? Try adding</span> <code>/v1</code> <span data-i18n="at the end of the URL!">at the end of the URL!</span>
</small>
</div>
<h4>
<span data-i18n="Custom API Key">Custom API Key</span>
<small>(Optional)</small>
<small data-i18n="(Optional)">(Optional)</small>
</h4>
<div class="flex-container">
<input id="api_key_custom" name="api_key_custom" class="text_pole flex1" maxlength="5000" value="" type="text" autocomplete="off">
@ -2874,9 +2874,9 @@
<div data-for="api_key_custom" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<h4>Enter a Model ID</h4>
<h4 data-i18n="Enter a Model ID">Enter a Model ID</h4>
<div class="flex-container">
<input id="custom_model_id" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" placeholder="Example: gpt-3.5-turbo">
<input id="custom_model_id" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-i18n="[placeholder]Example: gpt-3.5-turbo" placeholder="Example: gpt-3.5-turbo">
</div>
<h4 data-i18n="Available Models">Available Models</h4>
<div class="flex-container">
@ -2884,14 +2884,14 @@
</div>
<h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
<select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
<option value="">None</option>
<option data-i18n="prompt_post_processing_none" value="">None</option>
<option value="claude">Claude</option>
</select>
</form>
<div class="flex-container flex">
<div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
<div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon">Additional Parameters</div>
<div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon" data-i18n="Additional Parameters">Additional Parameters</div>
<div data-source="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div id="test_api_button" class="menu_button menu_button_icon" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!"><span data-i18n="Test Message">Test Message</span></div>
</div>
@ -4246,7 +4246,7 @@
<div name="STscriptToggles">
<h4 data-i18n="STscript Settings">STscript Settings</h4>
<div title="Sets default flags for the STscript parser." data-i18n="[title]Sets default flags for the STscript parser.">
<label data-i18n="Parser Flags"><small>Parser Flags</small></label>
<label><small data-i18n="Parser Flags">Parser Flags</small></label>
<label class="checkbox_label" title="Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well." data-i18n="[title]Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.">
<input id="stscript_parser_flag_strict_escaping" type="checkbox" />
<span data-i18n="STRICT_ESCAPING"><small>STRICT_ESCAPING</small></span>
@ -4714,7 +4714,7 @@
<div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
<label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
<span data-i18n="Join Prefix">Join Prefix</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
</div>
</label>
<textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
@ -4722,7 +4722,7 @@
<div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
<label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
<span data-i18n="Join Suffix">Join Suffix</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
</div>
</label>
<textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
@ -5413,15 +5413,15 @@
<div class="flex-container justifySpaceBetween">
<small for="group">
<span data-i18n="Inclusion Group">Inclusion Group</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;&#13;Documentation: World Info - Inclusion Group">
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</small>
<label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
<input type="checkbox" name="groupOverride" />
<span>
<small data-i18n="Prioritize Inclusion" title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.">
Prioritize Inclusion
<small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
<span data-i18n="Prioritize">Prioritize</span>
<div class="fa-solid fa-circle-info opacity50p"></div>
</small>
</span>
@ -5830,8 +5830,8 @@
</small>
<hr>
<div class="alternate_greetings_list flexFlowColumn flex-container wide100p">
<strong class="alternate_grettings_hint margin-bot-10px" data-i18n="Alternate Greetings Hint">
Click the <i class="fa-solid fa-plus"></i> button to get started!
<strong class="alternate_grettings_hint margin-bot-10px">
<span data-i18n="alternate_greetings_hint_1">Click the</span> <i class="fa-solid fa-plus"></i> <span data-i18n="alternate_greetings_hint_2">button to get started!</span>
</strong>
</div>
</div>
@ -5839,7 +5839,7 @@
<div id="alternate_greeting_form_template" class="template_element">
<div class="alternate_greeting">
<div class="title_restorable">
<strong>Alternate Greeting #<span class="greeting_index"></span></strong>
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
<div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div>
</div>
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text" maxlength="50000" value="" autocomplete="off" rows="16"></textarea>

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "انضم إلى بطاقات الشخصيات (بما في ذلك البطاقات الصامتة)",
"Inserted before each part of the joined fields.": "تم إدراجه قبل كل جزء من الحقول المرتبطة.",
"Join Prefix": "الانضمام إلى البادئة",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "عند تحديد \"الانضمام إلى بطاقات الأحرف\"، يتم ضم كافة الحقول الخاصة بالأحرف معًا.\rوهذا يعني أنه في سلسلة القصة على سبيل المثال، سيتم دمج جميع أوصاف الشخصيات في نص واحد كبير.\rإذا كنت تريد فصل هذه الحقول، فيمكنك تحديد بادئة أو لاحقة هنا.\r\rتدعم هذه القيمة وحدات الماكرو العادية وستستبدل أيضًا {{char}} باسم الحرف ذي الصلة و<FIELDNAME> باسم الجزء (على سبيل المثال: الوصف، والشخصية، والسيناريو، وما إلى ذلك)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "عند تحديد \"الانضمام إلى بطاقات الأحرف\"، يتم ضم كافة الحقول الخاصة بالأحرف معًا.\rوهذا يعني أنه في سلسلة القصة على سبيل المثال، سيتم دمج جميع أوصاف الشخصيات في نص واحد كبير.\rإذا كنت تريد فصل هذه الحقول، فيمكنك تحديد بادئة أو لاحقة هنا.\r\rتدعم هذه القيمة وحدات الماكرو العادية وستستبدل أيضًا {{char}} باسم الحرف ذي الصلة و<FIELDNAME> باسم الجزء (على سبيل المثال: الوصف، والشخصية، والسيناريو، وما إلى ذلك)",
"Inserted after each part of the joined fields.": "يتم إدراجه بعد كل جزء من الحقول المرتبطة.",
"Join Suffix": "انضم إلى لاحقة",
"Set a group chat scenario": "تعيين سيناريو للمحادثة الجماعية",
@ -1005,8 +1005,8 @@
"Character Exclusion": "استبعاد الشخصيات",
"-- Characters not found --": "-- الشخصيات غير موجودة --",
"Inclusion Group": "مجموعة الإدراج",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "تضمن مجموعات التضمين تنشيط إدخال واحد فقط من المجموعة في المرة الواحدة، إذا تم تشغيل عدة إدخالات.\rيدعم مجموعات متعددة مفصولة بفواصل.\r\rالتوثيق: معلومات العالم - مجموعة الإدماج",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "إعطاء الأولوية لهذا الإدخال: عند تحديده، يتم إعطاء الأولوية لهذا الإدخال من بين جميع التحديدات.\rإذا تم تحديد الأولوية لأكثر من مجموعة، فسيتم اختيار \"الترتيب\" الأعلى.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "تضمن مجموعات التضمين تنشيط إدخال واحد فقط من المجموعة في المرة الواحدة، إذا تم تشغيل عدة إدخالات.\rيدعم مجموعات متعددة مفصولة بفواصل.\r\rالتوثيق: معلومات العالم - مجموعة الإدماج",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "إعطاء الأولوية لهذا الإدخال: عند تحديده، يتم إعطاء الأولوية لهذا الإدخال من بين جميع التحديدات.\rإذا تم تحديد الأولوية لأكثر من مجموعة، فسيتم اختيار \"الترتيب\" الأعلى.",
"Only one entry with the same label will be activated": "سيتم تنشيط مدخل واحد فقط بنفس العلامة",
"A relative likelihood of entry activation within the group": "احتمال نسبي لتفعيل الدخول داخل المجموعة",
"Group Weight": "وزن المجموعة",
@ -1436,7 +1436,7 @@
"Reset Code:": "إعادة تعيين الرمز:",
"Want to update?": "هل ترغب في التحديث؟",
"How to start chatting?": "كيف تبدأ في المحادثة؟",
"Click ": "انقر",
"Click _space": "انقر",
"and select a": "وحدد أ",
"Chat API": "واجهة برمجة تطبيقات الدردشة",
"and pick a character.": "واختر شخصية.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Charakterkarten beitreten (auch stummgeschaltet)",
"Inserted before each part of the joined fields.": "Wird vor jedem Teil der verbundenen Felder eingefügt.",
"Join Prefix": "Präfix beitreten",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wenn „Charakterkarten zusammenfügen“ ausgewählt ist, werden alle entsprechenden Felder der Charaktere zusammengefügt. Das bedeutet, dass im Story-String beispielsweise alle Charakterbeschreibungen zu einem großen Text zusammengefügt werden. Wenn Sie möchten, dass diese Felder getrennt werden, können Sie hier ein Präfix oder Suffix definieren. Dieser Wert unterstützt normale Makros und ersetzt außerdem {{char}} durch den Namen des entsprechenden Charakters und <FIELDNAME> durch den Namen des Teils (z. B.: Beschreibung, Persönlichkeit, Szenario usw.).",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wenn „Charakterkarten zusammenfügen“ ausgewählt ist, werden alle entsprechenden Felder der Charaktere zusammengefügt. Das bedeutet, dass im Story-String beispielsweise alle Charakterbeschreibungen zu einem großen Text zusammengefügt werden. Wenn Sie möchten, dass diese Felder getrennt werden, können Sie hier ein Präfix oder Suffix definieren. Dieser Wert unterstützt normale Makros und ersetzt außerdem {{char}} durch den Namen des entsprechenden Charakters und <FIELDNAME> durch den Namen des Teils (z. B.: Beschreibung, Persönlichkeit, Szenario usw.).",
"Inserted after each part of the joined fields.": "Wird nach jedem Teil der verbundenen Felder eingefügt.",
"Join Suffix": "Join-Suffix",
"Set a group chat scenario": "Setze ein Gruppenchat-Szenario",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Charakterausschluss",
"-- Characters not found --": "-- Charaktere nicht gefunden --",
"Inclusion Group": "Einschlussgruppe",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inklusionsgruppen stellen sicher, dass immer nur ein Eintrag aus einer Gruppe aktiviert wird, wenn mehrere ausgelöst werden. Unterstützt mehrere durch Kommas getrennte Gruppen. Dokumentation: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Diesen Eintrag priorisieren: Wenn diese Option aktiviert ist, wird dieser Eintrag aus allen Auswahlen priorisiert. Wenn mehrere priorisiert sind, wird der Eintrag mit der höchsten „Reihenfolge“ ausgewählt.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inklusionsgruppen stellen sicher, dass immer nur ein Eintrag aus einer Gruppe aktiviert wird, wenn mehrere ausgelöst werden. Unterstützt mehrere durch Kommas getrennte Gruppen. Dokumentation: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Diesen Eintrag priorisieren: Wenn diese Option aktiviert ist, wird dieser Eintrag aus allen Auswahlen priorisiert. Wenn mehrere priorisiert sind, wird der Eintrag mit der höchsten „Reihenfolge“ ausgewählt.",
"Only one entry with the same label will be activated": "Nur ein Eintrag mit demselben Label wird aktiviert",
"A relative likelihood of entry activation within the group": "Eine relative Wahrscheinlichkeit der Eintrittsaktivierung innerhalb der Gruppe",
"Group Weight": "Gruppengewicht",
@ -1436,7 +1436,7 @@
"Reset Code:": "Code zurücksetzen:",
"Want to update?": "Möchten Sie aktualisieren?",
"How to start chatting?": "Wie fange ich an zu chatten?",
"Click ": "Klicken",
"Click _space": "Klicken",
"and select a": "und wähle eine",
"Chat API": "Chat-API",
"and pick a character.": "und wähle einen Charakter aus.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Unirse a tarjetas de personajes (incluidas silenciadas)",
"Inserted before each part of the joined fields.": "Insertado antes de cada parte de los campos unidos.",
"Join Prefix": "Unirse al prefijo",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Cuando se selecciona 'Unir tarjetas de personajes', todos los campos respectivos de los personajes se unen.\rEsto significa que en la cadena de la historia, por ejemplo, todas las descripciones de los personajes se unirán en un texto grande.\rSi desea que esos campos estén separados, puede definir un prefijo o sufijo aquí.\r\rEste valor admite macros normales y también reemplazará {{char}} con el nombre del personaje relevante y <FIELDNAME> con el nombre de la parte (por ejemplo: descripción, personalidad, escenario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Cuando se selecciona 'Unir tarjetas de personajes', todos los campos respectivos de los personajes se unen.\rEsto significa que en la cadena de la historia, por ejemplo, todas las descripciones de los personajes se unirán en un texto grande.\rSi desea que esos campos estén separados, puede definir un prefijo o sufijo aquí.\r\rEste valor admite macros normales y también reemplazará {{char}} con el nombre del personaje relevante y <FIELDNAME> con el nombre de la parte (por ejemplo: descripción, personalidad, escenario, etc.)",
"Inserted after each part of the joined fields.": "Insertado después de cada parte de los campos unidos.",
"Join Suffix": "Unirse al sufijo",
"Set a group chat scenario": "Establecer un escenario de chat grupal",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusión de Personaje",
"-- Characters not found --": "-- Personajes no encontrados --",
"Inclusion Group": "Grupo de Inclusión",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Los grupos de inclusión garantizan que solo se active una entrada de un grupo a la vez, si se activan varias.\rAdmite múltiples grupos separados por comas.\r\rDocumentación: World Info - Grupo de Inclusión",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: cuando está marcada, esta entrada tiene prioridad entre todas las selecciones.\rSi se priorizan varios, se elige el que tenga el 'Orden' más alto.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Los grupos de inclusión garantizan que solo se active una entrada de un grupo a la vez, si se activan varias.\rAdmite múltiples grupos separados por comas.\r\rDocumentación: World Info - Grupo de Inclusión",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: cuando está marcada, esta entrada tiene prioridad entre todas las selecciones.\rSi se priorizan varios, se elige el que tenga el 'Orden' más alto.",
"Only one entry with the same label will be activated": "Solo se activará una entrada con la misma etiqueta",
"A relative likelihood of entry activation within the group": "Una probabilidad relativa de activación de entrada dentro del grupo.",
"Group Weight": "Peso del grupo",
@ -1436,7 +1436,7 @@
"Reset Code:": "Restablecer Código:",
"Want to update?": "¿Quieres actualizar?",
"How to start chatting?": "¿Cómo empezar a chatear?",
"Click ": "Hacer clic",
"Click _space": "Hacer clic",
"and select a": "y selecciona una",
"Chat API": " API de chat",
"and pick a character.": "y elige un personaje.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Rejoignez les cartes de personnage (y compris en sourdine)",
"Inserted before each part of the joined fields.": "Inséré avant chaque partie des champs joints.",
"Join Prefix": "Rejoindre le préfixe",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Lorsque « Rejoindre les cartes de personnage » est sélectionné, tous les champs respectifs des personnages sont réunis.\rCela signifie que dans la chaîne d'histoire, par exemple, toutes les descriptions des personnages seront réunies en un seul grand texte.\rSi vous souhaitez que ces champs soient séparés, vous pouvez définir ici un préfixe ou un suffixe.\r\rCette valeur prend en charge les macros normales et remplacera également {{char}} par le nom du caractère concerné et <FIELDNAME> par le nom de la pièce (par exemple : description, personnalité, scénario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Lorsque « Rejoindre les cartes de personnage » est sélectionné, tous les champs respectifs des personnages sont réunis.\rCela signifie que dans la chaîne d'histoire, par exemple, toutes les descriptions des personnages seront réunies en un seul grand texte.\rSi vous souhaitez que ces champs soient séparés, vous pouvez définir ici un préfixe ou un suffixe.\r\rCette valeur prend en charge les macros normales et remplacera également {{char}} par le nom du caractère concerné et <FIELDNAME> par le nom de la pièce (par exemple : description, personnalité, scénario, etc.)",
"Inserted after each part of the joined fields.": "Inséré après chaque partie des champs joints.",
"Join Suffix": "Rejoindre le suffixe",
"Set a group chat scenario": "Définir un scénario de discussion de groupe",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusion de personnage",
"-- Characters not found --": "-- Personnages non trouvés --",
"Inclusion Group": "Groupe d'inclusion",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Les groupes d'inclusion garantissent qu'une seule entrée d'un groupe est activée à la fois, si plusieurs sont déclenchées.\rPrend en charge plusieurs groupes séparés par des virgules.\r\rDocumentation : Info Monde - Groupe Inclusion",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Prioriser cette entrée : Lorsque cette case est cochée, cette entrée est prioritaire parmi toutes les sélections.\rSi plusieurs sont prioritaires, celui avec l'ordre le plus élevé est choisi.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Les groupes d'inclusion garantissent qu'une seule entrée d'un groupe est activée à la fois, si plusieurs sont déclenchées.\rPrend en charge plusieurs groupes séparés par des virgules.\r\rDocumentation : Info Monde - Groupe Inclusion",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Prioriser cette entrée : Lorsque cette case est cochée, cette entrée est prioritaire parmi toutes les sélections.\rSi plusieurs sont prioritaires, celui avec l'ordre le plus élevé est choisi.",
"Only one entry with the same label will be activated": "Seule une entrée avec la même étiquette sera activée",
"A relative likelihood of entry activation within the group": "Une probabilité relative dactivation dentrée au sein du groupe",
"Group Weight": "Poids du groupe",
@ -1436,7 +1436,7 @@
"Reset Code:": "Code de réinitialisation :",
"Want to update?": "Envie de mettre à jour ?",
"How to start chatting?": "Comment commencer à discuter ?",
"Click ": "Cliquez sur",
"Click _space": "Cliquez sur",
"and select a": "et sélectionnez un",
"Chat API": "API de chat",
"and pick a character.": "et choisissez un personnage.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Tengdu persónuspjöld (innifalin þögguð)",
"Inserted before each part of the joined fields.": "Sett inn fyrir hvern hluta sameinaðra reita.",
"Join Prefix": "Skráðu þig í Forskeyti",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Þegar 'Tengdu persónukort' er valið er verið að tengja saman alla viðkomandi reitir persónanna.\rÞetta þýðir að í sögustrengnum verða til dæmis allar persónulýsingar sameinaðar í einn stóran texta.\rEf þú vilt að þessir reitir séu aðskildir geturðu skilgreint forskeyti eða viðskeyti hér.\r\rÞetta gildi styður venjulega fjölva og mun einnig skipta út {{char}} fyrir viðkomandi bleikjunafni og <FIELDNAME> fyrir nafn hlutans (t.d.: lýsing, persónuleiki, atburðarás osfrv.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Þegar 'Tengdu persónukort' er valið er verið að tengja saman alla viðkomandi reitir persónanna.\rÞetta þýðir að í sögustrengnum verða til dæmis allar persónulýsingar sameinaðar í einn stóran texta.\rEf þú vilt að þessir reitir séu aðskildir geturðu skilgreint forskeyti eða viðskeyti hér.\r\rÞetta gildi styður venjulega fjölva og mun einnig skipta út {{char}} fyrir viðkomandi bleikjunafni og <FIELDNAME> fyrir nafn hlutans (t.d.: lýsing, persónuleiki, atburðarás osfrv.)",
"Inserted after each part of the joined fields.": "Sett inn á eftir hverjum hluta sameinaðra reita.",
"Join Suffix": "Skráðu þig í viðskeyti",
"Set a group chat scenario": "Setja hópspjallsskipulag",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Persónuúteslutningur",
"-- Characters not found --": "-- Persónur finnast ekki --",
"Inclusion Group": "Innifólgur Hópur",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inntökuhópar tryggja að aðeins ein færsla úr hópi sé virkjuð í einu, ef margar eru ræstar.\rStyður marga hópa aðskilda með kommum.\r\rSkjöl: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Forgangsraða þessari færslu: Þegar hakað er við þá er þessari færslu forgangsraðað úr öllu vali.\rEf mörgum er forgangsraðað er sá sem hefur hæstu 'pöntun' valin.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inntökuhópar tryggja að aðeins ein færsla úr hópi sé virkjuð í einu, ef margar eru ræstar.\rStyður marga hópa aðskilda með kommum.\r\rSkjöl: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Forgangsraða þessari færslu: Þegar hakað er við þá er þessari færslu forgangsraðað úr öllu vali.\rEf mörgum er forgangsraðað er sá sem hefur hæstu 'pöntun' valin.",
"Only one entry with the same label will be activated": "Aðeins ein skrá með sömu merki verður virk",
"A relative likelihood of entry activation within the group": "Hlutfallslegar líkur á inngönguvirkjun innan hópsins",
"Group Weight": "Þyngd hópa",
@ -1436,7 +1436,7 @@
"Reset Code:": "Endurstilla kóða:",
"Want to update?": "Viltu uppfæra?",
"How to start chatting?": "Hvernig á að byrja að spjalla?",
"Click ": "Smellur",
"Click _space": "Smellur",
"and select a": "og veldu",
"Chat API": "Spjall API",
"and pick a character.": "og veldu karakter.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Unisci le carte dei personaggi (includi disattivate)",
"Inserted before each part of the joined fields.": "Inserito prima di ogni parte dei campi uniti.",
"Join Prefix": "Unisciti al prefisso",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando viene selezionato \"Unisci carte personaggio\", tutti i rispettivi campi dei personaggi verranno uniti.\rCiò significa che nella stringa della storia, ad esempio, tutte le descrizioni dei personaggi verranno unite in un unico grande testo.\rSe desideri che questi campi siano separati, puoi definire un prefisso o un suffisso qui.\r\rQuesto valore supporta le macro normali e sostituirà anche {{char}} con il nome del carattere rilevante e <FIELDNAME> con il nome della parte (es.: descrizione, personalità, scenario, ecc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando viene selezionato \"Unisci carte personaggio\", tutti i rispettivi campi dei personaggi verranno uniti.\rCiò significa che nella stringa della storia, ad esempio, tutte le descrizioni dei personaggi verranno unite in un unico grande testo.\rSe desideri che questi campi siano separati, puoi definire un prefisso o un suffisso qui.\r\rQuesto valore supporta le macro normali e sostituirà anche {{char}} con il nome del carattere rilevante e <FIELDNAME> con il nome della parte (es.: descrizione, personalità, scenario, ecc.)",
"Inserted after each part of the joined fields.": "Inserito dopo ogni parte dei campi uniti.",
"Join Suffix": "Unisciti al suffisso",
"Set a group chat scenario": "Imposta uno scenario di chat di gruppo",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Esclusione Personaggio",
"-- Characters not found --": "-- Personaggi non trovati --",
"Inclusion Group": "Gruppo di Inclusione",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "I gruppi di inclusione garantiscono che venga attivata solo una voce di un gruppo alla volta, se ne vengono attivate più.\rSupporta più gruppi separati da virgole.\r\rDocumentazione: World Info Gruppo Inclusione",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Dai priorità a questa voce: se selezionata, questa voce ha la priorità tra tutte le selezioni. Se vengono date priorità a più voci, viene scelta quella con l'\"Ordine\" più alto.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "I gruppi di inclusione garantiscono che venga attivata solo una voce di un gruppo alla volta, se ne vengono attivate più.\rSupporta più gruppi separati da virgole.\r\rDocumentazione: World Info Gruppo Inclusione",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Dai priorità a questa voce: se selezionata, questa voce ha la priorità tra tutte le selezioni. Se vengono date priorità a più voci, viene scelta quella con l'\"Ordine\" più alto.",
"Only one entry with the same label will be activated": "Sarà attivato solo un unico ingresso con lo stesso etichetta",
"A relative likelihood of entry activation within the group": "Una probabilità relativa di attivazione dell'ingresso all'interno del gruppo",
"Group Weight": "Peso del gruppo",
@ -1436,7 +1436,7 @@
"Reset Code:": "Codice di ripristino:",
"Want to update?": "Vuoi aggiornare?",
"How to start chatting?": "Come iniziare a chattare?",
"Click ": "Clic",
"Click _space": "Clic",
"and select a": " e seleziona un",
"Chat API": " API di chat",
"and pick a character.": "e scegli un personaggio.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "キャラクターカードに参加(ミュート含む)",
"Inserted before each part of the joined fields.": "結合されたフィールドの各部分の前に挿入されます。",
"Join Prefix": "結合プレフィックス",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "「キャラクターカードを結合」を選択すると、キャラクターのそれぞれのフィールドがすべて結合されます。つまり、たとえばストーリー文字列では、すべてのキャラクターの説明が 1 つの大きなテキストに結合されます。これらのフィールドを分離したい場合は、ここでプレフィックスまたはサフィックスを定義できます。この値は通常のマクロをサポートし、{{char}} を関連するキャラクターの名前に置き換え、<FIELDNAME> をパーツの名前 (例: 説明、性格、シナリオなど) に置き換えます。",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "「キャラクターカードを結合」を選択すると、キャラクターのそれぞれのフィールドがすべて結合されます。つまり、たとえばストーリー文字列では、すべてのキャラクターの説明が 1 つの大きなテキストに結合されます。これらのフィールドを分離したい場合は、ここでプレフィックスまたはサフィックスを定義できます。この値は通常のマクロをサポートし、{{char}} を関連するキャラクターの名前に置き換え、<FIELDNAME> をパーツの名前 (例: 説明、性格、シナリオなど) に置き換えます。",
"Inserted after each part of the joined fields.": "結合されたフィールドの各部分の後に挿入されます。",
"Join Suffix": "結合サフィックス",
"Set a group chat scenario": "グループチャットのシナリオを設定",
@ -1005,8 +1005,8 @@
"Character Exclusion": "キャラクターの除外",
"-- Characters not found --": "-- キャラクターが見つかりません --",
"Inclusion Group": "含蓋グループ",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "包含グループにより、複数のエントリがトリガーされた場合、一度に 1 つのグループから 1 つのエントリのみがアクティブ化されます。カンマで区切られた複数のグループをサポートします。ドキュメント: World Info - 包含グループ",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "このエントリを優先する: チェックすると、このエントリがすべての選択項目の中で優先されます。複数のエントリを優先する場合は、「順序」が最も高いエントリが選択されます。",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "包含グループにより、複数のエントリがトリガーされた場合、一度に 1 つのグループから 1 つのエントリのみがアクティブ化されます。カンマで区切られた複数のグループをサポートします。ドキュメント: World Info - 包含グループ",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "このエントリを優先する: チェックすると、このエントリがすべての選択項目の中で優先されます。複数のエントリを優先する場合は、「順序」が最も高いエントリが選択されます。",
"Only one entry with the same label will be activated": "同じラベルのエントリが1つだけ有効になります",
"A relative likelihood of entry activation within the group": "グループ内でのエントリー活性化の相対的な可能性",
"Group Weight": "グループの重み",
@ -1436,7 +1436,7 @@
"Reset Code:": "リセットコード:",
"Want to update?": "更新しますか?",
"How to start chatting?": "チャットを開始する方法は?",
"Click ": "クリック",
"Click _space": "クリック",
"and select a": "そして選択します ",
"Chat API": "チャットAPI",
"and pick a character.": "キャラクターを選択します。",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "캐릭터 카드 가입(뮤트 포함)",
"Inserted before each part of the joined fields.": "결합된 필드의 각 부분 앞에 삽입됩니다.",
"Join Prefix": "접두사 가입",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "'캐릭터 카드 합치기'를 선택하면 캐릭터의 모든 해당 필드가 합쳐집니다. 즉, 스토리 문자열에서 예를 들어 모든 캐릭터 설명이 하나의 큰 텍스트로 합쳐집니다. 이러한 필드를 분리하려면 여기에서 접두사나 접미사를 정의할 수 있습니다. 이 값은 일반 매크로를 지원하고 {{char}}를 해당 캐릭터의 이름으로, <FIELDNAME>을 해당 부분의 이름(예: 설명, 성격, 시나리오 등)으로 대체합니다.",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "'캐릭터 카드 합치기'를 선택하면 캐릭터의 모든 해당 필드가 합쳐집니다. 즉, 스토리 문자열에서 예를 들어 모든 캐릭터 설명이 하나의 큰 텍스트로 합쳐집니다. 이러한 필드를 분리하려면 여기에서 접두사나 접미사를 정의할 수 있습니다. 이 값은 일반 매크로를 지원하고 {{char}}를 해당 캐릭터의 이름으로, <FIELDNAME>을 해당 부분의 이름(예: 설명, 성격, 시나리오 등)으로 대체합니다.",
"Inserted after each part of the joined fields.": "결합된 필드의 각 부분 뒤에 삽입됩니다.",
"Join Suffix": "접미사 가입",
"Set a group chat scenario": "그룹 채팅 시나리오 설정",
@ -1005,8 +1005,8 @@
"Character Exclusion": "캐릭터 제외",
"-- Characters not found --": "-- 캐릭터를 찾을 수 없음 --",
"Inclusion Group": "포함 그룹",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "포함 그룹은 여러 항목이 트리거되는 경우 그룹에서 한 번에 하나의 항목만 활성화되도록 합니다.\r쉼표로 구분된 여러 그룹을 지원합니다.\r\r문서: 세계 정보 - 포함 그룹",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "이 항목 우선순위 지정: 선택하면 모든 선택 항목 중에서 이 항목의 우선순위가 지정됩니다.\r여러 항목의 우선순위가 높은 경우 '순서'가 가장 높은 항목이 선택됩니다.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "포함 그룹은 여러 항목이 트리거되는 경우 그룹에서 한 번에 하나의 항목만 활성화되도록 합니다.\r쉼표로 구분된 여러 그룹을 지원합니다.\r\r문서: 세계 정보 - 포함 그룹",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "이 항목 우선순위 지정: 선택하면 모든 선택 항목 중에서 이 항목의 우선순위가 지정됩니다.\r여러 항목의 우선순위가 높은 경우 '순서'가 가장 높은 항목이 선택됩니다.",
"Only one entry with the same label will be activated": "동일한 라벨을 가진 항목은 하나만 활성화됩니다",
"A relative likelihood of entry activation within the group": "그룹 내 항목 활성화의 상대적 가능성",
"Group Weight": "그룹 가중치",
@ -1436,7 +1436,7 @@
"Reset Code:": "재설정 코드:",
"Want to update?": "업데이트 하시겠습니까?",
"How to start chatting?": "채팅을 시작하는 방법은?",
"Click ": "딸깍 하는 소리",
"Click _space": "딸깍 하는 소리",
"and select a": "그리고 선택하십시오",
"Chat API": "채팅 API",
"and pick a character.": "그리고 캐릭터를 선택하세요.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Sluit je aan bij karakterkaarten (inclusief gedempt)",
"Inserted before each part of the joined fields.": "Ingevoegd vóór elk deel van de samengevoegde velden.",
"Join Prefix": "Sluit je aan bij Voorvoegsel",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wanneer 'Verbind karakterkaarten' is geselecteerd, worden alle respectieve velden van de karakters samengevoegd.\rDit betekent dat in de verhaalreeks bijvoorbeeld alle karakterbeschrijvingen worden samengevoegd tot één grote tekst.\rAls u wilt dat deze velden gescheiden worden, kunt u hier een voor- of achtervoegsel definiëren.\r\rDeze waarde ondersteunt normale macro's en zal ook {{char}} vervangen door de relevante naam van het char en <FIELDNAME> door de naam van het onderdeel (bijvoorbeeld: beschrijving, persoonlijkheid, scenario, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Wanneer 'Verbind karakterkaarten' is geselecteerd, worden alle respectieve velden van de karakters samengevoegd.\rDit betekent dat in de verhaalreeks bijvoorbeeld alle karakterbeschrijvingen worden samengevoegd tot één grote tekst.\rAls u wilt dat deze velden gescheiden worden, kunt u hier een voor- of achtervoegsel definiëren.\r\rDeze waarde ondersteunt normale macro's en zal ook {{char}} vervangen door de relevante naam van het char en <FIELDNAME> door de naam van het onderdeel (bijvoorbeeld: beschrijving, persoonlijkheid, scenario, etc.)",
"Inserted after each part of the joined fields.": "Ingevoegd na elk deel van de samengevoegde velden.",
"Join Suffix": "Sluit je aan bij het achtervoegsel",
"Set a group chat scenario": "Stel een scenario voor groepschat in",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Personage uitsluiting",
"-- Characters not found --": "-- Personages niet gevonden --",
"Inclusion Group": "Insluitingsgroep",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Inclusiegroepen zorgen ervoor dat slechts één item uit een groep tegelijk wordt geactiveerd, als er meerdere worden geactiveerd.\rOndersteunt meerdere door komma's gescheiden groepen.\r\rDocumentatie: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Geef prioriteit aan deze invoer: Als dit selectievakje is aangevinkt, krijgt deze invoer prioriteit boven alle selecties. Als er meerdere items prioriteit hebben, wordt de invoer met de hoogste 'volgorde' gekozen.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inclusiegroepen zorgen ervoor dat slechts één item uit een groep tegelijk wordt geactiveerd, als er meerdere worden geactiveerd.\rOndersteunt meerdere door komma's gescheiden groepen.\r\rDocumentatie: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Geef prioriteit aan deze invoer: Als dit selectievakje is aangevinkt, krijgt deze invoer prioriteit boven alle selecties. Als er meerdere items prioriteit hebben, wordt de invoer met de hoogste 'volgorde' gekozen.",
"Only one entry with the same label will be activated": "Slechts één item met hetzelfde label zal worden geactiveerd",
"A relative likelihood of entry activation within the group": "Een relatieve waarschijnlijkheid van activatie binnen de groep",
"Group Weight": "Groepsgewicht",
@ -1436,7 +1436,7 @@
"Reset Code:": "Reset code:",
"Want to update?": "Wil je SillyTavern updaten?",
"How to start chatting?": "Hoe begin je met chatten?",
"Click ": "Klik",
"Click _space": "Klik",
"and select a": " en selecteer een",
"Chat API": " Chat-API",
"and pick a character.": "en kies een personage.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Junte-se a cartões de personagem (inclua silenciado)",
"Inserted before each part of the joined fields.": "Inserido antes de cada parte dos campos unidos.",
"Join Prefix": "Prefixo de adesão",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando 'Unir cartas de personagem' é selecionado, todos os respectivos campos dos personagens são unidos.\rIsto significa que na sequência da história, por exemplo, todas as descrições dos personagens serão unidas a um grande texto.\rSe quiser que esses campos sejam separados, você pode definir um prefixo ou sufixo aqui.\r\rEste valor suporta macros normais e também substituirá {{char}} pelo nome do char relevante e <FIELDNAME> pelo nome da parte (por exemplo: descrição, personalidade, cenário, etc.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Quando 'Unir cartas de personagem' é selecionado, todos os respectivos campos dos personagens são unidos.\rIsto significa que na sequência da história, por exemplo, todas as descrições dos personagens serão unidas a um grande texto.\rSe quiser que esses campos sejam separados, você pode definir um prefixo ou sufixo aqui.\r\rEste valor suporta macros normais e também substituirá {{char}} pelo nome do char relevante e <FIELDNAME> pelo nome da parte (por exemplo: descrição, personalidade, cenário, etc.)",
"Inserted after each part of the joined fields.": "Inserido após cada parte dos campos unidos.",
"Join Suffix": "Junte-se ao sufixo",
"Set a group chat scenario": "Definir um cenário de bate-papo em grupo",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Exclusão de personagem",
"-- Characters not found --": "-- Personagens não encontrados --",
"Inclusion Group": "Grupo de inclusão",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Os Grupos de Inclusão garantem que apenas uma entrada de um grupo seja ativada por vez, se várias forem acionadas.\rSuporta vários grupos separados por vírgula.\r\rDocumentação: World Info - Grupo de Inclusão",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: quando marcada, esta entrada é priorizada entre todas as seleções.\rSe vários forem priorizados, aquele com a “Ordem” mais alta será escolhido.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Os Grupos de Inclusão garantem que apenas uma entrada de um grupo seja ativada por vez, se várias forem acionadas.\rSuporta vários grupos separados por vírgula.\r\rDocumentação: World Info - Grupo de Inclusão",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Priorizar esta entrada: quando marcada, esta entrada é priorizada entre todas as seleções.\rSe vários forem priorizados, aquele com a “Ordem” mais alta será escolhido.",
"Only one entry with the same label will be activated": "Apenas uma entrada com o mesmo rótulo será ativada",
"A relative likelihood of entry activation within the group": "Uma probabilidade relativa de ativação de entrada dentro do grupo",
"Group Weight": "Peso do grupo",
@ -1436,7 +1436,7 @@
"Reset Code:": "Reiniciar código:",
"Want to update?": "Quer atualizar?",
"How to start chatting?": "Como começar a conversar?",
"Click ": "Clique",
"Click _space": "Clique",
"and select a": "e selecione um",
"Chat API": "API de Chat",
"and pick a character.": "e escolha um personagem.",

File diff suppressed because it is too large Load Diff

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Приєднатися до карток персонажів (включно з приглушеними)",
"Inserted before each part of the joined fields.": "Вставляється перед кожною частиною об’єднаних полів.",
"Join Prefix": "Префікс приєднання",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Якщо вибрано «Об’єднати картки персонажів», усі відповідні поля персонажів об’єднуються.\rЦе означає, що, наприклад, у рядку історії всі описи персонажів будуть об’єднані в один великий текст.\rЯкщо ви хочете, щоб ці поля були розділені, ви можете визначити тут префікс або суфікс.\r\rЦе значення підтримує звичайні макроси, а також замінює {{char}} на ім’я відповідного символу, а <FIELDNAME> — на назву частини (наприклад, опис, особистість, сценарій тощо).",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Якщо вибрано «Об’єднати картки персонажів», усі відповідні поля персонажів об’єднуються.\rЦе означає, що, наприклад, у рядку історії всі описи персонажів будуть об’єднані в один великий текст.\rЯкщо ви хочете, щоб ці поля були розділені, ви можете визначити тут префікс або суфікс.\r\rЦе значення підтримує звичайні макроси, а також замінює {{char}} на ім’я відповідного символу, а <FIELDNAME> — на назву частини (наприклад, опис, особистість, сценарій тощо).",
"Inserted after each part of the joined fields.": "Вставляється після кожної частини об’єднаних полів.",
"Join Suffix": "Суфікс приєднання",
"Set a group chat scenario": "Встановити сценарій групового чату",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Виключення персонажів",
"-- Characters not found --": "-- Персонажі не знайдені --",
"Inclusion Group": "Група включення",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Групи включення гарантують, що одночасно активується лише один запис із групи, якщо їх активовано декілька.\rПідтримує кілька груп, розділених комами.\r\rДокументація: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Пріоритезувати цей запис: якщо позначено, цей запис має пріоритет серед усіх вибраних.\rЯкщо кілька пріоритетів, вибирається той, який має найвищий «порядок».",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Групи включення гарантують, що одночасно активується лише один запис із групи, якщо їх активовано декілька.\rПідтримує кілька груп, розділених комами.\r\rДокументація: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Пріоритезувати цей запис: якщо позначено, цей запис має пріоритет серед усіх вибраних.\rЯкщо кілька пріоритетів, вибирається той, який має найвищий «порядок».",
"Only one entry with the same label will be activated": "Буде активовано лише один запис з однією міткою",
"A relative likelihood of entry activation within the group": "Відносна ймовірність активації входу в групу",
"Group Weight": "Вага групи",
@ -1436,7 +1436,7 @@
"Reset Code:": "Скинути код:",
"Want to update?": "Хочете оновити?",
"How to start chatting?": "Як почати спілкування?",
"Click ": "Натисніть",
"Click _space": "Натисніть",
"and select a": "і виберіть",
"Chat API": "API чату",
"and pick a character.": "і виберіть персонажа.",

View File

@ -853,7 +853,7 @@
"Join character cards (include muted)": "Tham gia thẻ nhân vật (bao gồm cả tắt tiếng)",
"Inserted before each part of the joined fields.": "Được chèn trước mỗi phần của các trường đã nối.",
"Join Prefix": "Tham gia tiền tố",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Khi chọn 'Tham gia thẻ nhân vật', tất cả các trường tương ứng của các nhân vật sẽ được nối với nhau.\rĐiều này có nghĩa là trong chuỗi câu chuyện chẳng hạn, tất cả các mô tả nhân vật sẽ được nối thành một văn bản lớn.\rNếu muốn tách các trường đó, bạn có thể xác định tiền tố hoặc hậu tố tại đây.\r\rGiá trị này hỗ trợ các macro thông thường và cũng sẽ thay thế {{char}} bằng tên của char có liên quan và <FIELDNAME> bằng tên của phần đó (ví dụ: mô tả, tính cách, kịch bản, v.v.)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "Khi chọn 'Tham gia thẻ nhân vật', tất cả các trường tương ứng của các nhân vật sẽ được nối với nhau.\rĐiều này có nghĩa là trong chuỗi câu chuyện chẳng hạn, tất cả các mô tả nhân vật sẽ được nối thành một văn bản lớn.\rNếu muốn tách các trường đó, bạn có thể xác định tiền tố hoặc hậu tố tại đây.\r\rGiá trị này hỗ trợ các macro thông thường và cũng sẽ thay thế {{char}} bằng tên của char có liên quan và <FIELDNAME> bằng tên của phần đó (ví dụ: mô tả, tính cách, kịch bản, v.v.)",
"Inserted after each part of the joined fields.": "Được chèn sau mỗi phần của các trường đã nối.",
"Join Suffix": "Tham gia hậu tố",
"Set a group chat scenario": "Đặt một kịch bản trò chuyện nhóm",
@ -1005,8 +1005,8 @@
"Character Exclusion": "Loại trừ Nhân vật",
"-- Characters not found --": "-- Không tìm thấy Nhân vật --",
"Inclusion Group": "Nhóm Bao gồm",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "Nhóm Bao gồm đảm bảo chỉ một mục từ một nhóm được kích hoạt tại một thời điểm, nếu nhiều mục được kích hoạt.\rHỗ trợ nhiều nhóm được phân tách bằng dấu phẩy.\r\rTài liệu: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "Ưu tiên mục nhập này: Khi được chọn, mục nhập này được ưu tiên trong số tất cả các lựa chọn.\rNếu nhiều ưu tiên được ưu tiên thì cái có 'Thứ tự' cao nhất sẽ được chọn.",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Nhóm Bao gồm đảm bảo chỉ một mục từ một nhóm được kích hoạt tại một thời điểm, nếu nhiều mục được kích hoạt.\rHỗ trợ nhiều nhóm được phân tách bằng dấu phẩy.\r\rTài liệu: World Info - Inclusion Group",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Ưu tiên mục nhập này: Khi được chọn, mục nhập này được ưu tiên trong số tất cả các lựa chọn.\rNếu nhiều ưu tiên được ưu tiên thì cái có 'Thứ tự' cao nhất sẽ được chọn.",
"Only one entry with the same label will be activated": "Chỉ một mục có cùng nhãn sẽ được kích hoạt",
"A relative likelihood of entry activation within the group": "Khả năng tương đối của việc kích hoạt mục nhập trong nhóm",
"Group Weight": "Trọng lượng nhóm",
@ -1436,7 +1436,7 @@
"Reset Code:": "Đặt lại mã:",
"Want to update?": "Muốn cập nhật?",
"How to start chatting?": "Làm thế nào để bắt đầu trò chuyện?",
"Click ": "Nhấp chuột",
"Click _space": "Nhấp chuột",
"and select a": "và chọn một",
"Chat API": "API Trò chuyện",
"and pick a character.": "và chọn một nhân vật.",

File diff suppressed because it is too large Load Diff

View File

@ -854,7 +854,7 @@
"Join character cards (include muted)": "加入角色人物卡(包括靜音)",
"Inserted before each part of the joined fields.": "插入在合併欄位的每一部分之前。",
"Join Prefix": "加入前綴",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.\rThis means that in the story string for example all character descriptions will be joined to one big text.\rIf you want those fields to be separated, you can define a prefix or suffix here.\r\rThis value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "當選擇“加入角色卡”時,角色的所有對應欄位將連接在一起。\r這意味著例如在故事字串中所有角色描述都將連接到一個大文字中。\r如果您希望分隔這些字段可以在此處定義前綴或後綴。\r\r該值支援普通宏並且還將 {{char}} 替換為相關字元的名稱,並將 <FIELDNAME> 替換為部分的名稱(例如:描述、個性、場景等)",
"When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and <FIELDNAME> with the name of the part (e.g.: description, personality, scenario, etc.)": "當選擇“加入角色卡”時,角色的所有對應欄位將連接在一起。\r這意味著例如在故事字串中所有角色描述都將連接到一個大文字中。\r如果您希望分隔這些字段可以在此處定義前綴或後綴。\r\r該值支援普通宏並且還將 {{char}} 替換為相關字元的名稱,並將 <FIELDNAME> 替換為部分的名稱(例如:描述、個性、場景等)",
"Inserted after each part of the joined fields.": "插入在合併欄位的每一部分之後。",
"Join Suffix": "加入後綴",
"Set a group chat scenario": "設定群組聊天情境",
@ -1007,8 +1007,8 @@
"Character Exclusion": "角色人物排除",
"-- Characters not found --": "-- 未找到角色人物 --",
"Inclusion Group": "包含的群組",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\r\rDocumentation: World Info - Inclusion Group": "如果觸發多個條目,包含群組可確保一次僅啟動一組中的一個條目。\r支援多個以逗號分隔的群組。\r\r文件世界資訊 - 包容性集團",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.": "優先考慮此條目:選取後,此條目將在所有選擇中優先。\r如果有多個優先級則選擇「順序」最高的一個。",
"Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "如果觸發多個條目,包含群組可確保一次僅啟動一組中的一個條目。\r支援多個以逗號分隔的群組。\r\r文件世界資訊 - 包容性集團",
"Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "優先考慮此條目:選取後,此條目將在所有選擇中優先。\r如果有多個優先級則選擇「順序」最高的一個。",
"Only one entry with the same label will be activated": "僅會啟用具有相同標籤的一個條目",
"A relative likelihood of entry activation within the group": "群組內條目啟用的相對可能性",
"Group Weight": "群組權重",
@ -1439,7 +1439,7 @@
"Reset Code:": "重設驗證碼:",
"Want to update?": "想要更新嗎?",
"How to start chatting?": "如何開始聊天?",
"Click ": "點選",
"Click _space": "點選",
"and select a": "並選擇一個",
"Chat API": "聊天 API",
"and pick a character.": "並選擇一個角色人物。",

View File

@ -934,7 +934,7 @@ function cancelStatusCheck() {
export function displayOnlineStatus() {
if (online_status == 'no_connection') {
$('.online_status_indicator').removeClass('success');
$('.online_status_text').text('No connection...');
$('.online_status_text').text($('#API-status-top').attr('no_connection_text'));
} else {
$('.online_status_indicator').addClass('success');
$('.online_status_text').text(online_status);
@ -2341,6 +2341,16 @@ export function scrollChatToBottom() {
}
}
/**
* Substitutes {{macro}} parameters in a string.
* @param {string} content - The string to substitute parameters in.
* @param {Record<string,any>} additionalMacro - Additional environment variables for substitution.
* @returns {string} The string with substituted parameters.
*/
export function substituteParamsExtended(content, additionalMacro = {}) {
return substituteParams(content, undefined, undefined, undefined, undefined, true, additionalMacro);
}
/**
* Substitutes {{macro}} parameters in a string.
* @param {string} content - The string to substitute parameters in.
@ -2349,9 +2359,10 @@ export function scrollChatToBottom() {
* @param {string} [_original] - The original message for {{original}} substitution.
* @param {string} [_group] - The group members list for {{group}} substitution.
* @param {boolean} [_replaceCharacterCard] - Whether to replace character card macros.
* @param {Record<string,any>} [additionalMacro] - Additional environment variables for substitution.
* @returns {string} The string with substituted parameters.
*/
export function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true) {
export function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true, additionalMacro = {}) {
const environment = {};
if (typeof _original === 'string') {
@ -2401,6 +2412,10 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
environment.group = environment.charIfNotGroup = getGroupValue();
environment.model = getGeneratingModel();
if (additionalMacro && typeof additionalMacro === 'object') {
Object.assign(environment, additionalMacro);
}
return evaluateMacros(content, environment);
}
@ -4697,7 +4712,7 @@ function addChatsSeparator(mesSendString) {
async function DupeChar() {
if (!this_chid) {
toastr.warning('You must first select a character to duplicate!');
return;
return '';
}
const confirmMessage = `
@ -4708,7 +4723,7 @@ async function DupeChar() {
if (!confirm) {
console.log('User cancelled duplication');
return;
return '';
}
const body = { avatar_url: characters[this_chid].avatar };
@ -4723,6 +4738,8 @@ async function DupeChar() {
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
getCharacters();
}
return '';
}
export async function itemizedParams(itemizedPrompts, thisPromptSet) {
@ -7733,6 +7750,8 @@ window['SillyTavern'].getContext = function () {
activateSendButtons,
deactivateSendButtons,
saveReply,
substituteParams,
substituteParamsExtended,
registerSlashCommand: registerSlashCommand,
executeSlashCommands: executeSlashCommands,
timestampToMoment: timestampToMoment,
@ -8286,10 +8305,12 @@ async function selectInstructCallback(_, name) {
async function enableInstructCallback() {
$('#instruct_enabled').prop('checked', true).trigger('change');
return '';
}
async function disableInstructCallback() {
$('#instruct_enabled').prop('checked', false).trigger('change');
return '';
}
/**
@ -8495,23 +8516,25 @@ async function doDeleteChat() {
$(currentChatDeleteButton).trigger('click');
await delay(1);
$('#dialogue_popup_ok').trigger('click', { fromSlashCommand: true });
return '';
}
async function doRenameChat(_, chatName) {
if (!chatName) {
toastr.warning('Name must be provided as an argument to rename this chat.');
return;
return '';
}
const currentChatName = getCurrentChatId();
if (!currentChatName) {
toastr.warning('No chat selected that can be renamed.');
return;
return '';
}
await renameChat(currentChatName, chatName);
toastr.success(`Successfully renamed chat to: ${chatName}`);
return '';
}
/**
@ -8585,6 +8608,7 @@ function doCharListDisplaySwitch() {
function doCloseChat() {
$('#option_close_chat').trigger('click');
return '';
}
/**
@ -8680,6 +8704,7 @@ async function removeCharacterFromUI(name, avatar, reloadCharacters = true) {
function doTogglePanels() {
$('#option_settings').trigger('click');
return '';
}
function addDebugFunctions() {
@ -8767,6 +8792,7 @@ jQuery(async function () {
await saveSettings();
await saveChatConditional();
toastr.success('Chat and settings saved.');
return '';
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
@ -8918,7 +8944,10 @@ jQuery(async function () {
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'chat-manager',
callback: () => $('#option_select_chat').trigger('click'),
callback: () => {
$('#option_select_chat').trigger('click');
return '';
},
aliases: ['chat-history', 'manage-chats'],
helpString: 'Opens the chat manager for the current character/group.',
}));
@ -10745,4 +10774,4 @@ jQuery(async function () {
});
initCustomSelectedSamplers();
});
});

View File

@ -387,11 +387,15 @@ export class AutoComplete {
// no result and no input? hide autocomplete
return this.hide();
}
if (this.effectiveParserResult instanceof AutoCompleteSecondaryNameResult && !this.effectiveParserResult.forceMatch) {
// no result and matching is no forced? hide autocomplete
return this.hide();
}
// otherwise add "no match" notice
const option = new BlankAutoCompleteOption(
this.name.length ?
this.effectiveParserResult.makeNoMatchText()
: this.effectiveParserResult.makeNoOptionstext()
: this.effectiveParserResult.makeNoOptionsText()
,
);
this.result.push(option);

View File

@ -10,7 +10,7 @@ export class AutoCompleteNameResult {
/**@type {AutoCompleteOption[]} */ optionList = [];
/**@type {boolean} */ canBeQuoted = false;
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
/**@type {()=>string} */ makeNoOptionstext = ()=>'No options';
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
/**
@ -27,7 +27,7 @@ export class AutoCompleteNameResult {
this.optionList = optionList;
this.canBeQuoted = canBeQuoted;
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionstext;
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
}

View File

@ -6,6 +6,7 @@ import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
export class AutoCompleteOption {
/**@type {string}*/ name;
/**@type {string}*/ typeIcon;
/**@type {string}*/ type;
/**@type {number}*/ nameOffset = 0;
/**@type {AutoCompleteFuzzyScore}*/ score;
/**@type {string}*/ replacer;
@ -24,9 +25,10 @@ export class AutoCompleteOption {
/**
* @param {string} name
*/
constructor(name, typeIcon = ' ') {
constructor(name, typeIcon = ' ', type = '') {
this.name = name;
this.typeIcon = typeIcon;
this.type = type;
}
@ -181,6 +183,7 @@ export class AutoCompleteOption {
let li;
li = this.makeItem(this.name, this.typeIcon, true);
li.setAttribute('data-name', this.name);
li.setAttribute('data-option-type', this.type);
return li;
}

View File

@ -2,4 +2,5 @@ import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
/**@type {boolean}*/ isRequired = false;
/**@type {boolean}*/ forceMatch = true;
}

View File

@ -1,11 +1,11 @@
<div id="assets_ui">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Download Extensions & Assets</b>
<b data-i18n="Download Extensions & Assets">Download Extensions & Assets</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="assets-json-url-field">Assets URL</label>
<label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label>
<div class="assets-connect-div">
<input id="assets-json-url-field" class="text_pole widthUnset flex1">
<i id="assets-connect-button" class="menu_button fa-solid fa-plug-circle-exclamation fa-xl redOverlayGlow"></i>
@ -16,7 +16,7 @@
<input id="assets_search" class="text_pole flex1" placeholder="Search" type="search">
<div id="assets-characters-button" class="menu_button menu_button_icon">
<i class="fa-solid fa-image-portrait"></i>
Characters
<span data-i18n="Characters">Characters</span>
</div>
</div>
<div class="inline-drawer-content" id="assets_menu">

View File

@ -1,6 +1,6 @@
import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
import { SECRET_KEYS, secret_state } from '../../secrets.js';
import { getMultimodalCaption } from '../shared.js';
@ -95,7 +95,7 @@ async function sendCaptionedMessage(caption, image) {
template += ' {{caption}}';
}
let messageText = substituteParams(template).replace(/{{caption}}/i, caption);
let messageText = substituteParamsExtended(template, { caption: caption });
if (extension_settings.caption.refine_mode) {
messageText = await callPopup(

View File

@ -2,34 +2,34 @@
<div class="caption_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Image Captioning</b>
<b data-i18n="Image Captioning">Image Captioning</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="caption_source">Source</label>
<label for="caption_source" data-i18n="Source">Source</label>
<select id="caption_source" class="text_pole">
<option value="local">Local</option>
<option value="multimodal">Multimodal (OpenAI / Anthropic / llama / Google)</option>
<option value="extras">Extras</option>
<option value="horde">Horde</option>
<option value="local" data-i18n="Local">Local</option>
<option value="multimodal" data-i18n="Multimodal (OpenAI / Anthropic / llama / Google)">Multimodal (OpenAI / Anthropic / llama / Google)</option>
<option value="extras" data-i18n="Extras">Extras</option>
<option value="horde" data-i18n="Horde">Horde</option>
</select>
<div id="caption_multimodal_block" class="flex-container wide100p">
<div class="flex1 flex-container flexFlowColumn flexNoGap">
<label for="caption_multimodal_api">API</label>
<label for="caption_multimodal_api" data-i18n="API">API</label>
<select id="caption_multimodal_api" class="flex1 text_pole">
<option value="anthropic">Anthropic</option>
<option value="custom">Custom (OpenAI-compatible)</option>
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
<option value="google">Google MakerSuite</option>
<option value="koboldcpp">KoboldCpp</option>
<option value="llamacpp">llama.cpp</option>
<option value="ollama">Ollama</option>
<option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option>
<option value="ooba">Text Generation WebUI (oobabooga)</option>
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
</select>
</div>
<div class="flex1 flex-container flexFlowColumn flexNoGap">
<label for="caption_multimodal_model">Model</label>
<label for="caption_multimodal_model" data-i18n="Model">Model</label>
<select id="caption_multimodal_model" class="flex1 text_pole">
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
@ -54,36 +54,36 @@
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
<option data-type="ollama" value="ollama_current" data-i18n="currently_selected">[Currently selected]</option>
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
<option data-type="ollama" value="llava:latest">llava:latest</option>
<option data-type="llamacpp" value="llamacpp_current">[Currently loaded]</option>
<option data-type="ooba" value="ooba_current">[Currently loaded]</option>
<option data-type="koboldcpp" value="koboldcpp_current">[Currently loaded]</option>
<option data-type="custom" value="custom_current">[Currently selected]</option>
<option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
</select>
</div>
<label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
Allow reverse proxy
<span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
</label>
<div class="flexBasis100p m-b-1">
<small><b>Hint:</b> Set your API keys and endpoints in the 'API Connections' tab first.</small>
<small><b data-i18n="Hint:">Hint:</b> <span data-i18n="Set your API keys and endpoints in the 'API Connections' tab first.">Set your API keys and endpoints in the 'API Connections' tab first.</span></small>
</div>
</div>
<div id="caption_prompt_block">
<label for="caption_prompt">Caption Prompt</label>
<label for="caption_prompt" data-i18n="Caption Prompt">Caption Prompt</label>
<textarea id="caption_prompt" class="text_pole" rows="1" placeholder="&lt; Use default &gt;">${PROMPT_DEFAULT}</textarea>
<label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned.">
<input id="caption_prompt_ask" type="checkbox" class="checkbox">
Ask every time
<span data-i18n="Ask every time">Ask every time</span>
</label>
</div>
<label for="caption_template">Message Template <small>(use <code>&lcub;&lcub;caption&rcub;&rcub;</code> macro)</small></label>
<label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>&lcub;&lcub;caption&rcub;&rcub;</code> <span data-i18n="macro)">macro)</span></small></label>
<textarea id="caption_template" class="text_pole" rows="2" placeholder="&lt; Use default &gt;">${TEMPLATE_DEFAULT}</textarea>
<label class="checkbox_label margin-bot-10px" for="caption_refine_mode">
<input id="caption_refine_mode" type="checkbox" class="checkbox">
Edit captions before saving
<span data-i18n="Edit captions before saving">Edit captions before saving</span>
</label>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js';
import { dragElement, isMobile } from '../../RossAscends-mods.js';
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
import { loadMovingUIState, power_user } from '../../power-user.js';
@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js';
import { isFunctionCallingSupported } from '../../openai.js';
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
export { MODULE_NAME };
const MODULE_NAME = 'expressions';
@ -87,6 +88,7 @@ function getFallbackExpression() {
*/
function toggleTalkingHeadCommand(_) {
setTalkingHeadState(!extension_settings.expressions.talkinghead);
return String(extension_settings.expressions.talkinghead);
}
function isVisualNovelMode() {
@ -914,6 +916,7 @@ async function setSpriteSetCommand(_, folder) {
// moduleWorker();
const vnMode = isVisualNovelMode();
await sendExpressionCall(folder, lastExpression, true, vnMode);
return '';
}
async function classifyCommand(_, text) {
@ -935,7 +938,7 @@ async function classifyCommand(_, text) {
async function setSpriteSlashCommand(_, spriteId) {
if (!spriteId) {
console.log('No sprite id provided');
return;
return '';
}
spriteId = spriteId.trim().toLowerCase();
@ -955,7 +958,7 @@ async function setSpriteSlashCommand(_, spriteId) {
if (!spriteItem) {
console.log('No sprite found for search term ' + spriteId);
return;
return '';
}
label = spriteItem.label;
@ -963,6 +966,7 @@ async function setSpriteSlashCommand(_, spriteId) {
const vnMode = isVisualNovelMode();
await sendExpressionCall(spriteFolderName, label, true, vnMode);
return label;
}
/**
@ -1008,8 +1012,7 @@ async function getLlmPrompt(labels) {
}
const labelsString = labels.map(x => `"${x}"`).join(', ');
const prompt = substituteParams(String(extension_settings.expressions.llmPrompt))
.replace(/{{labels}}/gi, labelsString);
const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });
return prompt;
}
@ -1187,7 +1190,7 @@ function getLastCharacterMessage() {
const reversedChat = context.chat.slice().reverse();
for (let mes of reversedChat) {
if (mes.is_user || mes.is_system) {
if (mes.is_user || mes.is_system || mes.extra?.type === system_message_types.NARRATOR) {
continue;
}
@ -1326,10 +1329,18 @@ async function renderFallbackExpressionPicker() {
}
}
function getCachedExpressions() {
if (!Array.isArray(expressionsList)) {
return [];
}
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
}
async function getExpressionsList() {
// Return cached list if available
if (Array.isArray(expressionsList)) {
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
return getCachedExpressions();
}
/**
@ -2033,17 +2044,23 @@ function migrateSettings() {
});
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'sprite',
aliases: ['emote'],
callback: setSpriteSlashCommand,
unnamedArgumentList: [
new SlashCommandArgument(
'spriteId', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'spriteId',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => getCachedExpressions().map((x) => new SlashCommandEnumValue(x)),
}),
],
helpString: 'Force sets the sprite for the current character.',
returns: 'label',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'spriteoverride',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'spriteoverride',
aliases: ['costume'],
callback: setSpriteSetCommand,
unnamedArgumentList: [
@ -2053,8 +2070,9 @@ function migrateSettings() {
],
helpString: 'Sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lastsprite',
callback: (_, value) => lastExpression[value.trim()] ?? '',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'lastsprite',
callback: (_, value) => lastExpression[String(value).trim()] ?? '',
returns: 'sprite',
unnamedArgumentList: [
new SlashCommandArgument(
@ -2063,12 +2081,15 @@ function migrateSettings() {
],
helpString: 'Returns the last set sprite / expression for the named character.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'th',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'th',
callback: toggleTalkingHeadCommand,
aliases: ['talkinghead'],
helpString: 'Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.',
returns: ARGUMENT_TYPE.BOOLEAN,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'classify',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'classify',
callback: classifyCommand,
unnamedArgumentList: [
new SlashCommandArgument(

View File

@ -1,65 +1,65 @@
<div class="expression_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Character Expressions</b>
<b data-i18n="Character Expressions">Character Expressions</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label" for="expression_translate" title="Use the selected API from Chat Translation extension settings.">
<input id="expression_translate" type="checkbox">
<span>Translate text to English before classification</span>
<span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
</label>
<label class="checkbox_label" for="expressions_show_default">
<input id="expressions_show_default" type="checkbox">
<span>Show default images (emojis) if sprite missing</span>
<span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span>
</label>
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
<input id="image_type_toggle" type="checkbox">
<span>Image Type - talkinghead (extras)</span>
<span data-i18n="Image Type - talkinghead (extras)">Image Type - talkinghead (extras)</span>
</label>
<div class="expression_api_block m-b-1 m-t-1">
<label for="expression_api">Classifier API</label>
<small>Select the API for classifying expressions.</small>
<label for="expression_api" data-i18n="Classifier API">Classifier API</label>
<small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>
<select id="expression_api" class="flex1 margin0" data-i18n="Expression API" placeholder="Expression API">
<option value="0">Local</option>
<option value="1">Extras</option>
<option value="2">LLM</option>
<option value="0" data-i18n="Local">Local</option>
<option value="1" data-i18n="Extras">Extras</option>
<option value="2" data-i18n="LLM">LLM</option>
</select>
</div>
<div class="expression_llm_prompt_block m-b-1 m-t-1">
<label for="expression_llm_prompt" class="title_restorable">
<span>LLM Prompt</span>
<span data-i18n="LLM Prompt">LLM Prompt</span>
<div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
<i class="fa-solid fa-clock-rotate-left fa-sm"></i>
</div>
</label>
<small>Will be used if the API doesn't support JSON schemas or function calling.</small>
<small data-i18n="Will be used if the API doesn't support JSON schemas or function calling.">Will be used if the API doesn't support JSON schemas or function calling.</small>
<textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact" rows="2" placeholder="Use &lcub;&lcub;labels&rcub;&rcub; special macro."></textarea>
</div>
<div class="expression_fallback_block m-b-1 m-t-1">
<label for="expression_fallback">Default / Fallback Expression</label>
<small>Set the default and fallback expression being used when no matching expression is found.</small>
<label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
<small data-i18n="Set the default and fallback expression being used when no matching expression is found.">Set the default and fallback expression being used when no matching expression is found.</small>
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
</div>
<div class="expression_custom_block m-b-1 m-t-1">
<label for="expression_custom">Custom Expressions</label>
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
<label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>
<small><span data-i18n="Can be set manually or with an _space">Can be set manually or with an </span><tt>/emote</tt><span data-i18n="space_ slash command."> slash command.</span></small>
<div class="flex-container">
<select id="expression_custom" class="flex1 margin0"><select>
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
</div>
</div>
<div id="no_chat_expressions">
<div id="no_chat_expressions" data-i18n="Open a chat to see the character expressions.">
Open a chat to see the character expressions.
</div>
<div id="open_chat_expressions">
<div class="offline_mode">
<small>You are in offline mode. Click on the image below to set the expression.</small>
<small data-i18n="You are in offline mode. Click on the image below to set the expression.">You are in offline mode. Click on the image below to set the expression.</small>
</div>
<label for="expression_override">Sprite Folder Override</label>
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
<label for="expression_override" data-i18n="Sprite Folder Override">Sprite Folder Override</label>
<small><span data-i18n="Use a forward slash to specify a subfolder. Example: _space">Use a forward slash to specify a subfolder. Example: </span><tt>Bob/formal</tt></small>
<div class="flex-container flexnowrap">
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
@ -67,17 +67,17 @@
<div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i>
<span>Upload sprite pack (ZIP)</span>
<span data-i18n="Upload sprite pack (ZIP)">Upload sprite pack (ZIP)</span>
</div>
<div id="expression_override_cleanup_button" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Remove all image overrides</span>
<span data-i18n="Remove all image overrides">Remove all image overrides</span>
</div>
</div>
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>/characters/</b> folder of your user data directory and name it as the name of the character.
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
<p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p>
<h3 id="image_list_header">
<strong>Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
<strong data-i18n="Sprite set:">Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3>
<div id="image_list"></div>

View File

@ -11,7 +11,7 @@ import {
generateQuietPrompt,
is_send_press,
saveSettingsDebounced,
substituteParams,
substituteParamsExtended,
generateRaw,
getMaxContextSize,
} from '../../../script.js';
@ -43,8 +43,7 @@ const formatMemoryValue = function (value) {
value = value.trim();
if (extension_settings.memory.template) {
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
return substituteParams(result);
return substituteParamsExtended(extension_settings.memory.template, { summary: value });
} else {
return `Summary: ${value}`;
}
@ -447,7 +446,7 @@ async function summarizeCallback(args, text) {
}
const source = args.source || extension_settings.memory.source;
const prompt = substituteParams((resolveVariable(args.prompt) || extension_settings.memory.prompt)?.replace(/{{words}}/gi, extension_settings.memory.promptWords));
const prompt = substituteParamsExtended((resolveVariable(args.prompt) || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords });
try {
switch (source) {
@ -534,7 +533,7 @@ async function summarizeChatMain(context, force, skipWIAN) {
}
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords);
const prompt = substituteParamsExtended(extension_settings.memory.prompt, { words: extension_settings.memory.promptWords });
if (!prompt) {
console.debug('Summarization prompt is empty. Skipping summarization.');

View File

@ -2,7 +2,7 @@
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<div class="flex-container alignitemscenter margin0">
<b>Summarize</b>
<b data-i18n="ext_sum_title">Summarize</b>
<i id="summaryExtensionPopoutButton" class="fa-solid fa-window-restore menu_button margin0"></i>
</div>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
@ -24,7 +24,7 @@
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
<div class="memory_contents_controls">
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" data-i18n="[title]ext_sum_force_tip" title="Trigger a summary update right now." data-i18n="Trigger a summary update right now.">
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="[title]ext_sum_force_tip">
<i class="fa-solid fa-database"></i>
<span data-i18n="ext_sum_force_text">Summarize now</span>
</div>

View File

@ -1,33 +1,33 @@
<div id="qr--modalEditor">
<div id="qr--main">
<h3>Labels and Message</h3>
<h3 data-i18n="Labels and Message">Labels and Message</h3>
<div class="qr--labels">
<label>
<span class="qr--labelText">Label</span>
<span class="qr--labelText" data-i18n="Label">Label</span>
<input type="text" class="text_pole" id="qr--modal-label">
</label>
<label>
<span class="qr--labelText">Title</span>
<small class="qr--labelHint">(tooltip, leave empty to show message or /command)</small>
<span class="qr--labelText" data-i18n="Title">Title</span>
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
<input type="text" class="text_pole" id="qr--modal-title">
</label>
</div>
<div class="qr--modal-messageContainer">
<label for="qr--modal-message">
<label for="qr--modal-message" data-i18n="Message / Command:">
Message / Command:
</label>
<div class="qr--modal-editorSettings">
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-wrap">
<span>Word wrap</span>
<span data-i18n="Word wrap">Word wrap</span>
</label>
<label class="checkbox_label">
<span>Tab size:</span>
<span data-i18n="Tab size:">Tab size:</span>
<input type="number" min="1" max="9" id="qr--modal-tabSize" class="text_pole">
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-executeShortcut">
<span>Ctrl+Enter to execute</span>
<span data-i18n="Ctrl+Enter to execute">Ctrl+Enter to execute</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-syntax">
@ -44,14 +44,14 @@
<div id="qr--qrOptions">
<h3>Context Menu</h3>
<h3 data-i18n="Context Menu">Context Menu</h3>
<div id="qr--ctxEditor">
<template id="qr--ctxItem">
<div class="qr--ctxItem" data-order="0">
<div class="drag-handle ui-sortable-handle"></div>
<select class="qr--set"></select>
<label class="qr--isChainedLabel checkbox_label" title="When enabled, the current Quick Reply will be sent together with (before) the clicked QR from the context menu.">
Chaining:
<span data-i18n="Chaining:">Chaining:</span>
<input type="checkbox" class="qr--isChained">
</label>
<div class="qr--delete menu_button menu_button_icon fa-solid fa-trash-can" title="Remove entry"></div>
@ -63,48 +63,48 @@
</div>
<h3>Auto-Execute</h3>
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
<div class="flex-container flexFlowColumn">
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
<input type="checkbox" id="qr--preventAutoExecute" >
<span><i class="fa-solid fa-fw fa-plane-slash"></i> Don't trigger auto-execute</span>
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--isHidden" >
<span><i class="fa-solid fa-fw fa-eye-slash"></i> Invisible (auto-execute only)</span>
<span><i class="fa-solid fa-fw fa-eye-slash"></i><span data-i18n="Invisible (auto-execute only)">Invisible (auto-execute only)</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnStartup" >
<span><i class="fa-solid fa-fw fa-rocket"></i> Execute on app startup</span>
<span><i class="fa-solid fa-fw fa-rocket"></i><span data-i18n="Execute on startup">Execute on startup</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnUser" >
<span><i class="fa-solid fa-fw fa-user"></i> Execute on user message</span>
<span><i class="fa-solid fa-fw fa-user"></i><span data-i18n="Execute on user message">Execute on user message</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnAi" >
<span><i class="fa-solid fa-fw fa-robot"></i> Execute on AI message</span>
<span><i class="fa-solid fa-fw fa-robot"></i><span data-i18n="Execute on AI message">Execute on AI message</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnChatChange" >
<span><i class="fa-solid fa-fw fa-message"></i> Execute on opening chat</span>
<span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
<span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
</label>
<div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
<small>Automation ID</small>
<small data-i18n="Automation ID:">Automation ID</small>
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
</div>
</div>
<h3>Testing</h3>
<h3 data-i18n="Testing">Testing</h3>
<div id="qr--modal-executeButtons">
<div id="qr--modal-execute" class="qr--modal-executeButton menu_button" title="Execute the quick reply now">
<i class="fa-solid fa-play"></i>
Execute
<span data-i18n="Execute">Execute</span>
</div>
<div id="qr--modal-pause" class="qr--modal-executeButton menu_button" title="Pause / continue execution">
<span class="qr--modal-executeComboIcon">
@ -119,7 +119,7 @@
<div id="qr--modal-executeProgress"></div>
<label class="checkbox_label">
<input type="checkbox" id="qr--modal-executeHide">
<span> Hide editor while executing</span>
<span title="Hide editor while executing"> Hide editor while executing</span>
</label>
<div id="qr--modal-executeErrors"></div>
<div id="qr--modal-executeResult"></div>

View File

@ -1,22 +1,22 @@
<div id="qr--settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<strong>Quick Reply</strong>
<strong data-i18n="Quick Reply">Quick Reply</strong>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="flex-container">
<input type="checkbox" id="qr--isEnabled"> Enable Quick Replies
<input type="checkbox" id="qr--isEnabled"><span data-i18n="Enable Quick Replies">Enable Quick Replies</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--isCombined"> Combine buttons from all active sets
<input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span>
</label>
<hr>
<div id="qr--global">
<div class="qr--head">
<div class="qr--title">Global Quick Reply Sets</div>
<div class="qr--title" data-i18n="Global Quick Reply Sets">Global Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--global-setListAdd" title="Add quick reply set"></div>
</div>
@ -28,7 +28,7 @@
<div id="qr--chat">
<div class="qr--head">
<div class="qr--title">Chat Quick Reply Sets</div>
<div class="qr--title" data-i18n="Chat Quick Reply Sets">Chat Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--chat-setListAdd" title="Add quick reply set"></div>
</div>
@ -40,7 +40,7 @@
<div id="qr--editor">
<div class="qr--head">
<div class="qr--title">Edit Quick Replies</div>
<div class="qr--title" data-i18n="Edit Quick Replies">Edit Quick Replies</div>
<div class="qr--actions">
<select id="qr--set" class="text_pole"></select>
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
@ -52,13 +52,13 @@
</div>
<div id="qr--set-settings">
<label class="flex-container">
<input type="checkbox" id="qr--disableSend"> <span>Disable send (insert into input field)</span>
<input type="checkbox" id="qr--disableSend"> <span data-i18n="Disable Send (Insert Into Input Field)">Disable send (insert into input field)</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--placeBeforeInput"> <span>Place quick reply before input</span>
<input type="checkbox" id="qr--placeBeforeInput"> <span data-i18n="Place Quick Reply Before Input">Place quick reply before input</span>
</label>
<label class="flex-container" id="qr--injectInputContainer">
<input type="checkbox" id="qr--injectInput"> <span>Inject user input automatically <small>(if disabled, use <code>{{input}}</code> macro for manual injection)</small></span>
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
</label>
</div>
<div id="qr--set-qrList" class="qr--qrList"></div>

View File

@ -8,11 +8,11 @@
</div>
<div class="inline-drawer-content">
<div class="flex-container">
<div id="open_regex_editor" class="menu_button menu_button_icon" title="New global regex script">
<div id="open_regex_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_global_script_desc" title="New global regex script">
<i class="fa-solid fa-pen-to-square"></i>
<small data-i18n="ext_regex_new_global_script">+ Global</small>
</div>
<div id="open_scoped_editor" class="menu_button menu_button_icon" title="New scoped regex script">
<div id="open_scoped_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_scoped_script_desc" title="New scoped regex script">
<i class="fa-solid fa-address-card"></i>
<small data-i18n="ext_regex_new_scoped_script">+ Scoped</small>
</div>
@ -39,7 +39,7 @@
<label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">
<input type="checkbox" id="regex_scoped_toggle" class="enable_scoped" />
<span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" title="Disallow using scoped regex"></span>
<span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" title="Allow using scoped regex"></span>
<span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_scoped" title="Allow using scoped regex"></span>
</label>
</div>
<small data-i18n="ext_regex_scoped_scripts_desc">

View File

@ -17,6 +17,7 @@ import {
getCharacterAvatar,
formatCharacterAvatar,
substituteParams,
substituteParamsExtended,
} from '../../../script.js';
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
import { selected_group } from '../../group-chats.js';
@ -50,7 +51,15 @@ const sources = {
pollinations: 'pollinations',
};
const initiators = {
command: 'command',
action: 'action',
interactive: 'interactive',
wand: 'wand',
};
const generationMode = {
MESSAGE: -1,
CHARACTER: 0,
USER: 1,
SCENARIO: 2,
@ -62,6 +71,7 @@ const generationMode = {
CHARACTER_MULTIMODAL: 8,
USER_MULTIMODAL: 9,
FACE_MULTIMODAL: 10,
FREE_EXTENDED: 11,
};
const multimodalMap = {
@ -71,6 +81,7 @@ const multimodalMap = {
};
const modeLabels = {
[generationMode.MESSAGE]: 'Chat Message Template',
[generationMode.CHARACTER]: 'Character ("Yourself")',
[generationMode.FACE]: 'Portrait ("Your Face")',
[generationMode.USER]: 'User ("Me")',
@ -81,6 +92,7 @@ const modeLabels = {
[generationMode.CHARACTER_MULTIMODAL]: 'Character (Multimodal Mode)',
[generationMode.FACE_MULTIMODAL]: 'Portrait (Multimodal Mode)',
[generationMode.USER_MULTIMODAL]: 'User (Multimodal Mode)',
[generationMode.FREE_EXTENDED]: 'Free Mode (LLM-Extended)',
};
const triggerWords = {
@ -94,7 +106,7 @@ const triggerWords = {
};
const messageTrigger = {
activationRegex: /\b(send|mail|imagine|generate|make|create|draw|paint|render)\b.{0,10}\b(pic|picture|image|drawing|painting|photo|photograph)\b(?:\s+of)?(?:\s+(?:a|an|the|this|that|those)?)?(.+)/i,
activationRegex: /\b(send|mail|imagine|generate|make|create|draw|paint|render|show)\b.{0,10}\b(pic|picture|image|drawing|painting|photo|photograph)\b(?:\s+of)?(?:\s+(?:a|an|the|this|that|those|your)?)?(.+)/i,
specialCases: {
[generationMode.CHARACTER]: ['you', 'yourself'],
[generationMode.USER]: ['me', 'myself'],
@ -106,6 +118,8 @@ const messageTrigger = {
};
const promptTemplates = {
// Not really a prompt template, rather an outcome message template
[generationMode.MESSAGE]: '[{{char}} sends a picture that contains: {{prompt}}].',
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.CHARACTER]: '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\']',
//face-specific prompt
@ -143,6 +157,7 @@ const promptTemplates = {
[generationMode.FACE_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "close-up portrait".',
[generationMode.CHARACTER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
[generationMode.USER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
[generationMode.FREE_EXTENDED]: 'Pause your roleplay and provide an exhaustive comma-separated list of tags describing the appearance of "{0}" in great detail. Start with {{charPrefix}} (sic) if the subject is associated with {{char}}.',
};
const defaultPrefix = 'best quality, absurdres, aesthetic,';
@ -204,6 +219,7 @@ const defaultSettings = {
interactive_mode: false,
multimodal_captioning: false,
snap: false,
free_extend: false,
prompts: promptTemplates,
@ -261,6 +277,11 @@ const defaultSettings = {
// Pollinations settings
pollinations_enhance: false,
pollinations_refine: false,
// Visibility toggles
wand_visible: false,
command_visible: false,
interactive_visible: false,
};
const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed);
@ -312,7 +333,7 @@ function processTriggers(chat, _, abort) {
}
abort(true);
setTimeout(() => generatePicture('sd', subject, message), 1);
setTimeout(() => generatePicture(initiators.interactive, {}, subject, message), 1);
} catch {
console.log('SD: Failed to process triggers.');
return;
@ -419,6 +440,10 @@ async function loadSettings() {
$('#sd_clip_skip').val(extension_settings.sd.clip_skip);
$('#sd_clip_skip_value').text(extension_settings.sd.clip_skip);
$('#sd_seed').val(extension_settings.sd.seed);
$('#sd_free_extend').prop('checked', extension_settings.sd.free_extend);
$('#sd_wand_visible').prop('checked', extension_settings.sd.wand_visible);
$('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
$('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
for (const style of extension_settings.sd.styles) {
const option = document.createElement('option');
@ -476,7 +501,7 @@ async function loadSettingOptions() {
function addPromptTemplates() {
$('#sd_prompt_templates').empty();
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts)) {
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts).sort((a, b) => Number(a[0]) - Number(b[0]))) {
const label = $('<label></label>')
.text(modeLabels[name])
.attr('for', `sd_prompt_${name}`)
@ -683,6 +708,18 @@ function onChatChanged() {
$('#sd_character_prompt').val(characterPrompt);
$('#sd_character_negative_prompt').val(negativePrompt);
$('#sd_character_prompt_share').prop('checked', hasSharedData);
adjustElementScrollHeight();
}
function adjustElementScrollHeight(){
if (!$('.sd_settings').is(':visible')) {
return;
}
resetScrollHeight($('#sd_prompt_prefix'));
resetScrollHeight($('#sd_negative_prompt'));
resetScrollHeight($('#sd_character_prompt'));
resetScrollHeight($('#sd_character_negative_prompt'));
}
function onCharacterPromptInput() {
@ -762,6 +799,26 @@ function onRefineModeInput() {
saveSettingsDebounced();
}
function onFreeExtendInput() {
extension_settings.sd.free_extend = !!$('#sd_free_extend').prop('checked');
saveSettingsDebounced();
}
function onWandVisibleInput() {
extension_settings.sd.wand_visible = !!$('#sd_wand_visible').prop('checked');
saveSettingsDebounced();
}
function onCommandVisibleInput() {
extension_settings.sd.command_visible = !!$('#sd_command_visible').prop('checked');
saveSettingsDebounced();
}
function onInteractiveVisibleInput() {
extension_settings.sd.interactive_visible = !!$('#sd_interactive_visible').prop('checked');
saveSettingsDebounced();
}
function onClipSkipInput() {
extension_settings.sd.clip_skip = Number($('#sd_clip_skip').val());
$('#sd_clip_skip_value').text(extension_settings.sd.clip_skip);
@ -1026,6 +1083,7 @@ async function changeComfyWorkflow(_, name) {
} else {
toastr.error(`ComfyUI Workflow "${name}" does not exist.`);
}
return '';
}
async function validateAutoUrl() {
@ -2016,6 +2074,10 @@ function getGenerationType(prompt) {
mode = multimodalMap[mode];
}
if (mode === generationMode.FREE && extension_settings.sd.free_extend) {
mode = generationMode.FREE_EXTENDED;
}
return mode;
}
@ -2080,7 +2142,16 @@ function getRawLastMessage() {
return `((${processReply(lastMessage.mes)})), (${processReply(character.scenario)}:0.7), (${processReply(character.description)}:0.5)`;
}
async function generatePicture(args, trigger, message, callback) {
/**
* Generates an image based on the given trigger word.
* @param {string} initiator The initiator of the image generation
* @param {Record<string, object>} args Command arguments
* @param {string} trigger Subject trigger word
* @param {string} [message] Chat message
* @param {function} [callback] Callback function
* @returns {Promise<string>} Image path
*/
async function generatePicture(initiator, args, trigger, message, callback) {
if (!trigger || trigger.trim().length === 0) {
console.log('Trigger word empty, aborting');
return;
@ -2111,9 +2182,9 @@ async function generatePicture(args, trigger, message, callback) {
eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath });
if (typeof callbackOriginal === 'function') {
callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix);
callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix, initiator);
} else {
sendMessage(prompt, imagePath, generationType, negativePromptPrefix);
sendMessage(prompt, imagePath, generationType, negativePromptPrefix, initiator);
}
};
}
@ -2122,18 +2193,19 @@ async function generatePicture(args, trigger, message, callback) {
callback = () => { };
}
const negativePromptPrefix = resolveVariable(args?.negative) || '';
const dimensions = setTypeSpecificDimensions(generationType);
let negativePromptPrefix = resolveVariable(args?.negative) || '';
let imagePath = '';
try {
const prompt = await getPrompt(generationType, message, trigger, quietPrompt);
const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
console.log('Processed image prompt:', prompt);
context.deactivateSendButtons();
hideSwipeButtons();
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback);
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator);
} catch (err) {
console.trace(err);
throw new Error('SD prompt text generation failed.');
@ -2196,7 +2268,16 @@ function restoreOriginalDimensions(savedParams) {
extension_settings.sd.width = savedParams.width;
}
async function getPrompt(generationType, message, trigger, quietPrompt) {
/**
* Generates a prompt for image generation.
* @param {number} generationType The type of image generation to perform.
* @param {string} message A message text to use for the image generation.
* @param {string} trigger A trigger string to use for the image generation.
* @param {string} quietPrompt A quiet prompt to use for the image generation.
* @param {function} combineNegatives A function that combines the negative prompt with other prompts.
* @returns {Promise<string>} - A promise that resolves when the prompt generation completes.
*/
async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {
let prompt;
switch (generationType) {
@ -2204,7 +2285,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
prompt = message || getRawLastMessage();
break;
case generationMode.FREE:
prompt = generateFreeModePrompt(trigger.trim());
prompt = generateFreeModePrompt(trigger.trim(), combineNegatives);
break;
case generationMode.FACE_MULTIMODAL:
case generationMode.CHARACTER_MULTIMODAL:
@ -2216,6 +2297,10 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
break;
}
if (generationType === generationMode.FREE_EXTENDED) {
prompt = generateFreeModePrompt(prompt.trim(), combineNegatives);
}
if (generationType !== generationMode.FREE) {
prompt = await refinePrompt(prompt, true);
}
@ -2226,9 +2311,10 @@ async function getPrompt(generationType, message, trigger, quietPrompt) {
/**
* Generates a free prompt with a character-specific prompt prefix support.
* @param {string} trigger - The prompt to use for the image generation.
* @param {function} combineNegatives - A function that combines the negative prompt with other prompts.
* @returns {string}
*/
function generateFreeModePrompt(trigger) {
function generateFreeModePrompt(trigger, combineNegatives) {
return trigger
.replace(/(?:^char(\s|,)|\{\{charPrefix\}\})/gi, (_, suffix) => {
const getLastCharacterKey = () => {
@ -2249,7 +2335,9 @@ function generateFreeModePrompt(trigger) {
const key = getLastCharacterKey();
const value = (extension_settings.sd.character_prompts[key] || '').trim();
return value ? value + (suffix || '') : '';
const negativeValue = (extension_settings.sd.character_negative_prompts[key] || '').trim();
typeof combineNegatives === 'function' && negativeValue ? combineNegatives(negativeValue) : void 0;
return value ? combinePrefixes(value, (suffix || '')) : '';
});
}
@ -2335,12 +2423,13 @@ async function generatePrompt(quietPrompt) {
* @param {number} generationType Type of image generation
* @param {string} prompt Prompt to be used for image generation
* @param {string} additionalNegativePrefix Additional negative prompt to be used for image generation
* @param {string} [characterName] Name of the character
* @param {function} [callback] Callback function to be called after image generation
* @param {string} characterName Name of the character
* @param {function} callback Callback function to be called after image generation
* @param {string} initiator The initiator of the image generation
* @returns
*/
async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName = null, callback) {
const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL];
async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName, callback, initiator) {
const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL, generationMode.FREE_EXTENDED];
const prefix = noCharPrefix.includes(generationType)
? extension_settings.sd.prompt_prefix
: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix());
@ -2405,7 +2494,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
const filename = `${characterName}_${humanizedDateTime()}`;
const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format);
callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix);
callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix, initiator) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix, initiator);
return base64Image;
}
@ -3009,15 +3098,17 @@ async function onComfyDeleteWorkflowClick() {
* @param {string} image Base64 encoded image
* @param {number} generationType Generation type of the image
* @param {string} additionalNegativePrefix Additional negative prompt used for the image generation
* @param {string} initiator The initiator of the image generation
*/
async function sendMessage(prompt, image, generationType, additionalNegativePrefix) {
async function sendMessage(prompt, image, generationType, additionalNegativePrefix, initiator) {
const context = getContext();
const name = context.groupId ? systemUserName : context.name2;
const messageText = `[${name} sends a picture that contains: ${prompt}]`;
const template = extension_settings.sd.prompts[generationMode.MESSAGE] || '{{prompt}}';
const messageText = substituteParamsExtended(template, { char: name, prompt: prompt });
const message = {
name: name,
is_user: false,
is_system: true,
is_system: !getVisibilityByInitiator(initiator),
send_date: getMessageTimeStamp(),
mes: messageText,
extra: {
@ -3033,6 +3124,24 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
context.saveChat();
}
/**
* Gets the visibility of the resulting message based on the initiator.
* @param {string} initiator Generation initiator
* @returns {boolean} Is resulting message visible
*/
function getVisibilityByInitiator(initiator) {
switch (initiator) {
case initiators.interactive:
return !!extension_settings.sd.interactive_visible;
case initiators.wand:
return !!extension_settings.sd.wand_visible;
case initiators.command:
return !!extension_settings.sd.command_visible;
default:
return false;
}
}
async function addSDGenButtons() {
const buttonHtml = await renderExtensionTemplateAsync('stable-diffusion', 'button');
const dropdownHtml = await renderExtensionTemplateAsync('stable-diffusion', 'dropdown');
@ -3082,7 +3191,7 @@ async function addSDGenButtons() {
if (param) {
console.log('doing /sd ' + param);
generatePicture('sd', param);
generatePicture(initiators.wand, {}, param);
}
});
}
@ -3159,11 +3268,11 @@ async function sdMessageButton(e) {
const generationType = message?.extra?.generationType ?? generationMode.FREE;
console.log('Regenerating an image, using existing prompt:', prompt);
dimensions = setTypeSpecificDimensions(generationType);
await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage);
await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage, initiators.action);
}
else {
console.log('doing /sd raw last');
await generatePicture('sd', 'raw_last', messageText, saveGeneratedImage);
await generatePicture(initiators.action, {}, 'raw_last', messageText, saveGeneratedImage);
}
}
catch (error) {
@ -3226,7 +3335,7 @@ jQuery(async () => {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine',
callback: generatePicture,
callback: (args, trigger) => generatePicture(initiators.command, args, String(trigger)),
aliases: ['sd', 'img', 'image'],
namedArgumentList: [
new SlashCommandNamedArgument(
@ -3326,6 +3435,10 @@ jQuery(async () => {
$('#sd_clip_skip').on('input', onClipSkipInput);
$('#sd_seed').on('input', onSeedInput);
$('#sd_character_prompt_share').on('input', onCharacterPromptShareInput);
$('#sd_free_extend').on('input', onFreeExtendInput);
$('#sd_wand_visible').on('input', onWandVisibleInput);
$('#sd_command_visible').on('input', onCommandVisibleInput);
$('#sd_interactive_visible').on('input', onInteractiveVisibleInput);
$('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($('#sd_prompt_prefix'));

View File

@ -22,6 +22,11 @@
<input id="sd_multimodal_captioning" type="checkbox" />
<span data-i18n="sd_multimodal_captioning_txt">Use multimodal captioning for portraits</span>
</label>
<label for="sd_free_extend" class="checkbox_label" data-i18n="[title]sd_free_extend" title="Automatically extend free mode subject prompts (not portraits or backgrounds) using a currently selected LLM.">
<input id="sd_free_extend" type="checkbox" />
<span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
<small data-i18n="sd_free_extend_small">(interactive/commands)</small>
</label>
<label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
<input id="sd_expand" type="checkbox" />
<span data-i18n="sd_expand_txt">Auto-enhance prompts</span>
@ -279,16 +284,16 @@
</div>
</div>
<label for="sd_prompt_prefix" data-i18n="Common prompt prefix">Common prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea>
<label for="sd_negative_prompt" data-i18n="Negative common prompt prefix">Negative common prompt prefix</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact"></textarea>
<div id="sd_character_prompt_block">
<label for="sd_character_prompt" data-i18n="Character-specific prompt prefix">Character-specific prompt prefix</label>
<small data-i18n="Won't be used in groups.">Won't be used in groups.</small>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
<label for="sd_character_negative_prompt" data-i18n="Character-specific negative prompt prefix">Character-specific negative prompt prefix</label>
<small data-i18n="Won't be used in groups.">Won't be used in groups.</small>
<textarea id="sd_character_negative_prompt" class="text_pole textarea_compact" rows="3" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix.&#10;Example: jewellery, shoes, glasses"></textarea>
<textarea id="sd_character_negative_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix.&#10;Example: jewellery, shoes, glasses"></textarea>
<label for="sd_character_prompt_share" class="checkbox_label flexWrap marginTop5">
<input id="sd_character_prompt_share" type="checkbox" />
<span data-i18n="Shareable">
@ -299,6 +304,36 @@
</small>
</label>
</div>
<hr>
<h4 data-i18n="Chat Message Visibility (by source)">
Chat Message Visibility (by source)
</h4>
<small data-i18n="Uncheck to hide the extension's messages in chat prompts.">
Uncheck to hide the extension's messages in chat prompts.
</small>
<div class="flex-container flexFlowColumn marginTopBot5 flexGap10">
<label for="sd_wand_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-wand-magic-sparkles"></i>
<span data-i18n="Extensions Menu">Extensions Menu</span>
</span>
<input id="sd_wand_visible" type="checkbox" />
</label>
<label for="sd_command_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-terminal"></i>
<span data-i18n="Slash Command">Slash Command</span>
</span>
<input id="sd_command_visible" type="checkbox" />
</label>
<label for="sd_interactive_visible" class="checkbox_label">
<span class="flex1 flex-container alignItemsCenter">
<i class="fa-solid fa-message"></i>
<span data-i18n="Interactive Mode">Interactive Mode</span>
</span>
<input id="sd_interactive_visible" type="checkbox" />
</label>
</div>
</div>
</div>
<div class="inline-drawer">

View File

@ -0,0 +1 @@
<h3 data-i18n="ext_translate_delete_confirm_1">Are you sure?</h3><span data-i18n="ext_translate_delete_confirm_2">This will remove translated text from all messages in the current chat. This action cannot be undone.</span>

View File

@ -1,7 +1,7 @@
<div class="translation_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b data-i18n="Chat Translation">Chat Translation</b>
<b data-i18n="ext_translate_title">Chat Translation</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">

View File

@ -10,13 +10,12 @@ import {
substituteParams,
updateMessageBlock,
} from '../../../script.js';
import { extension_settings, getContext } from '../../extensions.js';
import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
import { findSecret, secret_state, writeSecret } from '../../secrets.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { splitRecursive } from '../../utils.js';
import { renderTemplateAsync } from '../../templates.js';
export const autoModeOptions = {
NONE: 'none',
@ -505,7 +504,8 @@ async function onTranslateChatClick() {
}
async function onTranslationsClearClick() {
const confirm = await callPopup('<h3>Are you sure?</h3>This will remove translated text from all messages in the current chat. This action cannot be undone.', 'confirm');
const popupHtml = await renderExtensionTemplateAsync('translate', 'deleteConfirmation');
const confirm = await callPopup(popupHtml, 'confirm');
if (!confirm) {
return;
@ -563,10 +563,10 @@ const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
window['translate'] = translate;
jQuery(async() => {
const html = await renderTemplateAsync('translateIndex');
jQuery(async () => {
const html = await renderExtensionTemplateAsync('translate', 'index');
const buttonHtml = await renderTemplateAsync('translateButtons');
const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons');
$('#extensionsMenu').append(buttonHtml);
$('#extensions_settings2').append(html);
$('#translate_chat').on('click', onTranslateChatClick);
@ -609,7 +609,7 @@ jQuery(async() => {
'libre': 'http://127.0.0.1:5000/translate',
'lingva': 'https://lingva.ml/api/v1',
'oneringtranslator': 'http://127.0.0.1:4990/translate',
'deeplx': 'http://127.0.0.1:1188/translate'
'deeplx': 'http://127.0.0.1:1188/translate',
};
const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`;
@ -653,5 +653,6 @@ jQuery(async() => {
: extension_settings.translate.target_language;
return await translate(String(value), target);
},
returns: ARGUMENT_TYPE.STRING,
}));
});

View File

@ -10,6 +10,7 @@ import {
setExtensionPrompt,
substituteParams,
generateRaw,
substituteParamsExtended,
} from '../../../script.js';
import {
ModuleWorkerWrapper,
@ -49,6 +50,7 @@ const settings = {
summarize_sent: false,
summary_source: 'main',
summary_prompt: 'Pause your roleplay. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.',
force_chunk_delimiter: '',
// For chats
enabled_chats: false,
@ -152,6 +154,20 @@ async function onVectorizeAllClick() {
let syncBlocked = false;
/**
* Gets the chunk delimiters for splitting text.
* @returns {string[]} Array of chunk delimiters
*/
function getChunkDelimiters() {
const delimiters = ['\n\n', '\n', ' ', ''];
if (settings.force_chunk_delimiter) {
delimiters.unshift(settings.force_chunk_delimiter);
}
return delimiters;
}
/**
* Splits messages into chunks before inserting them into the vector index.
* @param {object[]} items Array of vector items
@ -165,7 +181,7 @@ function splitByChunks(items) {
const chunkedItems = [];
for (const item of items) {
const chunks = splitRecursive(item.text, settings.message_chunk_size);
const chunks = splitRecursive(item.text, settings.message_chunk_size, getChunkDelimiters());
for (const chunk of chunks) {
const chunkedItem = { ...item, text: chunk };
chunkedItems.push(chunkedItem);
@ -441,7 +457,7 @@ async function injectDataBankChunks(queryText, collectionIds) {
return;
}
const insertedText = substituteParams(settings.file_template_db.replace(/{{text}}/i, textResult));
const insertedText = substituteParamsExtended(settings.file_template_db, { text: textResult });
setExtensionPrompt(EXTENSION_PROMPT_TAG_DB, insertedText, settings.file_position_db, settings.file_depth_db, settings.include_wi, settings.file_depth_role_db);
} catch (error) {
console.error('Vectors: Failed to insert Data Bank chunks', error);
@ -483,9 +499,10 @@ async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overla
const toast = toastr.info('Vectorization may take some time, please wait...', `Ingesting file ${fileName}`);
const overlapSize = Math.round(chunkSize * overlapPercent / 100);
const delimiters = getChunkDelimiters();
// Overlap should not be included in chunk size. It will be later compensated by overlapChunks
chunkSize = overlapSize > 0 ? (chunkSize - overlapSize) : chunkSize;
const chunks = splitRecursive(fileText, chunkSize).map((x, y, z) => overlapSize > 0 ? overlapChunks(x, y, z, overlapSize) : x);
const chunks = splitRecursive(fileText, chunkSize, delimiters).map((x, y, z) => overlapSize > 0 ? overlapChunks(x, y, z, overlapSize) : x);
console.debug(`Vectors: Split file ${fileName} into ${chunks.length} chunks with ${overlapPercent}% overlap`, chunks);
const items = chunks.map((chunk, index) => ({ hash: getStringHash(chunk), text: chunk, index: index }));
@ -592,7 +609,7 @@ async function rearrangeChat(chat) {
function getPromptText(queriedMessages) {
const queriedText = queriedMessages.map(x => collapseNewlines(`${x.name}: ${x.mes}`).trim()).join('\n\n');
console.log('Vectors: relevant past messages found.\n', queriedText);
return substituteParams(settings.template.replace(/{{text}}/i, queriedText));
return substituteParamsExtended(settings.template, { text: queriedText });
}
/**
@ -1479,6 +1496,12 @@ jQuery(async () => {
saveSettingsDebounced();
});
$('#vectors_force_chunk_delimiter').prop('checked', settings.force_chunk_delimiter).on('input', () => {
settings.force_chunk_delimiter = String($('#vectors_force_chunk_delimiter').val());
Object.assign(extension_settings.vectors, settings);
saveSettingsDebounced();
});
const validSecret = !!secret_state[SECRET_KEYS.NOMICAI];
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
$('#api_key_nomicai').attr('placeholder', placeholder);

View File

@ -1,12 +1,12 @@
<div class="vectors_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Vector Storage</b>
<b data-i18n="Vector Storage">Vector Storage</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<div class="flex-container flexFlowColumn">
<label for="vectors_source">
<label for="vectors_source" data-i18n="Vectorization Source">
Vectorization Source
</label>
<select id="vectors_source" class="text_pole">
@ -14,7 +14,7 @@
<option value="extras">Extras</option>
<option value="palm">Google MakerSuite</option>
<option value="llamacpp">llama.cpp</option>
<option value="transformers">Local (Transformers)</option>
<option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
<option value="mistral">MistralAI</option>
<option value="nomicai">NomicAI</option>
<option value="ollama">Ollama</option>
@ -24,28 +24,28 @@
</select>
</div>
<div class="flex-container flexFlowColumn" id="ollama_vectorsModel">
<label for="vectors_ollama_model">
<label for="vectors_ollama_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<input id="vectors_ollama_model" class="text_pole" type="text" placeholder="Model tag, e.g. llama3" />
<label for="vectors_ollama_keep" class="checkbox_label" title="When checked, the model will not be unloaded after use.">
<input id="vectors_ollama_keep" type="checkbox" />
<span>Keep model in memory</span>
<span data-i18n="Keep model in memory">Keep model in memory</span>
</label>
<i>
<i data-i18n="Hint: Download models and set the URL in the API connection settings.">
Hint: Download models and set the URL in the API connection settings.
</i>
</div>
<div class="flex-container flexFlowColumn" id="llamacpp_vectorsModel">
<span>
<span data-i18n="The server MUST be started with the --embedding flag to use this feature!">
The server MUST be started with the <code>--embedding</code> flag to use this feature!
</span>
<i>
<i data-i18n="Hint: Set the URL in the API connection settings.">
Hint: Set the URL in the API connection settings.
</i>
</div>
<div class="flex-container flexFlowColumn" id="openai_vectorsModel">
<label for="vectors_openai_model">
<label for="vectors_openai_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<select id="vectors_openai_model" class="text_pole">
@ -55,7 +55,7 @@
</select>
</div>
<div class="flex-container flexFlowColumn" id="cohere_vectorsModel">
<label for="vectors_cohere_model">
<label for="vectors_cohere_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<select id="vectors_cohere_model" class="text_pole">
@ -69,7 +69,7 @@
</select>
</div>
<div class="flex-container flexFlowColumn" id="together_vectorsModel">
<label for="vectors_togetherai_model">
<label for="vectors_togetherai_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<select id="vectors_togetherai_model" class="text_pole">
@ -84,11 +84,11 @@
</select>
</div>
<div class="flex-container flexFlowColumn" id="vllm_vectorsModel">
<label for="vectors_vllm_model">
<label for="vectors_vllm_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<input id="vectors_vllm_model" class="text_pole" type="text" placeholder="Model name, e.g. intfloat/e5-mistral-7b-instruct" />
<i>
<i data-i18n="Hint: Set the URL in the API connection settings.">
Hint: Set the URL in the API connection settings.
</i>
</div>
@ -102,7 +102,7 @@
<div class="flex-container flexFlowColumn" id="nomicai_apiKey">
<label for="api_key_nomicai">
<span>NomicAI API Key</span>
<span data-i18n="NomicAI API Key">NomicAI API Key</span>
</label>
<div class="flex-container">
<input id="api_key_nomicai" name="api_key_nomicai" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
@ -117,48 +117,54 @@
<div class="flex-container marginTopBot5">
<div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">
<label for="vectors_query">
<span>Query messages</span>
<small data-i18n="Query messages">Query messages</small>
</label>
<input type="number" id="vectors_query" class="text_pole widthUnset" min="1" max="99" />
<input type="number" id="vectors_query" class="text_pole" min="1" max="99" />
</div>
<div class="flex-container flex1 flexFlowColumn" title="Cut-off score for relevance. Helps to filter out irrelevant data.">
<label for="vectors_query">
<span>Score threshold</span>
<small data-i18n="Score threshold">Score threshold</small>
</label>
<input type="number" id="vectors_score_threshold" class="text_pole widthUnset" min="0" max="1" step="0.05" />
<input type="number" id="vectors_score_threshold" class="text_pole" min="0" max="1" step="0.05" />
</div>
<div class="flex-container flex1 flexFlowColumn" title="Prioritize chunking on the preferred delimiter.">
<label for="vectors_force_chunk_delimiter">
<small data-i18n="Chunk boundary">Chunk boundary</small>
</label>
<textarea id="vectors_force_chunk_delimiter" class="text_pole" rows="1" placeholder="(None)"></textarea>
</div>
</div>
<div class="flex-container">
<label class="checkbox_label expander" for="vectors_include_wi" title="Query results can activate World Info entries.">
<input id="vectors_include_wi" type="checkbox" class="checkbox">
Include in World Info Scanning
<span data-i18n="Include in World Info Scanning">Include in World Info Scanning</span>
</label>
</div>
<hr>
<h4>
<h4 data-i18n="World Info settings">
World Info settings
</h4>
<label class="checkbox_label" for="vectors_enabled_world_info" title="Enable activation of World Info entries based on vector similarity.">
<input id="vectors_enabled_world_info" type="checkbox" class="checkbox">
Enabled for World Info
<span data-i18n="Enable for World Info">Enable for World Info</span>
</label>
<div id="vectors_world_info_settings" class="marginTopBot5">
<div class="flex-container">
<label for="vectors_enabled_for_all" class="checkbox_label">
<input id="vectors_enabled_for_all" type="checkbox" />
<span>Enabled for all entries</span>
<span data-i18n="Enabled for all entries">Enabled for all entries</span>
</label>
<ul class="margin0">
<li>
<small>Checked: all entries except ❌ status can be activated.</small>
<small data-i18n="Checked: all entries except ❌ status can be activated.">Checked: all entries except ❌ status can be activated.</small>
</li>
<li>
<small>Unchecked: only entries with 🔗 status can be activated.</small>
<small data-i18n="Unchecked: only entries with ❌ status can be activated.">Unchecked: only entries with 🔗 status can be activated.</small>
</li>
</ul>
</div>
@ -168,7 +174,7 @@
</div>
<div class="flex1" title="Maximum number of entries to be activated">
<label for="vectors_max_entries" >
<small>Max Entries</small>
<small data-i18n="Max Entries">Max Entries</small>
</label>
<input id="vectors_max_entries" type="number" class="text_pole widthUnset" min="1" max="9999" />
</div>
@ -178,13 +184,13 @@
</div>
</div>
<h4>
<h4 data-i18n="File vectorization settings">
File vectorization settings
</h4>
<label class="checkbox_label" for="vectors_enabled_files">
<input id="vectors_enabled_files" type="checkbox" class="checkbox">
Enabled for files
<span data-i18n="Enable for files">Enable for files</span>
</label>
<div id="vectors_files_settings" class="marginTopBot5">
@ -196,152 +202,153 @@
<i class="fa-solid fa-flask" title="Experimental feature"></i>
</label>
<div class="flex justifyCenter" title="These settings apply to files attached directly to messages.">
<span>Message attachments</span>
<span data-i18n="Message attachments">Message attachments</span>
</div>
<div class="flex-container">
<div class="flex1" title="Only files past this size will be vectorized.">
<label for="vectors_size_threshold">
<small>Size threshold (KB)</small>
<small data-i18n="Size threshold (KB)">Size threshold (KB)</small>
</label>
<input id="vectors_size_threshold" type="number" class="text_pole" min="1" max="99999" />
</div>
<div class="flex1" title="Chunk size for file splitting.">
<label for="vectors_chunk_size">
<small>Chunk size (chars)</small>
<small data-i18n="Chunk size (chars)">Chunk size (chars)</small>
</label>
<input id="vectors_chunk_size" type="number" class="text_pole" min="1" max="99999" />
</div>
<div class="flex1" title="The overlap between adjacent chunks in % from chunk size. The overlap text is trimmed to sentence boundaries. 0 = disabled.">
<label for="vectors_overlap_percent">
<small>Chunk overlap (%)</small>
<small data-i18n="Chunk overlap (%)">Chunk overlap (%)</small>
</label>
<input id="vectors_overlap_percent" type="number" class="text_pole" min="0" max="99" step="1" />
</div>
<div class="flex1" title="How many chunks to retrieve when querying.">
<label for="vectors_chunk_count">
<small>Retrieve chunks</small>
<small data-i18n="Retrieve chunks">Retrieve chunks</small>
</label>
<input id="vectors_chunk_count" type="number" class="text_pole" min="1" max="99999" />
</div>
</div>
<div class="flex justifyCenter" title="These settings apply to files stored in the Data Bank.">
<span>Data Bank files</span>
<span data-i18n="Data Bank files">Data Bank files</span>
</div>
<div class="flex-container">
<div class="flex1" title="Only files past this size will be vectorized.">
<label for="vectors_size_threshold_db">
<small>Size threshold (KB)</small>
<small data-i18n="Size threshold (KB)">Size threshold (KB)</small>
</label>
<input id="vectors_size_threshold_db" type="number" class="text_pole" min="1" max="99999" />
</div>
<div class="flex1" title="Chunk size for file splitting.">
<label for="vectors_chunk_size_db">
<small>Chunk size (chars)</small>
<small data-i18n="Chunk size (chars)">Chunk size (chars)</small>
</label>
<input id="vectors_chunk_size_db" type="number" class="text_pole" min="1" max="99999" />
</div>
<div class="flex1" title="The overlap between adjacent chunks in % from chunk size. The overlap text is trimmed to sentence boundaries. 0 = disabled.">
<label for="vectors_overlap_percent_db">
<small>Chunk overlap (%)</small>
<small data-i18n="Chunk overlap (%)">Chunk overlap (%)</small>
</label>
<input id="vectors_overlap_percent_db" type="number" class="text_pole" min="0" max="99" step="1" />
</div>
<div class="flex1" title="How many chunks to retrieve when querying.">
<label for="vectors_chunk_count_db">
<small>Retrieve chunks</small>
<small data-i18n="Retrieve chunks">Retrieve chunks</small>
</label>
<input id="vectors_chunk_count_db" type="number" class="text_pole" min="1" max="99999" />
</div>
</div>
<div class="flex-container flexFlowColumn">
<label for="vectors_file_template_db">
<span>Injection Template</span>
<span data-i18n="Injection Template">Injection Template</span>
</label>
<textarea id="vectors_file_template_db" class="margin0 text_pole textarea_compact" rows="3" placeholder="Use &lcub;&lcub;text&rcub;&rcub; macro to specify the position of retrieved text."></textarea>
<label for="vectors_file_position_db">Injection Position</label>
<label for="vectors_file_position_db" data-i18n="Injection Position">Injection Position</label>
<div class="radio_group">
<label>
<input type="radio" name="vectors_file_position_db" value="2" />
<span>Before Main Prompt / Story String</span>
<span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>
</label>
<!--Keep these as 0 and 1 to interface with the setExtensionPrompt function-->
<label>
<input type="radio" name="vectors_file_position_db" value="0" />
<span>After Main Prompt / Story String</span>
<span data-i18n="After Main Prompt / Story String">After Main Prompt / Story String</span>
</label>
<label for="vectors_file_depth_db" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
<input type="radio" name="vectors_file_position_db" value="1" />
<span>In-chat @ Depth</span>
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
<input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="999" />
<span>as</span>
<select id="vectors_file_depth_role_db" class="text_pole widthNatural">
<option value="0">System</option>
<option value="1">User</option>
<option value="2">Assistant</option>
<option value="0" data-i18n="System">System</option>
<option value="1" data-i18n="User">User</option>
<option value="2" data-i18n="Assistant">Assistant</option>
</select>
</label>
</div>
</div>
<div class="flex-container">
<div id="vectors_files_vectorize_all" class="menu_button menu_button_icon" title="Vectorize all files in the Data Bank and current chat.">
Vectorize All
<span data-i18n="Vectorize All">Vectorize All</span>
</div>
<div id="vectors_files_purge" class="menu_button menu_button_icon" title="Purge all file vectors in the Data Bank and current chat.">
Purge Vectors
<span data-i18n="Purge Vectors">Purge Vectors</span>
</div>
</div>
</div>
<hr>
<h4>
<h4 data-i18n="Chat vectorization settings">
Chat vectorization settings
</h4>
<label class="checkbox_label" for="vectors_enabled_chats">
<input id="vectors_enabled_chats" type="checkbox" class="checkbox">
Enabled for chat messages
<span data-i18n="Enabled for chat messages">Enabled for chat messages</span>
</label>
<hr>
<div id="vectors_chats_settings">
<div id="vectors_advanced_settings">
<label for="vectors_template">
<label for="vectors_template" data-i18n="Injection Template">
Injection Template
</label>
<textarea id="vectors_template" class="text_pole textarea_compact" rows="3" placeholder="Use &lcub;&lcub;text&rcub;&rcub; macro to specify the position of retrieved text."></textarea>
<label for="vectors_position">Injection Position</label>
<label for="vectors_position" data-i18n="Injection Position">Injection Position</label>
<div class="radio_group">
<label>
<input type="radio" name="vectors_position" value="2" />
Before Main Prompt / Story String
<span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>
</label>
<!--Keep these as 0 and 1 to interface with the setExtensionPrompt function-->
<label>
<input type="radio" name="vectors_position" value="0" />
After Main Prompt / Story String
<span data-i18n="After Main Prompt / Story String">After Main Prompt / Story String</span>
</label>
<label for="vectors_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
<input type="radio" name="vectors_position" value="1" />
In-chat @ Depth <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
<span data-i18n="In-chat @ Depth">In-chat @ Depth </span>
<input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
</label>
</div>
<div class="flex-container">
<div class="flex1" title="Can increase the retrieval quality for the cost of processing. 0 = disabled.">
<label for="vectors_message_chunk_size">
<small>Chunk size (chars)</small>
<small data-i18n="Chunk size (chars)">Chunk size (chars)</small>
</label>
<input id="vectors_message_chunk_size" type="number" class="text_pole widthUnset" min="0" max="9999" />
</div>
<div class="flex1" title="Prevents last N messages from being placed out of order.">
<label for="vectors_protect">
<small>Retain#</small>
<small data-i18n="Retain#">Retain#</small>
</label>
<input type="number" id="vectors_protect" class="text_pole widthUnset" min="1" max="9999" />
</div>
<div class="flex1" title="How many past messages to insert as memories.">
<label for="vectors_insert">
<small>Insert#</small>
<small data-i18n="Insert#">Insert#</small>
</label>
<input type="number" id="vectors_insert" class="text_pole widthUnset" min="1" max="9999" />
</div>
@ -350,43 +357,43 @@
<div class="flex-container flexFlowColumn">
<div class="flex-container alignitemscenter justifyCenter">
<i class="fa-solid fa-flask" title="Summarization for vectors is an experimental feature that may improve vectors or may worsen them. Use at your own discretion."></i>
<span>Vector Summarization</span>
<span data-i18n="Vector Summarization">Vector Summarization</span>
</div>
<label class="checkbox_label expander" for="vectors_summarize" title="Summarize chat messages before generating embeddings.">
<input id="vectors_summarize" type="checkbox" class="checkbox">
Summarize chat messages for vector generation
<span data-i18n="Summarize chat messages for vector generation">Summarize chat messages for vector generation</span>
</label>
<i class="failure">Warning: This will slow down vector generation drastically, as all messages have to be summarized first.</i>
<i class="failure" data-i18n="Warning: This will slow down vector generation drastically, as all messages have to be summarized first.">Warning: This will slow down vector generation drastically, as all messages have to be summarized first.</i>
<label class="checkbox_label expander" for="vectors_summarize_user" title="Summarize sent chat messages before generating embeddings.">
<input id="vectors_summarize_user" type="checkbox" class="checkbox">
Summarize chat messages when sending
<span data-i18n="Summarize chat messages when sending">Summarize chat messages when sending</span>
</label>
<i class="failure">Warning: This might cause your sent messages to take a bit to process and slow down response time.</i>
<i class="failure" data-i18n="Warning: This might cause your sent messages to take a bit to process and slow down response time.">Warning: This might cause your sent messages to take a bit to process and slow down response time.</i>
<label for="vectors_summary_source">Summarize with:</label>
<label for="vectors_summary_source" title="Summarize with:">Summarize with:</label>
<select id="vectors_summary_source" class="text_pole">
<option value="main">Main API</option>
<option value="extras">Extras API</option>
<option value="main" data-i18n="Main API">Main API</option>
<option value="extras" data-i18n="Extras API">Extras API</option>
</select>
<label for="vectors_summary_prompt">Summary Prompt:</label>
<small>Only used when Main API is selected.</small>
<label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>
<small data-i18n="Only used when Main API is selected.">Only used when Main API is selected.</small>
<textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea>
</div>
</div>
<small>
<small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.">
Old messages are vectorized gradually as you chat.
To process all previous messages, click the button below.
</small>
<div class="flex-container">
<div id="vectors_vectorize_all" class="menu_button menu_button_icon">
<div id="vectors_vectorize_all" class="menu_button menu_button_icon" data-i18n="Vectorize All">
Vectorize All
</div>
<div id="vectors_purge" class="menu_button menu_button_icon">
<div id="vectors_purge" class="menu_button menu_button_icon" data-i18n="Purge Vectors">
Purge Vectors
</div>
<div id="vectors_view_stats" class="menu_button menu_button_icon">
<div id="vectors_view_stats" class="menu_button menu_button_icon" data-i18n="View Stats">
View Stats
</div>
</div>

View File

@ -28,6 +28,7 @@ import {
setOnlineStatus,
startStatusLoading,
substituteParams,
substituteParamsExtended,
system_message_types,
this_chid,
} from '../script.js';
@ -69,6 +70,7 @@ import { saveLogprobsForActiveMessage } from './logprobs.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
import { renderTemplateAsync } from './templates.js';
export {
openai_messages_count,
@ -722,7 +724,7 @@ function populationInjectionPrompts(prompts, messages) {
const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);
if (jointPrompt && jointPrompt.length) {
roleMessages.push({ 'role': role, 'content': jointPrompt });
roleMessages.push({ 'role': role, 'content': jointPrompt, injected: true });
}
}
@ -775,7 +777,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
const promptObject = {
identifier: 'continueNudge',
role: 'system',
content: oai_settings.continue_nudge_prompt.replace('{{lastChatMessage}}', String(cyclePrompt).trim()),
content: substituteParamsExtended(oai_settings.continue_nudge_prompt, { lastChatMessage: String(cyclePrompt).trim() }),
system_prompt: true,
};
const continuePrompt = new Prompt(promptObject);
@ -794,6 +796,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
// Insert chat messages as long as there is budget available
const chatPool = [...messages].reverse();
const firstNonInjected = chatPool.find(x => !x.injected);
for (let index = 0; index < chatPool.length; index++) {
const chatPrompt = chatPool[index];
@ -812,6 +815,12 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
}
if (chatCompletion.canAfford(chatMessage)) {
if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
const collection = new MessageCollection('continuePrefill', chatMessage);
chatCompletion.add(collection, -1);
continue;
}
chatCompletion.insertAtStart(chatMessage, 'chatHistory');
} else {
break;
@ -4401,23 +4410,8 @@ function updateScaleForm() {
}
}
function onCustomizeParametersClick() {
const template = $(`
<div class="flex-container flexFlowColumn height100p">
<h3>Additional Parameters</h3>
<div class="flex1 flex-container flexFlowColumn">
<h4>Include Body Parameters</h4>
<textarea id="custom_include_body" class="flex1" placeholder="Parameters to be included in the Chat Completion request body (YAML object)&#10;&#10;Example:&#10;- top_k: 20&#10;- repetition_penalty: 1.1"></textarea>
</div>
<div class="flex1 flex-container flexFlowColumn">
<h4>Exclude Body Parameters</h4>
<textarea id="custom_exclude_body" class="flex1" placeholder="Parameters to be excluded from the Chat Completion request body (YAML array)&#10;&#10;Example:&#10;- frequency_penalty&#10;- presence_penalty"></textarea>
</div>
<div class="flex1 flex-container flexFlowColumn">
<h4>Include Request Headers</h4>
<textarea id="custom_include_headers" class="flex1" placeholder="Additional headers for Chat Completion requests (YAML object)&#10;&#10;Example:&#10;- CustomHeader: custom-value&#10;- AnotherHeader: custom-value"></textarea>
</div>
</div>`);
async function onCustomizeParametersClick() {
const template = $(await renderTemplateAsync('customEndpointAdditionalParameters'));
template.find('#custom_include_body').val(oai_settings.custom_include_body).on('input', function () {
oai_settings.custom_include_body = String($(this).val());

View File

@ -46,6 +46,7 @@ import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandPa
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
export {
loadPowerUserSettings,
@ -975,6 +976,7 @@ function switchUiMode() {
function toggleWaifu() {
$('#waifuMode').trigger('click');
return '';
}
function switchWaifuMode() {
@ -2483,14 +2485,12 @@ async function resetMovablePanels(type) {
});
}
function doNewChat() {
setTimeout(() => {
$('#option_start_new_chat').trigger('click');
}, 1);
//$("#dialogue_popup").hide();
setTimeout(() => {
$('#dialogue_popup_ok').trigger('click');
}, 1);
async function doNewChat() {
$('#option_start_new_chat').trigger('click');
await delay(1);
$('#dialogue_popup_ok').trigger('click');
await delay(1);
return '';
}
/**
@ -2627,17 +2627,15 @@ async function doMesCut(_, text) {
}
async function doDelMode(_, text) {
//first enter delmode
$('#option_delete_mes').trigger('click', { fromSlashCommand: true });
//reject invalid args
if (text && isNaN(text)) {
toastr.warning('Must enter a number or nothing.');
await delay(300); //unsure why 300 is neccessary here, but any shorter and it wont see the delmode UI
$('#dialogue_del_mes_cancel').trigger('click');
return;
return '';
}
//first enter delmode
$('#option_delete_mes').trigger('click', { fromSlashCommand: true });
//parse valid args
if (text) {
await delay(300); //same as above, need event signal for 'entered del mode'
@ -2648,7 +2646,7 @@ async function doDelMode(_, text) {
if (oldestMesIDToDel < 0) {
toastr.warning(`Cannot delete more than ${chat.length} messages.`);
return;
return '';
}
let oldestMesToDel = $('#chat').find(`.mes[mesid=${oldestMesIDToDel}]`);
@ -2657,7 +2655,7 @@ async function doDelMode(_, text) {
oldestMesToDel = await loadUntilMesId(oldestMesIDToDel);
if (!oldestMesToDel || !oldestMesToDel.length) {
return;
return '';
}
}
@ -2670,12 +2668,15 @@ async function doDelMode(_, text) {
//await delay(1)
$('#dialogue_del_mes_ok').trigger('click');
toastr.success(`Deleted ${trueNumberOfDeletedMessage} messages.`);
return;
return '';
}
return '';
}
function doResetPanels() {
$('#movingUIreset').trigger('click');
return '';
}
function setAvgBG() {
@ -2906,6 +2907,7 @@ async function setThemeCallback(_, text) {
applyTheme(theme.name);
$('#themes').val(theme.name);
saveSettingsDebounced();
return '';
}
async function setmovingUIPreset(_, text) {
@ -2929,6 +2931,7 @@ async function setmovingUIPreset(_, text) {
applyMovingUIPreset(preset.name);
$('#movingUIPresets').val(preset.name);
saveSettingsDebounced();
return '';
}
const EPHEMERAL_STOPPING_STRINGS = [];
@ -3943,9 +3946,12 @@ $(document).ready(() => {
name: 'theme',
callback: setThemeCallback,
unnamedArgumentList: [
new SlashCommandArgument(
'name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => themes.map(theme => new SlashCommandEnumValue(theme.name)),
}),
],
helpString: 'sets a UI theme by name',
}));
@ -3953,9 +3959,12 @@ $(document).ready(() => {
name: 'movingui',
callback: setmovingUIPreset,
unnamedArgumentList: [
new SlashCommandArgument(
'name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => movingUIPresets.map(preset => new SlashCommandEnumValue(preset.name)),
}),
],
helpString: 'activates a movingUI preset by name',
}));

View File

@ -45,7 +45,7 @@ import { hideChatMessageRange } from './chats.js';
import { getContext, saveMetadataDebounced } from './extensions.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
import { chat_completion_sources, oai_settings } from './openai.js';
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, user_avatar } from './personas.js';
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@ -56,13 +56,12 @@ import { background_settings } from './backgrounds.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
import { AutoCompleteNameResult } from './autocomplete/AutoCompleteNameResult.js';
import { AutoCompleteOption } from './autocomplete/AutoCompleteOption.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { AutoComplete } from './autocomplete/AutoComplete.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
};
@ -117,9 +116,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
aliases: ['background'],
returns: 'the current background',
unnamedArgumentList: [
new SlashCommandArgument(
'filename', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'filename',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => [...document.querySelectorAll('.bg_example')]
.map((it) => new SlashCommandEnumValue(it.getAttribute('bgfile')))
.filter(it => it.value?.length)
,
}),
],
helpString: `
<div>
@ -323,9 +328,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'go',
callback: goToCharacterCallback,
unnamedArgumentList: [
new SlashCommandArgument(
'name', [ARGUMENT_TYPE.STRING], true,
),
SlashCommandArgument.fromProps({
description: 'name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => [
...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
],
}),
],
helpString: 'Opens up a chat with the character or group by its name',
aliases: ['char'],
@ -367,9 +378,13 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'ask',
callback: askCharacter,
namedArgumentList: [
new SlashCommandNamedArgument(
'name', 'character name', [ARGUMENT_TYPE.STRING], true, false, '',
),
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'character name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@ -1193,6 +1208,30 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
],
helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'setpromptentry',
aliases: ['setpromptentries'],
callback: setPromptEntryCallback,
namedArgumentList: [
new SlashCommandNamedArgument(
'identifier', 'Prompt entry identifier(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true,
),
new SlashCommandNamedArgument(
'name', 'Prompt entry name(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true,
),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Set entry/entries on or off',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
acceptsMultiple: false,
defaultValue: 'toggle', // unnamed arguments don't support default values yet
enumList: ['on', 'off', 'toggle'],
}),
],
helpString: 'Sets the specified prompt manager entry/entries on or off.',
}));
registerVariableCommands();
@ -1572,7 +1611,7 @@ function abortCallback({ _abortController, quiet }, reason) {
async function delayCallback(_, amount) {
if (!amount) {
console.warn('WARN: No amount provided for /delay command');
return;
return '';
}
amount = Number(amount);
@ -1581,6 +1620,7 @@ async function delayCallback(_, amount) {
}
await delay(amount);
return '';
}
async function inputCallback(args, prompt) {
@ -1767,27 +1807,27 @@ async function addSwipeCallback(_, arg) {
if (!lastMessage) {
toastr.warning('No messages to add swipes to.');
return;
return '';
}
if (!arg) {
console.warn('WARN: No argument provided for /addswipe command');
return;
return '';
}
if (lastMessage.is_user) {
toastr.warning('Can\'t add swipes to user messages.');
return;
return '';
}
if (lastMessage.is_system) {
toastr.warning('Can\'t add swipes to system messages.');
return;
return '';
}
if (lastMessage.extra?.image) {
toastr.warning('Can\'t add swipes to message containing an image.');
return;
return '';
}
if (!Array.isArray(lastMessage.swipes)) {
@ -1811,6 +1851,8 @@ async function addSwipeCallback(_, arg) {
await saveChatConditional();
await reloadCurrentChat();
return '';
}
async function deleteSwipeCallback(_, arg) {
@ -1818,19 +1860,19 @@ async function deleteSwipeCallback(_, arg) {
if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) {
toastr.warning('No messages to delete swipes from.');
return;
return '';
}
if (lastMessage.swipes.length <= 1) {
toastr.warning('Can\'t delete the last swipe.');
return;
return '';
}
const swipeId = arg && !isNaN(Number(arg)) ? (Number(arg) - 1) : lastMessage.swipe_id;
if (swipeId < 0 || swipeId >= lastMessage.swipes.length) {
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
return;
return '';
}
lastMessage.swipes.splice(swipeId, 1);
@ -1845,6 +1887,8 @@ async function deleteSwipeCallback(_, arg) {
await saveChatConditional();
await reloadCurrentChat();
return '';
}
async function askCharacter(args, text) {
@ -1855,13 +1899,13 @@ async function askCharacter(args, text) {
// TODO: Maybe support group chats?
if (selected_group) {
toastr.error('Cannot run this command in a group chat!');
return;
return '';
}
if (!text) {
console.warn('WARN: No text provided for /ask command');
toastr.warning('No text provided for /ask command');
return;
return '';
}
let name = '';
@ -1873,7 +1917,7 @@ async function askCharacter(args, text) {
if (!name && !mesText) {
toastr.warning('You must specify a name and text to ask.');
return;
return '';
}
}
@ -1885,7 +1929,7 @@ async function askCharacter(args, text) {
const chId = characters.findIndex((e) => e.name === name);
if (!characters[chId] || chId === -1) {
toastr.error('Character not found.');
return;
return '';
}
// Override character and send a user message
@ -1935,22 +1979,24 @@ async function askCharacter(args, text) {
// Restore previous character once message renders
// Hack for generate
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, restoreCharacter);
return '';
}
async function hideMessageCallback(_, arg) {
if (!arg) {
console.warn('WARN: No argument provided for /hide command');
return;
return '';
}
const range = stringToRange(arg, 0, chat.length - 1);
if (!range) {
console.warn(`WARN: Invalid range provided for /hide command: ${arg}`);
return;
return '';
}
await hideChatMessageRange(range.start, range.end, false);
return '';
}
async function unhideMessageCallback(_, arg) {
@ -2347,7 +2393,7 @@ export async function generateSystemMessage(_, prompt) {
if (!prompt) {
console.warn('WARN: No prompt provided for /sysgen command');
toastr.warning('You must provide a prompt for the system message');
return;
return '';
}
// Generate and regex the output if applicable
@ -2356,43 +2402,49 @@ export async function generateSystemMessage(_, prompt) {
message = getRegexedString(message, regex_placement.SLASH_COMMAND);
sendNarratorMessage(_, message);
return '';
}
function syncCallback() {
$('#sync_name_button').trigger('click');
return '';
}
function bindCallback() {
$('#lock_user_name').trigger('click');
return '';
}
function setStoryModeCallback() {
$('#chat_display').val(chat_styles.DOCUMENT).trigger('change');
return '';
}
function setBubbleModeCallback() {
$('#chat_display').val(chat_styles.BUBBLES).trigger('change');
return '';
}
function setFlatModeCallback() {
$('#chat_display').val(chat_styles.DEFAULT).trigger('change');
return '';
}
/**
* Sets a persona name and optionally an avatar.
* @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments
* @param {string} name Name to set
* @returns {void}
* @returns {string}
*/
function setNameCallback({ mode = 'all' }, name) {
if (!name) {
toastr.warning('You must specify a name to change to');
return;
return '';
}
if (!['lookup', 'temp', 'all'].includes(mode)) {
toastr.warning('Mode must be one of "lookup", "temp" or "all"');
return;
return '';
}
name = name.trim();
@ -2404,10 +2456,10 @@ function setNameCallback({ mode = 'all' }, name) {
if (persona) {
autoSelectPersona(persona);
retriggerFirstMessageOnEmptyChat();
return;
return '';
} else if (mode === 'lookup') {
toastr.warning(`Persona ${name} not found`);
return;
return '';
}
}
@ -2416,6 +2468,8 @@ function setNameCallback({ mode = 'all' }, name) {
setUserName(name); //this prevented quickReply usage
retriggerFirstMessageOnEmptyChat();
}
return '';
}
async function setNarratorName(_, text) {
@ -2423,11 +2477,12 @@ async function setNarratorName(_, text) {
chat_metadata[NARRATOR_NAME_KEY] = name;
toastr.info(`System narrator name set to ${name}`);
await saveChatConditional();
return '';
}
export async function sendMessageAs(args, text) {
if (!text) {
return;
return '';
}
let name;
@ -2439,7 +2494,7 @@ export async function sendMessageAs(args, text) {
if (!name && !text) {
toastr.warning('You must specify a name and text to send as');
return;
return '';
}
} else {
const namelessWarningKey = 'sendAsNamelessWarningShown';
@ -2500,11 +2555,13 @@ export async function sendMessageAs(args, text) {
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
await saveChatConditional();
}
return '';
}
export async function sendNarratorMessage(args, text) {
if (!text) {
return;
return '';
}
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
@ -2543,6 +2600,8 @@ export async function sendNarratorMessage(args, text) {
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
await saveChatConditional();
}
return '';
}
export async function promptQuietForLoudResponse(who, text) {
@ -2586,7 +2645,7 @@ export async function promptQuietForLoudResponse(who, text) {
async function sendCommentMessage(args, text) {
if (!text) {
return;
return '';
}
const compact = isTrueBoolean(args?.compact);
@ -2619,6 +2678,8 @@ async function sendCommentMessage(args, text) {
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
await saveChatConditional();
}
return '';
}
/**
@ -2656,6 +2717,8 @@ function helpCommandCallback(_, type) {
sendSystemMessage(system_message_types.HELP);
break;
}
return '';
}
$(document).on('click', '[data-displayHelp]', function (e) {
@ -2680,7 +2743,7 @@ function setBackgroundCallback(_, bg) {
if (!result.length) {
toastr.error(`No background found with name "${bg}"`);
return;
return '';
}
const bgElement = result[0].item.element;
@ -2688,6 +2751,8 @@ function setBackgroundCallback(_, bg) {
if (bgElement instanceof HTMLElement) {
bgElement.click();
}
return '';
}
/**
@ -2789,6 +2854,85 @@ function modelCallback(_, model) {
}
}
/**
* Sets state of prompt entries (toggles) either via identifier/uuid or name.
* @param {object} args Object containing arguments
* @param {string} args.identifier Select prompt entry using an identifier (uuid)
* @param {string} args.name Select prompt entry using name
* @param {string} targetState The targeted state of the entry/entries
* @returns {String} empty string
*/
function setPromptEntryCallback(args, targetState) {
// needs promptManager to manipulate prompt entries
const promptManager = setupChatCompletionPromptManager(oai_settings);
const prompts = promptManager.serviceSettings.prompts;
function parseArgs(arg) {
const list = [];
try {
const parsedArg = JSON.parse(arg);
list.push(...Array.isArray(parsedArg) ? parsedArg : [arg]);
} catch {
list.push(arg);
}
return list;
}
let identifiersList = parseArgs(args.identifier);
let nameList = parseArgs(args.name);
// Check if identifiers exists in prompt, else remove from list
if (identifiersList.length !== 0) {
identifiersList = identifiersList.filter(identifier => prompts.some(prompt => prompt.identifier === identifier));
}
if (nameList.length !== 0) {
nameList.forEach(name => {
// one name could potentially have multiple entries, find all identifiers that match given name
let identifiers = [];
prompts.forEach(entry => {
if (entry.name === name) {
identifiers.push(entry.identifier);
}
});
identifiersList = identifiersList.concat(identifiers);
});
}
// Remove duplicates to allow consistent 'toggle'
identifiersList = [...new Set(identifiersList)];
if (identifiersList.length === 0) return '';
// logic adapted from PromptManager.js, handleToggle
const getPromptOrderEntryState = (promptOrderEntry) => {
if (['toggle', 't', ''].includes(targetState.trim().toLowerCase())) {
return !promptOrderEntry.enabled;
}
if (isTrueBoolean(targetState)) {
return true;
}
if (isFalseBoolean(targetState)) {
return false;
}
return promptOrderEntry.enabled;
};
identifiersList.forEach(promptID => {
const promptOrderEntry = promptManager.getPromptOrderEntry(promptManager.activeCharacter, promptID);
const counts = promptManager.tokenHandler.getCounts();
counts[promptID] = null;
promptOrderEntry.enabled = getPromptOrderEntryState(promptOrderEntry);
});
// no need to render for each identifier
promptManager.render();
promptManager.saveServiceSettings();
return '';
}
export let isExecutingCommandsFromChatInput = false;
export let commandsFromChatInputAbortController;
@ -2910,6 +3054,8 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
result = await executeSlashCommandsWithOptions(text, {
abortController: commandsFromChatInputAbortController,
onProgress: (done, total) => ta.style.setProperty('--prog', `${done / total * 100}%`),
parserFlags: options.parserFlags,
scope: options.scope,
});
if (commandsFromChatInputAbortController.signal.aborted) {
document.querySelector('#form_sheld').classList.add('script_aborted');
@ -3006,7 +3152,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
* @param {boolean} handleParserErrors Whether to handle parser errors (show toast on error) or throw
* @param {SlashCommandScope} scope The scope to be used when executing the commands.
* @param {boolean} handleExecutionErrors Whether to handle execution errors (show toast on error) or throw
* @param {PARSER_FLAG[]} parserFlags Parser flags to apply
* @param {{[id:PARSER_FLAG]:boolean}} parserFlags Parser flags to apply
* @param {SlashCommandAbortController} abortController Controller used to abort or pause command execution
* @param {(done:number, total:number)=>void} onProgress Callback to handle progress events
* @returns {Promise<SlashCommandClosureResult>}

View File

@ -9,10 +9,10 @@ import { SlashCommandScope } from './SlashCommandScope.js';
/**
* @typedef {{
* _pipe:string|SlashCommandClosure,
* _scope:SlashCommandScope,
* _parserFlags:{[id:PARSER_FLAG]:boolean},
* _abortController:SlashCommandAbortController,
* _hasUnnamedArgument:boolean,
* [id:string]:string|SlashCommandClosure,
* }} NamedArguments
*/
@ -33,7 +33,7 @@ export class SlashCommand {
* Creates a SlashCommand from a properties object.
* @param {Object} props
* @param {string} [props.name]
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|void|Promise<string|SlashCommandClosure|void>} [props.callback]
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
* @param {string} [props.helpString]
* @param {boolean} [props.splitUnnamedArgument]
* @param {string[]} [props.aliases]

View File

@ -1,5 +1,6 @@
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
@ -29,6 +30,8 @@ export class SlashCommandArgument {
* @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values
* @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options
* @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values
*/
static fromProps(props) {
return new SlashCommandArgument(
@ -38,6 +41,8 @@ export class SlashCommandArgument {
props.acceptsMultiple ?? false,
props.defaultValue ?? null,
props.enumList ?? [],
props.enumProvider ?? null,
props.forceEnum ?? true,
);
}
@ -50,6 +55,8 @@ export class SlashCommandArgument {
/**@type {boolean}*/ acceptsMultiple = false;
/**@type {string|SlashCommandClosure}*/ defaultValue;
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
/**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null;
/**@type {boolean}*/ forceEnum = true;
/**
@ -57,8 +64,9 @@ export class SlashCommandArgument {
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
* @param {string|SlashCommandClosure} defaultValue
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
*/
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = []) {
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = true) {
this.description = description;
this.typeList = types ? Array.isArray(types) ? types : [types] : [];
this.isRequired = isRequired ?? false;
@ -68,6 +76,8 @@ export class SlashCommandArgument {
if (it instanceof SlashCommandEnumValue) return it;
return new SlashCommandEnumValue(it);
});
this.enumProvider = enumProvider;
this.forceEnum = forceEnum;
}
}
@ -85,6 +95,8 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
* @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values
* @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options
* @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values
*/
static fromProps(props) {
return new SlashCommandNamedArgument(
@ -96,6 +108,8 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
props.defaultValue ?? null,
props.enumList ?? [],
props.aliasList ?? [],
props.enumProvider ?? null,
props.forceEnum ?? true,
);
}
@ -112,9 +126,11 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
* @param {string|SlashCommandClosure} defaultValue
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
* @param {boolean} forceEnum
*/
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = []) {
super(description, types, isRequired, acceptsMultiple, defaultValue, enums);
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = true) {
super(description, types, isRequired, acceptsMultiple, defaultValue, enums, enumProvider, forceEnum);
this.name = name;
this.aliasList = aliases ? Array.isArray(aliases) ? aliases : [aliases] : [];
}

View File

@ -43,6 +43,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
[...namedResult.optionList, ...unnamedResult.optionList],
);
combinedResult.isRequired = namedResult.isRequired || unnamedResult.isRequired;
combinedResult.forceMatch = namedResult.forceMatch && unnamedResult.forceMatch;
return combinedResult;
}
}
@ -102,18 +103,19 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
if (name.includes('=') && cmdArg) {
// if cursor is already behind "=" check for enums
/**@type {SlashCommandNamedArgument} */
if (cmdArg && cmdArg.enumList?.length) {
if (isSelect && cmdArg.enumList.includes(value) && argAssign && argAssign.end == index) {
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
if (cmdArg && enumList?.length) {
if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
return null;
}
const result = new AutoCompleteSecondaryNameResult(
value,
start + name.length,
cmdArg.enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
true,
);
result.isRequired = true;
result.forceMatch = cmdArg.forceEnum;
return result;
}
}
@ -148,7 +150,8 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
if (idx > -1) {
argAssign = this.executor.unnamedArgumentList[idx];
cmdArg = this.executor.command.unnamedArgumentList[idx];
if (cmdArg && cmdArg.enumList.length > 0) {
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
if (cmdArg && enumList.length > 0) {
value = argAssign.value.toString().slice(0, index - argAssign.start);
start = argAssign.start;
} else {
@ -163,17 +166,19 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
return null;
}
if (cmdArg == null || cmdArg.enumList.length == 0) return null;
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
if (cmdArg == null || enumList.length == 0) return null;
const result = new AutoCompleteSecondaryNameResult(
value,
start,
cmdArg.enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
false,
);
const isCompleteValue = cmdArg.enumList.find(it=>it.value == value);
const isCompleteValue = enumList.find(it=>it.value == value);
const isSelectedValue = isSelect && isCompleteValue;
result.isRequired = cmdArg.isRequired && !isSelectedValue && !isCompleteValue;
result.forceMatch = cmdArg.forceEnum;
return result;
}
}

View File

@ -1,5 +1,6 @@
import { substituteParams } from '../../script.js';
import { delay, escapeRegex } from '../utils.js';
import { SlashCommand } from './SlashCommand.js';
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js';
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
@ -17,6 +18,7 @@ export class SlashCommandClosure {
/**@type {SlashCommandExecutor[]}*/ executorList = [];
/**@type {SlashCommandAbortController}*/ abortController;
/**@type {(done:number, total:number)=>void}*/ onProgress;
/**@type {string}*/ rawText;
/**@type {number}*/
get commandCount() {
@ -148,6 +150,9 @@ export class SlashCommandClosure {
}
let done = 0;
if (this.executorList.length == 0) {
this.scope.pipe = '';
}
for (const executor of this.executorList) {
this.onProgress?.(done, this.commandCount);
if (executor instanceof SlashCommandClosureExecutor) {
@ -158,10 +163,12 @@ export class SlashCommandClosure {
const result = await closure.execute();
this.scope.pipe = result.pipe;
} else {
/**@type {import('./SlashCommand.js').NamedArguments} */
let args = {
_scope: this.scope,
_parserFlags: executor.parserFlags,
_abortController: this.abortController,
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
};
let value;
// substitute named arguments
@ -191,6 +198,7 @@ export class SlashCommandClosure {
if (executor.unnamedArgumentList.length == 0) {
if (executor.injectPipe) {
value = this.scope.pipe;
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
}
} else {
value = [];
@ -214,7 +222,7 @@ export class SlashCommandClosure {
if (value.length == 1) {
value = value[0];
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
value = value.join(' ');
value = value.join('');
}
}
}
@ -241,6 +249,7 @@ export class SlashCommandClosure {
}
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
this.scope.pipe = await executor.command.callback(args, value ?? '');
this.#lintPipe(executor.command);
done += executor.commandCount;
this.onProgress?.(done, this.commandCount);
abortResult = await this.testAbortController();
@ -269,4 +278,15 @@ export class SlashCommandClosure {
return result;
}
}
/**
* Auto-fixes the pipe if it is not a valid result for STscript.
* @param {SlashCommand} command Command being executed
*/
#lintPipe(command) {
if (this.scope.pipe === undefined || this.scope.pipe === null) {
console.warn(`${command.name} returned undefined or null. Auto-fixing to empty string.`);
this.scope.pipe = '';
}
}
}

View File

@ -13,7 +13,7 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
* @param {SlashCommandEnumValue} enumValue
*/
constructor(cmd, enumValue) {
super(enumValue.value, '◊');
super(enumValue.value, enumValue.typeIcon, enumValue.type);
this.cmd = cmd;
this.enumValue = enumValue;
}
@ -21,9 +21,9 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
renderItem() {
let li;
li = this.makeItem(this.name, '◊', true, [], [], null, this.enumValue.description);
li = this.makeItem(this.name, this.typeIcon, true, [], [], null, this.enumValue.description);
li.setAttribute('data-name', this.name);
li.setAttribute('data-option-type', 'enum');
li.setAttribute('data-option-type', this.type);
return li;
}

View File

@ -1,10 +1,14 @@
export class SlashCommandEnumValue {
/**@type {string}*/ value;
/**@type {string}*/ description;
/**@type {string}*/ type = 'enum';
/**@type {string}*/ typeIcon = '◊';
constructor(value, description = null) {
constructor(value, description = null, type = 'enum', typeIcon = '◊') {
this.value = value;
this.description = description;
this.type = type;
this.typeIcon = typeIcon;
}
toString() {

View File

@ -598,6 +598,7 @@ export class SlashCommandParser {
this.closureIndex.push(closureIndexEntry);
let injectPipe = true;
if (!isRoot) this.take(2); // discard opening {:
const textStart = this.index;
let closure = new SlashCommandClosure(this.scope);
closure.abortController = this.abortController;
this.scope = closure.scope;
@ -638,13 +639,13 @@ export class SlashCommandParser {
}
this.discardWhitespace(); // discard further whitespace
}
closure.rawText = this.text.slice(textStart, this.index);
if (!isRoot) this.take(2); // discard closing :}
if (this.testSymbol('()')) {
this.take(2); // discard ()
closure.executeNow = true;
}
closureIndexEntry.end = this.index - 1;
this.discardWhitespace(); // discard trailing whitespace
this.scope = closure.scope.parent;
return closure;
}
@ -820,9 +821,8 @@ export class SlashCommandParser {
if (this.testClosure()) {
isList = true;
if (value.length > 0) {
assignment.end = assignment.end - (value.length - value.trim().length);
this.indexMacros(this.index - value.length, value);
assignment.value = value.trim();
assignment.value = value;
listValues.push(assignment);
assignment = new SlashCommandUnnamedArgumentAssignment();
assignment.start = this.index;
@ -834,6 +834,7 @@ export class SlashCommandParser {
listValues.push(assignment);
assignment = new SlashCommandUnnamedArgumentAssignment();
assignment.start = this.index;
if (split) this.discardWhitespace();
} else if (split) {
if (this.testQuotedValue()) {
assignment.start = this.index;
@ -862,8 +863,8 @@ export class SlashCommandParser {
assignment.end = this.index;
}
}
if (isList && value.trim().length > 0) {
assignment.value = value.trim();
if (isList && value.length > 0) {
assignment.value = value;
listValues.push(assignment);
}
if (isList) {

View File

@ -92,7 +92,7 @@ export class SlashCommandScope {
v = v[numIndex];
}
if (typeof v == 'object') return JSON.stringify(v);
return v;
return v ?? '';
} else {
const value = this.variables[key];
return (value === '' || isNaN(Number(value))) ? (value || '') : Number(value);

View File

@ -24,6 +24,8 @@ import { isMobile } from './RossAscends-mods.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { debounce_timeout } from './constants.js';
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
export {
TAG_FOLDER_TYPES,
@ -662,7 +664,7 @@ const tagImportSettings = {
ALWAYS_IMPORT_ALL: 1,
ONLY_IMPORT_EXISTING: 2,
IMPORT_NONE: 3,
ASK: 4
ASK: 4,
};
let globalTagImportSetting = tagImportSettings.ASK; // Default setting
@ -734,7 +736,7 @@ async function showTagImportPopup(character, existingTags, newTags) {
EXISTING: { result: 2, text: 'Import Existing' },
ALL: { result: 3, text: 'Import All' },
NONE: { result: 4, text: 'Import None' },
}
};
const customButtonsCaptions = Object.values(importButtons).map(button => `&quot;${button.text}&quot;`);
const customButtonsString = customButtonsCaptions.slice(0, -1).join(', ') + ' or ' + customButtonsCaptions.slice(-1);
@ -1810,10 +1812,28 @@ function registerTagsSlashCommands() {
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
SlashCommandNamedArgument.fromProps({ name: 'name',
description: 'Character name',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '{{char}}',
enumProvider: ()=>[
...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
],
}),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
SlashCommandArgument.fromProps({ description: 'tag name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: (executor)=>{
const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value));
if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title));
const assigned = getTagsList(key);
return tags.filter(it=>!assigned.includes(it)).map(it=>new SlashCommandEnumValue(it.name, it.title));
},
forceEnum: false,
}),
],
helpString: `
<div>
@ -1844,10 +1864,27 @@ function registerTagsSlashCommands() {
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
SlashCommandNamedArgument.fromProps({ name: 'name',
description: 'Character name',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '{{char}}',
enumProvider: ()=>[
...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
],
}),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
SlashCommandArgument.fromProps({ description: 'tag name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
/**@param {SlashCommandExecutor} executor */
enumProvider: (executor)=>{
const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value));
if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title));
return getTagsList(key).map(it=>new SlashCommandEnumValue(it.name, it.title));
},
}),
],
helpString: `
<div>

View File

@ -0,0 +1,15 @@
<div class="flex-container flexFlowColumn height100p">
<h3 data-i18n="Additional Parameters">Additional Parameters</h3>
<div class="flex1 flex-container flexFlowColumn">
<h4 data-i18n="Include Body Parameters">Include Body Parameters</h4>
<textarea id="custom_include_body" class="flex1" placeholder="Parameters to be included in the Chat Completion request body (YAML object)&#10;&#10;Example:&#10;- top_k: 20&#10;- repetition_penalty: 1.1" data-i18n="[placeholder]custom_include_body_desc"></textarea>
</div>
<div class="flex1 flex-container flexFlowColumn">
<h4 data-i18n="Exclude Body Parameters">Exclude Body Parameters</h4>
<textarea id="custom_exclude_body" class="flex1" placeholder="Parameters to be excluded from the Chat Completion request body (YAML array)&#10;&#10;Example:&#10;- frequency_penalty&#10;- presence_penalty" data-i18n="[placeholder]custom_exclude_body_desc"></textarea>
</div>
<div class="flex1 flex-container flexFlowColumn">
<h4 data-i18n="Include Request Headers">Include Request Headers</h4>
<textarea id="custom_include_headers" class="flex1" placeholder="Additional headers for Chat Completion requests (YAML object)&#10;&#10;Example:&#10;- CustomHeader: custom-value&#10;- AnotherHeader: custom-value" data-i18n="[placeholder]custom_include_headers_desc"></textarea>
</div>
</div>

View File

@ -1,25 +1,26 @@
Text formatting commands:
<ul>
<li><tt>*text*</tt> - displays as <i>italics</i></li>
<li><tt>**text**</tt> - displays as <b>bold</b></li>
<li><tt>***text***</tt> - displays as <b><i>bold italics</i></b></li>
<li><tt>__text__</tt> - displays as an <u>underline</u></li>
<li><tt>~~text~~</tt> - displays as a <del>strikethough</del></li>
<li><tt>[text](url)</tt> - displays as a <a href="#">hyperlink</a></li>
<li><tt>![text](url)</tt> - displays as an image</li>
<li><tt>```text```</tt> - displays as a code block (new lines allowed between the backticks)</li>
<span data-i18n="help_format_1">Text formatting commands:</span>
<ul> <!-- I know it seems like all these 'display as' could fit into just a couple entries, but other languages might need it with their grammar
Actually I feel that languages with SOV might need even more extra entries, but let's KISS it for now, those can be easily added in the future without breaking anything -->
<li><tt data-i18n="help_format_2">*text*</tt> - <span data-i18n="help_format_3">displays as </span><i data-i18n="help_format_4">italics</i></li>
<li><tt data-i18n="help_format_5">**text**</tt> - <span data-i18n="help_format_6">displays as </span><b data-i18n="help_format_7">bold</b></li>
<li><tt data-i18n="help_format_8">***text***</tt> - <span data-i18n="help_format_9">displays as </span><b><i data-i18n="help_format_10">bold italics</i></b></li>
<li><tt data-i18n="help_format_11">__text__</tt> - <span data-i18n="help_format_12">displays as an </span><u data-i18n="help_format_13">underline</u></li>
<li><tt data-i18n="help_format_14">~~text~~</tt> - <span data-i18n="help_format_15">displays as a </span><del data-i18n="help_format_16">strikethough</del></li>
<li><tt data-i18n="help_format_17">[text](url)</tt> - <span data-i18n="help_format_18">displays as a </span><a href="#" data-i18n="help_format_19">hyperlink</a></li>
<li><tt data-i18n="help_format_20">![text](url)</tt> - <span data-i18n="help_format_21">displays as an image</span></li>
<li><tt data-i18n="help_format_22">```text```</tt> - <span data-i18n="help_format_23">displays as a code block (new lines allowed between the backticks)</span></li>
</ul>
<pre><code> like this</code></pre>
<pre><code data-i18n="help_format_like_this"> like this</code></pre>
<ul>
<li><tt>`text`</tt> - displays as <code>inline code</code></li>
<li><tt>&gt; text</tt> - displays as a blockquote (note the space after &gt;)</li>
<blockquote>like this</blockquote>
<li><tt># text</tt> - displays as a large header (note the space)</li>
<h1>like this</h1>
<li><tt>## text</tt> - displays as a medium header (note the space)</li>
<h2>like this</h2>
<li><tt>### text</tt> - displays as a small header (note the space)</li>
<h3>like this</h3>
<li><tt>$$ text $$</tt> - renders a LaTeX formula (if enabled)</li>
<li><tt>$ text $</tt> - renders an AsciiMath formula (if enabled)</li>
<li><tt data-i18n="help_format_24">`text`</tt> - <span data-i18n="help_format_25">displays as </span><code data-i18n="help_format_26">inline code</code></li>
<li><tt data-i18n="help_format_27">&gt; text</tt> - <span data-i18n="help_format_28">displays as a blockquote (note the space after &gt;)</span></li>
<blockquote data-i18n="help_format_like_this">like this</blockquote>
<li><tt data-i18n="help_format_29"># text</tt> - <span data-i18n="help_format_30">displays as a large header (note the space)</span></li>
<h1 data-i18n="help_format_like_this">like this</h1>
<li><tt data-i18n="help_format_32">## text</tt> - <span data-i18n="help_format_33">displays as a medium header (note the space)</span></li>
<h2 data-i18n="help_format_like_this">like this</h2>
<li><tt data-i18n="help_format_35">### text</tt> - <span data-i18n="help_format_36">displays as a small header (note the space)</span></li>
<h3 data-i18n="help_format_like_this">like this</h3>
<li><tt data-i18n="help_format_38">$$ text $$</tt> - <span data-i18n="help_format_39">renders a LaTeX formula (if enabled)</span></li>
<li><tt data-i18n="help_format_40">$ text $</tt> - <span data-i18n="help_format_41">renders an AsciiMath formula (if enabled)</span></li>
</ul>

View File

@ -1,11 +1,11 @@
Hello there! Please select the help topic you would like to learn more about:
<span data-i18n="help_1">Hello there! Please select the help topic you would like to learn more about:</span>
<ul>
<li><a href="#" data-displayHelp="1">Slash Commands</a> (or <tt>/help slash</tt>)</li>
<li><a href="#" data-displayHelp="2">Formatting</a> (or <tt>/help format</tt>)</li>
<li><a href="#" data-displayHelp="3">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
<li><a href="#" data-displayHelp="4">&lcub;&lcub;Macros&rcub;&rcub;</a> (or <tt>/help macros</tt>)</li>
<li><a href="#" data-displayHelp="1" data-i18n="help_2">Slash Commands</a> (<span data-i18n="help_or">or</span> <tt>/help slash</tt>)</li>
<li><a href="#" data-displayHelp="2" data-i18n="help_3">Formatting</a> (<span data-i18n="help_or">or</span> <tt>/help format</tt>)</li>
<li><a href="#" data-displayHelp="3" data-i18n="help_4">Hotkeys</a> (<span data-i18n="help_or">or</span> <tt>/help hotkeys</tt>)</li>
<li><a href="#" data-displayHelp="4" data-i18n="help_5">&lcub;&lcub;Macros&rcub;&rcub;</a> (<span data-i18n="help_or">or</span> <tt>/help macros</tt>)</li>
</ul>
<br>
<b>
Still got questions left? The <a target="_blank" href="https://docs.sillytavern.app/">Official SillyTavern Documentation Website</a> has much more information!
<span data-i18n="help_6">Still got questions left? The</span> <a target="_blank" href="https://docs.sillytavern.app/" data-i18n="help_7">Official SillyTavern Documentation Website</a><span data-i18n="help_8"> has much more information!</span>
</b>

View File

@ -1,13 +1,13 @@
Hotkeys/Keybinds:
<span data-i18n="help_hotkeys_0">Hotkeys/Keybinds</span>:
<ul>
<li><tt>Up</tt> = Edit last message in chat</li>
<li><tt>Ctrl+Up</tt> = Edit last USER message in chat</li>
<li><tt>Left</tt> = swipe left</li>
<li><tt>Right</tt> = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</li>
<li><tt>Enter</tt> (with chat bar selected) = send your message to AI</li>
<li><tt>Ctrl+Enter</tt> = Regenerate the last AI response</li>
<li><tt>Alt+Enter</tt> = Continue the last AI response</li>
<li><tt>Escape</tt> = stop AI response generation, close UI panels, cancel message edit</li>
<li><tt>Ctrl+Shift+Up</tt> = Scroll to context line</li>
<li><tt>Ctrl+Shift+Down</tt> = Scroll chat to bottom</li>
<li><tt data-i18n="help_hotkeys_1">Up</tt> = <span data-i18n="help_hotkeys_2">Edit last message in chat</span></li>
<li><tt data-i18n="help_hotkeys_3">Ctrl+Up</tt> = <span data-i18n="help_hotkeys_4">Edit last USER message in chat</span></li>
<li><tt data-i18n="help_hotkeys_5">Left</tt> = <span data-i18n="help_hotkeys_6">swipe left</span></li>
<li><tt data-i18n="help_hotkeys_7">Right</tt> = <span data-i18n="help_hotkeys_8">swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</span></li>
<li><tt data-i18n="help_hotkeys_9">Enter</tt> <span data-i18n="help_hotkeys_10">(with chat bar selected)</span> = <span data-i18n="help_hotkeys_10_1">send your message to AI</span></li>
<li><tt data-i18n="help_hotkeys_11">Ctrl+Enter</tt> = <span data-i18n="help_hotkeys_12">Regenerate the last AI response</span></li>
<li><tt data-i18n="help_hotkeys_13">Alt+Enter</tt> = <span data-i18n="help_hotkeys_14">Continue the last AI response</span></li>
<li><tt data-i18n="help_hotkeys_15">Escape</tt> = <span data-i18n="help_hotkeys_16">stop AI response generation, close UI panels, cancel message edit</span></li>
<li><tt data-i18n="help_hotkeys_17">Ctrl+Shift+Up</tt> = <span data-i18n="help_hotkeys_18">Scroll to context line</span></li>
<li><tt data-i18n="help_hotkeys_19">Ctrl+Shift+Down</tt> = <span data-i18n="help_hotkeys_20">Scroll chat to bottom</span></li>
</ul>

View File

@ -1,91 +1,91 @@
<div>
<div data-i18n="System-wide Replacement Macros (in order of evaluation):">
System-wide Replacement Macros (in order of evaluation):
</div>
<ul>
<li><tt>&lcub;&lcub;pipe&rcub;&rcub;</tt> only for slash command batching. Replaced with the returned result of the previous command.</li>
<li><tt>&lcub;&lcub;newline&rcub;&rcub;</tt> just inserts a newline.</li>
<li><tt>&lcub;&lcub;trim&rcub;&rcub;</tt> trims newlines surrounding this macro.</li>
<li><tt>&lcub;&lcub;noop&rcub;&rcub;</tt> no operation, just an empty string.</li>
<li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</li>
<li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> the user input</li>
<li><tt>&lcub;&lcub;charPrompt&rcub;&rcub;</tt> the Character's Main Prompt override</li>
<li><tt>&lcub;&lcub;charJailbreak&rcub;&rcub;</tt> the Character's Jailbreak Prompt override</li>
<li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> the Character's Description</li>
<li><tt>&lcub;&lcub;personality&rcub;&rcub;</tt> the Character's Personality</li>
<li><tt>&lcub;&lcub;scenario&rcub;&rcub;</tt> the Character's Scenario</li>
<li><tt>&lcub;&lcub;persona&rcub;&rcub;</tt> your current Persona Description</li>
<li><tt>&lcub;&lcub;mesExamples&rcub;&rcub;</tt> the Character's Dialogue Examples</li>
<li><tt>&lcub;&lcub;mesExamplesRaw&rcub;&rcub;</tt> unformatted Dialogue Examples <b>(only for Story String)</b></li>
<li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> your current Persona username</li>
<li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> the Character's name</li>
<li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> the Character's version number</li>
<li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> a comma-separated list of group member names or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</li>
<li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> a text generation model name for the currently selected API. <b>Can be inaccurate!</b></li>
<li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> - the text of the latest chat message.</li>
<li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> index # of the latest chat message. Useful for slash command batching.</li>
<li><tt>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</tt> - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</li>
<li><tt>&lcub;&lcub;currentSwipeId&rcub;&rcub;</tt> the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</li>
<li><tt>&lcub;&lcub;lastSwipeId&rcub;&rcub;</tt> the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</li>
<li><tt>&lcub;&lcub;// (note)&rcub;&rcub;</tt> you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</li>
<li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> the current time</li>
<li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> the current date</li>
<li><tt>&lcub;&lcub;weekday&rcub;&rcub;</tt> the current weekday</li>
<li><tt>&lcub;&lcub;isotime&rcub;&rcub;</tt> the current ISO time (24-hour clock)</li>
<li><tt>&lcub;&lcub;isodate&rcub;&rcub;</tt> the current ISO date (YYYY-MM-DD)</li>
<li><tt>&lcub;&lcub;datetimeformat &hellip;&rcub;&rcub;</tt> the current date/time in the specified format, e. g. for German date/time: <tt>&lcub;&lcub;datetimeformat DD.MM.YYYY HH:mm&rcub;&rcub;</tt></li>
<li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li>
<li><tt>&lcub;&lcub;timeDiff::(time1)::(time2)&rcub;&rcub;</tt> the time difference between time1 and time2. Accepts time and date macros. (Ex: &lcub;&lcub;timeDiff::&lcub;&lcub;isodate&rcub;&rcub; &lcub;&lcub;time&rcub;&rcub;::2024/5/11 12:30:00&rcub;&rcub;)</li>
<li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> the time since the last user message was sent</li>
<li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li>
<li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> rolls a dice. (ex: <tt>>&lcub;&lcub;roll:1d6&rcub;&rcub;</tt> will roll a 6-sided dice and return a number between 1 and 6)</li>
<li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> returns a random item from the list. (ex: <tt>&lcub;&lcub;random:1,2,3,4&rcub;&rcub;</tt> will return 1 of the 4 numbers at random. Works with text lists too.</li>
<li><tt>&lcub;&lcub;random::(arg1)::(arg2)&rcub;&rcub;</tt> alternative syntax for random that allows to use commas in the list items.</li>
<li><tt>&lcub;&lcub;pick::(args)&rcub;&rcub;</tt> picks a random item from the list. Works the same as &lcub;&lcub;random&rcub;&rcub;, with the same possible syntax options, but the pick will stay consistent for this chat once picked and won't be re-rolled on consecutive messages and prompt processing.</li>
<li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.</li>
<li><tt>&lcub;&lcub;pipe&rcub;&rcub;</tt> <span data-i18n="help_macros_1">only for slash command batching. Replaced with the returned result of the previous command.</span></li>
<li><tt>&lcub;&lcub;newline&rcub;&rcub;</tt> <span data-i18n="help_macros_2">just inserts a newline.</span></li>
<li><tt>&lcub;&lcub;trim&rcub;&rcub;</tt> <span data-i18n="help_macros_3">trims newlines surrounding this macro.</span></li>
<li><tt>&lcub;&lcub;noop&rcub;&rcub;</tt> <span data-i18n="help_macros_4">no operation, just an empty string.</span></li>
<li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> <span data-i18n="help_macros_5">global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</span></li>
<li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> <span data-i18n="help_macros_6">the user input</span></li>
<li><tt>&lcub;&lcub;charPrompt&rcub;&rcub;</tt> <span data-i18n="help_macros_7">the Character's Main Prompt override</span></li>
<li><tt>&lcub;&lcub;charJailbreak&rcub;&rcub;</tt> <span data-i18n="help_macros_8">the Character's Jailbreak Prompt override</span></li>
<li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> <span data-i18n="help_macros_9">the Character's Description</span></li>
<li><tt>&lcub;&lcub;personality&rcub;&rcub;</tt> <span data-i18n="help_macros_10">the Character's Personality</span></li>
<li><tt>&lcub;&lcub;scenario&rcub;&rcub;</tt> <span data-i18n="help_macros_11">the Character's Scenario</span></li>
<li><tt>&lcub;&lcub;persona&rcub;&rcub;</tt> <span data-i18n="help_macros_12">your current Persona Description</span></li>
<li><tt>&lcub;&lcub;mesExamples&rcub;&rcub;</tt> <span data-i18n="help_macros_"13>the Character's Dialogue Examples</span></li>
<li><tt>&lcub;&lcub;mesExamplesRaw&rcub;&rcub;</tt> <span data-i18n="help_macros_14">unformatted Dialogue Examples </span><b>(only for Story String)</b></li>
<li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> <span data-i18n="help_macros_15">your current Persona username</span></li>
<li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> <span data-i18n="help_macros_16">the Character's name</span></li>
<li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> <span data-i18n="help_macros_17">the Character's version number</span></li>
<li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> <span data-i18n="help_macros_18">a comma-separated list of group member names or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>
<li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b>Can be inaccurate!</b></li>
<li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> - <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
<li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> <span data-i18n="help_macros_21">index # of the latest chat message. Useful for slash command batching.</span></li>
<li><tt>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</tt> - <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li>
<li><tt>&lcub;&lcub;currentSwipeId&rcub;&rcub;</tt> <span data-i18n="help_macros_23">the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
<li><tt>&lcub;&lcub;lastSwipeId&rcub;&rcub;</tt> <span data-i18n="help_macros_24">the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
<li><tt>&lcub;&lcub;// (note)&rcub;&rcub;</tt> <span data-i18n="help_macros_25">you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</span></li>
<li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> <span data-i18n="help_macros_26">the current time</span></li>
<li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> <span data-i18n="help_macros_27">the current date</span></li>
<li><tt>&lcub;&lcub;weekday&rcub;&rcub;</tt> <span data-i18n="help_macros_28">the current weekday</span></li>
<li><tt>&lcub;&lcub;isotime&rcub;&rcub;</tt> <span data-i18n="help_macros_29">the current ISO time (24-hour clock)</span></li>
<li><tt>&lcub;&lcub;isodate&rcub;&rcub;</tt> <span data-i18n="help_macros_30">the current ISO date (YYYY-MM-DD)</span></li>
<li><tt>&lcub;&lcub;datetimeformat &hellip;&rcub;&rcub;</tt> <span data-i18n="help_macros_31">the current date/time in the specified format, e. g. for German date/time: </span><tt>&lcub;&lcub;datetimeformat DD.MM.YYYY HH:mm&rcub;&rcub;</tt></li>
<li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> <span data-i18n="help_macros_32">the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</span></li>
<li><tt>&lcub;&lcub;timeDiff::(time1)::(time2)&rcub;&rcub;</tt> <span data-i18n="help_macros_33">the time difference between time1 and time2. Accepts time and date macros. (Ex: &lcub;&lcub;timeDiff::&lcub;&lcub;isodate&rcub;&rcub; &lcub;&lcub;time&rcub;&rcub;::2024/5/11 12:30:00&rcub;&rcub;)</span></li>
<li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> <span data-i18n="help_macros_34">the time since the last user message was sent</span></li>
<li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> <span data-i18n="help_macros_35">sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</span></li>
<li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> <span data-i18n="help_macros_36">rolls a dice. (ex: </span><tt>>&lcub;&lcub;roll:1d6&rcub;&rcub;</tt> will roll a 6-sided dice and return a number between 1 and 6)</li>
<li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> <span data-i18n="help_macros_37">returns a random item from the list. (ex: </span><tt>&lcub;&lcub;random:1,2,3,4&rcub;&rcub;</tt> will return 1 of the 4 numbers at random. Works with text lists too.</li>
<li><tt>&lcub;&lcub;random::(arg1)::(arg2)&rcub;&rcub;</tt> <span data-i18n="help_macros_38">alternative syntax for random that allows to use commas in the list items.</span></li>
<li><tt>&lcub;&lcub;pick::(args)&rcub;&rcub;</tt> <span data-i18n="help_macros_39">picks a random item from the list. Works the same as &lcub;&lcub;random&rcub;&rcub;, with the same possible syntax options, but the pick will stay consistent for this chat once picked and won't be re-rolled on consecutive messages and prompt processing.</span></li>
<li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> <span data-i18n="help_macros_40">dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.</span></li>
</ul>
<div>
<div data-i18n="Instruct Mode and Context Template Macros:">
Instruct Mode and Context Template Macros:
</div>
<div>
<small>(enabled in the Advanced Formatting settings)</small>
<small data-i18n="(enabled in the Advanced Formatting settings)">(enabled in the Advanced Formatting settings)</small>
</div>
<ul>
<li><tt>&lcub;&lcub;maxPrompt&rcub;&rcub;</tt> max allowed prompt length in tokens = (context size - response length)</li>
<li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> context template example dialogues separator</li>
<li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> context template chat start line</li>
<li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> main system prompt (either character prompt override if chosen, or instructSystemPrompt)</li>
<li><tt>&lcub;&lcub;instructSystemPrompt&rcub;&rcub;</tt> instruct system prompt</li>
<li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> instruct system prompt prefix sequence</li>
<li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> instruct system prompt suffix sequence</li>
<li><tt>&lcub;&lcub;instructUserPrefix&rcub;&rcub;</tt> instruct user prefix sequence</li>
<li><tt>&lcub;&lcub;instructUserSuffix&rcub;&rcub;</tt> instruct user suffix sequence</li>
<li><tt>&lcub;&lcub;instructAssistantPrefix&rcub;&rcub;</tt> instruct assistant prefix sequence</li>
<li><tt>&lcub;&lcub;instructAssistantSuffix&rcub;&rcub;</tt> instruct assistant suffix sequence</li>
<li><tt>&lcub;&lcub;instructFirstAssistantPrefix&rcub;&rcub;</tt> instruct assistant first output sequence</li>
<li><tt>&lcub;&lcub;instructLastAssistantPrefix&rcub;&rcub;</tt> instruct assistant last output sequence</li>
<li><tt>&lcub;&lcub;instructSystemPrefix&rcub;&rcub;</tt> instruct system message prefix sequence</li>
<li><tt>&lcub;&lcub;instructSystemSuffix&rcub;&rcub;</tt> instruct system message suffix sequence</li>
<li><tt>&lcub;&lcub;instructSystemInstructionPrefix&rcub;&rcub;</tt> instruct system instruction prefix</li>
<li><tt>&lcub;&lcub;instructUserFiller&rcub;&rcub;</tt> instruct first user message filler</li>
<li><tt>&lcub;&lcub;instructStop&rcub;&rcub;</tt> instruct stop sequence</li>
<li><tt>&lcub;&lcub;maxPrompt&rcub;&rcub;</tt> <span data-i18n="help_macros_41">max allowed prompt length in tokens = (context size - response length)</span></li>
<li><tt>&lcub;&lcub;exampleSeparator&rcub;&rcub;</tt> <span data-i18n="help_macros_42">context template example dialogues separator</span></li>
<li><tt>&lcub;&lcub;chatStart&rcub;&rcub;</tt> <span data-i18n="help_macros_43">context template chat start line</span></li>
<li><tt>&lcub;&lcub;systemPrompt&rcub;&rcub;</tt> <span data-i18n="help_macros_44">main system prompt (either character prompt override if chosen, or instructSystemPrompt)</span></li>
<li><tt>&lcub;&lcub;instructSystemPrompt&rcub;&rcub;</tt> <span data-i18n="help_macros_45">instruct system prompt</span></li>
<li><tt>&lcub;&lcub;instructSystemPromptPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_46">instruct system prompt prefix sequence</span></li>
<li><tt>&lcub;&lcub;instructSystemPromptSuffix&rcub;&rcub;</tt> <span data-i18n="help_macros_47">instruct system prompt suffix sequence</span></li>
<li><tt>&lcub;&lcub;instructUserPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_48">instruct user prefix sequence</span></li>
<li><tt>&lcub;&lcub;instructUserSuffix&rcub;&rcub;</tt> <span data-i18n="help_macros_49">instruct user suffix sequence</span></li>
<li><tt>&lcub;&lcub;instructAssistantPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_50">instruct assistant prefix sequence</span></li>
<li><tt>&lcub;&lcub;instructAssistantSuffix&rcub;&rcub;</tt> <span data-i18n="help_macros_51">instruct assistant suffix sequence</span></li>
<li><tt>&lcub;&lcub;instructFirstAssistantPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_52">instruct assistant first output sequence</span></li>
<li><tt>&lcub;&lcub;instructLastAssistantPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_53">instruct assistant last output sequence</span></li>
<li><tt>&lcub;&lcub;instructSystemPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_54">instruct system message prefix sequence</span></li>
<li><tt>&lcub;&lcub;instructSystemSuffix&rcub;&rcub;</tt> <span data-i18n="help_macros_55">instruct system message suffix sequence</span></li>
<li><tt>&lcub;&lcub;instructSystemInstructionPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_56">instruct system instruction prefix</span></li>
<li><tt>&lcub;&lcub;instructUserFiller&rcub;&rcub;</tt> <span data-i18n="help_macros_57">instruct first user message filler</span></li>
<li><tt>&lcub;&lcub;instructStop&rcub;&rcub;</tt> <span data-i18n="help_macros_58">instruct stop sequence</span></li>
</ul>
<div>
<div data-i18n="Chat variables Macros:">
Chat variables Macros:
</div>
<div><small>Local variables = unique to the current chat</small></div>
<div><small>Global variables = works in any chat for any character</small></div>
<div><small>Scoped variables = works in STscript</small></div>
<div><small data-i18n="Local variables = unique to the current chat">Local variables = unique to the current chat</small></div>
<div><small data-i18n="Global variables = works in any chat for any character">Global variables = works in any chat for any character</small></div>
<div><small data-i18n="Scoped variables = works in STscript">Scoped variables = works in STscript</small></div>
<ul>
<li><tt>&lcub;&lcub;getvar::name&rcub;&rcub;</tt> replaced with the value of the local variable "name"</li>
<li><tt>&lcub;&lcub;setvar::name::value&rcub;&rcub;</tt> replaced with empty string, sets the local variable "name" to "value"</li>
<li><tt>&lcub;&lcub;addvar::name::increment&rcub;&rcub;</tt> replaced with empty strings, adds a numeric value of "increment" to the local variable "name"</li>
<li><tt>&lcub;&lcub;incvar::name&rcub;&rcub;</tt> replaced with the result of the increment of value of the variable "name" by 1</li>
<li><tt>&lcub;&lcub;decvar::name&rcub;&rcub;</tt> replaced with the result of the decrement of value of the variable "name" by 1</li>
<li><tt>&lcub;&lcub;getglobalvar::name&rcub;&rcub;</tt> replaced with the value of the global variable "name"</li>
<li><tt>&lcub;&lcub;setglobalvar::name::value&rcub;&rcub;</tt> replaced with empty string, sets the global variable "name" to "value"</li>
<li><tt>&lcub;&lcub;addglobalvar::name::value&rcub;&rcub;</tt> replaced with empty string, adds a numeric value of "increment" to the global variable "name"</li>
<li><tt>&lcub;&lcub;incglobalvar::name&rcub;&rcub;</tt> replaced with the result of the increment of value of the global variable "name" by 1</li>
<li><tt>&lcub;&lcub;decglobalvar::name&rcub;&rcub;</tt> replaced with the result of the decrement of value of the global variable "name" by 1</li>
<li><tt>&lcub;&lcub;var::name&rcub;&rcub;</tt> replaced with the value of the scoped variable "name"</li>
<li><tt>&lcub;&lcub;var::name::index&rcub;&rcub;</tt> replaced with the value of item at index (for arrays / lists or objects / dictionaries) of the scoped variable "name"</li>
<li><tt>&lcub;&lcub;getvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_59">replaced with the value of the local variable "name"</span></li>
<li><tt>&lcub;&lcub;setvar::name::value&rcub;&rcub;</tt> <span data-i18n="help_macros_60">replaced with empty string, sets the local variable "name" to "value"</span></li>
<li><tt>&lcub;&lcub;addvar::name::increment&rcub;&rcub;</tt> <span data-i18n="help_macros_61">replaced with empty strings, adds a numeric value of "increment" to the local variable "name"</span></li>
<li><tt>&lcub;&lcub;incvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_62">replaced with the result of the increment of value of the variable "name" by 1</span></li>
<li><tt>&lcub;&lcub;decvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_63">replaced with the result of the decrement of value of the variable "name" by 1</span></li>
<li><tt>&lcub;&lcub;getglobalvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_64">replaced with the value of the global variable "name"</span></li>
<li><tt>&lcub;&lcub;setglobalvar::name::value&rcub;&rcub;</tt> <span data-i18n="help_macros_65">replaced with empty string, sets the global variable "name" to "value"</span></li>
<li><tt>&lcub;&lcub;addglobalvar::name::value&rcub;&rcub;</tt> <span data-i18n="help_macros_66">replaced with empty string, adds a numeric value of "increment" to the global variable "name"</span></li>
<li><tt>&lcub;&lcub;incglobalvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_67">replaced with the result of the increment of value of the global variable "name" by 1</span></li>
<li><tt>&lcub;&lcub;decglobalvar::name&rcub;&rcub;</tt> <span data-i18n="help_macros_68">replaced with the result of the decrement of value of the global variable "name" by 1</span></li>
<li><tt>&lcub;&lcub;var::name&rcub;&rcub;</tt> <span data-i18n="help_macros_69">replaced with the value of the scoped variable "name"</span></li>
<li><tt>&lcub;&lcub;var::name::index&rcub;&rcub;</tt> <span data-i18n="help_macros_70">replaced with the value of item at index (for arrays / lists or objects / dictionaries) of the scoped variable "name"</span></li>
</ul>

View File

@ -8,10 +8,10 @@
<h3 data-i18n="How to start chatting?">How to start chatting?</h3>
<ol>
<li>
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
</li>
<li>
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
<span data-i18n="Click _space">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
</li>
</ol>
<div>

View File

@ -306,6 +306,9 @@ export function throttle(func, limit = 300) {
* @returns {boolean} True if the element is in the viewport, false otherwise.
*/
export function isElementInViewport(el) {
if (!el) {
return false;
}
if (typeof jQuery === 'function' && el instanceof jQuery) {
el = el[0];
}

View File

@ -795,7 +795,7 @@ function letCallback(args, value) {
/**
* Set or retrieve a variable in the current scope or nearest ancestor scope.
* @param {{_scope:SlashCommandScope, key?:string, index?:string|number}} args Named arguments.
* @param {{_hasUnnamedArgument:boolean, _scope:SlashCommandScope, key?:string, index?:string|number}} args Named arguments.
* @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable.
* @returns The variable's value
*/
@ -803,9 +803,13 @@ function varCallback(args, value) {
if (!Array.isArray(value)) value = [value];
if (args.key !== undefined) {
const key = args.key;
const val = value.join(' ');
args._scope.setVariable(key, val, args.index);
return val;
if (args._hasUnnamedArgument) {
const val = value.join(' ');
args._scope.setVariable(key, val, args.index);
return val;
} else {
return args._scope.getVariable(key, args.index);
}
}
const key = value.shift();
if (value.length > 0) {
@ -817,6 +821,30 @@ function varCallback(args, value) {
}
}
/**
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
* @param {SlashCommandClosure} value
* @returns {string}
*/
function closureSerializeCallback(args, value) {
if (!(value instanceof SlashCommandClosure)) {
throw new Error('unnamed argument must be a closure');
}
return value.rawText;
}
/**
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value
* @returns {SlashCommandClosure}
*/
function closureDeserializeCallback(args, value) {
const parser = new SlashCommandParser();
const closure = parser.parse(value, true, args._parserFlags, args._abortController);
closure.scope.parent = args._scope;
return closure;
}
export function registerVariableCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'listvar',
callback: listVariablesCallback,
@ -1811,4 +1839,61 @@ export function registerVariableCommands() {
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-serialize',
/**
*
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
* @param {SlashCommandClosure} value
* @returns {string}
*/
callback: (args, value)=>closureSerializeCallback(args, value),
unnamedArgumentList: [
SlashCommandArgument.fromProps({ description: 'the closure to serialize',
typeList: [ARGUMENT_TYPE.CLOSURE],
isRequired: true,
}),
],
returns: 'serialized closure as string',
helpString: `
<div>
Serialize a closure as text that can be stored in global and chat variables.
</div>
<div>
<strong>Examples:</strong>
<ul>
<li>
<pre><code class="language-stscript">/closure-serialize {: x=1 /echo x is {{var::x}} and y is {{var::y}} :} |\n/setvar key=myClosure</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-deserialize',
/**
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value
* @returns {SlashCommandClosure}
*/
callback: (args, value)=>closureDeserializeCallback(args, value),
unnamedArgumentList: [
SlashCommandArgument.fromProps({ description: 'serialized closure',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
}),
],
returns: 'deserialized closure',
helpString: `
<div>
Deserialize a closure from text.
</div>
<div>
<strong>Examples:</strong>
<ul>
<li>
<pre><code class="language-stscript">/closure-deserialize {{getvar::myClosure}} |\n/let myClosure {{pipe}} |\n/let y bar |\n/:myClosure x=foo</code></pre>
</li>
</ul>
</div>
`,
}));
}

View File

@ -645,7 +645,7 @@ function registerWorldInfoSlashCommands() {
await saveWorldInfo(file, data, true);
reloadEditor(file);
return entry.uid;
return String(entry.uid);
}
async function setEntryFieldCallback(args, value) {
@ -1402,22 +1402,29 @@ function splitKeywordsAndRegexes(input) {
function customTokenizer(input, _selection, callback) {
let current = input.term;
let insideRegex = false, regexClosed = false;
// Go over the input and check the current state, if we can get a token
for (let i = 0; i < current.length; i++) {
let char = current[i];
// If we find an unascaped slash, set the current regex state
if (char === '/' && (i === 0 || current[i - 1] !== '\\')) {
if (!insideRegex) insideRegex = true;
else if (!regexClosed) regexClosed = true;
}
// If a comma is typed, we tokenize the input.
// unless we are inside a possible regex, which would allow commas inside
if (char === ',') {
// We take everything up till now and consider this a token
const token = current.slice(0, i).trim();
// Now how we test if this is a valid regex? And not a finished one, but a half-finished one?
// Easy, if someone typed a comma it can't be a delimiter escape.
// So we just check if this opening with a slash, and if so, we "close" the regex and try to parse it.
// So if we are inside a valid regex, we can't take the token now, we continue processing until the regex is closed,
// or this is not a valid regex anymore
if (token.startsWith('/') && isValidRegex(token + '/')) {
// Now how we test if this is a regex? And not a finished one, but a half-finished one?
// We use the state remembered from above to check whether the delimiter was opened but not closed yet.
// We don't check validity here if we are inside a regex, because it might only get valid after its finished. (Closing brackets, etc)
// Validity will be finally checked when the next comma is typed.
if (insideRegex && !regexClosed) {
continue;
}
@ -1437,6 +1444,7 @@ function customTokenizer(input, _selection, callback) {
// Now remove the token from the current input, and the comma too
current = current.slice(i + 1);
insideRegex = false, regexClosed = false;
i = 0;
}
}
@ -3533,6 +3541,7 @@ function onWorldInfoChange(args, text) {
saveSettingsDebounced();
eventSource.emit(event_types.WORLDINFO_SETTINGS_UPDATED);
return '';
}
export async function importWorldInfo(file) {

View File

@ -410,7 +410,8 @@ input[type='checkbox']:focus {
}
small {
color: var(--grey70);
color: var(--SmartThemeBodyColor);
opacity: 0.7;
}
.mes.smallSysMes {
@ -1254,6 +1255,10 @@ select {
z-index: 10000;
pointer-events: none;
.autoComplete {
pointer-events: all;
}
&.isFloating {
--direction: row;
@ -1270,7 +1275,6 @@ select {
.autoComplete {
flex: 0 0 auto;
width: 50vw;
pointer-events: all;
}
&:after {
@ -2066,11 +2070,17 @@ body[data-stscript-style] .hljs.language-stscript {
}
.text_pole::placeholder {
color: rgb(139, 139, 139);
color: var(--SmartThemeEmColor) !important;
opacity: 0.7;
}
textarea::placeholder {
color: var(--SmartThemeEmColor) !important;
opacity: 0.7;
}
#send_textarea::placeholder {
color: var(--SmartThemeEmColor);
color: var(--SmartThemeEmColor) !important;
text-align: center;
white-space: nowrap;
}

View File

@ -45,7 +45,8 @@ router.post('/caption-image', jsonParser, async (request, response) => {
});
if (!result.ok) {
console.log(`Claude API returned error: ${result.status} ${result.statusText}`);
const text = await result.text();
console.log(`Claude API returned error: ${result.status} ${result.statusText}`, text);
return response.status(result.status).send({ error: true });
}