Compare commits

...

49 Commits

Author SHA1 Message Date
LenAnderson 9fc1f9feab use blur tint color instead of chat tint color and use blur setting 2024-04-05 12:13:22 -04:00
LenAnderson 29d6ee45de improve autocomplete show/hide logic and editor selection 2024-04-05 11:45:50 -04:00
LenAnderson 6440fd3840 add theming to autocomplete (theme, dark, light) 2024-04-05 11:20:55 -04:00
LenAnderson 2ec8640870 Merge branch 'staging' into parser-v2 2024-04-05 10:43:25 -04:00
LenAnderson 6936485d94 autocomplete styling 2024-04-05 10:42:08 -04:00
LenAnderson 67eca6a50b autocomplete positioning 2024-04-05 10:41:45 -04:00
LenAnderson 35f2770d6e when to show autocomplete vs info only 2024-04-05 10:41:16 -04:00
Cohee acb623c6d8 Adjust automation id layout 2024-04-05 17:27:08 +03:00
Cohee b1c2617b0d Only init scroll height of WI keys when first opening the drawer 2024-04-05 13:53:39 +03:00
Cohee 3a0ceae80a Optimize scroll height resets on WI entry render, remove silly logs 2024-04-05 13:45:28 +03:00
Cohee 8f6e41428f Optimize tags template references 2024-04-05 12:43:43 +03:00
Cohee d27efb21d6
Merge pull request #2014 from Wolfsblvt/fix-multi-char-import
Fix multi char import on import button click not importing tags
2024-04-05 02:00:29 +03:00
Wolfsblvt 2e9c96d1c9 Fix multi char import on button
- Fixes #1983
- importCharacter has to be async await to await user input on tag creation
2024-04-05 00:53:32 +02:00
Cohee 7221549c65 #2013 Fix smooth stream event processing. 2024-04-05 01:25:48 +03:00
Cohee 144d115d6a Fix position of dynamic pop-outs control bar 2024-04-05 01:05:50 +03:00
Cohee b948e31a89 Remove tag debug logs if state unchanged 2024-04-05 00:54:17 +03:00
Cohee 0804843805 Add per-character and per-group overrides for external media 2024-04-05 00:39:54 +03:00
Cohee 6cc73c2a0b Add instruct last system sequence 2024-04-04 22:27:08 +03:00
Cohee 274abb4749
Merge pull request #2010 from aisu-wata0/wi_min_activations_perf_fix
performance: World Info min activations skips seen buffer
2024-04-04 21:35:36 +03:00
Cohee ee3718ad7a Forward error messages from Cohere streams 2024-04-04 21:20:30 +03:00
Cohee 813476d72a Fix stream error parsing when using Smooth Streaming 2024-04-04 21:20:10 +03:00
Aisu Wata 0d57f7ea4f fix: removed `recurseReset()` 2024-04-04 15:19:39 -03:00
Cohee ecc638a76d
Merge pull request #2011 from aisu-wata0/wi_remnant_debug_logs
removed some remnant debug logs
2024-04-04 20:41:09 +03:00
Cohee 42138ca09b Add command-r-plus 2024-04-04 20:38:34 +03:00
Aisu Wata 5ab9d9b863 removed some remnant debug logs 2024-04-04 03:08:17 -03:00
Aisu Wata 95c910a521 fix: WI min activations skips seen buffer 2024-04-04 02:56:39 -03:00
Cohee cf6705baff Fix response length override 2024-04-03 02:33:01 +03:00
Cohee 4d01000751 Switch default summary prompt builder to classic 2024-04-03 02:13:09 +03:00
Cohee d8fa692774 Unify API connect UI positioning 2024-04-03 01:45:38 +03:00
Cohee 7e8d4a5a75
Merge pull request #2005 from Wolfsblvt/improve-server-version-logging
Improve server version logging info
2024-04-03 01:00:48 +03:00
Cohee f71ec73d56 Fix tpyo + add clarity + lint 2024-04-03 01:00:20 +03:00
Cohee 27698fd024 Add ability to get model name with /model 2024-04-03 00:52:30 +03:00
Cohee c0bb90b649 Return instruct and context names when no name provided for slash command 2024-04-03 00:36:40 +03:00
Cohee d6b700483f Allow hyphens in SD prompts 2024-04-03 00:29:34 +03:00
Cohee 8a0997c47b Allow auto-continue in group chats 2024-04-03 00:27:11 +03:00
Wolfsblvt 3ccb63dd21 Server logging utilize tracking branch
- Use tracking branch instead of hardcoded "origin"
- Remove dev logging message if not on "staging" or "release"
2024-04-02 22:51:43 +02:00
Cohee 9221ddde57 +OpenRouter captioning models 2024-04-02 23:17:51 +03:00
Wolfsblvt 514c40228c Improve server version logging info
- Capture commit date and print that next to the branch
- Info for being on a dev branch
- Info for not being on the latest commit (fetch should've gotten it, if update script was run)
2024-04-02 22:17:21 +02:00
Cohee f9e74ea9bf
Merge pull request #2004 from SillyTavern/smooth-streaming
Smooth streaming
2024-04-02 23:01:05 +03:00
Cohee 54a6f4bc62 Add speed control 2024-04-02 22:52:51 +03:00
Cohee f13e718dc7 Compatibility with extensions 2024-04-02 20:25:37 +03:00
Cohee 534612db87 Merge branch 'staging' into smooth-streaming 2024-04-02 19:40:16 +03:00
Cohee 422b9e1b63 Fix sequences to stop strings if missing values 2024-04-02 18:34:29 +03:00
Cohee 759e8eed0c Fix for Together 2024-04-02 16:38:39 +03:00
Cohee 2859ae54ab Don't delay when not in focus 2024-04-02 16:21:55 +03:00
Cohee ca047034b7 Fix smooth stream for MakerSuite 2024-04-02 16:13:01 +03:00
Cohee 7389286862 Don't show logprobs when using smooth streaming 2024-04-02 15:51:00 +03:00
Cohee 8176e09d4a Refactor event parsing 2024-04-02 15:25:23 +03:00
Cohee 51b3b8bfaa Add smooth streaming 2024-04-02 14:56:15 +03:00
49 changed files with 911 additions and 259 deletions

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Adventure"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "\n\n",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Alpaca-Roleplay"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Alpaca-Single-Turn"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "\n\n",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Alpaca"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "<|im_end|>\n",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "ChatML"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "DreamGen Role-Play V1"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Koala"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Libra-32B"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Lightning 1.1"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Llama 2 Chat"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Metharme"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Mistral"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "OpenOrca-OpenChat"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Pygmalion"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Story"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "\n",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "Synthia"
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Vicuna 1.0"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "Vicuna 1.1"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": true,
"last_system_sequence": "",
"name": "WizardLM-13B"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "WizardLM"
}
}

View File

@ -19,5 +19,6 @@
"system_suffix": "",
"user_alignment_message": "",
"system_same_as_user": false,
"last_system_sequence": "",
"name": "simple-proxy-for-tavern"
}
}

View File

@ -439,3 +439,11 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
#openai_image_inlining:checked~#image_inlining_hint {
display: block;
}
#smooth_streaming:not(:checked)~#smooth_streaming_speed_control {
display: none;
}
#smooth_streaming:checked~#smooth_streaming_speed_control {
display: block;
}

View File

