Merge remote-tracking branch 'upstream/dev' into feature/chromadb

This commit is contained in:
Mark Ceter
2023-05-21 10:01:19 +00:00
50 changed files with 1396 additions and 741 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

1
public/css/toastr.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -14,6 +14,7 @@
<link href="css/jquery-ui.min.css" rel="stylesheet">
<link href="css/bright.min.css" rel="stylesheet">
<link href="css/cropper.min.css" rel="stylesheet">
<link href="css/toastr.min.css" rel="stylesheet">
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
@@ -41,6 +42,7 @@
<script src="scripts/moment.min.js"></script>
<script src="scripts/cropper.min.js"></script>
<script src="scripts/jquery-cropper.min.js"></script>
<script src="scripts/toastr.min.js"></script>
<script type="module" src="scripts/eventemitter.js"></script>
<script type="module" src="scripts/power-user.js"></script>
<script type="module" src="scripts/swiped-events.js"></script>
@@ -61,6 +63,7 @@
<script type="module" src="scripts/slash-commands.js"></script>
<script type="module" src="scripts/tags.js"></script>
<script type="module" src="scripts/secrets.js"></script>
<script type="module" src="scripts/context-template.js"></script>
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
<title>SillyTavern</title>
@@ -368,12 +371,14 @@
</div>
</div>
<div class="range-block">
<label for="oai_breakdown" class="checkbox_label widthFreeExpand">
<input id="oai_breakdown" type="checkbox" />
Token Breakdown
</label>
<div class="range-block-title justifyLeft">
<label for="legacy_streaming" class="checkbox_label">
<input id="legacy_streaming" type="checkbox" />
Legacy Streaming Processing
</label>
</div>
<div class="toggle-description justifyLeft">
Display a breakdown of the tokens used in the request.
Enable this if the streaming doesn't work with your proxy.
</div>
</div>
<div class="range-block">
@@ -1018,6 +1023,9 @@
<h5>Get it here: <a target="_blank" href="https://horde.koboldai.net/register">Register</a><br>
Enter <span class="monospace">0000000000</span> to use anonymous mode.
</h5>
<div>
<a id="horde_kudos" href="javascript:void(0);">View my Kudos</a>
</div>
<div class="flex-container">
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000" autocomplete="off">
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_horde"></div>
@@ -1156,7 +1164,7 @@
</span>
<div class="widthFreeExpand">
<div class="flex-container">
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" autocomplete="off" />
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" autocomplete="off" />
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_poe"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@@ -1287,7 +1295,7 @@
<div class="flex-container">
<div class="flex1">
<label for="instruct_system_sequence">
System Sequence
<small>System Sequence</small>
</label>
<div>
<input id="instruct_system_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
@@ -1295,12 +1303,20 @@
</div>
<div class="flex1">
<label for="instruct_stop_sequence">
Stop Sequence
<small>Stop Sequence</small>
</label>
<div>
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
<div class="flex1">
<label for="instruct_separator_sequence">
<small>Separator</small>
</label>
<div>
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
</div>
</div>
</div>
@@ -1339,6 +1355,21 @@
<label class="checkbox_label" for="collapse-newlines-checkbox"><input id="collapse-newlines-checkbox" type="checkbox" />
Remove Empty New Lines from Output
</label>
<div id="context-templates-block" class="template_element">
<h4>
Context Templates
</h4>
<div class="flex-container flexGap5">
<select id="prompt_template" class="flex1 margin0">
<option value="None">None (Default)</option>
<option value="Classic">Classic</option>
<option value="Pygmalion">Pygmalion</option>
</select>
<div id="context_template_edit" class="menu_button fa-solid fa-pencil margin0" title="Edit"></div>
<div id="context_template_new" class="menu_button fa-solid fa-plus margin0" title="Add new"></div>
<div id="context_template_delete" class="menu_button fa-solid fa-trash-can margin0" title="Delete"></div>
</div>
</div>
<div>
<h4>Pygmalion Formatting</h4>
<select id="pygmalion_formatting">
@@ -1371,24 +1402,6 @@
</label>
</div>
</div>
<div id="context-templates-block" style="display:none">
<h4>
Context Templates
</h4>
<div>
<label>Presets:</label>
<div class="flex-container flexGap5">
<select id="prompt_template" class="flex1 margin0">
<option value="None">None</option>
<option value="Classic">Classic</option>
<option value="Pygmalion">Pygmalion</option>
</select>
<div id="context_template_edit" class="menu_button fa-solid fa-pencil margin0" title="Edit"></div>
<div id="context_template_new" class="menu_button fa-solid fa-plus margin0" title="Add new"></div>
<div id="context_template_delete" class="menu_button fa-solid fa-trash-can margin0" title="Delete"></div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -1459,7 +1472,7 @@
</div>
</div>
<div class="flex1 range-block">
<div class="flex1 range-block flex-container flexFlowColumn">
<label title="Entries can activate other entries by mentioning their keywords" class="checkbox_label">
<input id="world_info_recursive" type="checkbox" />
<span>
@@ -1469,6 +1482,15 @@
</a>
</span>
</label>
<label title="Lookup for the entry keys in the context will respect the case" class="checkbox_label">
<input id="world_info_case_sensitive" type="checkbox" />
<span>
Case-sensitive keys
<a href="/notes#casesensitivekeys" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
</span>
</label>
</div>
</div>
@@ -1773,7 +1795,7 @@
<input id="auto_swipe_minimum_length" name="auto_swipe_minimum_length" type="number" min="0" step="1" value="0" class="text_pole">
<div>Blacklisted words</div>
<div class="auto_swipe">
<input id="auto_swipe_blacklist" name="auto_swipe_blacklist" placeholder="words you dont want generated separated by comma ','" class="text_pole" maxlength="500" value="" autocomplete="off">
<textarea id="auto_swipe_blacklist" name="auto_swipe_blacklist" placeholder="words you dont want generated separated by comma ','" class="text_pole textarea_compact" maxlength="500" value="" autocomplete="off" rows="3"></textarea>
<div>Blacklisted word count to swipe</div>
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
</div>
@@ -1909,7 +1931,7 @@
<label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character">
<input type="submit" id="create_button" name="create_button">
</label>
<div id="delete_button" class="menu_button fa-solid fa-trash-can " title="Delete Character"></div>
<div id="delete_button" class="menu_button fa-solid fa-skull " title="Delete Character"></div>
</div>
</div>
<div title="Token counts may be inaccurate and provided just for reference." id="result_info">&nbsp;</div>
@@ -2193,6 +2215,58 @@
</div>
<!-- templates for JS to reuse when needed -->
<div id="context_editor_template" class="template_element">
<div class="context_editor">
<h3>Context Template Editor</h3>
<div class="inline-drawer wide100p">
<div class="inline-drawer-toggle inline-drawer-header">
Substitution Parameters
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<i>Click to copy.</i>
<ul class="template_parameters_list justifyLeft margin0">
<li><code>{{char}}</code> - current character name</li>
<li><code>{{user}}</code> - current user name</li>
<li><code>{{description}}</code> - character description</li>
<li><code>{{scenario}}</code> - character or group scenario</li>
<li><code>{{personality}}</code> - character personality</li>
<li><code>{{mesExamples}}</code> - message examples</li>
<li><code>{{wiBeforeCharacter}}</code> - activated World Info entries (Before Char)</li>
<li><code>{{wiAfterCharacter}}</code> - activated World Info entries (After Char)</li>
<li><code>{{instructSystemPrompt}}</code> - system prompt (Instruct mode only)</li>
</ul>
</div>
</div>
<div>
<div class="margin-bot-10px wide100p justifyLeft">
Story String Template
</div>
<textarea class="wide100p textarea_compact story_string_template" rows="8"></textarea>
<div>
<small>Lines containing parameters resolving to an empty value will be removed from the template string.</small>
</div>
</div>
<div>
<div class="title_restorable">
<span>Chat Injections</span>
<div title="Add chat injection" class="menu_button">
<div class="fa-solid fa-plus"></div>
</div>
</div>
<div class="chat_injections_list flex-container flexFlowColumn flexGap5 wide100p"></div>
</div>
</div>
</div>
<div id="chat_injection_template" class="template_element">
<div class="chat_injection flex-container wide100p flexGap5 flexnowrap">
<input class="chat_injection_text textarea_compact text_pole flex2" placeholder="Injection text (supports parameters)" type="text" />
<input class="chat_injection_depth textarea_compact text_pole flex1" placeholder="Injection depth" type="number" min="0" max="100" />
<div title="Remove injection" class="menu_button fa-solid fa-xmark chat_injection_remove"></div>
</div>
</div>
<div id="group_scenario_template" class="template_element">
<div class="group_scenario range-block flexFlowColumn flex-container">
@@ -2380,7 +2454,7 @@
<div id="message_template" class="template_element">
<div class="mes" mesid="${count_view_mes}" ch_name="${characterName}" is_user="${mes.is_user}" is_system="${mes.is_system}">
<div class="for_checkbox"></div><input type="checkbox" class="del_checkbox">
<div>
<div class="mesAvatarWrapper">
<div class="avatar">
<img src="">
@@ -2389,7 +2463,7 @@
</div>
<div class="swipe_left fa-solid fa-chevron-left"></div>
<div class="mes_block">
<div class="ch_name">
<div class="ch_name flex-container justifySpaceBetween">
<span class="name_text">${characterName}</span>
<div class="mes_buttons">
@@ -2497,11 +2571,6 @@
<div id="chat">
</div>
<div id="form_sheld">
<div id="token_breakdown" style="display:none;">
<div>
<!-- Token Breakdown Goes Here -->
</div>
</div>
<div id="dialogue_del_mes">
<div id="dialogue_del_mes_ok" class="menu_button">Delete</div>
<div id="dialogue_del_mes_cancel" class="menu_button">Cancel</div>
@@ -2513,7 +2582,7 @@
<div id="loading_mes">
<div title="Loading" class="fa-solid fa-hourglass-half"></div>
</div>
<div id="send_but" class="fa-solid fa-feather-pointed" title="Send a message"></div>
<div id="send_but" class="fa-solid fa-paper-plane" title="Send a message"></div>
</div>
</form>
</div>
@@ -2532,7 +2601,7 @@
</a>
<a id="option_toggle_AN">
<i class="fa-lg fa-solid fa-note-sticky"></i>
<span>Open Author's Note</span>
<span>Author's Note Show/Hide</span>
</a>
<a id="option_convert_to_group">
<i class="fa-lg fa-solid fa-people-arrows"></i>
@@ -2575,6 +2644,15 @@
<div id="rawPromptPopup" class="list-group">
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
</div>
<script>
// Configure toast library:
toastr.options.escapeHtml = false; // Prevent raw HTML inserts
toastr.options.timeOut = 4000; // How long the toast will display without user interaction
toastr.options.extendedTimeOut = 10000; // How long the toast will display after a user hovers over it
toastr.options.progressBar = true; // Visually indicate how long before a toast expires.
toastr.options.closeButton = true; // enable a close button
</script>
</body>
</html>

