Merge branch 'dev' into pkg

This commit is contained in:
SillyLossy
2023-05-16 20:38:20 +03:00
33 changed files with 4542 additions and 841 deletions

16
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.5.3",
"version": "1.5.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.5.3",
"version": "1.5.4",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
@ -20,6 +20,7 @@
"exifreader": "^4.12.0",
"express": "^4.18.2",
"gpt3-tokenizer": "^1.1.5",
"ip-matching": "^2.1.2",
"ipaddr.js": "^2.0.1",
"jimp": "^0.22.7",
"jquery": "^3.6.4",
@ -34,6 +35,7 @@
"png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3",
"uniqolor": "^1.1.0",
"webp-converter": "2.3.2",
"ws": "^8.13.0",
"yargs": "^17.7.1"
@ -1657,6 +1659,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ip-matching": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ip-matching/-/ip-matching-2.1.2.tgz",
"integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg=="
},
"node_modules/ipaddr.js": {
"version": "2.0.1",
"license": "MIT",
@ -3084,6 +3091,11 @@
"version": "0.0.6",
"license": "MIT"
},
"node_modules/uniqolor": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.0.tgz",
"integrity": "sha512-j2XyokF24fsj+L5u6fbu4rM3RQc6VWJuAngYM2k0ZdG3yiVxt0smLkps2GmQIYqK8VkELGdM9vFU/HfOkK/zoQ=="
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",

View File