@ -1953,10 +1953,6 @@
<div data-for="api_key_novel" 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>
<div class="flex-container">
<div id="api_button_novel" class="api_button menu_button" type="submit" data-i18n="Connect">Connect</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
</div>
<h4><span data-i18n="Novel AI Model">Novel AI Model</span>
<a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
@ -1966,6 +1962,10 @@
<option value="clio-v1">Clio</option>
<option value="kayra-v1">Kayra</option>
</select>
<div class="flex-container">
<div id="api_button_novel" class="api_button menu_button" type="submit" data-i18n="Connect">Connect</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
</div>
</form>
<div class="online_status">
<div class="online_status_indicator"></div>
@ -2489,6 +2489,20 @@
</div>
</form>
<form id="openrouter_form" data-source="openrouter" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<h4 data-i18n="OpenRouter API Key">OpenRouter API Key</h4>
<div>
<small data-i18n="Click Authorize below or get the key from">
Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
<br>
<a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a>
</div>
<div class="flex-container">
<input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openrouter"></div>
</div>
<div data-for="api_key_openrouter" class="neutral_warning">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div>
<h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
<select id="model_openrouter_select">
@ -2552,20 +2566,6 @@
</span>
</div>
</div>
<h4 data-i18n="OpenRouter API Key">OpenRouter API Key</h4>
<div>
<small data-i18n="Click Authorize below or get the key from">
Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
<br>
<a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a>
</div>
<div class="flex-container">
<input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openrouter"></div>
</div>
<div data-for="api_key_openrouter" class="neutral_warning">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
</form>
<form id="scale_form" data-source="scale" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<div id="normal_scale_form">
@ -2691,6 +2691,7 @@
<option value="command-light">command-light</option>
<option value="command">command</option>
<option value="command-r">command-r</option>
<option value="command-r-plus">command-r-plus</option>
</optgroup>
<optgroup label="Nightly">
<option value="command-light-nightly">command-light-nightly</option>
@ -3033,12 +3034,12 @@
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Will be inserted at the start of the chat history if it doesn't start with a User message.">
<label for="instruct_user_alignment_message">
<small data-i18n="User Filler Message">User Filler Message</small>
<div class="flex1" title="Will be inserted as a last prompt line when using system/neutral generation.">
<label for="instruct_last_system_sequence">
<small data-i18n="System Instruction Prefix">System Instruction Prefix</small>
</label>
<div>
<textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_last_system_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1" title="If a stop sequence is generated, everything past it will be removed from the output (inclusive).">
@ -3050,6 +3051,16 @@
</div>
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Will be inserted at the start of the chat history if it doesn't start with a User message.">
<label for="instruct_user_alignment_message">
<small data-i18n="User Filler Message">User Filler Message</small>
</label>
<div>
<textarea id="instruct_user_alignment_message" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
@ -3618,6 +3629,24 @@
</div>
</div>
</div>
<label class="checkbox_label flexWrap" for="smooth_streaming">
<input id="smooth_streaming" type="checkbox" />
<div class="flex-container alignItemsBaseline">
<span data-i18n="Smooth Streaming">
Smooth Streaming
</span>
<i class="fa-solid fa-flask" title="Experimental feature. May not work for all backends."></i>
</div>
<div id="smooth_streaming_speed_control" class="flexBasis100p wide100p">
<small class="flex justifyCenter" data-i18n="Speed">Speed</small>
<input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
<div class="slider_hint">
<span data-i18n="Slow">Slow</span>
<span data-i18n=""></span>
<span data-i18n="Slow">Fast</span>
</div>
</div>
</label>
</div>
</div>
</div>
@ -4247,12 +4276,21 @@
</div>
<div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
<hr>
<div id="description_div" class="flex-container alignitemscenter">
<span data-i18n="Character Description">Description</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i>
<a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
<div id="description_div" class="title_restorable">
<div class="flex-container alignitemscenter">
<span data-i18n="Character Description">Description</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i>
<a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</div>
<div id="character_open_media_overrides" class="menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this character." data-i18n="[title]Click to allow/forbid the use of external media for this character.">
<i id="character_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
<i id="character_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
<span data-i18n="Ext. Media">
Ext. Media
</span>
</div>
</div>
<textarea id="description_textarea" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea>
<div class="extension_token_counter">
@ -4352,6 +4390,10 @@
<div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-scroll" title="Set a group chat scenario" data-i18n="[title]Set a group chat scenario"></div>
<div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
<input id="rm_group_fav" type="hidden" />
<div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
<i id="group_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
<i id="group_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
</div>
<div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-check" title="Create" data-i18n="[title]Create"></div>
<div id="rm_group_restore_avatar" class="heightFitContent margin0 menu_button fa-solid fa-images" title="Restore collage avatar" data-i18n="[title]Restore collage avatar"></div>
<div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-trash-can" title="Delete" data-i18n="[title]Delete"></div>
@ -4804,7 +4846,7 @@
<div class="flex-container alignitemscenter wide100p">
<div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
<div class="flex-container flex1">
<textarea class="text_pole autoSetHeight" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
<textarea class="text_pole" rows="1" name="comment" maxlength="5000" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
</div>
<!-- <span class="world_entry_form_position_value"></span> -->
<select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal❌ Disabled" title="WI Entry Status:&#13;🔵 Constant&#13;🟢 Normal&#13;❌ Disabled" name="entryStateSelector" class="text_pole widthNatural margin0">
@ -5359,6 +5401,32 @@
<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>
</div>
</div>
<div id="forbid_media_override_template" class="template_element">
<div class="forbid_media_override flex-container flexFlowColumn">
<h4 data-i18n="Forbid Media Override explanation" class="margin0">
Ability of the current character/group to use external media in chats.
</h4>
<small data-i18n="Forbid Media Override subtitle" class="marginBot5">
Media: images, videos, audio. External: not hosted on the local server.
</small>
<label class="checkbox_label" for="forbid_media_override_global">
<input type="radio" id="forbid_media_override_global" name="forbid_media_override" />
<span>
<span data-i18n="Use global setting">Use global setting</span>
<b class="forbid_media_global_state_forbidden">(forbidden)</b>
<b class="forbid_media_global_state_allowed">(allowed)</b>
</span>
</label>
<label class="checkbox_label" for="forbid_media_override_forbidden">
<input type="radio" id="forbid_media_override_forbidden" name="forbid_media_override" />
<span data-i18n="Always forbidden">Always forbidden</span>
</label>
<label class="checkbox_label" for="forbid_media_override_allowed">
<input type="radio" id="forbid_media_override_allowed" name="forbid_media_override" />
<span data-i18n="Always allowed">Always allowed</span>
</label>
</div>
</div>
<!-- chat and input bar -->
<div id="typing_indicator_template" class="template_element">
<div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div>

View File

@ -29,6 +29,12 @@ var EventEmitter = function () {
};
EventEmitter.prototype.on = function (event, listener) {
// Unknown event used by external libraries?
if (event === undefined) {
console.trace('EventEmitter: Cannot listen to undefined event');
return;
}
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}

View File