View File

@@ -5,5 +5,6 @@
"stop_sequence": "",
"input_sequence": "### Instruction:",
"output_sequence": "### Response:",
"separator_sequence": "",
"wrap": true
}

View File

@@ -1,9 +1,10 @@
{
"name": "Koala",
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
"system_sequence": "BEGINNING OF CONVERSATION:",
"system_sequence": "BEGINNING OF CONVERSATION: ",
"stop_sequence": "",
"input_sequence": "USER: ",
"output_sequence": "GPT: ",
"separator_sequence": "</s>",
"wrap": false
}

View File

@@ -5,5 +5,6 @@
"stop_sequence": "</s>",
"input_sequence": "<|user|>",
"output_sequence": "<|model|>",
"separator_sequence": "",
"wrap": false
}

View File

@@ -5,5 +5,6 @@
"stop_sequence": "",
"input_sequence": "### Human:",
"output_sequence": "### Assistant:",
"separator_sequence": "",
"wrap": true
}

View File

@@ -5,5 +5,6 @@
"stop_sequence": "",
"input_sequence": "USER: ",
"output_sequence": "ASSISTANT: ",
"wrap": true
"separator_sequence": "</s>",
"wrap": false
}

View File

@@ -3,7 +3,8 @@
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
"system_sequence": "",
"stop_sequence": "",
"input_sequence": "### Instruction:",
"input_sequence": "",
"output_sequence": "### Response:",
"separator_sequence": "</s>",
"wrap": true
}

View File