@ -11,6 +11,7 @@
"exifreader": "^4.12.0",
"express": "^4.18.2",
"gpt3-tokenizer": "^1.1.5",
"ip-matching": "^2.1.2",
"ipaddr.js": "^2.0.1",
"jimp": "^0.22.7",
"jquery": "^3.6.4",
@ -25,6 +26,7 @@
"png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3",
"uniqolor": "^1.1.0",
"webp-converter": "2.3.2",
"ws": "^8.13.0",
"yargs": "^17.7.1"
@ -40,7 +42,7 @@
"type": "git",
"url": "https://github.com/Cohee1207/SillyTavern.git"
},
"version": "1.5.3",
"version": "1.5.4",
"scripts": {
"start": "node server.js",
"pkg": "pkg ."

View File

@ -41,6 +41,7 @@
<script src="scripts/moment.min.js"></script>
<script src="scripts/cropper.min.js"></script>
<script src="scripts/jquery-cropper.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>
<link rel="stylesheet" type="text/css" href="style.css">
@ -366,6 +367,15 @@
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="100" />
</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="toggle-description justifyLeft">
Display a breakdown of the tokens used in the request.
</div>
</div>
<div class="range-block">
<div class="range-block-title">
Context Size (tokens)
@ -738,7 +748,7 @@
<div class="range-block-title">
Seed
</div>
<input type="number" id="seed_textgenerationwebui" class="text_pole" maxlength="100" />
<input type="number" id="seed_textgenerationwebui" class="text_pole wide100p" maxlength="100" />
</div>
</div>
<div id="openai_settings">
@ -961,7 +971,7 @@
</div>
<div id="rm_api_block" class="drawer-content closedDrawer">
<h3 id="title_api">API</h3>
<div class="flex-container">
<div class="flex-container flexFlowColumn">
<div id="main-API-selector-block">
<select id="main_api">
<option value="kobold">KoboldAI</option>
@ -1043,7 +1053,7 @@
</ol>
</span>
<div class="flex-container">
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1" maxlength="500" size="35" type="text">
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text">
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_novel"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@ -1065,24 +1075,31 @@
</div>
</div>
<div id="textgenerationwebui_api" style="display: none;position: relative;">
<div class="oobabooga_logo">
<div class="oobabooga_logo flex-container">
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
oobabooga/text-generation-webui
</a>
</div>
<span>
Make sure you run it with <tt>--api</tt> flag
</span>
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">
</div>
<div>
<div class="flex-container">
<div class="flex1">
<h4>Blocking API url</h4>
<h5>Example: http://127.0.0.1:5000/</h5>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole" maxlength="500" value="" autocomplete="off">
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p"
maxlength="500" value="">
</div>
<div class="flex1">
<h4>Streaming API url</h4>
<h5>Example: ws://127.0.0.1:5005/api/v1/stream</h5>
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole" maxlength="500" value="" autocomplete="off">
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="">
</div>
</div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
</form>
</div>
<div class="online_status4">
<div class="online_status_indicator4"></div>
<div class="online_status_text4">Not connected</div>
@ -1176,7 +1193,7 @@
<div id="advanced-formatting-button" class="drawer">
<div class="drawer-toggle">
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Reponse Formatting"></div>
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Response Formatting"></div>
</div>
<div class="drawer-content">
<h3>Advanced Formatting
@ -1207,6 +1224,15 @@
<input id="disable-start-formatting-checkbox" type="checkbox" />
Disable chat start formatting
</label>
<label class="checkbox_label" for="trim_sentences_checkbox">
<input id="trim_sentences_checkbox" type="checkbox" />
Trim Incomplete Sentences
</label>
<!-- Add margin since this is a child of above -->
<label style="margin-left: 1em;" class="checkbox_label" for="include_newline_checkbox">
<input id="include_newline_checkbox" type="checkbox" />
Include Newline
</label>
<div>
<h4>
Custom Chat Separator
@ -1247,7 +1273,7 @@
Input Sequence
</label>
<div>
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<input id="instruct_input_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
<div class="flex1">
@ -1255,7 +1281,7 @@
Output Sequence
</label>
<div>
<input id="instruct_output_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
</div>
@ -1265,7 +1291,7 @@
System Sequence
</label>
<div>
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<input id="instruct_system_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
<div class="flex1">
@ -1273,7 +1299,7 @@
Stop Sequence
</label>
<div>
<input id="instruct_stop_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
</div>
</div>
</div>
@ -1346,24 +1372,22 @@
</label>
</div>
</div>
<div id="anchors-block">
<div id="context-templates-block" style="display:none">
<h4>
Anchors Order
<a href="/notes#anchors" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
Context Templates
</h4>
<select id="anchor_order">
<option value="0">Character then Style</option>
<option value="1">Style then Character</option>
<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="anchor_checkbox">
<label for="character_anchor"><input id="character_anchor" type="checkbox" />
Character Anchor
</label>
<label for="style_anchor"><input id="style_anchor" type="checkbox" />
Style Anchor
</label>
<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>
@ -1373,12 +1397,20 @@
<div id="WI-SP-button" class="drawer">
<div class="drawer-toggle drawer-header">
<div class="drawer-icon fa-solid fa-book-atlas closedIcon " title="World Info & Soft Prompts"></div>
<div id="WIDrawerIcon" class="drawer-icon fa-solid fa-book-atlas closedIcon " title="World Info & Soft Prompts"></div>
</div>
<div id="WorldInfo" class="drawer-content closedDrawer">
<div id="WorldInfoheader" class="fa-solid fa-grip drag-grabber"></div>
<div id="WI_panel_pin_div" title="Locked = World Editor will stay open">
<input type="checkbox" id="WI_panel_pin">
<label for="WI_panel_pin">
<div class="unchecked fa-solid fa-lock-open "></div>
<div class="checked fa-solid fa-lock "></div>
</label>
</div>
<div class="drawer-content closedDrawer">
<div id="wi-holder">
<h3>
World Info
World Selector
<a href="/notes#worldinfo" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
@ -1440,6 +1472,41 @@
</label>
</div>
</div>
<div id="world_popup">
<hr>
<div id="world_popup_text">
<!-- <div id="world_cross" class="fa-solid fa-circle-xmark"></div> -->
<div id="world_popup_header" class="flex-container flexGap5">
<!-- Consider changing logo to something else -->
<div class="world_popup_logo_block">
<!-- <img src="img/book2.png" id="world_logo"> -->
<h3>
World Info Editor
<a href="/notes#worldinfoentry" class="notes-link" target="_blank"><span class="note-link-span">?</span></a>
</h3>
</div>
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand"></div>
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress"></div>
<!-- <div class="world_popup_expander">&nbsp;</div> -->
<form id="form_rename_world" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<input id="world_popup_name" name="world_popup_name" class="text_pole" maxlength="99" size="32" value="" autocomplete="off">
<input id="world_popup_name_button" class="menu_button" type="submit" value="Rename">
</form>
</div>
</div>
<div id="world_popup_entries_list">
</div>
<div id="world_popup_bottom_holder">
<div id="world_popup_new" class="menu_button">New Entry</div>
<!-- <span class="world_popup_expander">&nbsp;</span> -->
<div id="world_popup_export" class="menu_button">Export</div>
<div id="world_popup_delete" class="menu_button">Delete World</div>
</div>
</div>
</div>
<div id="softprompt_block">
<h4>Soft Prompt</h4>
@ -1663,6 +1730,10 @@
<input id="auto_scroll_chat_to_bottom" type="checkbox" />
Auto-scroll Chat
</label>
<label for="console_log_prompts">
<input id="console_log_prompts" type="checkbox" />
Log prompts to console
</label>
<label for="render_formulas">
<input id="render_formulas" type="checkbox" />
Render Formulas
@ -1670,6 +1741,14 @@
<span class="note-link-span">?</span>
</a>
</label>
</div>
</div>
<div name="NameAndAvatar" class="flex-container flexFlowColumn drawer25pWidth">
<div class="inline-drawer wide100p flexFlowColumn">
<div class="flex-container flexFlowColumn">
<h4>
Send on Enter
@ -1680,14 +1759,32 @@
<option value="1">Always enabled</option>
</select>
</div>
<div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
<b>Auto-swipe</b>
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label" for="auto_swipe">
<input id="auto_swipe" type="checkbox" />
Enabled
</label>
<div>Minimum generated message length</div>
<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">
<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>
</div>
</div>
<div name="NameAndAvatar" class="flex-container flexFlowColumn drawer25pWidth">
<div name="NameChanger">
<h4>Name</h4>
<div class="change_name">
<input id="your_name" name="your_name" placeholder="Enter your name" class="text_pole" maxlength="50" value="" autocomplete="off">
<input id="your_name" name="your_name" placeholder="Enter your name" class="text_pole wide100p" maxlength="50" value="" autocomplete="off">
<div id="your_name_button" class="menu_button fa-solid fa-check" title="Click to set a new User Name">
</div>
</div>
@ -1822,7 +1919,7 @@
<div id="tags_div">
<div class="tag_controls">
<input id="tagInput" class="text_pole tag_input" placeholder="Search / Create tags" maxlength="25" />
<input id="tagInput" class="text_pole tag_input wide100p margin0" placeholder="Search / Create tags" maxlength="25" />
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
</div>
<div id="tagList" class="tags"></div>
@ -1875,15 +1972,15 @@
</div>
<div class="inline-drawer-content">
<div name="group-metadata-controls" class="marginTopBot5">
<input id="rm_group_chat_name" class="text_pole" type="text" name="chat_name" placeholder="Chat Name (Optional)" maxlength="100" />
<input id="rm_group_chat_name" class="text_pole wide100p" type="text" name="chat_name" placeholder="Chat Name (Optional)" maxlength="100" />
<div id="group_tags_div" class="wide100p">
<div class="tag_controls">
<input id="groupTagInput" class="text_pole tag_input flex1" placeholder="Search / Create tags" maxlength="25" />
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
<input id="groupTagInput" class="text_pole tag_input flex1 margin0" placeholder="Search / Create tags" maxlength="25" />
<div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags"></div>
</div>
<div id="groupTagList" class="tags paddingTopBot5"></div>
</div>
<div id="rm_group_top_bar" class="flex-container spaceEvenly width100p">
<div id="rm_group_top_bar" class="flex-container spaceBetween width100p">
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5">
<div class="">
<div class="flex-container flexnowrap width100p whitespacenowrap">
@ -1925,21 +2022,6 @@
</div>
</div>
</div>
<div class="inline-drawer wide100p flexFlowColumn">
<div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
Add Members
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
<div id="rm_group_add_members_header">
<input id="rm_group_filter" class="text_pole" type="search" placeholder="Filter..." maxlength="100" />
<div id="group_fav_filter" class="menu_button fa-solid fa-ranking-star" title="Show only favorites"></div>
</div>
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
</div>
</div>
</div>
<div class="inline-drawer wide100p flexFlowColumn">
<div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
Current Members
@ -1951,6 +2033,21 @@
</div>
</div>
</div>
<div class="inline-drawer wide100p flexFlowColumn">
<div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
Add Members
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
<div id="rm_group_add_members_header">
<input id="rm_group_filter" class="text_pole margin0" type="search" placeholder="Filter..." maxlength="100" />
<div id="group_fav_filter" class="menu_button fa-solid fa-ranking-star margin0" title="Show only favorites"></div>
</div>
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
</div>
</div>
</div>
</div>
@ -1967,7 +2064,7 @@
<div id="rm_button_create" title="Create New Character" class="menu_button fa-solid fa-user-plus "></div>
<div id="character_import_button" title="Import Character from File" class="menu_button fa-solid fa-file-arrow-up "></div>
<div id="rm_button_group_chats" title="Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
<input id="character_search_bar" class="text_pole" type="search" placeholder="Search..." maxlength="50" />
<input id="character_search_bar" class="text_pole width100p" type="search" placeholder="Search..." maxlength="50" />
<select id="character_sort_order" title="Characters sorting order">
<option data-field="name" data-order="asc">A-Z</option>
<option data-field="name" data-order="desc">Z-A</option>
@ -2022,7 +2119,7 @@
</div>
<div>
<h3 id="character_popup_text_h3"></h3> - Advanced Defininitions
<h3 id="character_popup_text_h3"></h3> - Advanced Definitions
</div>
</div>
@ -2047,7 +2144,7 @@
<div id="talkativeness_div">
<h4>Talkativeness</h4>
<h5>How often the chracter speaks in &nbsp;<span class="warning">group chats!</span>
<h5>How often the character speaks in &nbsp;<span class="warning">group chats!</span>
</h5>
<input id="talkativeness_slider" name="talkativeness" type="range" min="0" max="1" step="0.05" value="0.5" form="form_create">
<div id="talkativeness_hint">
@ -2067,38 +2164,7 @@
<div id="character_popup_ok" class="menu_button">Save</div>
</div>
<div id="world_popup">
<div id="world_popup_text">
<div id="world_cross" class="fa-solid fa-circle-xmark"></div>
<div id="world_popup_header">
<!-- Consider changing logo to something else -->
<div class="world_popup_logo_block">
<img src="img/book2.png" id="world_logo">
<h3>
World Info Editor
<a href="/notes#worldinfoentry" class="notes-link" target="_blank"><span class="note-link-span">?</span></a>
</h3>
</div>
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand"></div>
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress"></div>
<div class="world_popup_expander">&nbsp;</div>
<form id="form_rename_world" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<input id="world_popup_name" name="world_popup_name" class="text_pole" maxlength="99" size="32" value="" autocomplete="off">
<input id="world_popup_name_button" class="menu_button" type="submit" value="Rename">
</form>
</div>
</div>
<div id="world_popup_entries_list">
</div>
<div id="world_popup_bottom_holder">
<div id="world_popup_new" class="menu_button">New Entry</div>
<span class="world_popup_expander">&nbsp;</span>
<div id="world_popup_export" class="menu_button">Export</div>
<div id="world_popup_delete" class="menu_button">Delete World</div>
</div>
</div>
<div id="shadow_select_chat_popup">
<div id="select_chat_popup">
<div id="select_chat_import"> <!-- import chat popup header -->
@ -2177,6 +2243,7 @@
</div>
</div>
<div id="entry_edit_template" class="template_element">
<div class="world_entry">
<form class="world_entry_form">
@ -2186,14 +2253,14 @@
<div class="world_entry_form_control">
<label for="key">
<h4>Keywords</h4>
<h5>Separate with commas</h5>
<h5>Split by commas</h5>
</label>
<textarea class="text_pole keyprimarytextpole" name="key" rows="1" placeholder="" maxlength="250"></textarea>
</div>
<div class="world_entry_form_control keysecondary">
<label for="keysecondary">
<h4>Secondary Required Keywords</h4>
<h5>Separate with commas</h5>
<h4>2nd Keywords</h4>
<h5>Split by commas</h5>
</label>
<textarea class="text_pole keysecondarytextpole" name="keysecondary" rows="1" placeholder="(secondary keywords here)" maxlength="250"></textarea>
</div>
@ -2327,6 +2394,8 @@
<span class="name_text">${characterName}</span>
<div class="mes_buttons">
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div>
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></div>
<div title="Copy" class="mes_copy fa-solid fa-copy "></div>
<div title="Edit" class="mes_edit fa-solid fa-pencil "></div>
</div>
@ -2415,12 +2484,18 @@
<div id="typing_indicator_template" class="template_element">
<div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div>
</div>
<div id="movingDivs"></div>
<div id="sheld">
<div id="sheldheader" class="fa-solid fa-grip drag-grabber"></div>
<!-- <div class="pull-tab"></div> -->
<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>
@ -2430,9 +2505,9 @@
<textarea id="send_textarea" placeholder="Not connected to API!" name="text"></textarea>
<div id="send_but_sheld">
<div id="loading_mes">
<div alt="" class="fa-solid fa-hourglass-half"></div>
<div title="Loading" class="fa-solid fa-hourglass-half"></div>
</div>
<div id="send_but" class="fa-solid fa-feather-pointed"></div>
<div id="send_but" class="fa-solid fa-feather-pointed" title="Send a message"></div>
</div>
</form>
</div>
@ -2449,6 +2524,10 @@
<i class="fa-lg fa-solid fa-bookmark"></i>
<span>Save bookmark</span>
</a>
<a id="option_toggle_AN">
<i class="fa-lg fa-solid fa-note-sticky"></i>
<span>Open Author's Note</span>
</a>
<a id="option_convert_to_group">
<i class="fa-lg fa-solid fa-people-arrows"></i>
<span>Convert to group</span>
@ -2487,6 +2566,9 @@
<div id="avatar_zoom_popupheader" class="fa-solid fa-grip drag-grabber"></div>
<img id="zoomed_avatar" src="">
</div>
<div id="rawPromptPopup" class="list-group">
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
</div>
</body>
</html>

View File

@ -393,26 +393,9 @@ _Lost API keys can't be restored! Make sure to keep it safe!_
## Anchors
Anchors are used to increase the length of messages.
There are two types of anchors: _Character Anchor_ and _Style Anchor_.
This feature is considered obsolete and has been removed.
_Character Anchor_ - affects the character played by the AI by motivating it to write longer messages.
Looks like: `[Elaborate speaker]`
_Style Anchor_ - affects the entire AI model, motivating the AI to write longer messages even when it is not acting as the character.
Looks like: `[Writing style: very long messages]`
***
Anchors Order sets the location of anchors in the prompt, the first anchor in the order is much further back in the context and thus has less influence than second.
The second anchor is only turned on after 8-12 messages, because when the chat still only has a few messages, the first anchor creates enough effect on its own.
Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages. For these cases, you can disable the anchors by unchecking their respective boxes.
_When using Pygmalion models these anchors are automatically disabled, since Pygmalion already generates long enough messages._
The use of the Author's Note extension is now a preferred way to add prompt injections of variable depth.
## Instruct Mode
@ -594,6 +577,8 @@ 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.*
SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches.
### Default settings:
@ -614,6 +599,7 @@ Next batches = 30 tokens
2. Character starts speaking for You.
3. &lt;|endoftext|&gt; token reached.
4. No text generated.
5. Stop sequence generated. (Instruct mode only)
## User Settings

File diff suppressed because it is too large Load Diff

View File

@ -30,11 +30,16 @@ import {
import { sortByCssOrder } from "./utils.js";
var NavToggle = document.getElementById("nav-toggle");
var RPanelPin = document.getElementById("rm_button_panel_pin");
var LPanelPin = document.getElementById("lm_button_panel_pin");
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
var WIPanelPin = document.getElementById("WI_panel_pin");
var RightNavPanel = document.getElementById("right-nav-panel");
var LeftNavPanel = document.getElementById("left-nav-panel")
var LeftNavPanel = document.getElementById("left-nav-panel");
var WorldInfo = document.getElementById("WorldInfo");
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
var AdvancedCharDefsPopup = document.getElementById("character_popup");
var ConfirmationPopup = document.getElementById("dialogue_popup");
var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox");
@ -101,10 +106,20 @@ function waitForElement(querySelector, timeout) {
waitForElement("#expression-image", 10000).then(function () {
dragElement(document.getElementById("expression-holder"));
dragElement(document.getElementById("floatingPrompt"));
}).catch(() => {
console.log("expression holder not loaded yet");
});
waitForElement("#floatingPrompt", 10000).then(function () {
dragElement(document.getElementById("floatingPrompt"));
}).catch(() => {
console.log("floating prompt box not loaded yet");
});
// Device detection
const deviceInfo = await getDeviceInfo();
@ -412,23 +427,18 @@ function OpenNavPanels() {
if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) {
//console.log("RA -- clicking right nav to open");
$("#rightNavDrawerIcon").click();
} else {
/* console.log('didnt see reason to open right nav on load: R-nav locked? ' +
LoadLocalBool("NavLockOn")
+ ' R-nav was open before? ' +
LoadLocalBool("NavOpened" == true)); */
}
//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();
} else {
/* console.log('didnt see reason to open left nav on load: L-Nav Locked? ' +
LoadLocalBool("LNavLockOn")
+ ' L-nav was open before? ' +
LoadLocalBool("LNavOpened" == true)); */
}
//auto-open WI if locked and previously open
if (LoadLocalBool("WINavLockOn") == true && LoadLocalBool("WINavOpened") == true) {
console.log("RA -- clicking WI to open");
$("#WIDrawerIcon").click();
}
}
@ -438,10 +448,12 @@ dragElement(document.getElementById("sheld"));
dragElement(document.getElementById("left-nav-panel"));
dragElement(document.getElementById("right-nav-panel"));
dragElement(document.getElementById("avatar_zoom_popup"));
dragElement(document.getElementById("WorldInfo"));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader"
// if present, the header is where you move the DIV from, but this overrides everything else:
@ -452,6 +464,7 @@ function dragElement(elmnt) {
}
function dragMouseDown(e) {
//console.log(e);
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
@ -546,6 +559,7 @@ function dragElement(elmnt) {
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
$(elmnt).css("bottom", "unset");
$(elmnt).css("right", "unset");
$(elmnt).css("margin", "unset");
/* console.log(`
offsetLeft: ${elmnt.offsetLeft}, offsetTop: ${elmnt.offsetTop}
@ -614,7 +628,7 @@ $("document").ready(function () {
if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
$(RightNavPanel).slideToggle(200, "swing");
$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
//$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
$(RightNavPanel).toggleClass('openDrawer closedDrawer');
}
}
@ -630,12 +644,30 @@ $("document").ready(function () {
if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
$(LeftNavPanel).slideToggle(200, "swing");
$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
//$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
$(LeftNavPanel).toggleClass('openDrawer closedDrawer');
}
}
});
$(WIPanelPin).on("click", function () {
SaveLocal("WINavLockOn", $(WIPanelPin).prop("checked"));
if ($(WIPanelPin).prop("checked") == true) {
console.log('adding pin class to WI');
$(WorldInfo).addClass('pinnedOpen');
} else {
console.log('removing pin class from WI');
$(WorldInfo).removeClass('pinnedOpen');
if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
console.log('closing WI after lock removal');
$(WorldInfo).slideToggle(200, "swing");
//$(WorldInfoDrawerIcon).toggleClass('openIcon closedIcon');
$(WorldInfo).toggleClass('openDrawer closedDrawer');
}
}
});
// read the state of right Nav Lock and apply to rightnav classlist
$(RPanelPin).prop('checked', LoadLocalBool("NavLockOn"));
if (LoadLocalBool("NavLockOn") == true) {
@ -657,6 +689,18 @@ $("document").ready(function () {
$(LeftNavPanel).addClass('pinnedOpen');
}
// read the state of left Nav Lock and apply to leftnav classlist
$(WIPanelPin).prop('checked', LoadLocalBool("WINavLockOn"));
if (LoadLocalBool("WINavLockOn") == true) {
//console.log('setting pin class via local var');
$(WorldInfo).addClass('pinnedOpen');
}
if ($(WIPanelPin).prop('checked' == true)) {
console.log('setting pin class via checkbox state');
$(WorldInfo).addClass('pinnedOpen');
}
//save state of Right nav being open or closed
$("#rightNavDrawerIcon").on("click", function () {
if (!$("#rightNavDrawerIcon").hasClass('openIcon')) {
@ -671,6 +715,13 @@ $("document").ready(function () {
} else { SaveLocal('LNavOpened', 'false'); }
});
//save state of Left nav being open or closed
$("#WorldInfo").on("click", function () {
if (!$("#WorldInfo").hasClass('openIcon')) {
SaveLocal('WINavOpened', 'true');
} else { SaveLocal('WINavOpened', 'false'); }
});
var chatbarInFocus = false;
$('#send_textarea').focus(function () {
chatbarInFocus = true;

View File

@ -0,0 +1,71 @@
/* Polyfill indexOf. */
var indexOf;
if (typeof Array.prototype.indexOf === 'function') {
indexOf = function (haystack, needle) {
return haystack.indexOf(needle);
};
} else {
indexOf = function (haystack, needle) {
var i = 0, length = haystack.length, idx = -1, found = false;
while (i < length && !found) {
if (haystack[i] === needle) {
idx = i;
found = true;
}
i++;
}
return idx;
};
};
/* Polyfill EventEmitter. */
var EventEmitter = function () {
this.events = {};
};
EventEmitter.prototype.on = function (event, listener) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
};
EventEmitter.prototype.removeListener = function (event, listener) {
var idx;
if (typeof this.events[event] === 'object') {
idx = indexOf(this.events[event], listener);
if (idx > -1) {
this.events[event].splice(idx, 1);
}
}
};
EventEmitter.prototype.emit = function (event) {
var i, listeners, length, args = [].slice.call(arguments, 1);
if (typeof this.events[event] === 'object') {
listeners = this.events[event].slice();
length = listeners.length;
for (i = 0; i < length; i++) {
listeners[i].apply(this, args);
}
}
};
EventEmitter.prototype.once = function (event, listener) {
this.on(event, function g () {
this.removeListener(event, g);
listener.apply(this, arguments);
});
};
export { EventEmitter }

View File

@ -1,4 +1,4 @@
import { callPopup, saveSettings, saveSettingsDebounced } from "../script.js";
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced } from "../script.js";
import { isSubsetOf } from "./utils.js";
export {
getContext,
@ -162,6 +162,7 @@ async function connectToApi(baseUrl) {
const data = await getExtensionsResult.json();
modules = data.modules;
await activateExtensions();
eventSource.emit(event_types.EXTRAS_CONNECTED, modules);
}
updateStatus(getExtensionsResult.ok);

View File

@ -96,6 +96,7 @@ $(document).ready(function () {
function addSendPictureButton() {
const sendButton = document.createElement('div');
sendButton.id = 'send_picture';
sendButton.title = 'Send a picture to chat';
sendButton.classList.add('fa-solid');
$(sendButton).hide();
$(sendButton).on('click', () => $('#img_file').click());

View File

@ -29,7 +29,7 @@ async function doDiceRoll() {
function addDiceRollButton() {
const buttonHtml = `
<div id="roll_dice" class="fa-solid fa-dice" /></div>
<div id="roll_dice" class="fa-solid fa-dice" title="Roll the dice" /></div>
`;
const dropdownHtml = `
<div id="dice_dropdown">

View File

@ -1,5 +1,6 @@
import { chat_metadata, saveSettingsDebounced } from "../../../script.js";
import { extension_settings, getContext } from "../../extensions.js";
import { registerSlashCommand } from "../../slash-commands.js";
import { debounce } from "../../utils.js";
export { MODULE_NAME };
@ -19,6 +20,10 @@ const metadata_keys = {
position: 'note_position',
}
function setNoteCommand(_, text) {
$('#extension_floating_prompt').val(text).trigger('input');
}
async function onExtensionFloatingPromptInput() {
chat_metadata[metadata_keys.prompt] = $(this).val();
saveMetadataDebounced();
@ -100,7 +105,7 @@ async function moduleWorker() {
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
context.setExtensionPrompt(MODULE_NAME, '');
$('#extension_floating_counter').text('No');
$('#extension_floating_counter').text('(disabled)');
return;
}
@ -110,25 +115,28 @@ async function moduleWorker() {
const shouldAddPrompt = messagesTillInsertion == 0;
const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
$('#extension_floating_counter').text(shouldAddPrompt ? 'This' : messagesTillInsertion);
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
}
(function () {
function addExtensionsSettings() {
const settingsHtml = `
<div class="floating_prompt_settings">
<div id="floatingPrompt" class="drawer-content flexGap5">
<div id="floatingPromptheader" class="fa-solid fa-grip drag-grabber"></div>
<div name="floatingPromptHolder">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Author's Note / Character Bias</b>
<div id="ANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
<b>Author's Note</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small>
Your notes are saved <b>PER CHAT</b>. When you start a new chat, you'll see the default / empty note.<br>
Saving a bookmark will copy your note to a bookmark chat. Making changes to it won't update the note in a parent chat.<br>
<b>Unique to this chat</b>.<br>
Bookmarks inherit the Note from their parent, and can be changed individually after that.<br>
</small>
<label for="extension_floating_prompt">Append the following text:</label>
<textarea id="extension_floating_prompt" class="text_pole" rows="8"></textarea>
<div class="floating_prompt_radio_group">
<label>
<input type="radio" name="extension_floating_position" value="0" />
@ -136,31 +144,38 @@ async function moduleWorker() {
</label>
<label>
<input type="radio" name="extension_floating_position" value="1" />
In-chat
In-chat @ Depth <input id="extension_floating_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
</label>
</div>
<label for="extension_floating_interval">Every N messages <b>you</b> send (set to 0 to disable):</label>
<input id="extension_floating_interval" class="text_pole" type="number" min="0" max="999" />
<label for="extension_floating_interval">Insertion depth (for in-chat positioning):</label>
<input id="extension_floating_depth" class="text_pole" type="number" min="0" max="99" />
<span>Appending to the prompt in next: <span id="extension_floating_counter">No</span> message(s)</span>
<!--<label for="extension_floating_interval">In-Chat Insertion Depth</label>-->
<label for="extension_floating_interval">Insertion Frequency</label>
<input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="999" /><small> (0 = Disable)</small>
<br>
<span>User inputs until next insertion: <span id="extension_floating_counter">(disabled)</span></span>
</div>
</div>
<hr class="sysHR">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Default note for new chats</b>
<div id="defaultANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
<b>Default Author's Note</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="extension_floating_default">Default Author's Note</label>
<small>Will be automatically added as the Author's Note for all new chats.</small>
<textarea id="extension_floating_default" class="text_pole" rows="8"
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
</div>
</div>
</div>
</div>
`;
$('#extensions_settings').append(settingsHtml);
$('#movingDivs').append(settingsHtml);
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
@ -170,4 +185,5 @@ async function moduleWorker() {
addExtensionsSettings();
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
registerSlashCommand('note', setNoteCommand, [], " sets an author's note for the currently selected chat", true, true);
})();

View File

@ -1,4 +1,26 @@
.floating_prompt_settings {
#floatingPrompt {
overflow-y: auto;
max-width: 90svw;
max-height: 90svh;
min-width: 100px;
min-height: 100px;
border-radius: 10px;
border: 1px solid var(--white30a);
position: fixed;
padding: 10px;
display: none;
flex-direction: column;
box-shadow: 0 0 10px var(--black70a);
z-index: 3000;
left: 0;
top: 0;
margin: 0;
right: unset;
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
}
.floating_prompt_radio_group {
display: flex;
flex-direction: column;
}
@ -12,8 +34,3 @@
font-size: calc(var(--mainFontSize) * 0.9);
line-height: 1.2;
}
.floating_prompt_radio_group {
display: flex;
flex-direction: column;
}

View File

@ -3,9 +3,13 @@ import {
saveSettingsDebounced,
systemUserName,
hideSwipeButtons,
showSwipeButtons
showSwipeButtons,
callPopup,
getRequestHeaders,
event_types,
eventSource
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, defaultRequestArgs } from "../../extensions.js";
import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
export { MODULE_NAME };
@ -94,6 +98,9 @@ const defaultSettings = {
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: '',
horde: false,
horde_nsfw: false,
}
async function loadSettings() {
@ -107,11 +114,10 @@ async function loadSettings() {
$('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input');
$('#sd_width').val(extension_settings.sd.width).trigger('input');
$('#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);
await Promise.all([loadSamplers(), loadModels()]);
}
function onScaleInput() {
@ -155,10 +161,29 @@ function onHeightInput() {
saveSettingsDebounced();
}
async function onHordeInput() {
extension_settings.sd.model = null;
extension_settings.sd.sampler = null;
extension_settings.sd.horde = !!$(this).prop('checked');
saveSettingsDebounced();
await Promise.all([loadModels(), loadSamplers()]);
}
async function onHordeNsfwInput() {
extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
saveSettingsDebounced();
}
async function onModelChange() {
extension_settings.sd.model = $('#sd_model').find(':selected').val();
saveSettingsDebounced();
if (extension_settings.sd.horde == false) {
await updateExtrasRemoteModel();
}
}
async function updateExtrasRemoteModel() {
const url = new URL(getApiUrl());
url.pathname = '/api/image/model';
const getCurrentModelResult = await fetch(url, {
@ -173,13 +198,14 @@ async function onModelChange() {
}
async function loadSamplers() {
const url = new URL(getApiUrl());
url.pathname = '/api/image/samplers';
const result = await fetch(url, defaultRequestArgs);
$('#sd_sampler').empty();
let samplers = [];
if (result.ok) {
const data = await result.json();
const samplers = data.samplers;
if (extension_settings.sd.horde) {
samplers = await loadHordeSamplers();
} else {
samplers = await loadExtrasSamplers();
}
for (const sampler of samplers) {
const option = document.createElement('option');
@ -189,9 +215,77 @@ async function loadSamplers() {
$('#sd_sampler').append(option);
}
}
async function loadHordeSamplers() {
const result = await fetch('/horde_samplers', {
method: 'POST',
headers: getRequestHeaders(),
});
if (result.ok) {
const data = await result.json();
return data;
}
return [];
}
async function loadExtrasSamplers() {
if (!modules.includes('sd')) {
return [];
}
const url = new URL(getApiUrl());
url.pathname = '/api/image/samplers';
const result = await fetch(url, defaultRequestArgs);
if (result.ok) {
const data = await result.json();
return data.samplers;
}
return [];
}
async function loadModels() {
$('#sd_model').empty();
let models = [];
if (extension_settings.sd.horde) {
models = await loadHordeModels();
} else {
models = await loadExtrasModels();
}
for (const model of models) {
const option = document.createElement('option');
option.innerText = model.text;
option.value = model.value;
option.selected = model.value === extension_settings.sd.model;
$('#sd_model').append(option);
}
}
async function loadHordeModels() {
const result = await fetch('/horde_models', {
method: 'POST',
headers: getRequestHeaders(),
});
if (result.ok) {
const data = await result.json();
const models = data.map(x => ({ value: x.name, text: `${x.name} (ETA: ${x.eta}s, Queue: ${x.queued}, Workers: ${x.count})` }));
return models;
}
return [];
}
async function loadExtrasModels() {
if (!modules.includes('sd')) {
return [];
}
const url = new URL(getApiUrl());
url.pathname = '/api/image/model';
const getCurrentModelResult = await fetch(url, defaultRequestArgs);
@ -206,16 +300,11 @@ async function loadModels() {
if (getModelsResult.ok) {
const data = await getModelsResult.json();
const models = data.models;
const view_models = data.models.map(x => ({ value: x, text: x }));
return view_models;
}
for (const model of models) {
const option = document.createElement('option');
option.innerText = model;
option.value = model;
option.selected = model === extension_settings.sd.model;
$('#sd_model').append(option);
}
}
return [];
}
function getGenerationType(prompt) {
@ -257,6 +346,14 @@ async function generatePicture(_, trigger) {
return;
}
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');
return;
}
extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val();
extension_settings.sd.model = $('#sd_model').find(':selected').val();
trigger = trigger.trim();
const generationMode = getGenerationType(trigger);
console.log('Generation mode', generationMode, 'triggered with', trigger);
@ -279,6 +376,22 @@ async function generatePicture(_, trigger) {
console.log('Processed Stable Diffusion prompt:', prompt);
if (extension_settings.sd.horde) {
await generateHordeImage(prompt);
} else {
await generateExtrasImage(prompt);
}
} catch (err) {
console.trace(err);
throw new Error('SD prompt text generation failed.')
}
finally {
context.activateSendButtons();
showSwipeButtons();
}
}
async function generateExtrasImage(prompt) {
const url = new URL(getApiUrl());
url.pathname = '/api/image';
const result = await fetch(url, {
@ -295,7 +408,6 @@ async function generatePicture(_, trigger) {
negative_prompt: extension_settings.sd.negative_prompt,
restore_faces: true,
face_restoration_model: 'GFPGAN',
}),
});
@ -303,14 +415,35 @@ async function generatePicture(_, trigger) {
const data = await result.json();
const base64Image = `data:image/jpeg;base64,${data.image}`;
sendMessage(prompt, base64Image);
} else {
callPopup('Image generation has failed. Please try again.', 'text');
}
} catch (err) {
console.trace(err);
throw new Error('SD prompt text generation failed.')
}
finally {
context.activateSendButtons();
showSwipeButtons();
async function generateHordeImage(prompt) {
const result = await fetch('/horde_generateimage', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
prompt: prompt,
sampler: extension_settings.sd.sampler,
steps: extension_settings.sd.steps,
scale: extension_settings.sd.scale,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
prompt_prefix: extension_settings.sd.prompt_prefix,
negative_prompt: extension_settings.sd.negative_prompt,
model: extension_settings.sd.model,
nsfw: extension_settings.sd.horde_nsfw,
}),
});
if (result.ok) {
const data = await result.text();
const base64Image = `data:image/webp;base64,${data}`;
sendMessage(prompt, base64Image);
} else {
callPopup('Image generation has failed. Please try again.', 'text');
}
}
@ -336,7 +469,7 @@ async function sendMessage(prompt, image) {
function addSDGenButtons() {
const buttonHtml = `
<div id="sd_gen" class="fa-solid fa-paintbrush" /></div>
<div id="sd_gen" class="fa-solid fa-paintbrush" title="Trigger Stable Diffusion" /></div>
`;
const waitButtonHtml = `
@ -386,16 +519,6 @@ function addSDGenButtons() {
async function moduleWorker() {
const context = getContext();
/* if (context.onlineStatus === 'no_connection') {
$('#sd_gen').hide(200);
} else if ($("#send_but").css('display') === 'flex') {
$('#sd_gen').show(200);
$("#sd_gen_wait").hide(200);
} else {
$('#sd_gen').hide(200);
$("#sd_gen_wait").show(200);
} */
context.onlineStatus === 'no_connection'
? $('#sd_gen').hide(200)
: $('#sd_gen').show(200)
@ -444,6 +567,16 @@ jQuery(async () => {
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands 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>
<label class="checkbox_label">
<input id="sd_horde" type="checkbox" />
Use Stable Horde
</label>
<label style="margin-left:1em;" class="checkbox_label">
<input id="sd_horde_nsfw" type="checkbox" />
Allow NSFW images
</label>
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
@ -472,12 +605,17 @@ jQuery(async () => {
$('#sd_negative_prompt').on('input', onNegativePromptInput);
$('#sd_width').on('input', onWidthInput);
$('#sd_height').on('input', onHeightInput);
$('#sd_horde').on('input', onHordeInput);
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
$('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($("#sd_prompt_prefix"));
initScrollHeight($("#sd_negative_prompt"));
})
await loadSettings();
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
await Promise.all([loadSamplers(), loadModels()]);
});
await loadSettings();
});

View File

@ -1,10 +1,10 @@
{
"display_name": "Stable Diffusion",
"loading_order": 10,
"requires": [
"requires": [],
"optional": [
"sd"
],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",

View File

@ -1,4 +1,4 @@
.sd_settings label {
.sd_settings label:not(.checkbox_label) {
display: block;
}
@ -16,7 +16,6 @@
display: flex;
align-items: center;
justify-content: center;
}
#sd_gen:hover {

View File

@ -7,6 +7,7 @@ class ElevenLabsTtsProvider {
settings
voices = []
separator = ' ... ... ... '
get settings() {
return this.settings

View File

@ -1,4 +1,4 @@
import { callPopup, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
import { callPopup, cancelTtsPlay, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
import { extension_settings, getContext } from '../../extensions.js'
import { getStringHash } from '../../utils.js'
import { ElevenLabsTtsProvider } from './elevenlabs.js'
@ -24,9 +24,47 @@ let ttsProviders = {
let ttsProvider
let ttsProviderName
async function onNarrateOneMessage() {
cancelTtsPlay();
const context = getContext();
const id = $(this).closest('.mes').attr('mesid');
const message = context.chat[id];
if (!message) {
return;
}
currentTtsJob = null;
audioElement.pause();
audioElement.currentTime = 0;
ttsJobQueue.splice(0, ttsJobQueue.length);
audioJobQueue.splice(0, audioJobQueue.length);
ttsJobQueue.push(message);
moduleWorker();
}
let isWorkerBusy = false;
async function moduleWorkerWrapper() {
// Don't touch me I'm busy...
if (isWorkerBusy) {
return;
}
// I'm free. Let's update!
try {
isWorkerBusy = true;
await moduleWorker();
}
finally {
isWorkerBusy = false;
}
}
async function moduleWorker() {
// Primarily determinign when to add new chat to the TTS queue
const enabled = $('#tts_enabled').is(':checked')
$('body').toggleClass('tts', enabled);
if (!enabled) {
return
}
@ -163,7 +201,7 @@ function onAudioControlClicked() {
function addAudioControl() {
$('#send_but_sheld').prepend('<div id="tts_media_control"/>')
$('#tts_media_control').on('click', onAudioControlClicked)
$('#tts_media_control').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
audioControl = document.getElementById('tts_media_control')
updateUiAudioPlayState()
}
@ -247,7 +285,8 @@ async function processTtsQueue() {
const special_quotes = /[“”]/g; // Extend this regex to include other special quotes
text = text.replace(special_quotes, '"');
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
text = matches ? matches.join(' ... ... ... ') : text;
const partJoiner = (ttsProvider?.separator || ' ... ');
text = matches ? matches.join(partJoiner) : text;
}
console.log(`TTS: ${text}`)
const char = currentTtsJob.name
@ -295,6 +334,7 @@ function loadSettings() {
)
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only)
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only)
$('body').toggleClass('tts', extension_settings.tts.enabled);
}
const defaultSettings = {
@ -506,10 +546,11 @@ $(document).ready(function () {
$('#tts_provider').append($("<option />").val(provider).text(provider))
}
$('#tts_provider').on('change', onTtsProviderChange)
$(document).on('click', '.mes_narrate', onNarrateOneMessage);
}
addExtensionControls() // No init dependencies
loadSettings() // Depends on Extension Controls and loadTtsProvider
loadTtsProvider(extension_settings.tts.currentProvider) // No dependencies
addAudioControl() // Depends on Extension Controls
setInterval(moduleWorker, UPDATE_INTERVAL) // Init depends on all the things
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL) // Init depends on all the things
})

View File

@ -9,6 +9,7 @@ class SileroTtsProvider {
settings
voices = []
separator = ' .. '
defaultSettings = {
provider_endpoint: "http://localhost:8001/tts",

View File

@ -21,6 +21,7 @@ class SystemTtsProvider {
fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
settings
voices = []
separator = ' ... '
defaultSettings = {
voiceMap: {},

View File

@ -101,6 +101,7 @@ const default_settings = {
openai_model: 'gpt-3.5-turbo',
jailbreak_system: false,
reverse_proxy: '',
oai_breakdown: false,
};
const oai_settings = {
@ -125,6 +126,7 @@ const oai_settings = {
openai_model: 'gpt-3.5-turbo',
jailbreak_system: false,
reverse_proxy: '',
oai_breakdown: false,
};
let openai_setting_names;
@ -205,22 +207,10 @@ function setOpenAIMessageExamples(mesExamplesArray) {
}
}
function generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, bottomAnchorThreshold, anchorBottom) {
function generateOpenAIPromptCache() {
openai_msgs = openai_msgs.reverse();
openai_msgs.forEach(function (msg, i, arr) {//For added anchors and others
openai_msgs.forEach(function (msg, i, arr) {
let item = msg["content"];
if (i === openai_msgs.length - topAnchorDepth) {
let personalityAndAnchor = [charPersonality, anchorTop].filter(x => x).join(' ');
if (personalityAndAnchor) {
item = `[${name2} is ${personalityAndAnchor}]\n${item}`;
}
}
if (i === openai_msgs.length - 1 && openai_msgs.length > bottomAnchorThreshold && msg.role === "user") {//For add anchor in end
if (anchorBottom) {
item = anchorBottom + "\n" + item;
}
}
msg["content"] = item;
openai_msgs[i] = msg;
});
@ -310,23 +300,25 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
let whole_prompt = getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate);
// Join by a space and replace placeholders with real user/char names
storyString = substituteParams(whole_prompt.join(" ")).replace(/\r/gm, '').trim();
storyString = substituteParams(whole_prompt.join("\n")).replace(/\r/gm, '').trim();
let prompt_msg = { "role": "system", "content": storyString }
let examples_tosend = [];
let openai_msgs_tosend = [];
// todo: static value, maybe include in the initial context calculation
const handler_instance = new TokenHandler(countTokens);
let new_chat_msg = { "role": "system", "content": "[Start a new chat]" };
let start_chat_count = countTokens([new_chat_msg], true);
let start_chat_count = handler_instance.count([new_chat_msg], true, 'start_chat');
await delay(1);
let total_count = countTokens([prompt_msg], true) + start_chat_count;
let total_count = handler_instance.count([prompt_msg], true, 'prompt') + start_chat_count;
await delay(1);
if (bias && bias.trim().length) {
let bias_msg = { "role": "system", "content": bias.trim() };
openai_msgs.push(bias_msg);
total_count += countTokens([bias_msg], true);
total_count += handler_instance.count([bias_msg], true, 'bias');
await delay(1);
}
@ -343,13 +335,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
openai_msgs.push(group_nudge);
// add a group nudge count
let group_nudge_count = countTokens([group_nudge], true);
let group_nudge_count = handler_instance.count([group_nudge], true, 'nudge');
await delay(1);
total_count += group_nudge_count;
// recount tokens for new start message
total_count -= start_chat_count
start_chat_count = countTokens([new_chat_msg], true);
handler_instance.uncount(start_chat_count, 'start_chat');
start_chat_count = handler_instance.count([new_chat_msg], true);
await delay(1);
total_count += start_chat_count;
}
@ -358,7 +351,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
const jailbreakMessage = { "role": "system", "content": substituteParams(oai_settings.jailbreak_prompt) };
openai_msgs.push(jailbreakMessage);
total_count += countTokens([jailbreakMessage], true);
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
await delay(1);
}
@ -366,7 +359,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
const impersonateMessage = { "role": "system", "content": substituteParams(oai_settings.impersonation_prompt) };
openai_msgs.push(impersonateMessage);
total_count += countTokens([impersonateMessage], true);
total_count += handler_instance.count([impersonateMessage], true, 'impersonate');
await delay(1);
}
@ -389,12 +382,12 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
examples_tosend.push(example);
}
}
total_count += countTokens(examples_tosend, true);
total_count += handler_instance.count(examples_tosend, true, 'examples');
await delay(1);
// go from newest message to oldest, because we want to delete the older ones from the context
for (let j = openai_msgs.length - 1; j >= 0; j--) {
let item = openai_msgs[j];
let item_count = countTokens(item, true);
let item_count = handler_instance.count(item, true, 'conversation');
await delay(1);
// If we have enough space for this message, also account for the max assistant reply size
if ((total_count + item_count) < (this_max_context - oai_settings.openai_max_tokens)) {
@ -403,13 +396,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
}
else {
// early break since if we still have more messages, they just won't fit anyway
handler_instance.uncount(item_count, 'conversation');
break;
}
}
} else {
for (let j = openai_msgs.length - 1; j >= 0; j--) {
let item = openai_msgs[j];
let item_count = countTokens(item, true);
let item_count = handler_instance.count(item, true, 'conversation');
await delay(1);
// If we have enough space for this message, also account for the max assistant reply size
if ((total_count + item_count) < (this_max_context - oai_settings.openai_max_tokens)) {
@ -418,11 +412,12 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
}
else {
// early break since if we still have more messages, they just won't fit anyway
handler_instance.uncount(item_count, 'conversation');
break;
}
}
console.log(total_count);
//console.log(total_count);
// each example block contains multiple user/bot messages
for (let example_block of openai_msgs_example) {
@ -432,7 +427,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
example_block = [new_chat_msg, ...example_block];
// add the block only if there is enough space for all its messages
const example_count = countTokens(example_block, true);
const example_count = handler_instance.count(example_block, true, 'examples');
await delay(1);
if ((total_count + example_count) < (this_max_context - oai_settings.openai_max_tokens)) {
examples_tosend.push(...example_block)
@ -440,6 +435,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
}
else {
// early break since more examples probably won't fit anyway
handler_instance.uncount(example_count, 'examples');
break;
}
}
@ -451,10 +447,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
openai_msgs_tosend.reverse();
openai_msgs_tosend = [prompt_msg, ...examples_tosend, new_chat_msg, ...openai_msgs_tosend]
console.log("We're sending this:")
console.log(openai_msgs_tosend);
console.log(`Calculated the total context to be ${total_count} tokens`);
return openai_msgs_tosend;
//console.log("We're sending this:")
//console.log(openai_msgs_tosend);
//console.log(`Calculated the total context to be ${total_count} tokens`);
handler_instance.log();
return [
openai_msgs_tosend,
oai_settings.oai_breakdown ? handler_instance.counts : false,
];
}
function getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate) {
@ -469,7 +469,7 @@ function getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefor
whole_prompt = [nsfw_toggle_prompt, oai_settings.main_prompt, enhance_definitions_prompt + "\n\n" + wiBefore, storyString, wiAfter, extensionPrompt];
}
else {
whole_prompt = [oai_settings.main_prompt, nsfw_toggle_prompt, enhance_definitions_prompt + "\n\n" + wiBefore, storyString, wiAfter, extensionPrompt];
whole_prompt = [oai_settings.main_prompt, nsfw_toggle_prompt, enhance_definitions_prompt, "\n", wiBefore, storyString, wiAfter, extensionPrompt].filter(elem => elem);
}
}
return whole_prompt;
@ -616,6 +616,39 @@ async function calculateLogitBias() {
}
}
class TokenHandler {
constructor(countTokenFn) {
this.countTokenFn = countTokenFn;
this.counts = {
'start_chat': 0,
'prompt': 0,
'bias': 0,
'nudge': 0,
'jailbreak': 0,
'impersonate': 0,
'examples': 0,
'conversation': 0,
};
}
uncount(value, type) {
this.counts[type] -= value;
}
count(messages, full, type) {
//console.log(messages);
const token_count = this.countTokenFn(messages, full);
this.counts[type] += token_count;
return token_count;
}
log() {
const total = Object.values(this.counts).reduce((a, b) => a + b);
console.table({ ...this.counts, 'total': total });
}
}
function countTokens(messages, full = false) {
let chatId = 'undefined';
@ -705,6 +738,7 @@ 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);
@ -720,6 +754,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);
if (settings.main_prompt !== undefined) oai_settings.main_prompt = settings.main_prompt;
if (settings.nsfw_prompt !== undefined) oai_settings.nsfw_prompt = settings.nsfw_prompt;
@ -839,6 +874,7 @@ 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,
};
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@ -1097,6 +1133,7 @@ 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],
@ -1269,6 +1306,16 @@ $(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');
@ -1322,18 +1369,18 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$("#api_button_openai").on('click', onConnectButtonClick);
$("#openai_reverse_proxy").on('input', onReverseProxyInput);
$("#model_openai_select").on('change', onModelChange);
$("#settings_perset_openai").on('change', onSettingsPresetChange);
$("#new_oai_preset").on('click', onNewPresetClick);
$("#delete_oai_preset").on('click', onDeletePresetClick);
$("#openai_api_usage").on('click', showApiKeyUsage);
$('#openai_logit_bias_preset').on('change', onLogitBiasPresetChange);
$('#openai_logit_bias_new_preset').on('click', createNewLogitBiasPreset);
$('#openai_logit_bias_new_entry').on('click', createNewLogitBiasEntry);
$('#openai_logit_bias_import_file').on('input', onLogitBiasPresetImportFileChange);
$('#openai_logit_bias_import_preset').on('click', onLogitBiasPresetImportClick);
$('#openai_logit_bias_export_preset').on('click', onLogitBiasPresetExportClick);
$('#openai_logit_bias_delete_preset').on('click', onLogitBiasPresetDeleteClick);
$("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange);
$("#settings_perset_openai").on("change", onSettingsPresetChange);
$("#new_oai_preset").on("click", onNewPresetClick);
$("#delete_oai_preset").on("click", onDeletePresetClick);
$("#openai_api_usage").on("click", showApiKeyUsage);
$("#openai_logit_bias_preset").on("change", onLogitBiasPresetChange);
$("#openai_logit_bias_new_preset").on("click", createNewLogitBiasPreset);
$("#openai_logit_bias_new_entry").on("click", createNewLogitBiasEntry);
$("#openai_logit_bias_import_file").on("input", onLogitBiasPresetImportFileChange);
$("#openai_logit_bias_import_preset").on("click", onLogitBiasPresetImportClick);
$("#openai_logit_bias_export_preset").on("click", onLogitBiasPresetExportClick);
$("#openai_logit_bias_delete_preset").on("click", onLogitBiasPresetDeleteClick);
});

View File

@ -76,6 +76,8 @@ let power_user = {
disable_personality_formatting: false,
disable_examples_formatting: false,
disable_start_formatting: false,
trim_sentences: false,
include_newline: false,
always_force_name2: false,
multigen: false,
multigen_first_chunk: 50,
@ -107,9 +109,14 @@ let power_user = {
noShadows: false,
theme: 'Default (Dark)',
auto_swipe: false,
auto_swipe_minimum_length: 0,
auto_swipe_blacklist: [],
auto_swipe_blacklist_threshold: 2,
auto_scroll_chat_to_bottom: true,
auto_fix_generated_markdown: true,
send_on_enter: send_on_enter_options.AUTO,
console_log_prompts: false,
render_formulas: false,
allow_name1_display: false,
allow_name2_display: false,
@ -477,6 +484,12 @@ function loadPowerUserSettings(settings, data) {
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 10);
$('#auto_swipe').prop("checked", power_user.auto_swipe);
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
$('#auto_scroll_chat_to_bottom').prop("checked", power_user.auto_scroll_chat_to_bottom);
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
@ -490,6 +503,8 @@ function loadPowerUserSettings(settings, data) {
$("#always-force-name2-checkbox").prop("checked", power_user.always_force_name2);
$("#disable-examples-formatting-checkbox").prop("checked", power_user.disable_examples_formatting);
$('#disable-start-formatting-checkbox').prop("checked", power_user.disable_start_formatting);
$("#trim_sentences_checkbox").prop("checked", power_user.trim_sentences);
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
$('#render_formulas').prop("checked", power_user.render_formulas);
$("#custom_chat_separator").val(power_user.custom_chat_separator);
$("#fast_ui_mode").prop("checked", power_user.fast_ui_mode);
@ -770,18 +785,21 @@ function resetMovablePanels() {
document.getElementById("sheld").style.right = '';
document.getElementById("sheld").style.height = '';
document.getElementById("sheld").style.width = '';
document.getElementById("sheld").style.margin = '';
document.getElementById("left-nav-panel").style.top = '';
document.getElementById("left-nav-panel").style.left = '';
document.getElementById("left-nav-panel").style.height = '';
document.getElementById("left-nav-panel").style.width = '';
document.getElementById("left-nav-panel").style.margin = '';
document.getElementById("right-nav-panel").style.top = '';
document.getElementById("right-nav-panel").style.left = '';
document.getElementById("right-nav-panel").style.right = '';
document.getElementById("right-nav-panel").style.height = '';
document.getElementById("right-nav-panel").style.width = '';
document.getElementById("right-nav-panel").style.margin = '';
document.getElementById("expression-holder").style.top = '';
document.getElementById("expression-holder").style.left = '';
@ -789,6 +807,7 @@ function resetMovablePanels() {
document.getElementById("expression-holder").style.bottom = '';
document.getElementById("expression-holder").style.height = '';
document.getElementById("expression-holder").style.width = '';
document.getElementById("expression-holder").style.margin = '';
document.getElementById("avatar_zoom_popup").style.top = '';
document.getElementById("avatar_zoom_popup").style.left = '';
@ -796,6 +815,15 @@ function resetMovablePanels() {
document.getElementById("avatar_zoom_popup").style.bottom = '';
document.getElementById("avatar_zoom_popup").style.height = '';
document.getElementById("avatar_zoom_popup").style.width = '';
document.getElementById("avatar_zoom_popup").style.margin = '';
document.getElementById("WorldInfo").style.top = '';
document.getElementById("WorldInfo").style.left = '';
document.getElementById("WorldInfo").style.right = '';
document.getElementById("WorldInfo").style.bottom = '';
document.getElementById("WorldInfo").style.height = '';
document.getElementById("WorldInfo").style.width = '';
document.getElementById("WorldInfo").style.margin = '';
}
$(document).ready(() => {
@ -841,6 +869,27 @@ $(document).ready(() => {
saveSettingsDebounced();
});
// include newline is the child of trim sentences
// if include newline is checked, trim sentences must be checked
// if trim sentences is unchecked, include newline must be unchecked
$("#trim_sentences_checkbox").change(function() {
power_user.trim_sentences = !!$(this).prop("checked");
if (!$(this).prop("checked")) {
$("#include_newline_checkbox").prop("checked", false);
power_user.include_newline = false;
}
saveSettingsDebounced();
});
$("#include_newline_checkbox").change(function() {
power_user.include_newline = !!$(this).prop("checked");
if ($(this).prop("checked")) {
$("#trim_sentences_checkbox").prop("checked", true);
power_user.trim_sentences = true;
}
saveSettingsDebounced();
});
$("#always-force-name2-checkbox").change(function () {
power_user.always_force_name2 = !!$(this).prop("checked");
saveSettingsDebounced();
@ -1002,12 +1051,47 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$('#auto_swipe').on('input', function () {
power_user.auto_swipe = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#auto_swipe_blacklist').on('input', function () {
power_user.auto_swipe_blacklist = $(this).val()
.split(",")
.map(str => str.trim())
.filter(str => str);
console.log("power_user.auto_swipe_blacklist", power_user.auto_swipe_blacklist)
saveSettingsDebounced();
});
$('#auto_swipe_minimum_length').on('input', function () {
const number = parseInt($(this).val());
if (!isNaN(number)) {
power_user.auto_swipe_minimum_length = number;
saveSettingsDebounced();
}
});
$('#auto_swipe_blacklist_threshold').on('input', function () {
const number = parseInt($(this).val());
if (!isNaN(number)) {
power_user.auto_swipe_blacklist_threshold = number;
saveSettingsDebounced();
}
});
$('#auto_fix_generated_markdown').on('input', function () {
power_user.auto_fix_generated_markdown = !!$(this).prop('checked');
reloadCurrentChat();
saveSettingsDebounced();
});
$("#console_log_prompts").on('input', function () {
power_user.console_log_prompts = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#auto_scroll_chat_to_bottom').on("input", function () {
power_user.auto_scroll_chat_to_bottom = !!$(this).prop('checked');
saveSettingsDebounced();

View File

@ -97,7 +97,9 @@ function executeSlashCommands(text) {
return false;
}
const lines = text.split('\n');
// Hack to allow multi-line slash commands
// All slash command messages should begin with a slash
const lines = [text];
const linesToRemove = [];
let interrupt = false;

303
public/scripts/uniqolor.js Normal file
View File

@ -0,0 +1,303 @@
const SATURATION_BOUND = [0, 100];
const LIGHTNESS_BOUND = [0, 100];
const pad2 = str => `${str.length === 1 ? '0' : ''}${str}`;
const clamp = (num, min, max) => Math.max(Math.min(num, max), min);
const random = (min, max) => Math.floor(Math.random() * ((max - min) + 1)) + min;
const randomExclude = (min, max, exclude) => {
const r = random(min, max);
for (let i = 0; i < exclude?.length; i++) {
const value = exclude[i];
if (value?.length === 2 && r >= value[0] && r <= value[1]) {
return randomExclude(min, max, exclude);
}
}
return r;
};
/**
* Generate hashCode
* @param {string} str
* @return {number}
*/
const hashCode = str => {
const len = str.length;
let hash = 0;
for (let i = 0; i < len; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash &= hash; // Convert to 32bit integer
}
return hash;
};
/**
* Clamps `num` within the inclusive `range` bounds
* @param {number} num
* @param {number|Array} range
* @return {number}
*/
const boundHashCode = (num, range) => {
if (typeof range === 'number') {
return range;
}
return (num % Math.abs(range[1] - range[0])) + range[0];
};
/**
* Sanitizing the `range`
* @param {number|Array} range
* @param {Array} bound
* @return {number|Array}
*/
const sanitizeRange = (range, bound) => {
if (typeof range === 'number') {
return clamp(Math.abs(range), ...bound);
}
if (range.length === 1 || range[0] === range[1]) {
return clamp(Math.abs(range[0]), ...bound);
}
return [
Math.abs(clamp(range[0], ...bound)),
clamp(Math.abs(range[1]), ...bound),
];
};
/**
* @param {number} p
* @param {number} q
* @param {number} t
* @return {number}
*/
const hueToRgb = (p, q, t) => {
if (t < 0) {
t += 1;
} else if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + ((q - p) * 6 * t);
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + ((q - p) * ((2 / 3) - t) * 6);
}
return p;
};
/**
* Converts an HSL color to RGB
* @param {number} h Hue
* @param {number} s Saturation
* @param {number} l Lightness
* @return {Array}
*/
const hslToRgb = (h, s, l) => {
let r;
let g;
let b;
h /= 360;
s /= 100;
l /= 100;
if (s === 0) {
// achromatic
r = g = b = l;
} else {
const q = l < 0.5
? l * (1 + s)
: (l + s) - (l * s);
const p = (2 * l) - q;
r = hueToRgb(p, q, h + (1 / 3));
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - (1 / 3));
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255),
];
};
/**
* Determines whether the RGB color is light or not
* http://www.w3.org/TR/AERT#color-contrast
* @param {number} r Red
* @param {number} g Green
* @param {number} b Blue
* @param {number} differencePoint
* @return {boolean}
*/
const rgbIsLight = (r, g, b, differencePoint) => ((r * 299) + (g * 587) + (b * 114)) / 1000 >= differencePoint; // eslint-disable-line max-len
/**
* Converts an HSL color to string format
* @param {number} h Hue
* @param {number} s Saturation
* @param {number} l Lightness
* @return {string}
*/
const hslToString = (h, s, l) => `hsl(${h}, ${s}%, ${l}%)`;
/**
* Converts RGB color to string format
* @param {number} r Red
* @param {number} g Green
* @param {number} b Blue
* @param {string} format Color format
* @return {string}
*/
const rgbFormat = (r, g, b, format) => {
switch (format) {
case 'rgb':
return `rgb(${r}, ${g}, ${b})`;
case 'hex':
default:
return `#${pad2(r.toString(16))}${pad2(g.toString(16))}${pad2(b.toString(16))}`;
}
};
/**
* Generate unique color from `value`
* @param {string|number} value
* @param {Object} [options={}]
* @param {string} [options.format='hex']
* The color format, it can be one of `hex`, `rgb` or `hsl`
* @param {number|Array} [options.saturation=[50, 55]]
* Determines the color saturation, it can be a number or a range between 0 and 100
* @param {number|Array} [options.lightness=[50, 60]]
* Determines the color lightness, it can be a number or a range between 0 and 100
* @param {number} [options.differencePoint=130]
* Determines the color brightness difference point. We use it to obtain the `isLight` value
* in the output, it can be a number between 0 and 255
* @return {Object}
* @example
*
* ```js
* uniqolor('Hello world!')
* // { color: "#5cc653", isLight: true }
*
* uniqolor('Hello world!', { format: 'rgb' })
* // { color: "rgb(92, 198, 83)", isLight: true }
*
* uniqolor('Hello world!', {
* saturation: 30,
* lightness: [70, 80],
* })
* // { color: "#afd2ac", isLight: true }
*
* uniqolor('Hello world!', {
* saturation: 30,
* lightness: [70, 80],
* differencePoint: 200,
* })
* // { color: "#afd2ac", isLight: false }
* ```
*/
const uniqolor = (value, {
format = 'hex',
saturation = [50, 55],
lightness = [50, 60],
differencePoint = 130,
} = {}) => {
const hash = Math.abs(hashCode(String(value)));
const h = boundHashCode(hash, [0, 360]);
const s = boundHashCode(hash, sanitizeRange(saturation, SATURATION_BOUND));
const l = boundHashCode(hash, sanitizeRange(lightness, LIGHTNESS_BOUND));
const [r, g, b] = hslToRgb(h, s, l);
return {
color: format === 'hsl'
? hslToString(h, s, l)
: rgbFormat(r, g, b, format),
isLight: rgbIsLight(r, g, b, differencePoint),
};
};
/**
* Generate random color
* @param {Object} [options={}]
* @param {string} [options.format='hex']
* The color format, it can be one of `hex`, `rgb` or `hsl`
* @param {number|Array} [options.saturation=[50, 55]]
* Determines the color saturation, it can be a number or a range between 0 and 100
* @param {number|Array} [options.lightness=[50, 60]]
* Determines the color lightness, it can be a number or a range between 0 and 100
* @param {number} [options.differencePoint=130]
* Determines the color brightness difference point. We use it to obtain the `isLight` value
* in the output, it can be a number between 0 and 255
* @param {Array} [options.excludeHue]
* Exclude certain hue ranges. For example to exclude red color range: `[[0, 20], [325, 359]]`
* @return {Object}
* @example
*
* ```js
* // Generate random color
* uniqolor.random()
* // { color: "#644cc8", isLight: false }
*
* // Generate a random color with HSL format
* uniqolor.random({ format: 'hsl' })
* // { color: "hsl(89, 55%, 60%)", isLight: true }
*
* // Generate a random color in specific saturation and lightness
* uniqolor.random({
* saturation: 80,
* lightness: [70, 80],
* })
* // { color: "#c7b9da", isLight: true }
*
* // Generate a random color but exclude red color range
* uniqolor.random({
* excludeHue: [[0, 20], [325, 359]],
* })
* // {color: '#53caab', isLight: true}
* ```
*/
uniqolor.random = ({
format = 'hex',
saturation = [50, 55],
lightness = [50, 60],
differencePoint = 130,
excludeHue,
} = {}) => {
saturation = sanitizeRange(saturation, SATURATION_BOUND);
lightness = sanitizeRange(lightness, LIGHTNESS_BOUND);
const h = excludeHue ? randomExclude(0, 359, excludeHue) : random(0, 359);
const s = typeof saturation === 'number'
? saturation
: random(...saturation);
const l = typeof lightness === 'number'
? lightness
: random(...lightness);
const [r, g, b] = hslToRgb(h, s, l);
return {
color: format === 'hsl'
? hslToString(h, s, l)
: rgbFormat(r, g, b, format),
isLight: rgbIsLight(r, g, b, differencePoint),
};
};
export default uniqolor;

View File

@ -189,3 +189,30 @@ export function sortByCssOrder(a, b) {
const _b = Number($(b).css('order'));
return _a - _b;
}
export function end_trim_to_sentence(input, include_newline = false) {
// inspired from https://github.com/kaihordewebui/kaihordewebui.github.io/blob/06b95e6b7720eb85177fbaf1a7f52955d7cdbc02/index.html#L4853-L4867
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$']); // extend this as you see fit
let last = -1;
for (let i = input.length - 1; i >= 0; i--) {
const char = input[i];
if (punctuation.has(char)) {
last = i;
break;
}
if (include_newline && char === '\n') {
last = i;
break;
}
}
if (last === -1) {
return input.trimEnd();
}
return input.substring(0, last + 1).trimEnd();
}

View File

@ -311,7 +311,7 @@ function appendWorldEntry(entry) {
const value = $(this).prop("checked");
world_info_data.entries[uid].disable = value;
saveWorldInfo();
console.log(`WI #${entry.uid} disabled? ${world_info_data.entries[uid].disable}`);
//console.log(`WI #${entry.uid} disabled? ${world_info_data.entries[uid].disable}`);
});
disableInput.prop("checked", entry.disable).trigger("input");
disableInput.siblings(".checkbox_fancy").click(function () {
@ -593,7 +593,8 @@ $(document).ready(() => {
await loadWorldInfoData();
}
hideWorldEditor();
if (selectedWorld === "None") { hideWorldEditor(); }
if (is_world_edit_open && selectedWorld !== "None") { showWorldEditor() };
saveSettingsDebounced();
});

View File

@ -132,6 +132,19 @@ table.responsiveTable {
border-top: 2px solid grey;
}
.tokenItemizingSubclass {
font-size: calc(var(--mainFontSize) * 0.8);
color: var(--SmartThemeEmColor);
white-space: pre-wrap;
}
.tokenGraph {
border-radius: 10px;
border: 1px solid var(--white30a);
max-height: 100%;
overflow: hidden;
}
.fa-solid::before,
.fa-regular::before {
vertical-align: middle;
@ -191,6 +204,16 @@ table.responsiveTable {
text-align: center;
}
.mes_narrate,
body.tts .mes[is_user="true"] .mes_narrate,
body.tts .mes[is_system="true"] .mes_narrate {
display: none;
}
body.tts .mes_narrate {
display: inline-block;
}
code {
font-family: Consolas, monospace;
white-space: pre-wrap;
@ -373,6 +396,18 @@ 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;
@ -814,6 +849,8 @@ select {
margin: 5px 0;
}
h3 {
margin: 10px 0;
}
@ -878,19 +915,22 @@ input[type="file"] {
#rm_button_characters,
#rm_button_panel_pin_div,
#lm_button_panel_pin_div {
#lm_button_panel_pin_div,
#WI_button_panel_pin_div {
font-size: 24px;
display: inline;
}
#rm_button_panel_pin_div,
#lm_button_panel_pin_div {
#lm_button_panel_pin_div,
#WI_button_panel_pin_div {
opacity: 0.5;
transition: 0.3s;
}
#rm_button_panel_pin_div:hover,
#lm_button_panel_pin_div:hover {
#lm_button_panel_pin_div:hover,
#WI_button_panel_pin_div:hover {
opacity: 1;
}
@ -899,32 +939,38 @@ input[type="file"] {
}
#rm_button_panel_pin,
#lm_button_panel_pin {
#lm_button_panel_pin,
#WI_panel_pin {
display: none;
}
#rm_button_panel_pin:checked+label,
#lm_button_panel_pin:checked+label {
#lm_button_panel_pin:checked+label,
#WI_panel_pin:checked+label {
display: inline;
}
#rm_button_panel_pin:checked+label .checked,
#lm_button_panel_pin:checked+label .checked {
#lm_button_panel_pin:checked+label .checked,
#WI_panel_pin:checked+label .checked {
display: inline;
}
#rm_button_panel_pin:checked+label .unchecked,
#lm_button_panel_pin:checked+label .unchecked {
#lm_button_panel_pin:checked+label .unchecked,
#WI_panel_pin:checked+label .unchecked {
display: none;
}
#rm_button_panel_pin:not(:checked)+label .checked,
#lm_button_panel_pin:not(:checked)+label .checked {
#lm_button_panel_pin:not(:checked)+label .checked,
#WI_panel_pin:not(:checked)+label .checked {
display: none;
}
#rm_button_panel_pin:not(:checked)+label .unchecked,
#lm_button_panel_pin:not(:checked)+label .unchecked {
#lm_button_panel_pin:not(:checked)+label .unchecked,
#WI_panel_pin:not(:checked)+label .unchecked {
display: inline;
}
@ -1034,15 +1080,10 @@ select option:not(:checked) {
display: none !important;
}
#api_url_text,
#textgenerationwebui_api_url_text {
#api_url_text {
display: block;
}
#textgenerationwebui_api pre {
display: inline;
}
#api_button:hover,
#api_button_novel:hover,
#api_button_textgenerationwebui:hover,
@ -1168,6 +1209,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
width: calc(100% - 70px);
}
.widthUnset {
width: unset;
}
#avatar_url_div {
display: none;
}
@ -1516,6 +1561,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.avatar_div .menu_button,
@ -1636,29 +1682,32 @@ input[type=search]:focus::-webkit-search-cancel-button {
#world_popup {
display: none;
background-color: var(--SmartThemeBlurTintColor);
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
max-width: var(--sheldWidth);
height: calc(100% - 40px);
position: absolute;
margin-left: auto;
margin-right: auto;
/* background-color: var(--SmartThemeBlurTintColor);
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); */
/* max-width: var(--sheldWidth); */
/* max-height: calc(100% - 100px); */
min-height: 100px;
min-width: 100px;
/* position: absolute; */
/* margin-left: auto;
margin-right: auto; */
left: 0;
right: 0;
top: 40px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
padding: 4px;
/* top: 40px; */
/* box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); */
/* padding: 10px; */
flex-direction: column;
z-index: 3010;
border-radius: 0 0 20px 20px;
overflow-y: hidden;
}
#world_popup_bottom_holder {
padding: 0.5rem 0;
margin: 0 18px;
/* padding: 0.5rem 0;
margin: 0 18px; */
display: flex;
flex-direction: row;
justify-content: flex-end;
justify-content: space-evenly;
align-items: center;
}
@ -1677,6 +1726,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
margin-left: 1rem;
}
.world_entry {
padding: 0 5px;
}
.world_entry:not(:last-child)::after {
margin-top: 1rem;
height: 1px;
@ -1695,7 +1748,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 18px;
/* margin-left: 18px; */
}
#world_popup_header h3 {
@ -1712,7 +1765,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
}
#form_rename_world {
margin-right: 50px;
/* margin-right: 50px; */
}
#form_rename_chat {
@ -1741,7 +1794,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
#world_popup_entries_list {
flex-grow: 1;
overflow-y: scroll;
overflow-y: auto;
}
#world_popup_entries_list:empty {
@ -1792,7 +1845,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
.world_entry_form_control textarea {
height: auto;
width: auto;
/* width: auto; */
margin-top: 0;
}
@ -1800,6 +1853,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
flex-direction: row;
align-items: center;
margin-top: 5px;
flex-wrap: wrap;
}
.world_entry_form_control input[type=button] {
@ -2131,6 +2185,8 @@ input[type="range"]::-webkit-slider-thumb {
right: 0px;
}
.mes_prompt,
.mes_narrate,
.mes_copy,
.mes_edit {
cursor: pointer;
@ -2143,11 +2199,14 @@ input[type="range"]::-webkit-slider-thumb {
.mes_edit:hover,
.mes_copy:hover,
.mes_narrate:hover,
.mes_stop:hover {
opacity: 1;
}
.last_mes .mes_copy {
.last_mes .mes_copy,
.last_mes .mes_narrate,
.last_mes .mes_prompt {
grid-row-start: 1;
position: relative;
right: -30px;
@ -2256,8 +2315,10 @@ input[type="range"]::-webkit-slider-thumb {
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
grid-template-rows: 50px 1fr 1fr 1fr 5fr;
grid-gap: 10px;
min-height: 100px;
min-width: 100px;
max-width: var(--sheldWidth);
height: calc(100svh - 40px);
max-height: calc(100svh - 100px);
position: absolute;
z-index: 3002;
margin-left: auto;
@ -2272,6 +2333,7 @@ input[type="range"]::-webkit-slider-thumb {
padding-bottom: 30px;
border: 1px solid var(--black30a);
border-radius: 0 0 20px 20px;
overflow-y: auto;
}
#character_popup h3 {
@ -2771,7 +2833,6 @@ body .ui-widget-content li:hover {
#rm_group_add_members_header {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
column-gap: 10px;
}
@ -3134,11 +3195,39 @@ a {
text-decoration: none;
}
#export_format_popup {
#export_format_popup,
#rawPromptPopup {
display: none;
z-index: 9999;
}
#rawPromptPopup {
inset: 0px auto auto 0px;
margin: 0px;
transform: translate(909px, 47px);
display: block;
overflow-wrap: break-word;
white-space: normal;
max-width: calc(((100svw - 500px) / 2) - 10px);
position: absolute;
z-index: 9999;
max-height: 90svh;
/*unsure why, but this prevents scrollbars*/
height: 49svh;
padding: 5px;
overflow-y: auto;
display: none;
}
#rawPopupWrapper {
word-wrap: break-word;
width: 100%;
text-align: start;
overflow-y: auto;
max-height: 100%;
}
.list-group {
display: flex;
flex-direction: column;
@ -3437,11 +3526,15 @@ 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,
.fillLeft {
.fillLeft,
#WorldInfo,
#floatingPrompt {
min-width: unset;
position: fixed;
}
.fillLeft {
@ -3796,7 +3889,8 @@ body.movingUI .drag-grabber {
body.movingUI #sheld,
body.movingUI .drawer-content,
body.movingUI #expression-holder,
body.movingUI #avatar_zoom_popup {
body.movingUI #avatar_zoom_popup,
body.movingUI #floatingPrompt {
resize: both;
}
@ -3949,10 +4043,10 @@ body.waifuMode #avatar_zoom_popup {
display: none;
}
#world_popup_header {
/* #world_popup_header {
flex-direction: column;
align-items: flex-start;
}
} */
#world_popup_header .world_popup_expander {
display: none;
@ -3998,14 +4092,18 @@ body.waifuMode #avatar_zoom_popup {
#sheld,
#character_popup,
#world_popup {
height: calc(100svh - 45px);
.drawer-content
/* ,
#world_popup */
{
max-height: calc(100svh - 45px);
width: 100% !important;
margin: 0 auto;
max-width: 100%;
left: 0 !important;
resize: none;
top: 42px;
resize: none !important;
top: 40px;
}
#character_popup,
@ -4014,7 +4112,6 @@ body.waifuMode #avatar_zoom_popup {
}
#character_popup,
#world_popup,
#send_form {
border: 1px solid var(--grey30);
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
@ -4036,6 +4133,10 @@ body.waifuMode #avatar_zoom_popup {
}
#showRawPrompt {
display: none;
}
.mes-text {
padding-right: 25px;
}

View File

@ -179,13 +179,23 @@ However, it can be used to allow remote connections from anywhere as well.
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
*IP ranges are not accepted. Each IP must be listed individually like this:*
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
```txt
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
192.168.0.20
```
or
```txt
192.168.0.*
```
(the above wildcard IP range will allow any device on the local network to connect)
CIDR masks are also accepted (eg. 10.0.0.0/24).
* Save the `whitelist.txt` file.
* Restart your TAI server.
@ -272,5 +282,6 @@ GNU Affero General Public License for more details.**
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
* Noto Sans font by Google (OFL license)
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
* Linux startup script by AlpinDale
* Thanks paniphons for providing a FAQ document

130
server.js
View File

@ -85,6 +85,11 @@ const allowKeysExposure = config.allowKeysExposure;
const axios = require('axios');
const tiktoken = require('@dqbd/tiktoken');
const WebSocket = require('ws');
const AIHorde = require("./src/horde");
const ai_horde = new AIHorde({
client_agent: getVersion()?.agent || 'SillyTavern:UNKNOWN:Cohee#1207',
});
const ipMatching = require('ip-matching');
var Client = require('node-rest-client').Client;
var client = new Client();
@ -242,7 +247,7 @@ app.use(function (req, res, next) { //Security
}
//clientIp = req.connection.remoteAddress.split(':').pop();
if (whitelistMode === true && !whitelist.includes(clientIp)) {
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
}
@ -312,27 +317,8 @@ app.get('/deviceinfo', function (request, response) {
return response.send(deviceInfo);
});
app.get('/version', function (_, response) {
let pkgVersion, gitRevision, gitBranch;
try {
const pkgJson = require('./package.json');
pkgVersion = pkgJson.version;
if (commandExistsSync('git')) {
gitRevision = require('child_process')
.execSync('git rev-parse --short HEAD', { cwd: process.cwd() })
.toString().trim();
gitBranch = require('child_process')
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: process.cwd() })
.toString().trim();
}
}
catch {
// suppress exception
}
finally {
const agent = `SillyTavern:${gitRevision || pkgVersion}:Cohee#1207`;
response.send({ agent, pkgVersion, gitRevision, gitBranch });
}
const data = getVersion();
response.send(data);
})
//**************Kobold api
@ -531,7 +517,7 @@ app.post("/savechat", jsonParser, function (request, response) {
var dir_name = String(request.body.avatar_url).replace('.png', '');
let chat_data = request.body.chat;
let jsonlData = chat_data.map(JSON.stringify).join('\n');
fs.writeFile(chatsPath + dir_name + "/" + request.body.file_name + '.jsonl', jsonlData, 'utf8', function (err) {
fs.writeFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, jsonlData, 'utf8', function (err) {
if (err) {
response.send(err);
return console.log(err);
@ -555,11 +541,10 @@ app.post("/getchat", jsonParser, function (request, response) {
if (err === null) { //if there is a dir, then read the requested file from the JSON call
fs.stat(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", function (err, stat) {
fs.stat(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, function (err, stat) {
if (err === null) { //if no error (the file exists), read the file
if (stat !== undefined) {
fs.readFile(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", 'utf8', (err, data) => {
fs.readFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, 'utf8', (err, data) => {
if (err) {
console.error(err);
response.send(err);
@ -588,9 +573,8 @@ app.post("/getchat", jsonParser, function (request, response) {
}
}
});
});
app.post("/getstatus", jsonParser, async function (request, response_getstatus = response) {
if (!request.body) return response_getstatus.sendStatus(400);
api_server = request.body.api_server;
@ -670,6 +654,31 @@ app.post("/setsoftprompt", jsonParser, async function (request, response) {
return response.sendStatus(200);
});
function getVersion() {
let pkgVersion = 'UNKNOWN';
let gitRevision = null;
let gitBranch = null;
try {
const pkgJson = require('./package.json');
pkgVersion = pkgJson.version;
if (!process.pkg && commandExistsSync('git')) {
gitRevision = require('child_process')
.execSync('git rev-parse --short HEAD', { cwd: process.cwd() })
.toString().trim();
gitBranch = require('child_process')
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: process.cwd() })
.toString().trim();
}
}
catch {
// suppress exception
}
const agent = `SillyTavern:${pkgVersion}:Cohee#1207`;
return { agent, pkgVersion, gitRevision, gitBranch };
}
function tryParse(str) {
try {
return json5.parse(str);
@ -2557,7 +2566,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
response_generate_openai.send({ error: true });
} else if (response.status == 429) {
console.log('Out of quota');
response_generate_openai.send({ error: true, quota_error: true, });
const quota_error = response?.data?.type === 'insufficient_quota';
response_generate_openai.send({ error: true, quota_error, });
} else if (response.status == 500 || response.status == 409 || response.status == 504) {
if (request.body.stream) {
response.data.on('data', chunk => {
@ -2580,7 +2590,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
}
}
try {
const quota_error = error?.response?.status === 429;
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';
if (!response_generate_openai.headersSent) {
response_generate_openai.send({ error: true, quota_error });
}
@ -2881,8 +2891,9 @@ app.post('/readsecretstate', jsonParser, (_, response) => {
}
});
app.post('/generate_horde', jsonParser, async (request, response) => {
const ANONYMOUS_KEY = "0000000000";
app.post('/generate_horde', jsonParser, async (request, response) => {
const api_key_horde = readSecret(SECRET_KEYS.HORDE) || ANONYMOUS_KEY;
const url = 'https://horde.koboldai.net/api/v2/generate/text/async';
@ -2925,6 +2936,63 @@ app.post('/viewsecrets', jsonParser, async (_, response) => {
}
});
app.post('/horde_samplers', jsonParser, async (_, response) => {
const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers);
response.send(samplers);
});
app.post('/horde_models', jsonParser, async (_, response) => {
const models = await ai_horde.getModels();
response.send(models);
});
app.post('/horde_generateimage', jsonParser, async (request, response) => {
const MAX_ATTEMPTS = 100;
const CHECK_INTERVAL = 3000;
const api_key_horde = readSecret(SECRET_KEYS.HORDE) || ANONYMOUS_KEY;
console.log('Stable Horde request:', request.body);
const generation = await ai_horde.postAsyncImageGenerate(
{
prompt: `${request.body.prompt_prefix} ${request.body.prompt} ### ${request.body.negative_prompt}`,
params:
{
sampler_name: request.body.sampler,
cfg_scale: request.body.scale,
steps: request.body.steps,
width: request.body.width,
height: request.body.height,
n: 1,
},
r2: false,
nsfw: request.body.nfsw,
models: [request.body.model],
},
{ token: api_key_horde });
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
await delay(CHECK_INTERVAL);
const check = await ai_horde.getImageGenerationCheck(generation.id);
console.log(check);
if (check.done) {
const result = await ai_horde.getImageGenerationStatus(generation.id);
return response.send(result.generations[0].img);
}
/*
if (!check.is_possible) {
return response.sendStatus(503);
}
*/
if (check.faulted) {
return response.sendStatus(500);
}
}
return response.sendStatus(504);
});
function writeSecret(key, value) {
if (!fs.existsSync(SECRETS_FILE)) {
const emptyFile = JSON.stringify({});

21
src/horde/LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 ZeldaFan0225
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2189
src/horde/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

1
src/horde/index.js Normal file

File diff suppressed because one or more lines are too long

3
src/horde/index.mjs Normal file
View File

@ -0,0 +1,3 @@
import AIHorde from './index.js'
export default AIHorde
export { AIHorde }

View File

@ -10,7 +10,8 @@ then
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
source ~/.bashrc
nvm install node;;
nvm install lts
nvm use lts;;
n|N )
echo "Nodejs and npm will not be installed."
exit;;