@ -208,7 +208,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set
import { hideLoader, showLoader } from './scripts/loader.js';
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags } from './scripts/chats.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js';
import { evaluateMacros } from './scripts/macros.js';
@ -324,10 +324,13 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
return;
}
if (!power_user.forbid_external_images) {
const isMediaAllowed = isExternalMediaAllowed();
if (isMediaAllowed) {
return;
}
let mediaBlocked = false;
switch (node.tagName) {
case 'AUDIO':
case 'VIDEO':
@ -350,6 +353,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
if (isExternalUrl(url)) {
console.warn('External media blocked', url);
node.remove();
mediaBlocked = true;
break;
}
}
@ -357,16 +361,37 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
if (src && isExternalUrl(src)) {
console.warn('External media blocked', src);
mediaBlocked = true;
node.remove();
}
if (data && isExternalUrl(data)) {
console.warn('External media blocked', data);
mediaBlocked = true;
node.remove();
}
}
break;
}
if (mediaBlocked) {
const entityId = getCurrentEntityId();
const warningShownKey = `mediaWarningShown:${entityId}`;
if (localStorage.getItem(warningShownKey) === null) {
const warningToast = toastr.warning(
'Use the "Ext. Media" button to allow it. Click on this message to dismiss.',
'External media has been blocked',
{
timeOut: 0,
preventDuplicates: true,
onclick: () => toastr.clear(warningToast),
},
);
localStorage.setItem(warningShownKey, 'true');
}
}
});
// API OBJECT FOR EXTERNAL WIRING
@ -416,6 +441,7 @@ export const event_types = {
// TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted',
CHARACTER_DUPLICATED: 'character_duplicated',
SMOOTH_STREAM_TOKEN_RECEIVED: 'smooth_stream_token_received',
};
export const eventSource = new EventEmitter();
@ -751,7 +777,6 @@ function reloadMarkdownProcessor(render_formulas = false) {
}
function getCurrentChatId() {
console.debug(`selectedGroup:${selected_group}, this_chid:${this_chid}`);
if (selected_group) {
return groups.find(x => x.id == selected_group)?.chat_id;
}
@ -1692,7 +1717,7 @@ export async function reloadCurrentChat() {
chat.length = 0;
if (selected_group) {
await getGroupChat(selected_group);
await getGroupChat(selected_group, true);
}
else if (this_chid) {
await getChat();
@ -3032,8 +3057,8 @@ function saveResponseLength(api, responseLength) {
oldValue = oai_settings.openai_max_tokens;
oai_settings.openai_max_tokens = responseLength;
} else {
oldValue = max_context;
max_context = responseLength;
oldValue = amount_gen;
amount_gen = responseLength;
}
return oldValue;
}
@ -3048,7 +3073,7 @@ function restoreResponseLength(api, responseLength) {
if (api === 'openai') {
oai_settings.openai_max_tokens = responseLength;
} else {
max_context = responseLength;
amount_gen = responseLength;
}
}
@ -4058,6 +4083,10 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
await streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage);
streamingProcessor = null;
triggerAutoContinue(messageChunk, isImpersonate);
return Object.defineProperties(new String(getMessage), {
'messageChunk': { value: messageChunk },
'fromStream': { value: true },
});
}
} else {
return await sendGenerationRequest(type, generate_data);
@ -4068,6 +4097,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
async function onSuccess(data) {
if (!data) return;
if (data?.fromStream) {
return data;
}
let messageChunk = '';
if (data.error) {
@ -4177,6 +4211,9 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
if (type !== 'quiet') {
triggerAutoContinue(messageChunk, isImpersonate);
}
// Don't break the API chain that expects a single string in return
return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk });
}
function onError(exception) {
@ -4271,57 +4308,81 @@ export function getNextMessageId(type) {
}
/**
*
* @param {string} messageChunk
* @param {boolean} isImpersonate
* @returns {void}
* Determines if the message should be auto-continued.
* @param {string} messageChunk Current message chunk
* @param {boolean} isImpersonate Is the user impersonation
* @returns {boolean} Whether the message should be auto-continued
*/
export function shouldAutoContinue(messageChunk, isImpersonate) {
if (!power_user.auto_continue.enabled) {
console.debug('Auto-continue is disabled by user.');
return false;
}
if (typeof messageChunk !== 'string') {
console.debug('Not triggering auto-continue because message chunk is not a string');
return false;
}
if (isImpersonate) {
console.log('Continue for impersonation is not implemented yet');
return false;
}
if (is_send_press) {
console.debug('Auto-continue is disabled because a message is currently being sent.');
return false;
}
if (power_user.auto_continue.target_length <= 0) {
console.log('Auto-continue target length is 0, not triggering auto-continue');
return false;
}
if (main_api === 'openai' && !power_user.auto_continue.allow_chat_completions) {
console.log('Auto-continue for OpenAI is disabled by user.');
return false;
}
const textareaText = String($('#send_textarea').val());
const USABLE_LENGTH = 5;
if (textareaText.length > 0) {
console.log('Not triggering auto-continue because user input is not empty');
return false;
}
if (messageChunk.trim().length > USABLE_LENGTH && chat.length) {
const lastMessage = chat[chat.length - 1];
const messageLength = getTokenCount(lastMessage.mes);
const shouldAutoContinue = messageLength < power_user.auto_continue.target_length;
if (shouldAutoContinue) {
console.log(`Triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}. Message chunk: ${messageChunk}`);
return true;
} else {
console.log(`Not triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}`);
return false;
}
} else {
console.log('Last generated chunk was empty, not triggering auto-continue');
return false;
}
}
/**
* Triggers auto-continue if the message meets the criteria.
* @param {string} messageChunk Current message chunk
* @param {boolean} isImpersonate Is the user impersonation
*/
export function triggerAutoContinue(messageChunk, isImpersonate) {
if (selected_group) {
console.log('Auto-continue is disabled for group chat');
console.debug('Auto-continue is disabled for group chat');
return;
}
if (power_user.auto_continue.enabled && !is_send_press) {
if (power_user.auto_continue.target_length <= 0) {
console.log('Auto-continue target length is 0, not triggering auto-continue');
return;
}
if (main_api === 'openai' && !power_user.auto_continue.allow_chat_completions) {
console.log('Auto-continue for OpenAI is disabled by user.');
return;
}
if (isImpersonate) {
console.log('Continue for impersonation is not implemented yet');
return;
}
const textareaText = String($('#send_textarea').val());
const USABLE_LENGTH = 5;
if (textareaText.length > 0) {
console.log('Not triggering auto-continue because user input is not empty');
return;
}
if (messageChunk.trim().length > USABLE_LENGTH && chat.length) {
const lastMessage = chat[chat.length - 1];
const messageLength = getTokenCount(lastMessage.mes);
const shouldAutoContinue = messageLength < power_user.auto_continue.target_length;
if (shouldAutoContinue) {
console.log(`Triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}. Message chunk: ${messageChunk}`);
$('#option_continue').trigger('click');
} else {
console.log(`Not triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}`);
return;
}
} else {
console.log('Last generated chunk was empty, not triggering auto-continue');
return;
}
if (shouldAutoContinue(messageChunk, isImpersonate)) {
$('#option_continue').trigger('click');
}
}
@ -6863,6 +6924,12 @@ export function select_selected_character(chid) {
$('#form_create').attr('actiontype', 'editcharacter');
$('.form_create_bottom_buttons_block .chat_lorebook_button').show();
const externalMediaState = isExternalMediaAllowed();
$('#character_open_media_overrides').toggle(!selected_group);
$('#character_media_allowed_icon').toggle(externalMediaState);
$('#character_media_forbidden_icon').toggle(!externalMediaState);
saveSettingsDebounced();
}
@ -6923,6 +6990,7 @@ function select_rm_create() {
$('#form_create').attr('actiontype', 'createcharacter');
$('.form_create_bottom_buttons_block .chat_lorebook_button').hide();
$('#character_open_media_overrides').hide();
}
function select_rm_characters() {
@ -8211,8 +8279,7 @@ const CONNECT_API_MAP = {
async function selectContextCallback(_, name) {
if (!name) {
toastr.warning('Context preset name is required');
return '';
return power_user.context.preset;
}
const contextNames = context_presets.map(preset => preset.name);
@ -8231,8 +8298,7 @@ async function selectContextCallback(_, name) {
async function selectInstructCallback(_, name) {
if (!name) {
toastr.warning('Instruct preset name is required');
return '';
return power_user.instruct.preset;
}
const instructNames = instruct_presets.map(preset => preset.name);
@ -8581,10 +8647,10 @@ jQuery(async function () {
registerSlashCommand('closechat', doCloseChat, [], ' closes the current chat', true, true);
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], ' toggle UI panels on/off', true, true);
registerSlashCommand('forcesave', doForceSave, [], ' forces a save of the current chat and settings', true, true);
registerSlashCommand('instruct', selectInstructCallback, [], '<span class="monospace">(name)</span> selects instruct mode preset by name', true, true);
registerSlashCommand('instruct', selectInstructCallback, [], '<span class="monospace">(name)</span> selects instruct mode preset by name. Gets the current instruct if no name is provided', true, true);
registerSlashCommand('instruct-on', enableInstructCallback, [], ' enables instruct mode', true, true);
registerSlashCommand('instruct-off', disableInstructCallback, [], ' disables instruct mode', true, true);
registerSlashCommand('context', selectContextCallback, [], '<span class="monospace">(name)</span> selects context template by name', true, true);
registerSlashCommand('context', selectContextCallback, [], '<span class="monospace">(name)</span> selects context template by name. Gets the current template if no name is provided', true, true);
registerSlashCommand('chat-manager', () => $('#option_select_chat').trigger('click'), ['chat-history', 'manage-chats'], ' opens the chat manager for the current character/group', true, true);
setTimeout(function () {
@ -9873,14 +9939,14 @@ jQuery(async function () {
$('#character_import_file').click();
});
$('#character_import_file').on('change', function (e) {
$('#character_import_file').on('change', async function (e) {
$('#rm_info_avatar').html('');
if (!e.target.files.length) {
return;
}
for (const file of e.target.files) {
importCharacter(file);
await importCharacter(file);
}
});

View File

@ -5,6 +5,7 @@ import {
addCopyToCodeBlocks,
appendMediaToMessage,
callPopup,
characters,
chat,
eventSource,
event_types,
@ -12,9 +13,14 @@ import {
getRequestHeaders,
hideSwipeButtons,
name2,
reloadCurrentChat,
saveChatDebounced,
saveSettingsDebounced,
showSwipeButtons,
this_chid,
} from '../script.js';
import { selected_group } from './group-chats.js';
import { power_user } from './power-user.js';
import {
extractTextFromHTML,
extractTextFromMarkdown,
@ -416,6 +422,56 @@ export function decodeStyleTags(text) {
});
}
async function openExternalMediaOverridesDialog() {
const entityId = getCurrentEntityId();
if (!entityId) {
toastr.info('No character or group selected');
return;
}
const template = $('#forbid_media_override_template > .forbid_media_override').clone();
template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_images);
template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_images);
if (power_user.external_media_allowed_overrides.includes(entityId)) {
template.find('#forbid_media_override_allowed').prop('checked', true);
}
else if (power_user.external_media_forbidden_overrides.includes(entityId)) {
template.find('#forbid_media_override_forbidden').prop('checked', true);
}
else {
template.find('#forbid_media_override_global').prop('checked', true);
}
callPopup(template, 'text', '', { wide: false, large: false });
}
export function getCurrentEntityId() {
if (selected_group) {
return String(selected_group);
}
return characters[this_chid]?.avatar ?? null;
}
export function isExternalMediaAllowed() {
const entityId = getCurrentEntityId();
if (!entityId) {
return !power_user.forbid_external_images;
}
if (power_user.external_media_allowed_overrides.includes(entityId)) {
return true;
}
if (power_user.external_media_forbidden_overrides.includes(entityId)) {
return false;
}
return !power_user.forbid_external_images;
}
jQuery(function () {
$(document).on('click', '.mes_hide', async function () {
const messageBlock = $(this).closest('.mes');
@ -511,6 +567,32 @@ jQuery(function () {
$(this).closest('.mes').find('.mes_edit').trigger('click');
});
$(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog);
$(document).on('input', '#forbid_media_override_allowed', function () {
const entityId = getCurrentEntityId();
if (!entityId) return;
power_user.external_media_allowed_overrides.push(entityId);
power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId);
saveSettingsDebounced();
reloadCurrentChat();
});
$(document).on('input', '#forbid_media_override_forbidden', function () {
const entityId = getCurrentEntityId();
if (!entityId) return;
power_user.external_media_forbidden_overrides.push(entityId);
power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId);
saveSettingsDebounced();
reloadCurrentChat();
});
$(document).on('input', '#forbid_media_override_global', function () {
const entityId = getCurrentEntityId();
if (!entityId) return;
power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId);
power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId);
saveSettingsDebounced();
reloadCurrentChat();
});
$('#file_form_input').on('change', onFileAttach);
$('#file_form').on('reset', function () {
$('#file_form').addClass('displayNone');

View File

@ -354,15 +354,15 @@ jQuery(function () {
<div class="flex1 flex-container flexFlowColumn flexNoGap">
<label for="caption_multimodal_api">API</label>
<select id="caption_multimodal_api" class="flex1 text_pole">
<option value="llamacpp">llama.cpp</option>
<option value="ooba">Text Generation WebUI (oobabooga)</option>
<option value="anthropic">Anthropic</option>
<option value="custom">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="anthropic">Anthropic</option>
<option value="openrouter">OpenRouter</option>
<option value="google">Google MakerSuite</option>
<option value="custom">Custom (OpenAI-compatible)</option>
<option value="ooba">Text Generation WebUI (oobabooga)</option>
</select>
</div>
<div class="flex1 flex-container flexFlowColumn flexNoGap">
@ -375,6 +375,14 @@ jQuery(function () {
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
<option data-type="openrouter" value="anthropic/claude-3-haiku:beta">anthropic/claude-3-haiku:beta</option>
<option data-type="openrouter" value="anthropic/claude-3-sonnet:beta">anthropic/claude-3-sonnet:beta</option>
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
<option data-type="openrouter" value="nousresearch/nous-hermes-2-vision-7b">nousresearch/nous-hermes-2-vision-7b</option>
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
<option data-type="ollama" value="llava:latest">llava:latest</option>

View File

@ -91,7 +91,7 @@ const defaultSettings = {
maxMessagesPerRequestMin: 0,
maxMessagesPerRequestMax: 250,
maxMessagesPerRequestStep: 1,
prompt_builder: prompt_builders.RAW_BLOCKING,
prompt_builder: prompt_builders.DEFAULT,
};
function loadSettings() {

View File

@ -78,7 +78,7 @@
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
</label>
<div class="flex-container alignItemsBaseline" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
<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>
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
</div>

View File

@ -104,7 +104,7 @@ const loadSets = async () => {
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
qr.automationId = slot.automationId ?? false;
qr.automationId = slot.automationId ?? '';
qr.contextList = (slot.contextMenu ?? []).map(it=>({
set: it.preset,
isChained: it.chain,

View File

@ -1808,7 +1808,7 @@ function processReply(str) {
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(/[^a-zA-Z0-9,:()\-']+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim();

View File

@ -69,9 +69,11 @@ import {
loadItemizedPrompts,
animation_duration,
depth_prompt_role_default,
shouldAutoContinue,
} from '../script.js';
import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { isExternalMediaAllowed } from './chats.js';
export {
selected_group,
@ -175,7 +177,7 @@ async function loadGroupChat(chatId) {
return [];
}
export async function getGroupChat(groupId) {
export async function getGroupChat(groupId, reload = false) {
const group = groups.find((x) => x.id === groupId);
const chat_id = group.chat_id;
const data = await loadGroupChat(chat_id);
@ -215,6 +217,10 @@ export async function getGroupChat(groupId) {
updateChatMetadata(metadata, true);
}
if (reload) {
select_group_chats(groupId, true);
}
await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
}
@ -678,9 +684,10 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
await delay(1);
}
const group = groups.find((x) => x.id === selected_group);
let typingIndicator = $('#chat .typing_indicator');
/** @type {any} Caution: JS war crimes ahead */
let textResult = '';
let typingIndicator = $('#chat .typing_indicator');
const group = groups.find((x) => x.id === selected_group);
if (!group || !Array.isArray(group.members) || !group.members.length) {
sendSystemMessage(system_message_types.EMPTY, '', { isSmallSys: true });
@ -778,8 +785,15 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
}
// Wait for generation to finish
const generateFinished = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
textResult = await generateFinished;
textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
let messageChunk = textResult?.messageChunk;
if (messageChunk) {
while (shouldAutoContinue(messageChunk, type === 'impersonate')) {
textResult = await Generate('continue', { automatic_trigger: by_auto_mode, ...(params || {}) });
messageChunk = textResult?.messageChunk;
}
}
}
} finally {
typingIndicator.hide();
@ -1297,6 +1311,10 @@ function select_group_chats(groupId, skipAnimation) {
$('#rm_group_delete').show();
$('#rm_group_scenario').show();
$('#group-metadata-controls .chat_lorebook_button').removeClass('disabled').prop('disabled', false);
$('#group_open_media_overrides').show();
const isMediaAllowed = isExternalMediaAllowed();
$('#group_media_allowed_icon').toggle(isMediaAllowed);
$('#group_media_forbidden_icon').toggle(!isMediaAllowed);
} else {
$('#rm_group_submit').show();
if ($('#groupAddMemberListToggle .inline-drawer-content').css('display') !== 'block') {
@ -1305,6 +1323,7 @@ function select_group_chats(groupId, skipAnimation) {
$('#rm_group_delete').hide();
$('#rm_group_scenario').hide();
$('#group-metadata-controls .chat_lorebook_button').addClass('disabled').prop('disabled', true);
$('#group_open_media_overrides').hide();
}
updateFavButtonState(group?.fav ?? false);

View File

@ -26,6 +26,7 @@ const controls = [
{ id: 'instruct_output_suffix', property: 'output_suffix', isCheckbox: false },
{ id: 'instruct_system_sequence', property: 'system_sequence', isCheckbox: false },
{ id: 'instruct_system_suffix', property: 'system_suffix', isCheckbox: false },
{ id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },
{ id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
{ id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },
{ id: 'instruct_names', property: 'names', isCheckbox: true },
@ -56,6 +57,7 @@ function migrateInstructModeSettings(settings) {
system_sequence: '',
system_suffix: '',
user_alignment_message: '',
last_system_sequence: '',
names_force_groups: true,
skip_examples: false,
system_same_as_user: false,
@ -243,14 +245,15 @@ export function getInstructStoppingSequences() {
const result = [];
if (power_user.instruct.enabled) {
const stop_sequence = power_user.instruct.stop_sequence;
const input_sequence = power_user.instruct.input_sequence.replace(/{{name}}/gi, name1);
const output_sequence = power_user.instruct.output_sequence.replace(/{{name}}/gi, name2);
const first_output_sequence = power_user.instruct.first_output_sequence.replace(/{{name}}/gi, name2);
const last_output_sequence = power_user.instruct.last_output_sequence.replace(/{{name}}/gi, name2);
const system_sequence = power_user.instruct.system_sequence.replace(/{{name}}/gi, 'System');
const stop_sequence = power_user.instruct.stop_sequence || '';
const input_sequence = power_user.instruct.input_sequence?.replace(/{{name}}/gi, name1) || '';
const output_sequence = power_user.instruct.output_sequence?.replace(/{{name}}/gi, name2) || '';
const first_output_sequence = power_user.instruct.first_output_sequence?.replace(/{{name}}/gi, name2) || '';
const last_output_sequence = power_user.instruct.last_output_sequence?.replace(/{{name}}/gi, name2) || '';
const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';
const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}`;
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}\n${last_system_sequence}`;
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
}
@ -452,9 +455,10 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
return power_user.instruct.input_sequence;
}
// Neutral / system prompt
// Neutral / system / quiet prompt
// Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
if (isQuiet && !isQuietToLoud) {
return power_user.instruct.output_sequence;
return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;
}
// Quiet in-character prompt
@ -517,20 +521,28 @@ export function replaceInstructMacros(input) {
if (!input) {
return '';
}
const instructMacros = {
'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt,
'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,
'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,
'instructInput|instructUserPrefix': power_user.instruct.input_sequence,
'instructUserSuffix': power_user.instruct.input_suffix,
'instructOutput|instructAssistantPrefix': power_user.instruct.output_sequence,
'instructSeparator|instructAssistantSuffix': power_user.instruct.output_suffix,
'instructSystemPrefix': power_user.instruct.system_sequence,
'instructSystemSuffix': power_user.instruct.system_suffix,
'instructFirstOutput|instructFirstAssistantPrefix': power_user.instruct.first_output_sequence || power_user.instruct.output_sequence,
'instructLastOutput|instructLastAssistantPrefix': power_user.instruct.last_output_sequence || power_user.instruct.output_sequence,
'instructStop': power_user.instruct.stop_sequence,
'instructUserFiller': power_user.instruct.user_alignment_message,
'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence,
};
for (const [placeholder, value] of Object.entries(instructMacros)) {
const regex = new RegExp(`{{(${placeholder})}}`, 'gi');
input = input.replace(regex, power_user.instruct.enabled ? value : '');
}
input = input.replace(/{{(instructSystem|instructSystemPrompt)}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : '');
input = input.replace(/{{instructSystemPromptPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : '');
input = input.replace(/{{instructSystemPromptSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : '');
input = input.replace(/{{(instructInput|instructUserPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : '');
input = input.replace(/{{instructUserSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.input_suffix : '');
input = input.replace(/{{(instructOutput|instructAssistantPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : '');
input = input.replace(/{{(instructSeparator|instructAssistantSuffix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_suffix : '');
input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence : '');
input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_suffix : '');
input = input.replace(/{{(instructFirstOutput|instructFirstAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : '');
input = input.replace(/{{(instructLastOutput|instructLastAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : '');
input = input.replace(/{{instructStop}}/gi, power_user.instruct.enabled ? power_user.instruct.stop_sequence : '');
input = input.replace(/{{instructUserFiller}}/gi, power_user.instruct.enabled ? power_user.instruct.user_alignment_message : '');
input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);

View File

@ -9,7 +9,7 @@ import {
import {
power_user,
} from './power-user.js';
import EventSourceStream from './sse-stream.js';
import { getEventSourceStream } from './sse-stream.js';
import { getSortableDelay } from './utils.js';
export const kai_settings = {
@ -174,7 +174,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) {
tryParseStreamingError(response, await response.text());
throw new Error(`Got response status ${response.status}`);
}
const eventStream = new EventSourceStream();
const eventStream = getEventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();

View File

@ -8,6 +8,7 @@ import {
Generate,
getGeneratingApi,
is_send_press,
isStreamingEnabled,
} from '../script.js';
import { debounce, delay, getStringHash } from './utils.js';
import { decodeTextTokens, getTokenizerBestMatch } from './tokenizers.js';
@ -64,11 +65,15 @@ function renderAlternativeTokensView() {
renderTopLogprobs();
const { messageLogprobs, continueFrom } = getActiveMessageLogprobData() || {};
if (!messageLogprobs?.length) {
const usingSmoothStreaming = isStreamingEnabled() && power_user.smooth_streaming;
if (!messageLogprobs?.length || usingSmoothStreaming) {
const emptyState = $('<div></div>');
const noTokensMsg = usingSmoothStreaming
? 'Token probabilities are not available when using Smooth Streaming.'
: 'No token probabilities available for the current message.';
const msg = power_user.request_token_probabilities
? 'No token probabilities available for the current message.'
: `<span>Enable <b>Request token probabilities</b> in the User Settings menu to use this feature.</span>`;
? noTokensMsg
: '<span>Enable <b>Request token probabilities</b> in the User Settings menu to use this feature.</span>';
emptyState.html(msg);
emptyState.addClass('logprobs_empty_state');
view.append(emptyState);

View File

@ -10,7 +10,7 @@ import {
import { getCfgPrompt } from './cfg-scale.js';
import { MAX_CONTEXT_DEFAULT, MAX_RESPONSE_DEFAULT, power_user } from './power-user.js';
import { getTextTokens, tokenizers } from './tokenizers.js';
import EventSourceStream from './sse-stream.js';
import { getEventSourceStream } from './sse-stream.js';
import {
getSortableDelay,
getStringHash,
@ -614,7 +614,7 @@ export async function generateNovelWithStreaming(generate_data, signal) {
tryParseStreamingError(response, await response.text());
throw new Error(`Got response status ${response.status}`);
}
const eventStream = new EventSourceStream();
const eventStream = getEventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();

View File

@ -45,7 +45,7 @@ import {
import { getCustomStoppingStrings, persona_description_positions, power_user } from './power-user.js';
import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js';
import EventSourceStream from './sse-stream.js';
import { getEventSourceStream } from './sse-stream.js';
import {
delay,
download,
@ -1772,7 +1772,7 @@ async function sendOpenAIRequest(type, messages, signal) {
throw new Error(`Got response status ${response.status}`);
}
if (stream) {
const eventStream = new EventSourceStream();
const eventStream = getEventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();
return async function* streamData() {
@ -3661,7 +3661,7 @@ async function onModelChange() {
else if (['command-light-nightly', 'command-nightly'].includes(oai_settings.cohere_model)) {
$('#openai_max_context').attr('max', max_8k);
}
else if (['command-r'].includes(oai_settings.cohere_model)) {
else if (['command-r', 'command-r-plus'].includes(oai_settings.cohere_model)) {
$('#openai_max_context').attr('max', max_128k);
}
else {

View File

@ -118,6 +118,8 @@ let power_user = {
markdown_escape_strings: '',
chat_truncation: 100,
streaming_fps: 30,
smooth_streaming: false,
smooth_streaming_speed: 50,
ui_mode: ui_mode.POWER,
fast_ui_mode: true,
@ -202,6 +204,7 @@ let power_user = {
output_suffix: '',
system_sequence: '',
system_suffix: '',
last_system_sequence: '',
first_output_sequence: '',
last_output_sequence: '',
system_sequence_prefix: '',
@ -255,6 +258,8 @@ let power_user = {
auto_connect: false,
auto_load_chat: false,
forbid_external_images: false,
external_media_allowed_overrides: [],
external_media_forbidden_overrides: [],
};
let themes = [];
@ -1548,6 +1553,9 @@ function loadPowerUserSettings(settings, data) {
$('#streaming_fps').val(power_user.streaming_fps);
$('#streaming_fps_counter').val(power_user.streaming_fps);
$('#smooth_streaming').prop('checked', power_user.smooth_streaming);
$('#smooth_streaming_speed').val(power_user.smooth_streaming_speed);
$('#font_scale').val(power_user.font_scale);
$('#font_scale_counter').val(power_user.font_scale);
@ -2759,22 +2767,35 @@ export function getCustomStoppingStrings(limit = undefined) {
}
$(document).ready(() => {
const adjustAutocompleteDebounced = debounce(() => {
$('.ui-autocomplete-input').each(function () {
const isOpen = $(this).autocomplete('widget')[0].style.display !== 'none';
if (isOpen) {
$(this).autocomplete('search');
}
});
});
$(window).on('resize', async () => {
if (isMobile()) {
return;
}
//console.log('Window resized!');
const reportZoomLevelDebounced = debounce(() => {
const zoomLevel = Number(window.devicePixelRatio).toFixed(2);
const winWidth = window.innerWidth;
const winHeight = window.innerHeight;
console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}`);
});
$(window).on('resize', async () => {
adjustAutocompleteDebounced();
setHotswapsDebounced();
if (isMobile()) {
return;
}
reportZoomLevelDebounced();
if (Object.keys(power_user.movingUIState).length > 0) {
resetMovablePanels('resize');
}
// Adjust layout and styling here
setHotswapsDebounced();
});
// Settings that go to settings.json
@ -2945,6 +2966,16 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$('#smooth_streaming').on('input', function () {
power_user.smooth_streaming = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#smooth_streaming_speed').on('input', function () {
power_user.smooth_streaming_speed = Number($('#smooth_streaming_speed').val());
saveSettingsDebounced();
});
$('input[name="font_scale"]').on('input', async function (e) {
power_user.font_scale = Number(e.target.value);
$('#font_scale_counter').val(power_user.font_scale);

View File

@ -470,7 +470,7 @@ async function waitForConnection() {
export async function initPresetManager() {
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
registerPresetManagers();
registerSlashCommand('preset', presetCommandCallback, [], '<span class="monospace">(name)</span> sets a preset by name for the current API', true, true);
registerSlashCommand('preset', presetCommandCallback, [], '<span class="monospace">(name)</span> sets a preset by name for the current API. Gets the current preset if no name is provided', true, true);
$(document).on('click', '[data-preset-manager-update]', async function () {
const apiId = $(this).data('preset-manager-update');

View File

@ -254,7 +254,7 @@ parser.addCommand('inject', injectCallback, [], '<span class="monospace">id=inje
parser.addCommand('listinjects', listInjectsCallback, [], ' lists all script injections for the current chat.', true, true);
parser.addCommand('flushinjects', flushInjectsCallback, [], ' removes all script injections for the current chat.', true, true);
parser.addCommand('tokens', (_, text) => getTokenCount(text), [], '<span class="monospace">(text)</span> counts the number of tokens in the text.', true, true);
parser.addCommand('model', modelCallback, [], '<span class="monospace">(model name)</span> sets the model for the current API.', true, true);
parser.addCommand('model', modelCallback, [], '<span class="monospace">(model name)</span> sets the model for the current API. Gets the current model name if no argument is provided.', true, true);
registerVariableCommands();
const NARRATOR_NAME_KEY = 'narrator_name';
@ -1653,16 +1653,10 @@ function setBackgroundCallback(_, bg) {
/**
* Sets a model for the current API.
* @param {object} _ Unused
* @param {string} model Model name
* @returns {void}
* @param {string} model New model name
* @returns {string} New or existing model name
*/
function modelCallback(_, model) {
if (!model) {
return;
}
console.log('Set model to ' + model);
const modelSelectMap = [
{ id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI },
{ id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER },
@ -1700,23 +1694,31 @@ function modelCallback(_, model) {
if (!modelSelectItem) {
toastr.info('Setting a model for your API is not supported or not implemented yet.');
return;
return '';
}
const modelSelectControl = document.getElementById(modelSelectItem);
if (!(modelSelectControl instanceof HTMLSelectElement)) {
toastr.error(`Model select control not found: ${main_api}[${apiSubType}]`);
return;
return '';
}
const options = Array.from(modelSelectControl.options);
if (!options.length) {
toastr.warning('No model options found. Check your API settings.');
return;
return '';
}
model = String(model || '').trim();
if (!model) {
return modelSelectControl.value;
}
console.log('Set model to ' + model);
let newSelectedOption = null;
const fuse = new Fuse(options, { keys: ['text', 'value'] });
@ -1737,8 +1739,10 @@ function modelCallback(_, model) {
modelSelectControl.value = newSelectedOption.value;
$(modelSelectControl).trigger('change');
toastr.success(`Model set to "${newSelectedOption.text}"`);
return newSelectedOption.value;
} else {
toastr.warning(`No model found with name "${model}"`);
return '';
}
}
@ -1991,6 +1995,7 @@ function setSlashCommandAutocomplete(textarea) {
export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
const dom = document.createElement('ul'); {
dom.classList.add('slashCommandAutoComplete');
dom.classList.add('defaultThemed');
}
let isReplacable = false;
let result = [];
@ -2006,7 +2011,7 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
};
const show = (isInput = false, isForced = false) => {
//TODO check if isInput and isForced are both required
isForced = isForced || isInput;
// isForced = isForced || isInput;
text = textarea.value;
// only show with textarea in focus
if (document.activeElement != textarea) return hide();
@ -2016,7 +2021,7 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
executor = parser.getCommandAt(text, textarea.selectionStart);
let slashCommand = executor?.name?.toLowerCase() ?? '';
isReplacable = isInput && (!executor ? true : textarea.selectionStart == executor.start - 2 + executor.name.length + 1);
if (isForced && textarea.selectionStart > executor.start - 2 && textarea.selectionStart <= executor.start - 2 + executor.name.length + 1) {
if ((isForced || isInput) && textarea.selectionStart > executor.start - 2 && textarea.selectionStart <= executor.start - 2 + executor.name.length + 1) {
slashCommand = slashCommand.slice(0, textarea.selectionStart - (executor.start - 2) - 1);
executor.name = slashCommand;
executor.end = executor.start + slashCommand.length;
@ -2099,7 +2104,7 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
else {
const helpStrings = Object
.keys(parser.commands) // Get all slash commands
.filter(it => executor.name == '' || isReplacable || isForced ? matchers[matchType](it) : it.toLowerCase() == slashCommand) // Filter by the input
.filter(it => executor.name == '' || isReplacable ? matchers[matchType](it) : it.toLowerCase() == slashCommand) // Filter by the input
// .sort((a, b) => a.localeCompare(b)) // Sort alphabetically
;
result = helpStrings
@ -2139,7 +2144,14 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
li.classList.add('selected');
}
li.innerHTML = item.label;
li.addEventListener('click', ()=>{
li.addEventListener('pointerdown', ()=>{
mouseup = new Promise(resolve=>{
const resolver = ()=>{
window.removeEventListener('mouseup', resolver);
resolve();
};
window.addEventListener('mouseup', resolver);
});
selectedItem = item;
select();
});
@ -2148,8 +2160,13 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
}
}
selectedItem = result[0];
updatePosition();
document.body.append(dom);
isActive = true;
};
const updatePosition = () => {
if (isFloating) {
updatePosition();
updateFloatingPosition();
} else {
const rect = textarea.getBoundingClientRect();
dom.style.setProperty('--bottom', `${window.innerHeight - rect.top}px`);
@ -2157,25 +2174,26 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
dom.style.left = `${rect.left}px`;
dom.style.right = `${window.innerWidth - rect.right}px`;
}
document.body.append(dom);
isActive = true;
};
const updatePosition = () => {
const updateFloatingPosition = () => {
const location = getCursorPosition();
if (location.y <= window.innerHeight / 2) {
const rect = textarea.getBoundingClientRect();
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) return hide();
const left = Math.max(rect.left, location.left);
if (location.top <= window.innerHeight / 2) {
dom.style.top = `${location.bottom}px`;
dom.style.bottom = 'auto';
dom.style.left = `${location.left}px`;
dom.style.left = `${left}px`;
dom.style.right = 'auto';
dom.style.maxWidth = `calc(99vw - ${location.left}px)`;
dom.style.maxHeight = `calc(99vh - ${location.bottom}px)`;
dom.style.maxWidth = `calc(99vw - ${left}px)`;
dom.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
} else {
dom.style.top = 'auto';
dom.style.bottom = `calc(100vh - ${location.top}px)`;
dom.style.left = `${location.left}px`;
dom.style.left = `${left}px`;
dom.style.right = 'auto';
dom.style.maxWidth = `calc(99vw - ${location.left}px)`;
dom.style.maxHeight = `calc(99vh - ${location.top}px)`;
dom.style.maxWidth = `calc(99vw - ${left}px)`;
dom.style.maxHeight = `calc(${location.top}px - 1vh)`;
}
};
const getCursorPosition = () => {
@ -2214,10 +2232,12 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
clone.remove();
return location;
};
const select = () => {
let mouseup = Promise.resolve();
const select = async() => {
if (isReplacable) {
textarea.focus();
textarea.value = `${text.slice(0, executor.start - 2)}${selectedItem.value}${text.slice(executor.start - 2 + executor.name.length + 1)}`;
await mouseup;
textarea.focus();
textarea.selectionStart = executor.start - 2 + selectedItem.value.length;
textarea.selectionEnd = textarea.selectionStart;
show();
@ -2309,8 +2329,9 @@ export function setNewSlashCommandAutoComplete(textarea, isFloating = false) {
});
textarea.addEventListener('blur', ()=>hide());
if (isFloating) {
textarea.addEventListener('scroll', debounce(updatePosition, 100));
textarea.addEventListener('scroll', debounce(updateFloatingPosition, 100));
}
window.addEventListener('resize', debounce(updatePosition, 100));
}
jQuery(function () {

View File

@ -1,3 +1,7 @@
import { eventSource, event_types } from '../script.js';
import { power_user } from './power-user.js';
import { delay } from './utils.js';
/**
* A stream which handles Server-Sent Events from a binary ReadableStream like you get from the fetch API.
*/
@ -74,4 +78,215 @@ class EventSourceStream {
}
}
/**
* Gets a delay based on the character.
* @param {string} s The character.
* @returns {number} The delay in milliseconds.
*/
function getDelay(s) {
if (!s) {
return 0;
}
const speedFactor = Math.max(100 - power_user.smooth_streaming_speed, 1);
const defaultDelayMs = speedFactor * 0.4;
const punctuationDelayMs = defaultDelayMs * 25;
if ([',', '\n'].includes(s)) {
return punctuationDelayMs / 2;
}
if (['.', '!', '?'].includes(s)) {
return punctuationDelayMs;
}
return defaultDelayMs;
}
/**
* Parses the stream data and returns the parsed data and the chunk to be sent.
* @param {object} json The JSON data.
* @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
*/
async function* parseStreamData(json) {
// Claude
if (typeof json.delta === 'object') {
if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {
for (let i = 0; i < json.delta.text.length; i++) {
const str = json.delta.text[i];
yield {
data: { ...json, delta: { text: str } },
chunk: str,
};
}
}
return;
}
// MakerSuite
else if (Array.isArray(json.candidates)) {
for (let i = 0; i < json.candidates.length; i++) {
const isNotPrimary = json.candidates?.[0]?.index > 0;
if (isNotPrimary || json.candidates.length === 0) {
return null;
}
if (typeof json.candidates[0].content === 'object' && Array.isArray(json.candidates[i].content.parts)) {
for (let j = 0; j < json.candidates[i].content.parts.length; j++) {
if (typeof json.candidates[i].content.parts[j].text === 'string') {
for (let k = 0; k < json.candidates[i].content.parts[j].text.length; k++) {
const str = json.candidates[i].content.parts[j].text[k];
const candidateClone = structuredClone(json.candidates[0]);
candidateClone.content.parts[j].text = str;
const candidates = [candidateClone];
yield {
data: { ...json, candidates },
chunk: str,
};
}
}
}
}
}
return;
}
// NovelAI / KoboldCpp Classic
else if (typeof json.token === 'string' && json.token.length > 0) {
for (let i = 0; i < json.token.length; i++) {
const str = json.token[i];
yield {
data: { ...json, token: str },
chunk: str,
};
}
return;
}
// llama.cpp?
else if (typeof json.content === 'string' && json.content.length > 0) {
for (let i = 0; i < json.content.length; i++) {
const str = json.content[i];
yield {
data: { ...json, content: str },
chunk: str,
};
}
return;
}
// OpenAI-likes
else if (Array.isArray(json.choices)) {
const isNotPrimary = json?.choices?.[0]?.index > 0;
if (isNotPrimary || json.choices.length === 0) {
return null;
}
if (typeof json.choices[0].text === 'string' && json.choices[0].text.length > 0) {
for (let j = 0; j < json.choices[0].text.length; j++) {
const str = json.choices[0].text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.text = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
return;
}
else if (typeof json.choices[0].delta === 'object') {
if (typeof json.choices[0].delta.text === 'string' && json.choices[0].delta.text.length > 0) {
for (let j = 0; j < json.choices[0].delta.text.length; j++) {
const str = json.choices[0].delta.text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.text = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
return;
}
else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
const str = json.choices[0].delta.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.content = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
return;
}
}
else if (typeof json.choices[0].message === 'object') {
if (typeof json.choices[0].message.content === 'string' && json.choices[0].message.content.length > 0) {
for (let j = 0; j < json.choices[0].message.content.length; j++) {
const str = json.choices[0].message.content[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.message.content = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
};
}
return;
}
}
}
throw new Error('Unknown event data format');
}
/**
* Like the default one, but multiplies the events by the number of letters in the event data.
*/
export class SmoothEventSourceStream extends EventSourceStream {
constructor() {
super();
let lastStr = '';
const transformStream = new TransformStream({
async transform(chunk, controller) {
const event = chunk;
const data = event.data;
try {
const hasFocus = document.hasFocus();
if (data === '[DONE]') {
lastStr = '';
return controller.enqueue(event);
}
const json = JSON.parse(data);
if (!json) {
lastStr = '';
return controller.enqueue(event);
}
for await (const parsed of parseStreamData(json)) {
hasFocus && await delay(getDelay(lastStr));
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
lastStr = parsed.chunk;
hasFocus && await eventSource.emit(event_types.SMOOTH_STREAM_TOKEN_RECEIVED, parsed.chunk);
}
} catch (error) {
console.error('Smooth Streaming parsing error', error);
controller.enqueue(event);
}
},
});
this.readable = this.readable.pipeThrough(transformStream);
}
}
export function getEventSourceStream() {
if (power_user.smooth_streaming) {
return new SmoothEventSourceStream();
}
return new EventSourceStream();
}
export default EventSourceStream;

View File

@ -43,6 +43,9 @@ export {
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
const TAG_TEMPLATE = $('#tag_template .tag');
const FOLDER_TEMPLATE = $('#bogus_folder_template .bogus_folder_select');
const VIEW_TAG_TEMPLATE = $('#tag_view_template .tag_view_item');
function getFilterHelper(listSelector) {
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
@ -271,7 +274,7 @@ function getTagBlock(tag, entities, hidden = 0) {
const tagFolder = TAG_FOLDER_TYPES[tag.folder_type];
const template = $('#bogus_folder_template .bogus_folder_select').clone();
const template = FOLDER_TEMPLATE.clone();
template.addClass(tagFolder.class);
template.attr({ 'tagid': tag.id, 'id': `BogusFolder${tag.id}` });
template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
@ -665,7 +668,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
return;
}
let tagElement = $('#tag_template .tag').clone();
let tagElement = TAG_TEMPLATE.clone();
tagElement.attr('id', tag.id);
//tagElement.css('color', 'var(--SmartThemeBodyColor)');
@ -765,7 +768,9 @@ function toggleTagThreeState(element, { stateOverride = undefined, simulateClick
element.toggleClass(FILTER_STATES[state].class, state === states[targetStateIndex]);
});
console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element);
if (states[currentStateIndex] !== states[targetStateIndex]) {
console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element);
}
}
@ -1129,7 +1134,7 @@ function onTagCreateClick() {
function appendViewTagToList(list, tag, everything) {
const count = everything.filter(x => x == tag.id).length;
const template = $('#tag_view_template .tag_view_item').clone();
const template = VIEW_TAG_TEMPLATE.clone();
template.attr('id', tag.id);
template.find('.tag_view_counter_value').text(count);
template.find('.tag_view_name').text(tag.name);
@ -1146,16 +1151,18 @@ function appendViewTagToList(list, tag, everything) {
template.find('.tag_as_folder').hide();
}
template.find('.tagColorPickerHolder').html(
`<toolcool-color-picker id="${colorPickerId}" color="${tag.color}" class="tag-color"></toolcool-color-picker>`,
);
template.find('.tagColorPicker2Holder').html(
`<toolcool-color-picker id="${colorPicker2Id}" color="${tag.color2}" class="tag-color2"></toolcool-color-picker>`,
);
const primaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
.addClass('tag-color')
.attr({ id: colorPickerId, color: tag.color });
const secondaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
.addClass('tag-color2')
.attr({ id: colorPicker2Id, color: tag.color2 });
template.find('.tagColorPickerHolder').append(primaryColorPicker);
template.find('.tagColorPicker2Holder').append(secondaryColorPicker);
template.find('.tag_as_folder').attr('id', tagAsFolderId);
template.find('.tag-color').attr('id', colorPickerId);
template.find('.tag-color2').attr('id', colorPicker2Id);
list.append(template);

View File

@ -60,6 +60,7 @@
<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>
</ul>

View File

@ -12,7 +12,7 @@ import {
import { BIAS_CACHE, createNewLogitBiasEntry, displayLogitBias, getLogitBiasListResult } from './logit-bias.js';
import { power_user, registerDebugFunction } from './power-user.js';
import EventSourceStream from './sse-stream.js';
import { getEventSourceStream } from './sse-stream.js';
import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer } from './textgen-models.js';
import { SENTENCEPIECE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
import { getSortableDelay, onlyUnique } from './utils.js';
@ -821,7 +821,7 @@ async function generateTextGenWithStreaming(generate_data, signal) {
throw new Error(`Got response status ${response.status}`);
}
const eventStream = new EventSourceStream();
const eventStream = getEventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();

View File

@ -42,6 +42,8 @@ const world_info_logic = {
AND_ALL: 3,
};
const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
let world_info = {};
let selected_world_info = [];
let world_names;
@ -95,6 +97,11 @@ class WorldInfoBuffer {
*/
#skew = 0;
/**
* @type {number} The starting depth of the global scan depth. Incremented by "min activations" feature to not repeat scans. When > 0 it means a complete scan was done up to #startDepth already, and `advanceScanPosition` was called.
*/
#startDepth = 0;
/**
* Initialize the buffer with the given messages.
* @param {string[]} messages Array of messages to add to the buffer
@ -137,7 +144,10 @@ class WorldInfoBuffer {
* @returns {string} A slice of buffer until the given depth (inclusive)
*/
get(entry) {
let depth = entry.scanDepth ?? (world_info_depth + this.#skew);
let depth = entry.scanDepth ?? this.getDepth();
if (depth <= this.#startDepth) {
return '';
}
if (depth < 0) {
console.error(`Invalid WI scan depth ${depth}. Must be >= 0`);
@ -149,7 +159,7 @@ class WorldInfoBuffer {
depth = MAX_SCAN_DEPTH;
}
let result = this.#depthBuffer.slice(0, depth).join('\n');
let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n');
if (this.#recurseBuffer.length > 0) {
result += '\n' + this.#recurseBuffer.join('\n');
@ -197,11 +207,19 @@ class WorldInfoBuffer {
}
/**
* Adds an increment to depth skew.
* Increments skew and sets startDepth to previous depth.
*/
addSkew() {
advanceScanPosition() {
this.#startDepth = this.getDepth();
this.#skew++;
}
/**
* @returns {number} Settings' depth + current skew.
*/
getDepth() {
return world_info_depth + this.#skew;
}
}
export function getWorldInfoSettings() {
@ -783,6 +801,11 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) {
afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value);
},
afterPaging: function () {
$('#world_popup_entries_list textarea[name="comment"]').each(function () {
initScrollHeight($(this));
});
},
});
if (typeof navigation === 'number' && Number(navigation) >= 0) {
@ -970,7 +993,7 @@ function getWorldEntry(name, data, entry) {
return;
}
const template = $('#entry_edit_template .world_entry').clone();
const template = WI_ENTRY_EDIT_TEMPLATE.clone();
template.data('uid', entry.uid);
template.attr('uid', entry.uid);
@ -982,10 +1005,10 @@ function getWorldEntry(name, data, entry) {
event.stopPropagation();
});
keyInput.on('input', function () {
keyInput.on('input', function (_, { skipReset } = {}) {
const uid = $(this).data('uid');
const value = String($(this).val());
resetScrollHeight(this);
!skipReset && resetScrollHeight(this);
data.entries[uid].key = value
.split(',')
.map((x) => x.trim())
@ -994,7 +1017,7 @@ function getWorldEntry(name, data, entry) {
setOriginalDataValue(data, uid, 'keys', data.entries[uid].key);
saveWorldInfo(name, data);
});
keyInput.val(entry.key.join(', ')).trigger('input');
keyInput.val(entry.key.join(', ')).trigger('input', { skipReset: true });
//initScrollHeight(keyInput);
// logic AND/NOT
@ -1008,7 +1031,6 @@ function getWorldEntry(name, data, entry) {
selectiveLogicDropdown.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
console.debug(`logic for ${entry.uid} set to ${value}`);
data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
saveWorldInfo(name, data);
@ -1118,10 +1140,10 @@ function getWorldEntry(name, data, entry) {
// keysecondary
const keySecondaryInput = template.find('textarea[name="keysecondary"]');
keySecondaryInput.data('uid', entry.uid);
keySecondaryInput.on('input', function () {
keySecondaryInput.on('input', function (_, { skipReset } = {}) {
const uid = $(this).data('uid');
const value = String($(this).val());
resetScrollHeight(this);
!skipReset && resetScrollHeight(this);
data.entries[uid].keysecondary = value
.split(',')
.map((x) => x.trim())
@ -1131,17 +1153,17 @@ function getWorldEntry(name, data, entry) {
saveWorldInfo(name, data);
});
keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input');
initScrollHeight(keySecondaryInput);
keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input', { skipReset: true });
//initScrollHeight(keySecondaryInput);
// comment
const commentInput = template.find('textarea[name="comment"]');
const commentToggle = template.find('input[name="addMemo"]');
commentInput.data('uid', entry.uid);
commentInput.on('input', function () {
commentInput.on('input', function (_, { skipReset } = {}) {
const uid = $(this).data('uid');
const value = $(this).val();
resetScrollHeight(this);
!skipReset && resetScrollHeight(this);
data.entries[uid].comment = value;
setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
@ -1160,8 +1182,8 @@ function getWorldEntry(name, data, entry) {
value ? commentContainer.show() : commentContainer.hide();
});
commentInput.val(entry.comment).trigger('input');
initScrollHeight(commentInput);
commentInput.val(entry.comment).trigger('input', { skipReset: true });
//initScrollHeight(commentInput);
commentToggle.prop('checked', true /* entry.addMemo */).trigger('input');
commentToggle.parent().hide();
@ -1196,6 +1218,8 @@ function getWorldEntry(name, data, entry) {
if (counter.data('first-run')) {
counter.data('first-run', false);
countTokensDebounced(counter, contentInput.val());
initScrollHeight(keyInput);
initScrollHeight(keySecondaryInput);
}
});
@ -1362,7 +1386,7 @@ function getWorldEntry(name, data, entry) {
}
const positionInput = template.find('select[name="position"]');
initScrollHeight(positionInput);
//initScrollHeight(positionInput);
positionInput.data('uid', entry.uid);
positionInput.on('click', function (event) {
// Prevent closing the drawer on clicking the input
@ -1419,7 +1443,6 @@ function getWorldEntry(name, data, entry) {
//new tri-state selector for constant/normal/disabled
const entryStateSelector = template.find('select[name="entryStateSelector"]');
entryStateSelector.data('uid', entry.uid);
console.log(entry.uid);
entryStateSelector.on('click', function (event) {
// Prevent closing the drawer on clicking the input
event.stopPropagation();
@ -1434,7 +1457,6 @@ function getWorldEntry(name, data, entry) {
setOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', true);
template.removeClass('disabledWIEntry');
console.debug('set to constant');
break;
case 'normal':
data.entries[uid].constant = false;
@ -1442,7 +1464,6 @@ function getWorldEntry(name, data, entry) {
setOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', false);
template.removeClass('disabledWIEntry');
console.debug('set to normal');
break;
case 'disabled':
data.entries[uid].constant = false;
@ -1450,7 +1471,6 @@ function getWorldEntry(name, data, entry) {
setOriginalDataValue(data, uid, 'enabled', false);
setOriginalDataValue(data, uid, 'constant', false);
template.addClass('disabledWIEntry');
console.debug('set to disabled');
break;
}
saveWorldInfo(name, data);
@ -1458,19 +1478,13 @@ function getWorldEntry(name, data, entry) {
});
const entryState = function () {
console.log(`constant: ${entry.constant}, disabled: ${entry.disable}`);
if (entry.constant === true) {
console.debug('found constant');
return 'constant';
} else if (entry.disable === true) {
console.debug('found disabled');
return 'disabled';
} else {
console.debug('found normal');
return 'normal';
}
};
template
.find(`select[name="entryStateSelector"] option[value=${entryState()}]`)
@ -1966,15 +1980,12 @@ async function getSortedEntries() {
switch (Number(world_info_character_strategy)) {
case world_info_insertion_strategy.evenly:
console.debug('WI using evenly');
entries = [...globalLore, ...characterLore].sort(sortFn);
break;
case world_info_insertion_strategy.character_first:
console.debug('WI using char first');
entries = [...characterLore.sort(sortFn), ...globalLore.sort(sortFn)];
break;
case world_info_insertion_strategy.global_first:
console.debug('WI using global first');
entries = [...globalLore.sort(sortFn), ...characterLore.sort(sortFn)];
break;
default:
@ -2009,7 +2020,6 @@ async function checkWorldInfo(chat, maxContext) {
const buffer = new WorldInfoBuffer(chat);
// Combine the chat
let minActivationMsgIndex = world_info_depth; // tracks chat index to satisfy `world_info_min_activations`
// Add the depth or AN if enabled
// Put this code here since otherwise, the chat reference is modified
@ -2102,8 +2112,6 @@ async function checkWorldInfo(chat, maxContext) {
const substituted = substituteParams(key);
const textToScan = buffer.get(entry);
console.debug(`${entry.uid}: ${substituted}`);
if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) {
console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`);
@ -2160,7 +2168,7 @@ async function checkWorldInfo(chat, maxContext) {
activatedNow.add(entry);
break primary;
}
} else { console.debug(`No active entries for logic checks for word: ${substituted}.`); }
}
}
}
}
@ -2225,15 +2233,14 @@ async function checkWorldInfo(chat, maxContext) {
// world_info_min_activations
if (!needsToScan && !token_budget_overflowed) {
if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) {
let over_max = false;
over_max = (
let over_max = (
world_info_min_activations_depth_max > 0 &&
minActivationMsgIndex > world_info_min_activations_depth_max
) || (minActivationMsgIndex >= chat.length);
buffer.getDepth() > world_info_min_activations_depth_max
) || (buffer.getDepth() > chat.length);
if (!over_max) {
needsToScan = true;
minActivationMsgIndex += 1;
buffer.addSkew();
needsToScan = true; // loop
buffer.advanceScanPosition();
}
}
}

View File

@ -539,6 +539,7 @@ body.reduced-motion #bg_custom {
margin-right: 5px;
z-index: 2000;
min-width: 55px;
justify-content: flex-end;
}
.panelControlBar .drag-grabber {
@ -1061,30 +1062,78 @@ select {
order: 3;
}
.slashCommandAutoComplete.defaultDark {
--ac-color-border: rgb(69 69 69);
--ac-color-background: rgb(32 32 32);
--ac-color-text: rgb(204 204 204);
--ac-color-matched-background: transparent;
--ac-color-matched-text: rgb(108 171 251);
--ac-color-selected-background: rgb(32 57 92);
--ac-color-selected-text: rgb(255 255 255);
--ac-color-hovered-background: rgb(43 45 46);
--ac-color-hovered-text: rgb(204 204 204);
}
.slashCommandAutoComplete.defaultLight {
--ac-color-border: rgb(200 200 200);
--ac-color-background: rgb(248 248 248);
--ac-color-text: rgb(59 59 59);
--ac-color-matched-background: transparent;
--ac-color-matched-text: rgb(61 104 188);
--ac-color-selected-background: rgb(232 232 232);
--ac-color-selected-text: rgb(0 0 0);
--ac-color-hovered-background: rgb(242 242 242);
--ac-color-hovered-text: rgb(59 59 59);
}
.slashCommandAutoComplete.defaultThemed {
--ac-color-border: var(--SmartThemeBorderColor);
--ac-color-background: var(--SmartThemeBlurTintColor);
--ac-color-text: var(--SmartThemeEmColor);
--ac-color-matched-background: transparent;
--ac-color-matched-text: var(--SmartThemeQuoteColor);
--ac-color-selected-background: color-mix(in srgb, rgb(128 128 128) 75%, var(--SmartThemeChatTintColor));
--ac-color-selected-text: var(--SmartThemeBodyColor);
--ac-color-hovered-background: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor));
--ac-color-hovered-text: var(--SmartThemeEmColor);
}
.slashCommandAutoComplete {
--ac-color-border: rgb(69, 69, 69);
--ac-color-background: rgb(32, 32, 32);
--ac-color-text: rgb(204 204 204);
--ac-color-matched-background: transparent;
--ac-color-matched-text: rgb(108, 171, 251);
--ac-color-selected-background: rgb(32, 57, 92);
--ac-color-selected-text: rgb(255, 255, 255);
--ac-color-hovered-background: rgb(43 45 46);
--ac-color-hovered-text: rgb(204 204 204);
--bottom: 50vh;
background: black;
border: 1px solid white;
color: rgb(204 204 204);
background: var(--ac-color-background);
backdrop-filter: blur(var(--SmartThemeBlurStrength));
border: 1px solid var(--ac-color-border);
border-radius: 3px;
color: var(--ac-color-text);
max-height: calc(95vh - var(--bottom));
list-style: none;
margin: 0px;
overflow: auto;
padding: 0px;
position: fixed;
position: absolute;
z-index: 10000;
> .item {
cursor: pointer;
padding: 3px;
text-shadow: none;
&:hover {
background-color: rgb(43 45 46);
background-color: var(--ac-color-hovered-background);
color: var(--ac-color-hovered-text);
}
&.selected {
background-color: #20395C;
color: white;
background-color: var(--ac-color-selected-background);
color: var(--ac-color-selected-text);
}
.matched {
color: #6CABFB;
background-color: var(--ac-color-matched-background);
color: var(--ac-color-matched-text);
font-weight: bold;
}
}

View File

@ -475,7 +475,15 @@ const autorunUrl = new URL(
const setupTasks = async function () {
const version = await getVersion();
console.log(`SillyTavern ${version.pkgVersion}` + (version.gitBranch ? ` '${version.gitBranch}' (${version.gitRevision})` : ''));
// Print formatted header
console.log();
console.log(`SillyTavern ${version.pkgVersion}`);
console.log(version.gitBranch ? `Running '${version.gitBranch}' (${version.gitRevision}) - ${version.commitDate}` : '');
if (version.gitBranch && !version.isLatest && ['staging', 'release'].includes(version.gitBranch)) {
console.log('INFO: Currently not on the latest commit.');
console.log(' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.');
}
console.log();
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.

View File

@ -36,7 +36,13 @@ async function parseCohereStream(jsonStream, request, response) {
} catch (e) {
break;
}
if (json.event_type === 'text-generation') {
if (json.message) {
const message = json.message || 'Unknown error';
const chunk = { error: { message: message } };
response.write(`data: ${JSON.stringify(chunk)}\n\n`);
partialData = '';
break;
} else if (json.event_type === 'text-generation') {
const text = json.text || '';
const chunk = { choices: [{ text }] };
response.write(`data: ${JSON.stringify(chunk)}\n\n`);

View File

@ -73,19 +73,31 @@ function getBasicAuthHeader(auth) {
/**
* Returns the version of the running instance. Get the version from the package.json file and the git revision.
* Also returns the agent string for the Horde API.
* @returns {Promise<{agent: string, pkgVersion: string, gitRevision: string | null, gitBranch: string | null}>} Version info object
* @returns {Promise<{agent: string, pkgVersion: string, gitRevision: string | null, gitBranch: string | null, commitDate: string | null, isLatest: boolean}>} Version info object
*/
async function getVersion() {
let pkgVersion = 'UNKNOWN';
let gitRevision = null;
let gitBranch = null;
let commitDate = null;
let isLatest = true;
try {
const pkgJson = require(path.join(process.cwd(), './package.json'));
pkgVersion = pkgJson.version;
if (!process['pkg'] && commandExistsSync('git')) {
const git = simpleGit();
gitRevision = await git.cwd(process.cwd()).revparse(['--short', 'HEAD']);
gitBranch = await git.cwd(process.cwd()).revparse(['--abbrev-ref', 'HEAD']);
const cwd = process.cwd();
gitRevision = await git.cwd(cwd).revparse(['--short', 'HEAD']);
gitBranch = await git.cwd(cwd).revparse(['--abbrev-ref', 'HEAD']);
commitDate = await git.cwd(cwd).show(['-s', '--format=%ci', gitRevision]);
const trackingBranch = await git.cwd(cwd).revparse(['--abbrev-ref', '@{u}']);
// Might fail, but exception is caught. Just don't run anything relevant after in this block...
const localLatest = await git.cwd(cwd).revparse(['HEAD']);
const remoteLatest = await git.cwd(cwd).revparse([trackingBranch]);
isLatest = localLatest === remoteLatest;
}
}
catch {
@ -93,7 +105,7 @@ async function getVersion() {
}
const agent = `SillyTavern:${pkgVersion}:Cohee#1207`;
return { agent, pkgVersion, gitRevision, gitBranch };
return { agent, pkgVersion, gitRevision, gitBranch, commitDate: commitDate?.trim() ?? null, isLatest };
}
/**