@@ -16,7 +16,7 @@ Usually it all takes 200-350 tokens.
For most Kobold's models the easiest way is to use a free form for description, and in each sentence it is desirable to specify the name of the character.
The entire description should be in one line without hyphenation.
The entire description should be in one line without hyphenation.
For example:
@@ -50,11 +50,11 @@ This is because every AI model has a limit to the amount of context it can proce
This is the information that gets sent to the AI each time you ask it to generate a response:
* Character definitions
* Chat history
* Author's Notes
* Special Format strings
* [bracket commands]
* Character definitions
* Chat history
* Author's Notes
* Special Format strings
* [bracket commands]
SillyTavern automatically calculates the best way to allocate the available context tokens before sending the information to the AI model.
@@ -62,23 +62,23 @@ SillyTavern automatically calculates the best way to allocate the available cont
These will always be sent to the AI with every generation request:
* Character Name (keep the name short! Sent at the start of EVERY Character message)
* Character Description Box
* Character Personality Box
* Scenario Box
* Character Name (keep the name short! Sent at the start of EVERY Character message)
* Character Description Box
* Character Personality Box
* Scenario Box
### What parts of a Character's Definitions are NOT permanent?
* The first message box - only sent once at the start of the chat.
* Example messages box - only kept until chat history fills up the context (optionally these can be forced to be kept in context)
* The first message box - only sent once at the start of the chat.
* Example messages box - only kept until chat history fills up the context (optionally these can be forced to be kept in context)
### Popular AI Model Context Token Limits
* Older models below 6B parameters - 1024
* Pygmalion 6B - 2048
* Poe.com (Claude-instant or ChatGPT) - 2048
* OpenAI ChatGPT - 4000-ish?
* OpenAI GPT-4 - 8000?
* Older models below 6B parameters - 1024
* Pygmalion 6B - 2048
* Poe.com (Claude-instant or ChatGPT) - 2048
* OpenAI ChatGPT - 4000-ish?
* OpenAI GPT-4 - 8000?
### Personality summary
@@ -96,16 +96,15 @@ Another example:
### First message
The First Message is an important thing that sets exactly how and in what style the character will communicate.
The First Message is an important thing that sets exactly how and in what style the character will communicate.
It is desirable that the character's first message be long, so that later it would be less likely that the character would respond in with very short messages.
It is desirable that the character's first message be long, so that later it would be less likely that the character would respond in with very short messages.
You can also use asterisks ** to describe the character's actions.
For example:
`*I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here. *I said with toothy smug sunny smile looking you straight in the eye* What brings you...`
`*I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here. *I said with toothy smug sunny smile looking you straight in the eye* What brings you...`
### Examples of dialogue
@@ -117,13 +116,13 @@ Example:
```
<START>
{{user}}: Hi Aqua, I heard you like to spend time in the pub.
{{char}}: *excitedly* Oh my goodness, yes! I just love spending time at the pub! It's so much fun to talk to all the adventurers and hear about their exciting adventures! And you are?
{{user}}: I'm a new here and I wanted to ask for your advice.
{{char}}: *giggles* Oh, advice! I love giving advice! And in gratitude for that, treat me to a drink! *gives signals to the bartender*
{{user}}: Hi Aqua, I heard you like to spend time in the pub.
{{char}}: *excitedly* Oh my goodness, yes! I just love spending time at the pub! It's so much fun to talk to all the adventurers and hear about their exciting adventures! And you are?
{{user}}: I'm a new here and I wanted to ask for your advice.
{{char}}: *giggles* Oh, advice! I love giving advice! And in gratitude for that, treat me to a drink! *gives signals to the bartender*
<START>
{{user}}: Hello
{{user}}: Hello
{{char}}: *excitedly* Hello there, dear! Are you new to Axel? Don't worry, I, Aqua the goddess of water, am here to help you! Do you need any assistance? And may I say, I look simply radiant today! *strikes a pose and looks at you with puppy eyes*
```
@@ -135,8 +134,10 @@ Circumstances and context of the dialogue.
_A list of tags that are replaced when sending to generate:_
1. {{user}} and &lt;USER&gt; are replaced by the User's Name
1. {{user}} and &lt;USER&gt; are replaced by the User's Name
2. {{char}} and &lt;BOT&gt; are replaced by the Character's Name
3. {{time}} is replaced with the current system time.
4. {{date}} is replaced with the current system date.
### Favorite Character
@@ -162,7 +163,7 @@ _It is important to note that while World Info helps guide the AI towards your d
#### Key
A list of keywords that trigger the activation of a World Info entry.
A list of keywords that trigger the activation of a World Info entry. Keys are not case-sensitive by default (this is [configurable](#casesensitivekeys)).
#### Secondary Key
@@ -217,7 +218,7 @@ Entries inserted by direct mentioning of their keys have higher priority than th
**Entries can activate other entries by mentioning their keywords in the content text.**
For example, if your World Info contains two entries:
For example, if your World Info contains two entries:
```
Entry #1
@@ -233,6 +234,14 @@ Content: Rufus is a dog.
**Both** of them will be pulled into the context if the message text mentions **just Bessie**.
### Case-sensitive keys
**To get pulled into the context, entry keys need to match the case as they are defined in the World Info entry.**
This is useful when your keys are common words or parts of common words.
For example, when this setting is active, keys 'rose' and 'Rose' will be treated differently, depending on the inputs.
## KoboldAI
### Basic Settings
@@ -257,7 +266,7 @@ The maximum amount of tokens that the AI will generate to respond. One word is a
#### Context size
How much will the AI remember. Context size also affects the speed of generation.
How much will the AI remember. Context size also affects the speed of generation.
_Important_: The setting of Context Size in SillyTavern GUI overrides the setting for KoboldAI GUI
@@ -322,10 +331,10 @@ They are created by training the AI with a special type of prompt using a collec
To get a NovelAI API key, follow these instructions:
1. Go to the NovelAI website and Login.
2. Create a new story, or open an existing story.
3. Open the Network Tools on your web browser. (For Chrome or Firefox, you do this by pressing Ctrl+Shift+I, then switching to the Network tab.)
4. Generate something. You should see two requests to [api.novelai.net/ai/generate-stream](http://api.novelai.net/ai/generate-stream), which might look something like this:
1. Go to the NovelAI website and Login.
2. Create a new story, or open an existing story.
3. Open the Network Tools on your web browser. (For Chrome or Firefox, you do this by pressing Ctrl+Shift+I, then switching to the Network tab.)
4. Generate something. You should see two requests to [api.novelai.net/ai/generate-stream](http://api.novelai.net/ai/generate-stream), which might look something like this:
![1.png](1.png)
@@ -339,7 +348,7 @@ The long string (after "Bearer", not including it) is your API key.
### Settings
The files with the settings are here (SillyTavern\public\NovelAI Settings).
The files with the settings are here (SillyTavern\public\NovelAI Settings).
You can also manually add your own settings files.
#### Temperature
@@ -366,7 +375,7 @@ The range of influence of Repetition penalty in tokens.
If your subscription tier is Paper, Tablet or Scroll use only Euterpe model otherwise you can not get an answer from NovelAI API.
## OpenAI
## OpenAI
### API key
@@ -385,11 +394,14 @@ _Lost API keys can't be restored! Make sure to keep it safe!_
**How to get your access token / cookie:**
1. Login to [poe.com](https://poe.com)
2. Open browser DevTools (F12) and navigate to "Application" tab
3. Find a _p-b_ cookie for poe.com domain and copy its value
4. Paste cookie value to the box below and click "Connect"
5. Select a character and start chatting
1. Login to [poe.com](https://poe.com)
2. Open browser DevTools (F12) and navigate to "Application" tab.
3. Type any message into the poe.com chat, and get a response from the AI.
4. Find the 'Cookie' section on the left side of Dev Tools 'Application' tab, expand it
5. Click "<http://poe.com/>" listing inside the Cookies section.
6. Look to the right for the listing of _p-b_ and copy its Value.
7. Paste the cookie value into the Poe API connection URL box, and click "Connect".
8. Select a character and start chatting
## Anchors
@@ -419,7 +431,7 @@ Write one reply in internet RP style for {{char}}. Be verbose and creative.
Provides ready-made presets with prompts and sequences for some well-known instruct models.
*Changing a preset resets your system prompt to default!*
_Changing a preset resets your system prompt to default!_
#### Input Sequence
@@ -433,6 +445,10 @@ Text added before the character's reply.
Text added before the system prompt.
#### Separator Sequence
Text added after the character reply to separate the chat history logs.
#### Stop Sequence
Text that denotes the end of the reply. Will be trimmed from the output text.
@@ -441,7 +457,7 @@ Text that denotes the end of the reply. Will be trimmed from the output text.
If enabled, prepend character and user names to chat history logs after inserting the sequences.
*Always enabled for group chats!*
_Always enabled for group chats!_
#### Wrap Sequences with Newline
@@ -457,7 +473,7 @@ To import Character.AI chats, use this tool: [https://github.com/0x000011b/chara
**Important: This section doesn't apply to OpenAI API. SillyTavern will always use a matching tokenizer for OpenAI models.**
A tokenizer is a tool that breaks down a piece of text into smaller units called tokens. These tokens can be individual words or even parts of words, such as prefixes, suffixes, or punctuation. A rule of thumb is that one token generally corresponds to 3~4 characters of text.
A tokenizer is a tool that breaks down a piece of text into smaller units called tokens. These tokens can be individual words or even parts of words, such as prefixes, suffixes, or punctuation. A rule of thumb is that one token generally corresponds to 3~4 characters of text.
SillyTavern can use the following tokenizers while forming a request to the AI backend:
@@ -470,7 +486,7 @@ SillyTavern can use the following tokenizers while forming a request to the AI b
**Important: This section doesn't apply to OpenAI API. SillyTavern will always use a matching tokenizer for OpenAI models.**
SillyTavern cannot use a proper tokenizer provided by the model running on a remote instance of KoboldAI or Oobabooga's TextGen, so all token counts assumed during prompt generation are estimated based on the selected [tokenizer](#Tokenizer) type.
SillyTavern cannot use a proper tokenizer provided by the model running on a remote instance of KoboldAI or Oobabooga's TextGen, so all token counts assumed during prompt generation are estimated based on the selected [tokenizer](#tokenizer) type.
Since the results of tokenization can be inaccurate on context sizes close to the model-defined maximum, some parts of the prompt may be trimmed or dropped, which may negatively affect the coherence of character definitions.
@@ -490,24 +506,24 @@ Overrides the default separators controlled by "Disable example chats formatting
#### Disable description formatting
`**NAME's Persona:** `won't be prepended to the content of your character's Description box.
`**NAME's Persona:**`won't be prepended to the content of your character's Description box.
#### Disable scenario formatting
`**Scenario:** `won't be prepended to the content of your character's Scenario box.
`**Scenario:**`won't be prepended to the content of your character's Scenario box.
#### Disable personality formatting
`**Personality:** `won't be prepended to the content of your character's Personality box.
`**Personality:**`won't be prepended to the content of your character's Personality box.
#### Disable example chats formatting
`<START>` won't be added at the beginning of each example message block.
`<START>` won't be added at the beginning of each example message block.
_(If custom separator is not set)_
#### Disable chat start formatting
`<START>` won't be added between the character card and the chat log.
`<START>` won't be added between the character card and the chat log.
_(If custom separator is not set)_
#### Always add character's name to prompt
@@ -522,20 +538,20 @@ Has no effect.
#### Disable scenario formatting
`**Circumstances and context of the dialogue:** `won't be prepended to the content of your character's Scenario box.
`**Circumstances and context of the dialogue:**`won't be prepended to the content of your character's Scenario box.
#### Disable personality formatting
`**NAME's personality:** `won't be prepended to the content of your character's Personality box.
`**NAME's personality:**`won't be prepended to the content of your character's Personality box.
#### Disable example chats formatting
`This is how **Character** should talk` won't be added at the beginning of each example message block.
`This is how **Character** should talk` won't be added at the beginning of each example message block.
_(If custom separator is not set)_
#### Disable chat start formatting
`Then the roleplay chat between **User** and **Character** begins` won't be added between the character card and the chat log.
`Then the roleplay chat between **User** and **Character** begins` won't be added between the character card and the chat log.
_(If custom separator is not set)_
#### Always add character's name to prompt
@@ -544,7 +560,7 @@ Appends character's name to the prompt to force the model to complete the messag
```
** OTHER CONTEXT HERE **
Character:
Character:
```
## Group Chats
@@ -577,31 +593,31 @@ Characters are drafted based on the order they are presented in group members li
## Multigen
*This feature provides a pseudo-streaming functionality which conflicts with token streaming. When Multigen is enabled and generation API supports streaming, only Multigen streaming will be used.*
_This feature provides a pseudo-streaming functionality which conflicts with token streaming. When Multigen is enabled and generation API supports streaming, only Multigen streaming will be used._
SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches.
### Default settings:
### Default settings
First batch = 50 tokens
Next batches = 30 tokens
### Algorithm:
### Algorithm
1. Generate the first batch (if amount of generation setting is more than batch length).
2. Generate next batch of tokens until one of the stopping conditions is reached.
3. Append the generated text to the next cycle's prompt.
### Stopping conditions:
### Stopping conditions
1. Generated enough text.
2. Character starts speaking for You.
3. &lt;|endoftext|&gt; token reached.
4. No text generated.
5. Stop sequence generated. (Instruct mode only)
5. Stop sequence generated. (Instruct mode only)
## User Settings
## User Settings
### Message Sound
@@ -620,13 +636,15 @@ Enables math formulas rendering using the [showdown-katex](https://obedm503.gith
The following formatting rules are supported:
#### LaTeX syntax
```
$$ formula goes here $$
```
#### Asciimath syntax
```
$ formula goes here $
formula goes here $
```
More information: [KaTeX](https://katex.org/)
More information: [KaTeX](https://katex.org/)

File diff suppressed because it is too large Load Diff

View File

@@ -423,22 +423,25 @@ function isUrlOrAPIKey(string) {
}
function OpenNavPanels() {
//auto-open R nav if locked and previously open
if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) {
//console.log("RA -- clicking right nav to open");
$("#rightNavDrawerIcon").click();
}
//auto-open L nav if locked and previously open
if (LoadLocalBool("LNavLockOn") == true && LoadLocalBool("LNavOpened") == true) {
console.log("RA -- clicking left nav to open");
$("#leftNavDrawerIcon").click();
}
if (deviceInfo.device.type === 'desktop') {
//auto-open R nav if locked and previously open
if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) {
//console.log("RA -- clicking right nav to open");
$("#rightNavDrawerIcon").click();
}
//auto-open WI if locked and previously open
if (LoadLocalBool("WINavLockOn") == true && LoadLocalBool("WINavOpened") == true) {
console.log("RA -- clicking WI to open");
$("#WIDrawerIcon").click();
//auto-open L nav if locked and previously open
if (LoadLocalBool("LNavLockOn") == true && LoadLocalBool("LNavOpened") == true) {
console.log("RA -- clicking left nav to open");
$("#leftNavDrawerIcon").click();
}
//auto-open WI if locked and previously open
if (LoadLocalBool("WINavLockOn") == true && LoadLocalBool("WINavOpened") == true) {
console.log("RA -- clicking WI to open");
$("#WIDrawerIcon").click();
}
}
}

View File

@@ -266,7 +266,7 @@ async function convertSoloToGroupChat() {
$(`.group_select[grid="${group.id}"]`).click();
await delay(1);
callPopup('The chat has been successfully converted!', 'text');
toastr.success('The chat has been successfully converted!');
}
$(document).ready(function () {

View File

@@ -0,0 +1,28 @@
import {
callPopup,
} from '../script.js';
function openContextTemplateEditor() {
const editor = $('#context_editor_template .context_editor').clone();
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
callPopup(editor.html(), 'text');
}
function copyTemplateParameter(event) {
const text = $(event.target).text();
navigator.clipboard.writeText(text);
const copiedMsg = document.createElement("div");
copiedMsg.classList.add('code-copied');
copiedMsg.innerText = "Copied!";
copiedMsg.style.top = `${event.clientY - 55}px`;
copiedMsg.style.left = `${event.clientX - 55}px`;
document.body.append(copiedMsg);
setTimeout(() => {
document.body.removeChild(copiedMsg);
}, 1000);
}
jQuery(() => {
$('#context_template_edit').on('click', openContextTemplateEditor);
$(document).on('pointerup', '.template_parameters_list code', copyTemplateParameter);
})

View File

@@ -195,7 +195,6 @@ async function connectToApi(baseUrl) {
modules = data.modules;
await activateExtensions();
eventSource.emit(event_types.EXTRAS_CONNECTED, modules);
$("#extensionsMenuButton").css("display", "flex");
}
updateStatus(getExtensionsResult.ok);
@@ -319,12 +318,12 @@ async function loadExtensionSettings(settings) {
}
}
async function runGenerationInterceptors() {
async function runGenerationInterceptors(chat) {
for (const manifest of Object.values(manifests)) {
const interceptorKey = manifest.generate_interceptor;
if (typeof window[interceptorKey] === 'function') {
try {
await window[interceptorKey]();
await window[interceptorKey](chat);
} catch(e) {
console.error(`Failed running interceptor for ${manifest.display_name}`, e);
}
@@ -333,7 +332,11 @@ async function runGenerationInterceptors() {
}
$(document).ready(async function () {
setTimeout(function () { addExtensionsButtonAndMenu(); }, 100)
setTimeout(function () {
addExtensionsButtonAndMenu();
$("#extensionsMenuButton").css("display", "flex");
}, 100)
$("#extensions_connect").on('click', connectClickHandler);
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
$("#extensions_details").on('click', showExtensionsDetails);

View File

@@ -15,9 +15,9 @@ async function moduleWorker() {
async function setImageIcon() {
try {
const sendButton = document.getElementById('send_picture');
sendButton.classList.add('fa-image');
sendButton.classList.remove('fa-hourglass-half');
const sendButton = $('#send_picture .extensionsMenuExtensionButton');
sendButton.addClass('fa-image');
sendButton.removeClass('fa-hourglass-half');
}
catch (error) {
console.log(error);
@@ -26,9 +26,9 @@ async function setImageIcon() {
async function setSpinnerIcon() {
try {
const sendButton = document.getElementById('send_picture');
sendButton.classList.remove('fa-image');
sendButton.classList.add('fa-hourglass-half');
const sendButton = $('#send_picture .extensionsMenuExtensionButton');
sendButton.removeClass('fa-image');
sendButton.addClass('fa-hourglass-half');
}
catch (error) {
console.log(error);
@@ -92,15 +92,17 @@ async function onSelectImage(e) {
}
}
$(document).ready(function () {
jQuery(function () {
function addSendPictureButton() {
const sendButton = document.createElement('div');
sendButton.id = 'send_picture';
sendButton.title = 'Send a picture to chat';
sendButton.classList.add('fa-solid');
const sendButton = $(`
<div id="send_picture" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
Send a picture
</div>`);
$('#extensionsMenu').prepend(sendButton);
$(sendButton).hide();
$(sendButton).on('click', () => $('#img_file').click());
$('#send_but_sheld').prepend(sendButton);
$(sendButton).on('click', () => $('#img_file').trigger('click'));
}
function addPictureSendForm() {
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;

View File

@@ -1,24 +1,3 @@
#send_picture {
order: 200;
width: 40px;
height: 40px;
margin: 0;
padding: 1px;
outline: none;
border: none;
cursor: pointer;
transition: 0.3s;
opacity: 0.7;
display: flex;
align-items: center;
justify-content: center;
}
#send_picture:hover {
opacity: 1;
filter: brightness(1.2);
}
#img_form {
display: none;
}

View File

@@ -20,8 +20,50 @@ const metadata_keys = {
position: 'note_position',
}
function setNoteCommand(_, text) {
function setNoteTextCommand(_, text) {
$('#extension_floating_prompt').val(text).trigger('input');
toastr.success("Author's Note text updated");
}
function setNoteDepthCommand(_, text) {
const value = Number(text);
if (Number.isNaN(value)) {
toastr.error('Not a valid number');
return;
}
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
toastr.success("Author's Note depth updated");
}
function setNoteIntervalCommand(_, text) {
const value = Number(text);
if (Number.isNaN(value)) {
toastr.error('Not a valid number');
return;
}
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
toastr.success("Author's Note frequency updated");
}
function setNotePositionCommand(_, text) {
const validPositions = {
'scenario': 0,
'chat': 1,
};
const position = validPositions[text?.trim()];
if (Number.isNaN(position)) {
toastr.error('Not a valid position');
return;
}
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
toastr.info("Author's Note position updated");
}
async function onExtensionFloatingPromptInput() {
@@ -185,5 +227,8 @@ async function moduleWorker() {
addExtensionsSettings();
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
registerSlashCommand('note', setNoteCommand, [], " sets an author's note for the currently selected chat", true, true);
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> sets an author's note for the currently selected chat", true, true);
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> sets an author's note depth for in-chat positioning", true, true);
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> sets an author's note insertion frequency", true, true);
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) sets an author's note position", true, true);
})();

View File

@@ -1,5 +1,5 @@
{
"display_name": "Author's Note / Character Bias",
"display_name": "Author's Note (Located in Lower Left Options Menu)",
"loading_order": 1,
"requires": [],
"optional": [],

View File

@@ -56,7 +56,31 @@ const quietPrompts = {
//prompt for only the last message
[generationMode.USER]: "[Pause your roleplay and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. 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,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]",
[generationMode.SCENARIO]: "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
[generationMode.NOW]: "[Pause your roleplay and provide a brief description of the last chat message. Focus on visual details, clothing, actions and make them all coherent to the current state of the story. Ignore the non-visible feelings and thoughts of {{char}} and {{user}} as well as any spoken dialog. Do not roleplay as {{char}} while writing this description. Do not continue the roleplay story.]",
[generationMode.NOW]: `[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.
Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').
Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.
Add keywords in this precise order:
a keyword to describe the location of the scene,
a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:
{{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),
keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',
a single keyword or phrase to describe the primary act taking place in the last chat message,
keywords to describe {{char}}'s physical appearance and facial expression,
keywords to describe {{char}}'s actions,
keywords to describe {{user}}'s physical appearance and actions.
If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.
A correctly formatted example response would be:
'(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']`,
[generationMode.RAW_LAST]: "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",
}
@@ -94,7 +118,7 @@ const defaultSettings = {
width: 512,
height: 512,
prompt_prefix: 'best quality, absurdres, masterpiece, detailed, intricate,',
prompt_prefix: 'best quality, absurdres, masterpiece,',
negative_prompt: 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry',
sampler: 'DDIM',
model: '',
@@ -106,6 +130,7 @@ const defaultSettings = {
// Horde settings
horde: false,
horde_nsfw: false,
horde_karras: true,
}
async function loadSettings() {
@@ -121,6 +146,7 @@ async function loadSettings() {
$('#sd_height').val(extension_settings.sd.height).trigger('input');
$('#sd_horde').prop('checked', extension_settings.sd.horde);
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
@@ -181,6 +207,11 @@ async function onHordeNsfwInput() {
saveSettingsDebounced();
}
async function onHordeKarrasInput() {
extension_settings.sd.horde_karras = !!$(this).prop('checked');
saveSettingsDebounced();
}
function onRestoreFacesInput() {
extension_settings.sd.restore_faces = !!$(this).prop('checked');
saveSettingsDebounced();
@@ -289,8 +320,10 @@ async function loadHordeModels() {
headers: getRequestHeaders(),
});
if (result.ok) {
const data = await result.json();
data.sort((a, b) => b.count - a.count);
const models = data.map(x => ({ value: x.name, text: `${x.name} (ETA: ${x.eta}s, Queue: ${x.queued}, Workers: ${x.count})` }));
return models;
}
@@ -351,6 +384,7 @@ function processReply(str) {
str = str.replaceAll('"', '')
str = str.replaceAll('“', '')
str = str.replaceAll('.', ',')
str = str.replaceAll('\n', ', ')
str = str.replace(/[^a-zA-Z0-9,:]+/g, ' ') // Replace everything except alphanumeric characters and commas with spaces
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
@@ -380,7 +414,7 @@ async function generatePicture(_, trigger, message, callback) {
}
if (!modules.includes('sd') && !extension_settings.sd.horde) {
callPopup("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.", 'text');
toastr.warning("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.");
return;
}
@@ -473,6 +507,7 @@ async function generateExtrasImage(prompt, callback) {
negative_prompt: extension_settings.sd.negative_prompt,
restore_faces: !!extension_settings.sd.restore_faces,
enable_hr: !!extension_settings.sd.enable_hr,
karras: !!extension_settings.sd.horde_karras,
}),
});
@@ -510,7 +545,7 @@ async function generateHordeImage(prompt, callback) {
const base64Image = `data:image/webp;base64,${data}`;
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
} else {
callPopup('Image generation has failed. Please try again.', 'text');
toastr.error('Image generation has failed. Please try again.');
}
}
@@ -537,9 +572,9 @@ async function sendMessage(prompt, image) {
function addSDGenButtons() {
const buttonHtml = `
<div id="sd_gen" class="list-group-item flex-container flexGap5">
<div id="sd_gen" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" /></div>
StableDiffusion
Stable Diffusion
</div>
`;
@@ -592,40 +627,62 @@ function addSDGenButtons() {
});
}
async function moduleWorker() {
const context = getContext();
function isConnectedToExtras() {
return modules.includes('sd');
}
if (context.onlineStatus === 'no_connection') {
$('#sd_gen').hide(200);
$('.sd_message_gen').hide();
}
else {
async function moduleWorker() {
if (isConnectedToExtras() || extension_settings.sd.horde) {
$('#sd_gen').show(200);
$('.sd_message_gen').show();
}
else {
$('#sd_gen').hide(200);
$('.sd_message_gen').hide();
}
}
addSDGenButtons();
setInterval(moduleWorker, UPDATE_INTERVAL);
function sdMessageButton(e) {
async function sdMessageButton(e) {
function setBusyIcon(isBusy) {
$icon.toggleClass('fa-paintbrush', !isBusy);
$icon.toggleClass(busyClass, isBusy);
}
const busyClass = 'fa-hourglass';
const context = getContext();
const $mes = $(e.currentTarget).closest('.mes');
const $icon = $(e.currentTarget);
const $mes = $icon.closest('.mes');
const characterName = $mes.find('.name_text').text();
const messageText = $mes.find('.mes_text').text();
const message_id = $mes.attr('mesid');
const message = context.chat[message_id];
const hasSavedImage = message?.extra?.image && message?.extra?.title;
if (hasSavedImage) {
const prompt = message?.extra?.title;
console.log('Regenerating an image, using existing prompt:', prompt);
sendGenerationRequest(prompt, saveGeneratedImage);
if ($icon.hasClass(busyClass)) {
console.log('Previous image is still being generated...');
return;
}
else {
console.log("doing /sd raw last");
generatePicture('sd', 'raw_last', `${characterName} said: ${messageText}`, saveGeneratedImage);
try {
setBusyIcon(true);
if (hasSavedImage) {
const prompt = message?.extra?.title;
console.log('Regenerating an image, using existing prompt:', prompt);
await sendGenerationRequest(prompt, saveGeneratedImage);
}
else {
console.log("doing /sd raw last");
await generatePicture('sd', 'raw_last', `${characterName} said: ${messageText}`, saveGeneratedImage);
}
}
catch (error) {
console.error('Could not generate inline image: ', error);
}
finally {
setBusyIcon(false);
}
function saveGeneratedImage(prompt, image) {
@@ -689,7 +746,7 @@ jQuery(async () => {
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands or the bottom Paintbrush buttron to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<br>
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
@@ -725,6 +782,12 @@ jQuery(async () => {
<select id="sd_model"></select>
<label for="sd_sampler">Sampling method</label>
<select id="sd_sampler"></select>
<div class="flex-container flexGap5 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
Karras (only for Horde, not all samplers supported)
</label>
</div>
<label for="sd_prompt_prefix">Generated prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="2"></textarea>
<label for="sd_negative_prompt">Negative prompt</label>
@@ -743,6 +806,7 @@ jQuery(async () => {
$('#sd_height').on('input', onHeightInput);
$('#sd_horde').on('input', onHordeInput);
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
$('#sd_horde_karras').on('input', onHordeKarrasInput);
$('#sd_restore_faces').on('input', onRestoreFacesInput);
$('#sd_enable_hr').on('input', onHighResFixInput);

View File

@@ -17,6 +17,7 @@ class ElevenLabsTtsProvider {
stability: 0.75,
similarity_boost: 0.75,
apiKey: "",
multilingual: false,
voiceMap: {}
}
@@ -28,6 +29,10 @@ class ElevenLabsTtsProvider {
<input id="elevenlabs_tts_stability" type="range" value="${this.defaultSettings.stability}" min="0" max="1" step="0.05" />
<label for="elevenlabs_tts_similarity_boost">Similarity Boost: <span id="elevenlabs_tts_similarity_boost_output"></span></label>
<input id="elevenlabs_tts_similarity_boost" type="range" value="${this.defaultSettings.similarity_boost}" min="0" max="1" step="0.05" />
<label class="checkbox_label" for="elevenlabs_tts_multilingual">
<input id="elevenlabs_tts_multilingual" type="checkbox" value="${this.defaultSettings.multilingual}" />
Enable Multilingual
</label>
`
return html
}
@@ -36,6 +41,7 @@ class ElevenLabsTtsProvider {
// Update dynamically
this.settings.stability = $('#elevenlabs_tts_stability').val()
this.settings.similarity_boost = $('#elevenlabs_tts_similarity_boost').val()
this.settings.multilingual = $('#elevenlabs_tts_multilingual').prop('checked')
}
@@ -59,6 +65,7 @@ class ElevenLabsTtsProvider {
$('#elevenlabs_tts_stability').val(this.settings.stability)
$('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost)
$('#elevenlabs_tts_api_key').val(this.settings.apiKey)
$('#tts_auto_generation').prop('checked', this.settings.multilingual)
console.info("Settings loaded")
}
@@ -165,6 +172,10 @@ class ElevenLabsTtsProvider {
}
async fetchTtsGeneration(text, voiceId) {
let model = "eleven_monolingual_v1"
if (this.settings.multilingual == true) {
model = "eleven_multilingual_v1"
}
console.info(`Generating new TTS for voice_id ${voiceId}`)
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
@@ -175,6 +186,7 @@ class ElevenLabsTtsProvider {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
text: text,
voice_settings: this.settings
})

View File

@@ -71,7 +71,6 @@ async function moduleWorker() {
processAudioJobQueue()
updateUiAudioPlayState()
// Auto generation is disabled
if (extension_settings.tts.auto_generation == false) {
return
@@ -163,6 +162,23 @@ function isTtsProcessing() {
return processing
}
function debugTtsPlayback() {
console.log(JSON.stringify(
{
"ttsProviderName": ttsProviderName,
"currentMessageNumber": currentMessageNumber,
"isWorkerBusy":isWorkerBusy,
"audioPaused": audioPaused,
"audioJobQueue": audioJobQueue,
"currentAudioJob": currentAudioJob,
"audioQueueProcessorReady": audioQueueProcessorReady,
"ttsJobQueue": ttsJobQueue,
"currentTtsJob": currentTtsJob,
}
))
}
window.debugTtsPlayback = debugTtsPlayback
//##################//
// Audio Control //
//##################//
@@ -229,7 +245,7 @@ async function onTtsVoicesClick() {
function updateUiAudioPlayState() {
if (extension_settings.tts.enabled == true) {
audioControl.style.display = 'flex'
$('#ttsExtensionMenuItem').show();
let img
// Give user feedback that TTS is active by setting the stop icon if processing or playing
if (!audioElement.paused || isTtsProcessing()) {
@@ -237,9 +253,9 @@ function updateUiAudioPlayState() {
} else {
img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'
}
audioControl.className = img
$('#tts_media_control').attr('class', img);
} else {
audioControl.style.display = 'none'
$('#ttsExtensionMenuItem').hide();
}
}
@@ -262,7 +278,7 @@ function addAudioControl() {
<div id="tts_media_control" class="extensionsMenuExtensionButton "/></div>
TTS Playback
</div>`)
$('#tts_media_control').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
$('#ttsExtensionMenuItem').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
audioControl = document.getElementById('tts_media_control')
updateUiAudioPlayState()
}

View File

@@ -30,7 +30,7 @@ class SystemTtsProvider {
}
get settingsHtml() {
if (!window.speechSynthesis) {
if (!('speechSynthesis' in window)) {
return "Your browser or operating system doesn't support speech synthesis";
}
@@ -81,7 +81,7 @@ class SystemTtsProvider {
// TTS Interfaces //
//#################//
fetchTtsVoiceIds() {
if (!window.speechSynthesis) {
if (!('speechSynthesis' in window)) {
return [];
}
@@ -92,6 +92,10 @@ class SystemTtsProvider {
}
previewTtsVoice(voiceId) {
if (!('speechSynthesis' in window)) {
throw 'Speech synthesis API is not supported';
}
const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
if (!voice) {
@@ -108,11 +112,11 @@ class SystemTtsProvider {
}
async getVoice(voiceName) {
if (!window.speechSynthesis) {
if (!('speechSynthesis' in window)) {
return { voice_id: null }
}
const voices = window.speechSynthesis.getVoices();
const voices = speechSynthesis.getVoices();
const match = voices.find(x => x.name == voiceName);
if (!match) {
@@ -123,7 +127,7 @@ class SystemTtsProvider {
}
async generateTts(text, voiceId) {
if (!window.speechSynthesis) {
if (!('speechSynthesis' in window)) {
throw 'Speech synthesis API is not supported';
}

View File

@@ -47,6 +47,7 @@ import {
select_selected_character,
cancelTtsPlay,
isMultigenEnabled,
displayPastChats,
} from "../script.js";
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
@@ -292,6 +293,12 @@ async function getGroups() {
if (group.past_metadata == undefined) {
group.past_metadata = {};
}
if (typeof group.chat_id === 'number') {
group.chat_id = String(group.chat_id);
}
if (Array.isArray(group.chats) && group.chats.some(x => typeof x === 'number')) {
group.chats = group.chats.map(x => String(x));
}
}
}
}
@@ -473,7 +480,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
activatedMembers = activateSwipe(group.members);
if (activatedMembers.length === 0) {
callPopup('<h3>Deleted group member swiped. To get a reply, add them back to the group.</h3>', 'text');
toastr.warning('Deleted group member swiped. To get a reply, add them back to the group.');
throw new Error('Deleted group member swiped');
}
}
@@ -954,7 +961,7 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_delete").off();
$("#rm_group_delete").on("click", function () {
if (is_group_generating) {
callPopup('<h3>Not so fast! Wait for the characters to stop typing before deleting the group.</h3>', 'text');
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
return;
}
@@ -1282,6 +1289,34 @@ export async function deleteGroupChat(groupId, chatId) {
}
}
export async function importGroupChat(formData) {
await jQuery.ajax({
type: "POST",
url: "/importgroupchat",
data: formData,
beforeSend: function () {
},
cache: false,
contentType: false,
processData: false,
success: async function (data) {
if (data.res) {
const chatId = data.res;
const group = groups.find(x => x.id == selected_group);
if (group) {
group.chats.push(chatId);
await editGroup(selected_group, true, true);
await displayPastChats();
}
}
},
error: function () {
$("#create_button").removeAttr("disabled");
},
});
}
export async function saveGroupBookmarkChat(groupId, name, metadata) {
const group = groups.find(x => x.id === groupId);

View File

@@ -41,7 +41,7 @@ function validateHordeModel() {
let selectedModels = models.filter(m => horde_settings.models.includes(m.name));
if (selectedModels.length === 0) {
callPopup('No Horde model selected or the selected models are no longer available. Please choose another model', 'text');
toastr.warning('No Horde model selected or the selected models are no longer available. Please choose another model');
throw new Error('No Horde model available');
}
@@ -102,7 +102,7 @@ async function generateHorde(prompt, params) {
const response = await fetch("/generate_horde", {
method: 'POST',
headers: {
...getRequestHeaders(),
...getRequestHeaders(),
"Client-Agent": CLIENT_VERSION,
},
body: JSON.stringify(payload)
@@ -129,9 +129,10 @@ async function generateHorde(prompt, params) {
setGenerationProgress(100);
const generatedText = statusCheckJson.generations[0].text;
const WorkerName = statusCheckJson.generations[0].worker_name;
const WorkerModel = statusCheckJson.generations[0].model;
console.log(generatedText);
console.log(`Generated by Horde Worker: ${WorkerName}`);
return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName}` };
console.log(`Generated by Horde Worker: ${WorkerName} [${WorkerModel}]`);
return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName} [${WorkerModel}]` };
}
else if (!queue_position_first) {
queue_position_first = statusCheckJson.queue_position;
@@ -184,6 +185,28 @@ function loadHordeSettings(settings) {
$('#horde_auto_adjust_context_length').prop("checked", horde_settings.auto_adjust_context_length);
}
async function showKudos() {
const response = await fetch('/horde_userinfo', {
method: 'POST',
headers: getRequestHeaders(),
});
if (!response.ok) {
toastr.warning('Could not load user info from Horde. Please try again later.');
return;
}
const data = await response.json();
if (data.anonymous) {
toastr.info('You are in anonymous mode. Set your personal Horde API key to see kudos.')
return;
}
console.log('Horde user data', data);
toastr.info(`${data.username}<br>Kudos: ${data.kudos}`);
}
jQuery(function () {
$("#use_horde").on("input", async function () {
horde_settings.use_horde = !!$(this).prop("checked");
@@ -224,4 +247,5 @@ jQuery(function () {
});
$("#horde_refresh").on("click", getHordeModels);
})
$("#horde_kudos").on("click", showKudos);
})

View File

@@ -18,6 +18,7 @@ import {
callPopup,
getRequestHeaders,
system_message_types,
replaceBiasMarkup,
} from "../script.js";
import { groups, selected_group } from "./group-chats.js";
@@ -102,7 +103,7 @@ const default_settings = {
openai_model: 'gpt-3.5-turbo',
jailbreak_system: false,
reverse_proxy: '',
oai_breakdown: false,
legacy_streaming: false,
};
const oai_settings = {
@@ -127,7 +128,7 @@ const oai_settings = {
openai_model: 'gpt-3.5-turbo',
jailbreak_system: false,
reverse_proxy: '',
oai_breakdown: false,
legacy_streaming: false,
};
let openai_setting_names;
@@ -147,7 +148,7 @@ function validateReverseProxy() {
new URL(oai_settings.reverse_proxy);
}
catch (err) {
callPopup('Entered reverse proxy address is not a valid URL', 'text');
toastr.error('Entered reverse proxy address is not a valid URL');
setOnlineStatus('no_connection');
resultCheckStatusOpen();
throw err;
@@ -171,13 +172,12 @@ function setOpenAIMessages(chat) {
role = 'system';
}
// for groups - prepend a character's name
if (selected_group) {
// for groups or sendas command - prepend a character's name
if (selected_group || chat[j].force_avatar) {
content = `${chat[j].name}: ${content}`;
}
// replace bias markup
content = (content ?? '').replace(/{{(\*?.*\*?)}}/g, '');
content = replaceBiasMarkup(content);
// remove caret return (waste of tokens)
content = content.replace(/\r/gm, '');
@@ -460,7 +460,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
handler_instance.log();
return [
openai_msgs_tosend,
oai_settings.oai_breakdown ? handler_instance.counts : false,
handler_instance.counts,
];
}
@@ -562,13 +562,25 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
const decoder = new TextDecoder();
const reader = response.body.getReader();
let getMessage = "";
let messageBuffer = "";
while (true) {
const { done, value } = await reader.read();
let response = decoder.decode(value);
tryParseStreamingError(response);
let eventList = response.split("\n");
let eventList = [];
// ReadableStream's buffer is not guaranteed to contain full SSE messages as they arrive in chunks
// We need to buffer chunks until we have one or more full messages (separated by double newlines)
if (!oai_settings.legacy_streaming) {
messageBuffer += response;
eventList = messageBuffer.split("\n\n");
// Last element will be an empty string or a leftover partial message
messageBuffer = eventList.pop();
} else {
eventList = response.split("\n");
}
for (let event of eventList) {
if (!event.startsWith("data"))
@@ -690,7 +702,7 @@ function countTokens(messages, full = false) {
else {
jQuery.ajax({
async: false,
type: 'POST', //
type: 'POST', //
url: `/tokenize_openai?model=${oai_settings.openai_model}`,
data: JSON.stringify([message]),
dataType: "json",
@@ -737,6 +749,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens;
oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;
oai_settings.bias_presets = settings.bias_presets ?? default_settings.bias_presets;
oai_settings.legacy_streaming = settings.legacy_streaming ?? default_settings.legacy_streaming;
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
@@ -745,7 +758,6 @@ function loadOpenAISettings(data, settings) {
if (settings.nsfw_first !== undefined) oai_settings.nsfw_first = !!settings.nsfw_first;
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
if (settings.jailbreak_system !== undefined) oai_settings.jailbreak_system = !!settings.jailbreak_system;
if (settings.oai_breakdown !== undefined) oai_settings.oai_breakdown = !!settings.oai_breakdown;
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
@@ -761,7 +773,7 @@ function loadOpenAISettings(data, settings) {
$('#wrap_in_quotes').prop('checked', oai_settings.wrap_in_quotes);
$('#nsfw_first').prop('checked', oai_settings.nsfw_first);
$('#jailbreak_system').prop('checked', oai_settings.jailbreak_system);
$('#oai_breakdown').prop('checked', oai_settings.oai_breakdown);
$('#legacy_streaming').prop('checked', oai_settings.legacy_streaming);
if (settings.main_prompt !== undefined) oai_settings.main_prompt = settings.main_prompt;
if (settings.nsfw_prompt !== undefined) oai_settings.nsfw_prompt = settings.nsfw_prompt;
@@ -810,8 +822,8 @@ async function getStatusOpen() {
};
return jQuery.ajax({
type: 'POST', //
url: '/getstatus_openai', //
type: 'POST', //
url: '/getstatus_openai', //
data: JSON.stringify(data),
beforeSend: function () {
if (oai_settings.reverse_proxy) {
@@ -881,7 +893,8 @@ async function saveOpenAIPreset(name, settings) {
jailbreak_system: settings.jailbreak_system,
impersonation_prompt: settings.impersonation_prompt,
bias_preset_selected: settings.bias_preset_selected,
oai_breakdown: settings.oai_breakdown,
reverse_proxy: settings.reverse_proxy,
legacy_streaming: settings.legacy_streaming,
};
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@@ -928,7 +941,7 @@ async function showApiKeyUsage() {
}
catch (err) {
console.error(err);
callPopup('Invalid API key', 'text');
toastr.error('Invalid API key');
}
}
@@ -993,7 +1006,7 @@ async function createNewLogitBiasPreset() {
}
if (name in oai_settings.bias_presets) {
callPopup('Preset name should be unique.', 'text');
toastr.error('Preset name should be unique.');
return;
}
@@ -1030,12 +1043,12 @@ async function onLogitBiasPresetImportFileChange(e) {
e.target.value = '';
if (name in oai_settings.bias_presets) {
callPopup('Preset name should be unique.', 'text');
toastr.error('Preset name should be unique.');
return;
}
if (!Array.isArray(importedFile)) {
callPopup('Invalid logit bias preset file.', 'text');
toastr.error('Invalid logit bias preset file.');
return;
}
@@ -1140,12 +1153,13 @@ function onSettingsPresetChange() {
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
nsfw_first: ['#nsfw_first', 'nsfw_first', true],
jailbreak_system: ['#jailbreak_system', 'jailbreak_system', true],
oai_breakdown: ['#oai_breakdown', 'oai_breakdown', true],
main_prompt: ['#main_prompt_textarea', 'main_prompt', false],
nsfw_prompt: ['#nsfw_prompt_textarea', 'nsfw_prompt', false],
jailbreak_prompt: ['#jailbreak_prompt_textarea', 'jailbreak_prompt', false],
impersonation_prompt: ['#impersonation_prompt_textarea', 'impersonation_prompt', false],
bias_preset_selected: ['#openai_logit_bias_preset', 'bias_preset_selected', false],
reverse_proxy: ['#openai_reverse_proxy', 'reverse_proxy', false],
legacy_streaming: ['#legacy_streaming', 'legacy_streaming', false],
};
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
@@ -1313,16 +1327,6 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$("#oai_breakdown").on('change', function () {
oai_settings.oai_breakdown = !!$(this).prop("checked");
if (!oai_settings.oai_breakdown) {
$("#token_breakdown").css('display', 'none');
} else {
$("#token_breakdown").css('display', 'flex');
}
saveSettingsDebounced();
});
// auto-select a preset based on character/group name
$(document).on("click", ".character_select", function () {
const chid = $(this).attr('chid');
@@ -1349,7 +1353,7 @@ $(document).ready(function () {
$("#update_oai_preset").on('click', async function () {
const name = oai_settings.preset_settings_openai;
await saveOpenAIPreset(name, oai_settings);
callPopup('Preset updated', 'text');
toastr.success('Preset updated');
});
$("#main_prompt_restore").on('click', function () {
@@ -1376,6 +1380,11 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$('#legacy_streaming').on('input', function () {
oai_settings.legacy_streaming = !!$(this).prop('checked');
saveSettingsDebounced();
});
$("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange);

View File

@@ -134,6 +134,7 @@ let power_user = {
input_sequence: '### Instruction:',
output_sequence: '### Response:',
preset: 'Alpaca',
separator_sequence: '',
}
};
@@ -584,6 +585,7 @@ function loadInstructMode() {
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
@@ -638,11 +640,14 @@ function loadInstructMode() {
});
}
export function formatInstructModeChat(name, mes, isUser, isNarrator) {
const includeNames = isNarrator ? false : power_user.instruct.names || !!selected_group;
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar) {
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
const sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
const separator = power_user.instruct.wrap ? '\n' : '';
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separator] : [sequence, mes, separator];
const separator = power_user.instruct.wrap ? '\n' : '';
const separatorSequence = power_user.instruct.separator_sequence && !isUser
? power_user.instruct.separator_sequence
: (power_user.instruct.wrap ? '\n' : '');
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separatorSequence] : [sequence, mes, separatorSequence];
const text = textArray.filter(x => x).join(separator);
return text;
}
@@ -656,12 +661,17 @@ export function formatInstructStoryString(story) {
return text;
}
export function formatInstructModePrompt(name, isImpersonate) {
export function formatInstructModePrompt(name, isImpersonate, promptBias) {
const includeNames = power_user.instruct.names || !!selected_group;
const sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
const separator = power_user.instruct.wrap ? '\n' : '';
const text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
return text;
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
if (!isImpersonate && promptBias) {
text += (includeNames ? promptBias : (separator + promptBias));
}
return text.trimEnd();
}
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);

View File

@@ -1,7 +1,12 @@
import {
addOneMessage,
characters,
chat,
chat_metadata,
default_avatar,
extractMessageBias,
getThumbnailUrl,
replaceBiasMarkup,
saveChatConditional,
sendSystemMessage,
system_avatar,
@@ -22,6 +27,11 @@ class SlashCommandParser {
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
const fnObj = { callback, helpString, interruptsGeneration, purgeFromMessage };
if ([command, ...aliases].some(x => this.commands.hasOwnProperty(x))) {
console.trace('WARN: Duplicate slash command registered!');
}
this.commands[command] = fnObj;
if (Array.isArray(aliases)) {
@@ -80,15 +90,67 @@ const registerSlashCommand = parser.addCommand.bind(parser);
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
parser.addCommand('help', helpCommandCallback, ['?'], ' displays this help message', true, true);
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> sets a background according to filename, partial names allowed, will set the first one alphebetically if multiple files begin with the provided argument string', false, true);
parser.addCommand('sys', sendNarratorMessage, [], ' sends message as a system narrator', false, true);
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> sets a background according to filename, partial names allowed, will set the first one alphabetically if multiple files begin with the provided argument string', false, true);
parser.addCommand('sendas', sendMessageAs, [], ` sends message as a specific character.<br>Example:<br><pre><code>/sendas Chloe\nHello, guys!</code></pre>will send "Hello, guys!" from "Chloe".<br>Uses character avatar if it exists in the characters list.`, true, true);
parser.addCommand('sys', sendNarratorMessage, [], '<span class="monospace">(text)</span> sends message as a system narrator', false, true);
parser.addCommand('sysname', setNarratorName, [], '<span class="monospace">(name)</span> sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true);
const NARRATOR_NAME_KEY = 'narrator_name';
const NARRATOR_NAME_DEFAULT = 'System';
function setNarratorName(_, text) {
chat_metadata[NARRATOR_NAME_KEY] = text || NARRATOR_NAME_DEFAULT;
const name = text || NARRATOR_NAME_DEFAULT;
chat_metadata[NARRATOR_NAME_KEY] = name;
toastr.info(`System narrator name set to ${name}`);
saveChatConditional();
}
function sendMessageAs(_, text) {
if (!text) {
return;
}
const parts = text.split('\n');
if (parts.length <= 1) {
toastr.warning('Both character name and message are required. Separate them with a new line.');
return;
}
const name = parts.shift().trim();
const mesText = parts.join('\n').trim();
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(mesText);
const isSystem = replaceBiasMarkup(mesText).trim().length === 0;
const character = characters.find(x => x.name === name);
let force_avatar, original_avatar;
if (character && character.avatar !== 'none') {
force_avatar = getThumbnailUrl('avatar', character.avatar);
original_avatar = character.avatar;
}
else {
force_avatar = default_avatar;
original_avatar = default_avatar;
}
const message = {
name: name,
is_user: false,
is_name: true,
is_system: isSystem,
send_date: humanizedDateTime(),
mes: mesText,
force_avatar: force_avatar,
original_avatar: original_avatar,
extra: {
bias: bias.trim().length ? bias : null,
}
};
chat.push(message);
addOneMessage(message);
saveChatConditional();
}
@@ -98,21 +160,27 @@ function sendNarratorMessage(_, text) {
}
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(text);
const isSystem = replaceBiasMarkup(text).trim().length === 0;
const message = {
name: name,
is_user: false,
is_name: false,
is_system: false,
is_system: isSystem,
send_date: humanizedDateTime(),
mes: text.trim(),
force_avatar: system_avatar,
extra: {
type: system_message_types.NARRATOR,
bias: bias.trim().length ? bias : null,
},
};
chat.push(message);
addOneMessage(message);
saveChatConditional();
}
function helpCommandCallback() {

File diff suppressed because one or more lines are too long

7
public/scripts/toastr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -216,3 +216,19 @@ export function end_trim_to_sentence(input, include_newline = false) {
return input.substring(0, last + 1).trimEnd();
}
export function countOccurrences(string, character) {
let count = 0;
for (let i = 0; i < string.length; i++) {
if (string[i] === character) {
count++;
}
}
return count;
}
export function isOdd(number) {
return number % 2 !== 0;
}

View File

@@ -7,6 +7,7 @@ export {
world_info_budget,
world_info_depth,
world_info_recursive,
world_info_case_sensitive,
world_names,
imported_world_name,
checkWorldInfo,
@@ -23,6 +24,7 @@ let world_info_depth = 2;
let world_info_budget = 128;
let is_world_edit_open = false;
let world_info_recursive = false;
let world_info_case_sensitive = false;
let imported_world_name = "";
const saveWorldDebounced = debounce(async () => await _save(), 500);
const saveSettingsDebounced = debounce(() => saveSettings(), 500);
@@ -51,6 +53,8 @@ function setWorldInfoSettings(settings, data) {
world_info_budget = Number(settings.world_info_budget);
if (settings.world_info_recursive !== undefined)
world_info_recursive = Boolean(settings.world_info_recursive);
if (settings.world_info_case_sensitive !== undefined)
world_info_case_sensitive = Boolean(settings.world_info_case_sensitive);
$("#world_info_depth_counter").text(world_info_depth);
$("#world_info_depth").val(world_info_depth);
@@ -59,6 +63,7 @@ function setWorldInfoSettings(settings, data) {
$("#world_info_budget").val(world_info_budget);
$("#world_info_recursive").prop('checked', world_info_recursive);
$("#world_info_case_sensitive").prop('checked', world_info_case_sensitive);
world_names = data.world_names?.length ? data.world_names : [];
@@ -80,7 +85,7 @@ function setWorldInfoSettings(settings, data) {
// World Info Editor
async function showWorldEditor() {
if (!world_info) {
callPopup("<h3>Select a world info first!</h3>", "text");
toastr.warning("Select a world info first!");
return;
}
@@ -476,13 +481,18 @@ async function createNewWorldInfo() {
}
}
// Gets a string that respects the case sensitivity setting
function transformString(str) {
return world_info_case_sensitive ? str : str.toLowerCase();
}
function checkWorldInfo(chat) {
if (world_info_data.entries.length == 0) {
return "";
}
const messagesToLookBack = world_info_depth * 2;
let textToScan = chat.slice(0, messagesToLookBack).join("").toLowerCase();
let textToScan = transformString(chat.slice(0, messagesToLookBack).join(""));
let worldInfoBefore = "";
let worldInfoAfter = "";
let needsToScan = true;
@@ -506,16 +516,18 @@ function checkWorldInfo(chat) {
if (Array.isArray(entry.key) && entry.key.length) {
primary: for (let key of entry.key) {
if (key && textToScan.includes(key.trim().toLowerCase())) {
const substituted = substituteParams(key);
if (substituted && textToScan.includes(transformString(substituted.trim()))) {
if (
entry.selective &&
Array.isArray(entry.keysecondary) &&
entry.keysecondary.length
) {
secondary: for (let keysecondary of entry.keysecondary) {
const secondarySubstituted = substituteParams(keysecondary);
if (
keysecondary &&
textToScan.includes(keysecondary.trim().toLowerCase())
secondarySubstituted &&
textToScan.includes(transformString(secondarySubstituted.trim()))
) {
activatedNow.add(entry.uid);
break secondary;
@@ -555,11 +567,7 @@ function checkWorldInfo(chat) {
}
if (needsToScan) {
textToScan =
newEntries
.map((x) => x.content)
.join("\n")
.toLowerCase() + textToScan;
textToScan = (transformString(newEntries.map(x => x.content).join('\n')) + textToScan);
}
allActivatedEntries = new Set([...allActivatedEntries, ...activatedNow]);
@@ -581,7 +589,7 @@ function selectImportedWorldInfo() {
imported_world_name = "";
}
$(document).ready(() => {
jQuery(() => {
$("#world_info").change(async function () {
const selectedWorld = $("#world_info").find(":selected").val();
world_info = null;
@@ -686,4 +694,9 @@ $(document).ready(() => {
world_info_recursive = !!$(this).prop('checked');
saveSettingsDebounced();
})
});
$('#world_info_case_sensitive').on('input', function () {
world_info_case_sensitive = !!$(this).prop('checked');
saveSettingsDebounced();
})
});

View File

@@ -103,10 +103,12 @@ body {
::-webkit-scrollbar-thumb {
background-color: var(--grey7070a);
border: 2px solid transparent;
box-shadow: inset 0 0 0 1px var(--black50a);
border-radius: 10px;
background-clip: content-box;
border: 2px solid transparent;
border-top: 20px solid transparent;
min-height: 40px;
}
table.responsiveTable {
@@ -132,6 +134,33 @@ table.responsiveTable {
border-top: 2px solid grey;
}
.animated {
-webkit-animation-duration: 3s !important;
animation-duration: 3s !important;
-webkit-animation-fill-mode: both !important;
animation-fill-mode: both !important;
box-shadow: inset 0 0 5px var(--SmartThemeQuoteColor);
}
@keyframes flash {
20%,
60%,
100% {
opacity: 1;
}
0%,
40%,
80% {
opacity: 0.2;
}
}
.flash {
animation-name: flash;
}
.tokenItemizingSubclass {
font-size: calc(var(--mainFontSize) * 0.8);
color: var(--SmartThemeEmColor);
@@ -219,12 +248,12 @@ body.tts .mes_narrate {
code {
font-family: Consolas, monospace;
white-space: pre-wrap;
word-wrap: break-word;
/* word-wrap: break-word; */
border: 1px solid var(--white30a);
border-radius: 5px;
background-color: var(--black70a);
padding: 0 3px;
max-width: calc(100svw - 95px);
/* max-width: calc(100svw - 95px); */
line-height: var(--mainFontSize);
}
@@ -398,19 +427,6 @@ code {
justify-content: center;
}
#token_breakdown div {
display: flex;
width: 100%;
justify-content: center;
}
.token_breakdown_segment {
min-width: 40px !important;
border: solid 2px;
border-radius: 5px;
}
#loading_mes {
display: none;
width: 40px;
@@ -500,8 +516,6 @@ code {
display: flex;
align-items: center;
justify-content: center;
display: none;
}
#extensionsMenuButton:hover {
@@ -540,6 +554,16 @@ code {
cursor: pointer;
}
#extensionsMenu>div {
transition: 0.3s;
opacity: 0.7;
}
#extensionsMenu>div:hover {
filter: brightness(1.2);
opacity: 1;
}
.options-content a div:first-child {
min-width: 20px;
}
@@ -564,12 +588,13 @@ code {
}
.mes {
display: grid;
grid-template-columns: min-content min-content auto min-content;
display: flex;
align-items: flex-start;
padding: 20px 10px 0 10px;
margin-top: 0;
width: 100%;
color: var(--SmartThemeBodyColor);
position: relative;
}
.mes q:before,
@@ -579,9 +604,6 @@ code {
}
.last_mes {
grid-template-columns: [checkbox] fit-content(60px) [avatar-leftswipe] 50px [name-mestext] auto [edit-rightswipe] 30px !important;
grid-template-rows: [avatar-NameMesText-edit] 50px [swipes] auto;
grid-row-gap: 30px;
margin-bottom: 0 !important;
/*only affects bubblechat to make it sit nicely at the bottom*/
}
@@ -593,7 +615,6 @@ code {
height: 40px;
width: 40px;
opacity: 0.3;
right: 5px;
align-items: center;
justify-content: center;
z-index: 9999;
@@ -602,12 +623,9 @@ code {
flex-flow: column;
font-size: 30px;
cursor: pointer;
}
.swipe_right img,
.swipe_left img {
height: 30px;
width: 30px;
align-self: center;
position: absolute;
bottom: 15px;
}
.swipes-counter {
@@ -620,11 +638,12 @@ code {
}
.swipe_left {
left: 15px;
right: auto;
grid-column-start: 2;
align-items: flex-end;
padding-bottom: 17px;
left: 20px;
}
.swipe_right {
right: 5px;
}
.ui-settings {
@@ -660,9 +679,12 @@ code {
border-style: none;
}
.last_mes .mesAvatarWrapper {
padding-bottom: 50px;
}
.mes .avatar {
cursor: pointer;
}
.hotswapAvatar,
@@ -754,11 +776,6 @@ body.big-avatars #user_avatar_block .avatar img {
width: 60px;
}
body.big-avatars .last_mes {
grid-template-rows: [avatar-NameMesText-edit] 80px [swipes] auto;
grid-template-columns: [checkbox] fit-content(60px) [avatar-leftswipe] fit-content(60px) [name-mestext] auto [edit-rightswipe] 30px !important;
}
body.big-avatars .avatar img {
width: 100%;
height: 100%;
@@ -771,9 +788,7 @@ body.big-avatars .avatar img {
.mes_block {
padding-top: 0;
padding-left: 10px;
grid-row-start: 1;
grid-row-end: 3;
grid-column-start: 3;
width: 100%;
}
@@ -893,6 +908,26 @@ select {
}
.chat_injections_list:empty {
width: 100%;
height: 100%;
}
.chat_injections_list:empty::before {
display: flex;
align-items: center;
justify-content: center;
content: "No injections";
font-weight: bolder;
width: 100%;
height: 100%;
opacity: 0.8;
min-height: 3rem;
}
.template_parameters_list code {
cursor: pointer;
}
h3 {
margin: 10px 0;
@@ -1227,6 +1262,14 @@ input[type=search]:focus::-webkit-search-cancel-button {
font-weight: bolder;
}
.justifySpaceBetween {
justify-content: space-between;
}
.mes_block .ch_name {
max-width: 100%;
}
/*applies to both groups and solos chars in the char list*/
#rm_print_characters_block .ch_name {
width: 100%;
@@ -1304,13 +1347,28 @@ input[type=search]:focus::-webkit-search-cancel-button {
margin: 5px;
cursor: pointer;
aspect-ratio: 16/9;
justify-content: flex-end;
}
.BGSampleTitle {
display: flex;
width: 100%;
height: min-content;
text-align: center;
justify-content: center;
align-self: flex-end;
bottom: 0;
position: relative;
word-break: break-word;
background-color: var(--black50a);
font-size: calc(var(--fontScale) * 0.9em);
}
.bg_example_cross {
width: 15px;
height: 15px;
position: relative;
float: right;
/* float: right; */
right: 10px;
top: 5px;
cursor: pointer;
@@ -1808,10 +1866,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
gap: 5px;
}
#form_rename_world {
/* margin-right: 50px; */
}
#form_rename_chat {
flex: 1;
}
@@ -2222,11 +2276,21 @@ input[type="range"]::-webkit-slider-thumb {
}
.mes_buttons {
float: right;
height: 20px;
grid-row-start: 1;
position: relative;
right: 0px;
display: flex;
gap: 10px;
flex-wrap: nowrap;
justify-content: flex-end;
}
.last_mes .mes_buttons {
right: -30px;
}
.last_mes .mes_block {
margin-right: 30px;
}
.mes_prompt,
@@ -2236,8 +2300,6 @@ input[type="range"]::-webkit-slider-thumb {
.mes_edit {
cursor: pointer;
transition: 0.3s ease-in-out;
height: 20px;
width: 20px;
filter: drop-shadow(0px 0px 2px black);
opacity: 0.2;
}
@@ -2250,24 +2312,6 @@ input[type="range"]::-webkit-slider-thumb {
opacity: 1;
}
.last_mes .sd_message_gen,
.last_mes .mes_copy,
.last_mes .mes_narrate,
.last_mes .mes_prompt {
grid-row-start: 1;
position: relative;
right: -30px;
}
.last_mes .mes_edit,
.last_mes .mes_edit_buttons,
.last_mes .mes_stop {
grid-row-start: 1;
position: relative;
right: -30px;
}
.mes_edit_buttons {
display: none;
flex-direction: row;
@@ -3565,7 +3609,7 @@ label[for="extensions_autoconnect"] {
.code-copied {
position: absolute;
z-index: 99;
z-index: 10000;
font-size: var(--mainFontSize);
color: var(--SmartThemeBodyColor);
background-color: var(--SmartThemeFastUIBGColor);
@@ -3623,7 +3667,6 @@ label[for="extensions_autoconnect"] {
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
z-index: 9999 !important;
border: 1px solid var(--white30a);
}
.fillRight,
@@ -3708,6 +3751,10 @@ toolcool-color-picker {
flex: 1;
}
.flex2 {
flex: 2;
}
.flexFlowColumn {
flex-flow: column;
}
@@ -3995,16 +4042,6 @@ body.movingUI #floatingPrompt {
resize: both;
}
body.movingUI #chat::-webkit-scrollbar-thumb {
background-color: var(--grey7070a);
border: 2px solid transparent;
border-top: 20px solid transparent;
box-shadow: inset 0 0 0 1px var(--black50a);
border-radius: 10px;
background-clip: content-box;
}
#expression-image.default,
#expression-holder:has(.default) {
height: 120px;