mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of https://github.com/BlipRanger/SillyTavern into feature/random
This commit is contained in:
@ -4,3 +4,4 @@ npm-debug.log
|
||||
readme*
|
||||
Start.bat
|
||||
/dist
|
||||
/backups/
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,3 +20,4 @@ whitelist.txt
|
||||
secrets.json
|
||||
/dist
|
||||
poe_device.json
|
||||
/backups/
|
||||
|
@ -5,3 +5,4 @@ node_modules/
|
||||
secrets.json
|
||||
/dist
|
||||
poe_device.json
|
||||
/backups/
|
||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@mlc-ai/web-tokenizers": "^0.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
@ -561,6 +562,11 @@
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@mlc-ai/web-tokenizers": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mlc-ai/web-tokenizers/-/web-tokenizers-0.1.0.tgz",
|
||||
"integrity": "sha512-whiQ+40ohtAFoFOGcje1Io7BMr434Wh3hM3nBCWlJMpXxL5Rlig/AH9wjyUPsytKwWTEe7RoYPyXSbFw5Vs6Tw=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@mlc-ai/web-tokenizers": "^0.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
|
@ -1,73 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(36, 37, 37);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
font-size: 16px;
|
||||
/*1rem*/
|
||||
color: #999;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/*z-index:0;*/
|
||||
}
|
||||
|
||||
#main {
|
||||
padding-top: 20px;
|
||||
/*z-index:1;*/
|
||||
}
|
||||
|
||||
#content {
|
||||
margin: 0 auto;
|
||||
max-width: 700px;
|
||||
border: 1px solid #333;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
line-height: 1.5rem;
|
||||
box-shadow: 0 0 5px black;
|
||||
/*z-index: 2;*/
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid #999;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
a {
|
||||
color: orange;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted orange;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table img {
|
||||
max-width: 200px;
|
||||
}
|
1
public/css/select2.min.css
vendored
Normal file
1
public/css/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -15,6 +15,7 @@
|
||||
<link href="css/bright.min.css" rel="stylesheet">
|
||||
<link href="css/cropper.min.css" rel="stylesheet">
|
||||
<link href="css/toastr.min.css" rel="stylesheet">
|
||||
<link href="css/select2.min.css" rel="stylesheet">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
||||
@ -35,6 +36,7 @@
|
||||
<script src="scripts/jquery-cropper.min.js"></script>
|
||||
<script src="scripts/toastr.min.js"></script>
|
||||
<script src="scripts/fuse.js"></script>
|
||||
<script src="scripts/select2.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>
|
||||
@ -1875,18 +1877,18 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div id="wi-holder" class="margin5">
|
||||
<div class="wi-settings flex-container gap10px alignitemscenter">
|
||||
<div class="flex1 flex-container flexFlowColumn">
|
||||
<div class="flex range-block">
|
||||
<div class="range-block-title justifyLeft">
|
||||
<small>Active World</small>
|
||||
</div>
|
||||
<div class="range-block-range">
|
||||
<select id="world_info" class="flexGrow margin0">
|
||||
<option value="">--- None ---</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="justifyContentSpaceAround wi-settings flex-container gap10px alignitemscenter">
|
||||
<div id="WIMultiSelector" class="flex2 flex alignSelfStart range-block">
|
||||
<div class="range-block-title justifyLeft">
|
||||
<small>Active World(s)</small>
|
||||
</div>
|
||||
<div class="range-block-range">
|
||||
<select id="world_info" multiple>
|
||||
<option value="">-- World Info not found --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex2 flex-container flexFlowColumn">
|
||||
<div class="flex range-block">
|
||||
<div class="range-block-title justifyLeft">
|
||||
<label for="world_info_character_strategy">
|
||||
@ -1901,42 +1903,43 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div name="WIScanAndTokens" class="flex1 flex-container flexFlowColumn">
|
||||
<div class="flex1 gap5px range-block">
|
||||
<div class="wide10pMinFit">
|
||||
<small data-i18n="Scan Depth">Scan Depth</small>
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range paddingLeftRight5">
|
||||
<input type="range" id="world_info_depth" name="volume" min="0" max="10" step="1">
|
||||
<div name="WIScanAndTokens" class="flex1 flex-container flexFlowColumn">
|
||||
<div class="flex1 gap5px range-block">
|
||||
<div class="wide10pMinFit">
|
||||
<small data-i18n="Scan Depth">Scan Depth</small>
|
||||
</div>
|
||||
<div class="range-block-counter margin0">
|
||||
<div contenteditable="true" data-for="world_info_depth" id="world_info_depth_counter">
|
||||
depth
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range paddingLeftRight5">
|
||||
<input type="range" id="world_info_depth" name="volume" min="0" max="10" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter margin0">
|
||||
<div contenteditable="true" data-for="world_info_depth" id="world_info_depth_counter">
|
||||
depth
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex1 gap5px range-block">
|
||||
<div class="wide10pMinFit">
|
||||
<small data-i18n="Token Budget">Context %</small>
|
||||
</div>
|
||||
<div class="range-block-range-and-counter ">
|
||||
<div class="range-block-range paddingLeftRight5">
|
||||
<input type="range" id="world_info_budget" name="volume" min="1" max="100" step="1">
|
||||
<div class="flex1 gap5px range-block">
|
||||
<div class="wide10pMinFit">
|
||||
<small data-i18n="Token Budget">Context %</small>
|
||||
</div>
|
||||
<div class="range-block-counter margin0">
|
||||
<div contenteditable="true" data-for="world_info_budget" id="world_info_budget_counter">
|
||||
budget
|
||||
<div class="range-block-range-and-counter ">
|
||||
<div class="range-block-range paddingLeftRight5">
|
||||
<input type="range" id="world_info_budget" name="volume" min="1" max="100" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter margin0">
|
||||
<div contenteditable="true" data-for="world_info_budget" id="world_info_budget_counter">
|
||||
budget
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block flex-container flexFlowColumn">
|
||||
|
||||
<div class="flex1 range-block flex-container flexFlowColumn">
|
||||
<label title="Entries can activate other entries by mentioning their keywords" class="checkbox_label">
|
||||
<input id="world_info_recursive" type="checkbox" />
|
||||
<small>
|
||||
@ -2000,7 +2003,7 @@
|
||||
|
||||
<div id="user-settings-button" class="drawer">
|
||||
<div class="drawer-toggle">
|
||||
<div class="drawer-icon fa-solid fa-face-smile closedIcon" title="User Settings"></div>
|
||||
<div class="drawer-icon fa-solid fa-user-cog closedIcon" title="User Settings"></div>
|
||||
</div>
|
||||
<div id="user-settings-block" class="drawer-content closedDrawer">
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||
@ -2008,7 +2011,7 @@
|
||||
<div id="version_display"></div>
|
||||
</div>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div name="UI Customization" class="flex-container drawer25pWidth">
|
||||
<div name="UI Customization" class="flex-container drawer33pWidth">
|
||||
<div class="ui-settings">
|
||||
<h4><span data-i18n="UI Customization">UI Customization</span></h4>
|
||||
<div>
|
||||
@ -2033,7 +2036,7 @@
|
||||
<span data-i18n="Bubbles">Bubbles</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div id="sheldWidthToggleBlock">
|
||||
<span data-i18n="Chat Width (PC):">Chat Width (PC):</span><br>
|
||||
<label>
|
||||
<input name="sheld_width" type="radio" value="0" />
|
||||
@ -2092,18 +2095,30 @@
|
||||
<span data-i18n="Characters Hotswap">Characters Hotswap</span>
|
||||
</label>
|
||||
|
||||
<label for="movingUImode" class="checkbox_label">
|
||||
<label id="movingUIModeCheckBlock" for="movingUImode" class="checkbox_label">
|
||||
<input id="movingUImode" type="checkbox" />
|
||||
<span data-i18n="Movable UI Panels">Movable UI Panels</span>
|
||||
</label>
|
||||
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<h4 data-i18n="Send on Enter">
|
||||
Send on Enter
|
||||
</h4>
|
||||
<select id="send_on_enter">
|
||||
<option value="-1"><span data-i18n="Always disabled">Always disabled</span></option>
|
||||
<option value="0"><span data-i18n="Automatic (desktop)">Automatic (desktop)</span>
|
||||
</option>
|
||||
<option value="1"><span data-i18n="Always enabled">Always enabled</span></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="movingUIreset" class="menu_button whitespacenowrap" data-i18n="Reset Panels">
|
||||
Reset Panels</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="UI-Theme-Block" class="flex-container flexFlowColumn drawer25pWidth">
|
||||
<div id="UI-Theme-Block" class="flex-container flexFlowColumn drawer33pWidth">
|
||||
<div id="color-picker-block" class="flex-container flexFlowColumn">
|
||||
<h4><span data-i18n="UI Colors">UI Colors</span></h4>
|
||||
<div class="flex-container">
|
||||
@ -2200,7 +2215,7 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="power-user-options-block" class="flex-container drawer25pWidth">
|
||||
<div id="power-user-options-block" class="flex-container drawer33pWidth">
|
||||
<div id="power-user-option-checkboxes">
|
||||
<h4 data-i18n="Power User Options">Power User Options</h4>
|
||||
<label for="swipes-checkbox">
|
||||
@ -2249,8 +2264,32 @@
|
||||
</a>
|
||||
</label>
|
||||
<label for="never_resize_avatars"><input id="never_resize_avatars" type="checkbox" />
|
||||
<span data-i18n="Never Resize Avatars">Never Resize Avatars</span>
|
||||
<span data-i18n="Never resize avatars">Never resize avatars</span>
|
||||
</label>
|
||||
<label for="show_card_avatar_urls"><input id="show_card_avatar_urls" type="checkbox" />
|
||||
<span data-i18n="Show avatar filenames">Show avatar filenames</span>
|
||||
</label>
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div 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">
|
||||
<textarea id="auto_swipe_blacklist" name="auto_swipe_blacklist" placeholder="words you dont want generated separated by comma ','" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="3"></textarea>
|
||||
<div>Blacklisted word count to swipe</div>
|
||||
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reload_chat" class="menu_button whitespacenowrap" data-i18n="Reload Chat">
|
||||
Reload Chat
|
||||
@ -2258,68 +2297,6 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div name="NameAndAvatar" class="flex-container flexFlowColumn drawer25pWidth">
|
||||
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<h4 data-i18n="Send on Enter">
|
||||
Send on Enter
|
||||
</h4>
|
||||
<select id="send_on_enter">
|
||||
<option value="-1"><span data-i18n="Always disabled">Always disabled</span></option>
|
||||
<option value="0"><span data-i18n="Automatic (desktop)">Automatic (desktop)</span>
|
||||
</option>
|
||||
<option value="1"><span data-i18n="Always enabled">Always enabled</span></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div 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">
|
||||
<textarea id="auto_swipe_blacklist" name="auto_swipe_blacklist" placeholder="words you dont want generated separated by comma ','" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="3"></textarea>
|
||||
<div>Blacklisted word count to swipe</div>
|
||||
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div name="NameChanger">
|
||||
<h4 data-i18n="Name">Name</h4>
|
||||
<div class="change_name">
|
||||
<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 id="lock_user_name" class="menu_button fa-solid fa-lock" title="Click to lock your selected persona to the current chat. Click again to remove the lock.">
|
||||
</div>
|
||||
<div id="sync_name_button" class="menu_button fa-solid fa-sync" title="Click to set user name for all messages">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="AvatarSelector">
|
||||
<h4 data-i18n="Your Avatar">Your Persona</h4>
|
||||
<div id="user_avatar_block">
|
||||
<div class="avatar_upload">+</div>
|
||||
</div>
|
||||
|
||||
<form id="form_upload_avatar" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<input type="file" id="avatar_upload_file" accept="image/*" name="avatar">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2382,12 +2359,66 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="persona-management-button" class="drawer">
|
||||
<div class="drawer-toggle">
|
||||
<div class="drawer-icon fa-solid fa-face-smile closedIcon" title="Persona Management"></div>
|
||||
</div>
|
||||
<div class="drawer-content closedDrawer">
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||
<h3><span data-i18n="Persona Management">Persona Management</span></h3>
|
||||
|
||||
<div id="persona-management-block" class="flex-container wide100p">
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Name">Name</h4>
|
||||
<div class="change_name">
|
||||
<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 id="lock_user_name" class="menu_button fa-solid fa-unlock" title="Click to lock your selected persona to the current chat. Click again to remove the lock.">
|
||||
</div>
|
||||
<div id="sync_name_button" class="menu_button fa-solid fa-sync" title="Click to set user name for all messages">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="Name">Persona Description</h4>
|
||||
<textarea id="persona_description" name="persona_description" placeholder="Example: [{{user}} is a 28-year-old Romanian cat girl.]" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="4"></textarea>
|
||||
<label for="persona_description_position">Position:</label>
|
||||
<select id="persona_description_position">
|
||||
<option value="0">Before Character Card</option>
|
||||
<option value="1">After Character Card</option>
|
||||
<option value="2">Top of Author's Note</option>
|
||||
<option value="3">Bottom of Author's Note</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4 data-i18n="Your Avatar" class="title_restorable">
|
||||
<span>Your Persona</span>
|
||||
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona">
|
||||
<i class="fa-solid fa-person-circle-question fa-fw"></i>
|
||||
<span>Blank</span>
|
||||
</div>
|
||||
</h4>
|
||||
<div id="user_avatar_block">
|
||||
<div class="avatar_upload">+</div>
|
||||
</div>
|
||||
|
||||
<form id="form_upload_avatar" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<input type="file" id="avatar_upload_file" accept="image/*" name="avatar">
|
||||
<input type="hidden" id="avatar_upload_overwrite" name="overwrite_name" value="">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rightNavHolder" class="drawer">
|
||||
<div id="unimportantYes" class="drawer-toggle drawer-header">
|
||||
<div id="rightNavDrawerIcon" class="drawer-icon fa-solid fa-address-card closedIcon" title="Character Management">
|
||||
</div>
|
||||
</div>
|
||||
<nav id="right-nav-panel" class="drawer-content closedDrawer fillRight gap5px">
|
||||
<nav id="right-nav-panel" class="drawer-content closedDrawer fillRight">
|
||||
<div id="right-nav-panelheader" class="fa-solid fa-grip drag-grabber">
|
||||
|
||||
</div>
|
||||
@ -2429,70 +2460,76 @@
|
||||
<input id="character_name_pole" name="ch_name" class="text_pole" placeholder="Name this character" maxlength="50" value="" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div id="result_info" class="flex-container" title="Token counts may be inaccurate and provided just for reference."> </div>
|
||||
|
||||
<div id="avatar_div" class="avatar_div alignitemsflexstart justifySpaceBetween flexnowrap flexGap5">
|
||||
<label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character">
|
||||
<img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
|
||||
<input hidden type="file" id="add_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
||||
</label>
|
||||
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="char-management-dropdown">
|
||||
<select id="char-management-dropdown">
|
||||
<option value="default" disabled selected>Options</option>
|
||||
<option id="set_character_world">
|
||||
<i class="fa-solid fa-globe"></i> Link to World Info
|
||||
</option>
|
||||
<option id="import_character_info">
|
||||
<i class="fa-solid fa-file-import"></i> Import Embedded World Info
|
||||
</option>
|
||||
<option id="set_chat_scenario">
|
||||
Scenario Override
|
||||
</option>
|
||||
<option id="renameCharButton">
|
||||
Rename
|
||||
</option>
|
||||
<option id="dupe_button">
|
||||
Duplicate
|
||||
</option>
|
||||
<option id="export_button">
|
||||
Export
|
||||
</option>
|
||||
<option id="delete_button">
|
||||
Delete
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div class="flex-container justifyContentFlexEnd">
|
||||
<div class="form_create_bottom_buttons_block flexnowrap">
|
||||
<div id="rm_button_back" class="menu_button fa-solid fa-left-long "></div>
|
||||
|
||||
<div class="form_create_bottom_buttons_block">
|
||||
<div id="rm_button_back" class="menu_button fa-solid fa-left-long "></div>
|
||||
<!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
|
||||
<div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites"></div>
|
||||
<input type="hidden" id="fav_checkbox" name="fav" />
|
||||
<div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions"></div>
|
||||
<div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore"></div>
|
||||
<div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download"></div>
|
||||
<!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
||||
<!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
|
||||
<div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character"></div>
|
||||
<label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character">
|
||||
<input type="submit" id="create_button" name="create_button">
|
||||
</label>
|
||||
<div id="delete_button" class="menu_button fa-solid fa-skull " title="Delete Character"></div>
|
||||
</div>
|
||||
<label class="flex1" for="char-management-dropdown">
|
||||
<select id="char-management-dropdown">
|
||||
<option value="default" disabled selected>More...</option>
|
||||
<option id="set_character_world">
|
||||
<i class="fa-solid fa-globe"></i> Link to World Info
|
||||
</option>
|
||||
<option id="import_character_info">
|
||||
<i class="fa-solid fa-file-import"></i> Import Card Lore
|
||||
</option>
|
||||
<option id="set_chat_scenario">
|
||||
Scenario Override
|
||||
</option>
|
||||
<option id="renameCharButton">
|
||||
Rename
|
||||
</option>
|
||||
<!--<option id="dupe_button">
|
||||
Duplicate
|
||||
</option>
|
||||
<option id="export_button">
|
||||
Export
|
||||
</option>
|
||||
<option id="delete_button">
|
||||
Delete
|
||||
</option>-->
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
|
||||
<div id="favorite_button" class="menu_button fa-solid fa-star" title="Add to Favorites"></div>
|
||||
<input type="hidden" id="fav_checkbox" name="fav" />
|
||||
<div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions"></div>
|
||||
<!-- <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download"></div> -->
|
||||
<!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
||||
<!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
|
||||
<!-- <div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character"></div> -->
|
||||
<label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character">
|
||||
<input type="submit" id="create_button" name="create_button">
|
||||
</label>
|
||||
<!-- <div id="delete_button" class="menu_button fa-solid fa-skull " title="Delete Character"></div> -->
|
||||
<div id="tags_div" class="marginBot5">
|
||||
<div class="tag_controls">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div id="result_info" class="justifyCenter flex-container" title="Token counts may be inaccurate and provided just for reference."> </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div id="tags_div" class="marginBot5">
|
||||
<div class="tag_controls">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="description_div" class="marginBot5">
|
||||
<span data-i18n="Description">Description</span>
|
||||
@ -2548,8 +2585,8 @@
|
||||
<div id="groupTagList" class="tags paddingTopBot5"></div>
|
||||
</div>
|
||||
<div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p">
|
||||
<div class="flex1 flex-container alignitemscenter justifyCenter">
|
||||
<label class="add_avatar avatar" for="group_avatar_button" title="Click to select a new avatar for this group">
|
||||
<div>
|
||||
<label class="add_avatar avatar flex-container justifyCenter" for="group_avatar_button" title="Click to select a new avatar for this group">
|
||||
<div id="group_avatar_preview">
|
||||
<div class="avatar">
|
||||
<img src="img/ai4.png" alt="avatar">
|
||||
@ -2641,6 +2678,7 @@
|
||||
<form id="form_character_search_form" action="javascript:void(null);">
|
||||
<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 faSmallFontSquareFix"></div>
|
||||
<div id="external_import_button" title="Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></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 width100p" type="search" placeholder="Search..." maxlength="50" />
|
||||
<select id="character_sort_order" title="Characters sorting order">
|
||||
@ -2693,7 +2731,7 @@
|
||||
|
||||
<div id="character_popup_text">
|
||||
<h3 id="character_popup_text_h3"></h3> <span data-i18n="Advanced Defininitions">- Advanced
|
||||
Defininitions</span>
|
||||
Definitions</span>
|
||||
</div>
|
||||
<hr class="margin-bot-10px">
|
||||
<div id="character_cross" class="fa-solid fa-circle-xmark"></div>
|
||||
@ -2895,16 +2933,32 @@
|
||||
Select a World Info file for <span class="character_name"></span>:
|
||||
</h3>
|
||||
</div>
|
||||
<h4>Primary Lorebook</h4>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||
A selected World Info / Lorebook will be bound to this character.
|
||||
When generating an AI reply, it will be combined with the entries from a global World Info / Lorebook selector.
|
||||
Exporting a character would also export the selected World Info file embedded in the JSON data.
|
||||
A selected World Info will be bound to this character as its own Lorebook.
|
||||
When generating an AI reply, it will be combined with the entries from a global World Info selector.
|
||||
Exporting a character would also export the selected Lorebook file embedded in the JSON data.
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<select class="character_world_info_selector">
|
||||
<select class="character_world_info_selector wide100p">
|
||||
<option value="">--- None ---</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="range-block-title">
|
||||
<h4>
|
||||
Additional Lorebooks
|
||||
</h4>
|
||||
</div>
|
||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||
Associate one or more auxillary Lorebooks with this character.<br>
|
||||
NOTE: These choices are optional and won't be preserved on character export!
|
||||
</div>
|
||||
<div class="range-block-range wide100p">
|
||||
<select class="character_extra_world_info_selector wide100p" multiple>
|
||||
<option value="">-- World Info not found --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3103,7 +3157,7 @@
|
||||
<img src="">
|
||||
</div>
|
||||
<div class="flex-container wide100pLess70px">
|
||||
<div class="ch_name"></div>
|
||||
<div class="wide100p"><span class="ch_name"></span> <i class="ch_avatar_url"></i></div>
|
||||
<i class="ch_fav_icon fa-solid fa-star"></i>
|
||||
<input class="ch_fav" value="" hidden />
|
||||
<div class="ch_description"></div>
|
||||
@ -3346,10 +3400,13 @@
|
||||
<div class="export_format list-group-item" data-format="webp">WEBP</div>
|
||||
</div>
|
||||
|
||||
<div id="avatar_zoom_popup">
|
||||
<div id="avatar_zoom_popupheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<img id="zoomed_avatar" src="">
|
||||
<div id="zoomed_avatar_template" class="template_element">
|
||||
<div class="zoomed_avatar">
|
||||
<div id="" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<img class="zoomed_avatar_img" src="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rawPromptPopup" class="list-group">
|
||||
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
|
||||
</div>
|
||||
@ -3368,8 +3425,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="avatar-buttons avatar-buttons-bottom">
|
||||
<button class="menu_button set_user_info" title="Under construction">
|
||||
<i class="fa-solid fa-circle-user"></i>
|
||||
<button class="menu_button set_persona_image" title="Change persona image">
|
||||
<i class="fa-solid fa-image"></i>
|
||||
</button>
|
||||
|
||||
<button class="menu_button delete_avatar" title="Delete persona">
|
||||
|
@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>SillyTavern Documentation</title>
|
||||
<link rel="stylesheet" href="/css/notes.css">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="content">
|
||||
<h2>You weren't supposed to be able to get here, you know.</h1>
|
||||
<h3>All help materials has been moved here:</h3>
|
||||
<h3><a href="https://docs.sillytavern.app/">SillyTavern Documentation</a></h3>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
613
public/script.js
613
public/script.js
@ -1,4 +1,4 @@
|
||||
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp } from "./scripts/RossAscends-mods.js";
|
||||
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile } from "./scripts/RossAscends-mods.js";
|
||||
import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import {
|
||||
@ -30,6 +30,9 @@ import {
|
||||
world_names,
|
||||
world_info_character_strategy,
|
||||
importEmbeddedWorldInfo,
|
||||
checkEmbeddedWorld,
|
||||
setWorldInfoButtonClass,
|
||||
importWorldInfo,
|
||||
} from "./scripts/world-info.js";
|
||||
|
||||
import {
|
||||
@ -69,6 +72,8 @@ import {
|
||||
formatInstructModeChat,
|
||||
formatInstructStoryString,
|
||||
formatInstructModePrompt,
|
||||
persona_description_positions,
|
||||
loadMovingUIState,
|
||||
} from "./scripts/power-user.js";
|
||||
|
||||
import {
|
||||
@ -128,6 +133,7 @@ import {
|
||||
timestampToMoment,
|
||||
download,
|
||||
isDataURL,
|
||||
getCharaFilename,
|
||||
} from "./scripts/utils.js";
|
||||
|
||||
import { extension_settings, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
||||
@ -151,7 +157,8 @@ import {
|
||||
import { EventEmitter } from './scripts/eventemitter.js';
|
||||
import { context_settings, loadContextTemplatesFromSettings } from "./scripts/context-template.js";
|
||||
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
|
||||
import { setFloatingPrompt } from "./scripts/extensions/floating-prompt/index.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/extensions/floating-prompt/index.js";
|
||||
import { deviceInfo } from "./scripts/RossAscends-mods.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -222,6 +229,7 @@ export {
|
||||
extension_prompt_types,
|
||||
updateVisibleDivs,
|
||||
mesForShowdownParse,
|
||||
printCharacters,
|
||||
}
|
||||
|
||||
// API OBJECT FOR EXTERNAL WIRING
|
||||
@ -274,7 +282,7 @@ let is_mes_reload_avatar = false;
|
||||
let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
|
||||
placement: 'top-start'
|
||||
});
|
||||
let exportPopper = Popper.createPopper(document.getElementById('char-management-dropdown'), document.getElementById('export_format_popup'), {
|
||||
let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), {
|
||||
placement: 'left'
|
||||
});
|
||||
let rawPromptPopper = Popper.createPopper(document.getElementById('dialogue_popup'), document.getElementById('rawPromptPopup'), {
|
||||
@ -287,13 +295,14 @@ let streamingProcessor = null;
|
||||
let crop_data = undefined;
|
||||
let is_delete_mode = false;
|
||||
let fav_ch_checked = false;
|
||||
let scrollLock = false;
|
||||
|
||||
//initialize global var for future cropped blobs
|
||||
let currentCroppedAvatar = '';
|
||||
|
||||
const durationSaveEdit = 1000;
|
||||
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
||||
const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
|
||||
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
|
||||
const getStatusDebounced = debounce(() => getStatus(), 300_000);
|
||||
const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit);
|
||||
|
||||
@ -799,11 +808,14 @@ async function printCharacters() {
|
||||
template.find('img').attr('src', this_avatar);
|
||||
template.find('.avatar').attr('title', item.avatar);
|
||||
template.find('.ch_name').text(item.name);
|
||||
if (power_user.show_card_avatar_urls) {
|
||||
template.find('.ch_avatar_url').text(item.avatar);
|
||||
}
|
||||
template.find('.ch_fav_icon').css("display", 'none');
|
||||
template.toggleClass('is_fav', item.fav || item.fav == 'true');
|
||||
template.find('.ch_fav').val(item.fav);
|
||||
|
||||
const description = item.data?.creator_notes || '';
|
||||
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
|
||||
if (description) {
|
||||
template.find('.ch_description').text(description);
|
||||
}
|
||||
@ -1008,6 +1020,10 @@ function clearChat() {
|
||||
count_view_mes = 0;
|
||||
extension_prompts = {};
|
||||
$("#chat").children().remove();
|
||||
if ($('.zoomed_avatar[forChar]').length) {
|
||||
console.debug('saw avatars to remove')
|
||||
$('.zoomed_avatar[forChar]').remove();
|
||||
} else { console.debug('saw no avatars') }
|
||||
itemizedPrompts = [];
|
||||
}
|
||||
|
||||
@ -1373,8 +1389,18 @@ function formatGenerationTimer(gen_started, gen_finished) {
|
||||
|
||||
function scrollChatToBottom() {
|
||||
if (power_user.auto_scroll_chat_to_bottom) {
|
||||
var $textchat = $("#chat");
|
||||
$textchat.scrollTop(($textchat[0].scrollHeight));
|
||||
const chatElement = $("#chat");
|
||||
let position = chatElement[0].scrollHeight;
|
||||
|
||||
if (power_user.waifuMode) {
|
||||
const lastMessage = chatElement.find('.mes').last();
|
||||
if (lastMessage.length) {
|
||||
const lastMessagePosition = lastMessage.position().top;
|
||||
position = chatElement.scrollTop() + lastMessagePosition;
|
||||
}
|
||||
}
|
||||
|
||||
chatElement.scrollTop(position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1555,6 +1581,28 @@ function cleanGroupMessage(getMessage) {
|
||||
return getMessage;
|
||||
}
|
||||
|
||||
function getPersonaDescription(storyString) {
|
||||
if (!power_user.persona_description) {
|
||||
return storyString;
|
||||
}
|
||||
|
||||
switch (power_user.persona_description_position) {
|
||||
case persona_description_positions.BEFORE_CHAR:
|
||||
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
|
||||
case persona_description_positions.AFTER_CHAR:
|
||||
return `${storyString}\n${substituteParams(power_user.persona_description)}`;
|
||||
default:
|
||||
if (shouldWIAddPrompt) {
|
||||
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
|
||||
const ANWithDesc = persona_description_positions.TOP_AN
|
||||
? `${power_user.persona_description}\n${originalAN}`
|
||||
: `${originalAN}\n${power_user.persona_description}`;
|
||||
setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
}
|
||||
return storyString;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllExtensionPrompts() {
|
||||
const value = Object
|
||||
.values(extension_prompts)
|
||||
@ -1719,7 +1767,9 @@ class StreamingProcessor {
|
||||
this.setFirstSwipe(messageId);
|
||||
}
|
||||
|
||||
scrollChatToBottom();
|
||||
if (!scrollLock) {
|
||||
scrollChatToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
onFinishStreaming(messageId, text) {
|
||||
@ -1813,6 +1863,7 @@ class StreamingProcessor {
|
||||
if (this.messageId == -1) {
|
||||
this.messageId = this.onStartStreaming(this.firstMessageText);
|
||||
await delay(1); // delay for message to be rendered
|
||||
scrollLock = false;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1999,12 +2050,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
|
||||
}
|
||||
|
||||
if (main_api === 'openai') {
|
||||
message_already_generated = ''; // OpenAI doesn't have multigen
|
||||
setOpenAIMessages(coreChat);
|
||||
setOpenAIMessageExamples(mesExamplesArray);
|
||||
}
|
||||
|
||||
let storyString = "";
|
||||
|
||||
if (is_pygmalion) {
|
||||
@ -2034,7 +2079,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
let chat2 = [];
|
||||
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
|
||||
// For OpenAI it's only used in WI
|
||||
if (main_api == 'openai' && !world_info) {
|
||||
if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
|
||||
console.debug('No WI, skipping chat2 for OAI');
|
||||
break;
|
||||
}
|
||||
@ -2063,11 +2108,19 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
setFloatingPrompt();
|
||||
// Add WI to prompt (and also inject WI to AN value via hijack)
|
||||
let { worldInfoString, worldInfoBefore, worldInfoAfter } = await getWorldInfoPrompt(chat2, this_max_context);
|
||||
// Add persona description to prompt
|
||||
storyString = getPersonaDescription(storyString);
|
||||
// Call combined AN into Generate
|
||||
let allAnchors = getAllExtensionPrompts();
|
||||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
|
||||
let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
|
||||
|
||||
if (main_api === 'openai') {
|
||||
message_already_generated = ''; // OpenAI doesn't have multigen
|
||||
setOpenAIMessages(coreChat);
|
||||
setOpenAIMessageExamples(mesExamplesArray);
|
||||
}
|
||||
|
||||
// Moved here to not overflow the Poe context with added prompt bits
|
||||
if (main_api == 'poe') {
|
||||
allAnchors = appendPoeAnchors(type, allAnchors);
|
||||
@ -3955,8 +4008,6 @@ function changeMainAPI() {
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
async function getUserAvatars() {
|
||||
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
|
||||
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
|
||||
const response = await fetch("/getuseravatars", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
@ -3968,6 +4019,8 @@ async function getUserAvatars() {
|
||||
const getData = await response.json();
|
||||
//background = getData;
|
||||
//console.log(getData.length);
|
||||
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
|
||||
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
|
||||
|
||||
for (var i = 0; i < getData.length; i++) {
|
||||
//console.log(1);
|
||||
@ -3978,6 +4031,56 @@ async function getUserAvatars() {
|
||||
}
|
||||
}
|
||||
|
||||
function setPersonaDescription() {
|
||||
$("#persona_description").val(power_user.persona_description);
|
||||
$("#persona_description_position")
|
||||
.val(power_user.persona_description_position)
|
||||
.find(`option[value='${power_user.persona_description_position}']`)
|
||||
.attr("selected", true);
|
||||
}
|
||||
|
||||
function onPersonaDescriptionPositionInput() {
|
||||
power_user.persona_description_position = Number(
|
||||
$("#persona_description_position").find(":selected").val()
|
||||
);
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: power_user.persona_description_position,
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.position = power_user.persona_description_position;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onPersonaDescriptionInput() {
|
||||
power_user.persona_description = $("#persona_description").val();
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: Number($("#persona_description_position").find(":selected").val()),
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.description = power_user.persona_description;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function highlightSelectedAvatar() {
|
||||
$("#user_avatar_block").find(".avatar").removeClass("selected");
|
||||
$("#user_avatar_block")
|
||||
@ -4000,12 +4103,15 @@ function appendUserAvatar(name) {
|
||||
highlightSelectedAvatar();
|
||||
}
|
||||
|
||||
function reloadUserAvatar() {
|
||||
function reloadUserAvatar(force = false) {
|
||||
$(".mes").each(function () {
|
||||
const avatarImg = $(this).find(".avatar img");
|
||||
if (force) {
|
||||
avatarImg.attr("src", avatarImg.attr("src"));
|
||||
}
|
||||
|
||||
if ($(this).attr("is_user") == 'true' && $(this).attr('force_avatar') == 'false') {
|
||||
$(this)
|
||||
.find(".avatar img")
|
||||
.attr("src", getUserAvatar(user_avatar));
|
||||
avatarImg.attr("src", getUserAvatar(user_avatar));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -4054,9 +4160,20 @@ async function bindUserNameToPersona() {
|
||||
// If the user clicked ok and entered a name, bind the name to the persona
|
||||
console.log(`Binding persona ${avatarId} to name ${personaName}`);
|
||||
power_user.personas[avatarId] = personaName;
|
||||
const descriptor = power_user.persona_descriptions[avatarId];
|
||||
const isCurrentPersona = avatarId === user_avatar;
|
||||
|
||||
// Create a description object if it doesn't exist
|
||||
if (!descriptor) {
|
||||
// If the user is currently using this persona, set the description to the current description
|
||||
power_user.persona_descriptions[avatarId] = {
|
||||
description: isCurrentPersona ? power_user.persona_description : '',
|
||||
position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.BEFORE_CHAR,
|
||||
};
|
||||
}
|
||||
|
||||
// If the user is currently using this persona, update the name
|
||||
if (avatarId === user_avatar) {
|
||||
if (isCurrentPersona) {
|
||||
console.log(`Auto-updating user name to ${personaName}`);
|
||||
setUserName(personaName);
|
||||
}
|
||||
@ -4064,16 +4181,39 @@ async function bindUserNameToPersona() {
|
||||
// If the user clicked ok, but didn't enter a name, delete the persona
|
||||
console.log(`Unbinding persona ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
setPersonaDescription();
|
||||
}
|
||||
|
||||
async function createDummyPersona() {
|
||||
const fetchResult = await fetch(default_avatar);
|
||||
const blob = await fetchResult.blob();
|
||||
const file = new File([blob], "avatar.png", { type: "image/png" });
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", file);
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/uploaduseravatar",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
await getUserAvatars();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserLockIcon() {
|
||||
const hasLock = !!chat_metadata['persona'];
|
||||
$('#lock_user_name').toggleClass('fa-lock', !hasLock);
|
||||
$('#lock_user_name').toggleClass('fa-unlock', hasLock);
|
||||
$('#lock_user_name').toggleClass('fa-unlock', !hasLock);
|
||||
$('#lock_user_name').toggleClass('fa-lock', hasLock);
|
||||
}
|
||||
|
||||
function setUserAvatar() {
|
||||
@ -4094,12 +4234,75 @@ function setUserAvatar() {
|
||||
}
|
||||
|
||||
setUserName(personaName);
|
||||
|
||||
const descriptor = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (descriptor) {
|
||||
power_user.persona_description = descriptor.description;
|
||||
power_user.persona_description_position = descriptor.position;
|
||||
} else {
|
||||
power_user.persona_description = '';
|
||||
power_user.persona_description_position = persona_description_positions.BEFORE_CHAR;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
|
||||
}
|
||||
|
||||
setPersonaDescription();
|
||||
}
|
||||
}
|
||||
|
||||
async function setUserInfo() {
|
||||
// TODO Replace with actual implementation
|
||||
callPopup('This functionality is under development.<br>Please check back later.', 'text');
|
||||
async function uploadUserAvatar(e) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
$("#form_upload_avatar").trigger("reset");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData($("#form_upload_avatar").get(0));
|
||||
|
||||
const dataUrl = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = "/uploaduseravatar";
|
||||
|
||||
if (crop_data !== undefined) {
|
||||
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function () {
|
||||
// If the user uploaded a new avatar, we want to make sure it's not cached
|
||||
const name = formData.get("overwrite_name");
|
||||
if (name) {
|
||||
await fetch(getUserAvatar(name), { cache: "no-cache" });
|
||||
reloadUserAvatar(true);
|
||||
}
|
||||
|
||||
crop_data = undefined;
|
||||
await getUserAvatars();
|
||||
},
|
||||
error: (jqXHR, exception) => { },
|
||||
});
|
||||
|
||||
// Will allow to select the same file twice in a row
|
||||
$("#form_upload_avatar").trigger("reset");
|
||||
}
|
||||
|
||||
async function setDefaultPersona() {
|
||||
@ -4162,7 +4365,7 @@ async function deleteUserAvatar() {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('Are you sure you want to delete this avatar?', 'confirm');
|
||||
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled deleting avatar');
|
||||
@ -4180,6 +4383,7 @@ async function deleteUserAvatar() {
|
||||
if (request.ok) {
|
||||
console.log(`Deleted avatar ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
|
||||
if (avatarId === power_user.default_persona) {
|
||||
toastr.warning('The default persona was deleted. You will need to set a new default persona.', 'Default persona deleted');
|
||||
@ -4216,6 +4420,7 @@ function lockUserNameToChat() {
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, },
|
||||
);
|
||||
power_user.personas[user_avatar] = name1;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
|
||||
}
|
||||
|
||||
chat_metadata['persona'] = user_avatar;
|
||||
@ -4413,6 +4618,7 @@ async function getSettings(type) {
|
||||
user_avatar = settings.user_avatar;
|
||||
reloadUserAvatar();
|
||||
highlightSelectedAvatar();
|
||||
setPersonaDescription();
|
||||
|
||||
//Load the API server URL from settings
|
||||
api_server = settings.api_server;
|
||||
@ -4875,39 +5081,13 @@ export function select_selected_character(chid) {
|
||||
$("#renameCharButton").css("display", "");
|
||||
$('.open_alternate_greetings').data('chid', chid);
|
||||
$('#set_character_world').data('chid', chid);
|
||||
const world = characters[chid]?.data?.extensions?.world;
|
||||
const worldSet = Boolean(world && world_names.includes(world));
|
||||
$('#set_character_world').toggleClass('world_set', worldSet);
|
||||
setWorldInfoButtonClass(chid);
|
||||
checkEmbeddedWorld(chid);
|
||||
|
||||
$("#form_create").attr("actiontype", "editcharacter");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function checkEmbeddedWorld(chid) {
|
||||
$('#import_character_info').hide();
|
||||
|
||||
if (chid === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (characters[chid]?.data?.character_book) {
|
||||
$('#import_character_info').data('chid', chid).show();
|
||||
|
||||
// Only show the alert once per character
|
||||
const checkKey = `AlertWI_${characters[chid].avatar}`;
|
||||
const worldName = characters[chid]?.data?.extensions?.world;
|
||||
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
|
||||
toastr.info(
|
||||
'To import and use it, select "Import Embedded World Info" in the Options dropdown menu on the character panel.',
|
||||
`${characters[chid].name} has an embedded World/Lorebook`,
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, positionClass: 'toast-top-center' },
|
||||
);
|
||||
localStorage.setItem(checkKey, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function select_rm_create() {
|
||||
menu_type = "create";
|
||||
|
||||
@ -4956,7 +5136,7 @@ function select_rm_create() {
|
||||
$("#name_div").addClass('displayBlock');
|
||||
$('.open_alternate_greetings').data('chid', undefined);
|
||||
$('#set_character_world').data('chid', undefined);
|
||||
$('#set_character_world').toggleClass('world_set', !!create_save.world);
|
||||
setWorldInfoButtonClass(undefined, !!create_save.world);
|
||||
updateFavButtonState(false);
|
||||
checkEmbeddedWorld();
|
||||
|
||||
@ -5339,7 +5519,7 @@ function openCharacterWorldPopup() {
|
||||
}
|
||||
|
||||
function onSelectCharacterWorld() {
|
||||
const value = $(this).find('option:selected').val();
|
||||
const value = $('.character_world_info_selector').find('option:selected').val();
|
||||
const worldIndex = value !== '' ? Number(value) : NaN;
|
||||
const name = !isNaN(worldIndex) ? world_names[worldIndex] : '';
|
||||
|
||||
@ -5353,15 +5533,45 @@ function openCharacterWorldPopup() {
|
||||
createOrEditCharacter();
|
||||
}
|
||||
|
||||
$('#set_character_world').toggleClass('world_set', !!value);
|
||||
setWorldInfoButtonClass(undefined, !!value);
|
||||
}
|
||||
|
||||
function onExtraWorldInfoChanged() {
|
||||
const selectedWorlds = $('.character_extra_world_info_selector').val();
|
||||
let charLore = world_info.charLore ?? [];
|
||||
|
||||
// TODO: Maybe make this utility function not use the window context?
|
||||
const fileName = getCharaFilename(chid);
|
||||
const tempExtraBooks = selectedWorlds.map((index) => world_names[index]).filter((e) => e !== undefined);
|
||||
|
||||
const existingCharLore = charLore.find((e) => e.name === fileName);
|
||||
if (existingCharLore) {
|
||||
if (tempExtraBooks.length === 0) {
|
||||
charLore.splice(existingCharLore, 1);
|
||||
} else {
|
||||
existingCharLore.extraBooks = tempExtraBooks;
|
||||
}
|
||||
} else {
|
||||
const newCharLoreEntry = {
|
||||
name: fileName,
|
||||
extraBooks: tempExtraBooks
|
||||
}
|
||||
|
||||
charLore.push(newCharLoreEntry);
|
||||
}
|
||||
Object.assign(world_info, { charLore: charLore });
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless';
|
||||
const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || '';
|
||||
const template = $('#character_world_template .character_world').clone();
|
||||
const select = template.find('.character_world_info_selector');
|
||||
const extraSelect = template.find('.character_extra_world_info_selector');
|
||||
const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless';
|
||||
const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || '';
|
||||
template.find('.character_name').text(name);
|
||||
|
||||
|
||||
// Apped to base dropdown
|
||||
world_names.forEach((item, i) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = i;
|
||||
@ -5370,7 +5580,47 @@ function openCharacterWorldPopup() {
|
||||
select.append(option);
|
||||
});
|
||||
|
||||
// Append to extras dropdown
|
||||
if (world_names.length > 0) {
|
||||
extraSelect.empty();
|
||||
}
|
||||
world_names.forEach((item, i) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = i;
|
||||
option.innerText = item;
|
||||
|
||||
const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());
|
||||
if (existingCharLore) {
|
||||
option.selected = existingCharLore.extraBooks.includes(item);
|
||||
} else {
|
||||
option.selected = false;
|
||||
}
|
||||
extraSelect.append(option);
|
||||
});
|
||||
|
||||
select.on('change', onSelectCharacterWorld);
|
||||
extraSelect.on('mousedown change', async function (e) {
|
||||
// If there's no world names, don't do anything
|
||||
if (world_names.length === 0) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
let selectScrollTop = null;
|
||||
|
||||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(extraSelect)[0];
|
||||
selectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = selectScrollTop;
|
||||
}
|
||||
|
||||
onExtraWorldInfoChanged();
|
||||
});
|
||||
|
||||
callPopup(template, 'text');
|
||||
}
|
||||
|
||||
@ -6078,7 +6328,13 @@ const isPwaMode = window.navigator.standalone;
|
||||
if (isPwaMode) { $("body").addClass('PWA') }
|
||||
|
||||
$(document).ready(function () {
|
||||
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
||||
|
||||
if (isMobile() === true) {
|
||||
console.debug('hiding movingUI and sheldWidth toggles for mobile')
|
||||
$("#sheldWidthToggleBlock").hide();
|
||||
$("#movingUIModeCheckBlock").hide();
|
||||
|
||||
}
|
||||
|
||||
registerSlashCommand('dupe', DupeChar, [], "– duplicates the currently selected character", true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], "– connect to an API", true, true);
|
||||
@ -6093,11 +6349,11 @@ $(document).ready(function () {
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}, 5));
|
||||
|
||||
// This does not actually increase performance.
|
||||
/*$("#chat").on('scroll', debounce(() => {
|
||||
updateVisibleDivs('#chat', false);
|
||||
}, 10));*/
|
||||
$("#chat").on('mousewheel', () => {
|
||||
scrollLock = true;
|
||||
});
|
||||
|
||||
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
||||
let S_TAFocused = false;
|
||||
let S_TAPreviouslyFocused = false;
|
||||
$('#send_textarea').on('focusin focus click', () => {
|
||||
@ -6113,7 +6369,8 @@ $(document).ready(function () {
|
||||
});
|
||||
$(document).click(event => {
|
||||
if ($(':focus').attr('id') !== 'send_textarea') {
|
||||
if (!$(event.target.id).is("#options_button, #send_but, #send_textarea, #option_regenerate")) {
|
||||
var validIDs = ["options_button", "send_but", "send_textarea", "option_regenerate"];
|
||||
if ($(event.target).attr('id') !== validIDs) {
|
||||
S_TAFocused = false;
|
||||
S_TAPreviouslyFocused = false;
|
||||
}
|
||||
@ -6265,56 +6522,21 @@ $(document).ready(function () {
|
||||
|
||||
$(document).on("click", "#user_avatar_block .avatar", setUserAvatar);
|
||||
$(document).on("click", "#user_avatar_block .avatar_upload", function () {
|
||||
$("#avatar_upload_file").click();
|
||||
$("#avatar_upload_overwrite").val("");
|
||||
$("#avatar_upload_file").trigger('click');
|
||||
});
|
||||
$("#avatar_upload_file").on("change", async function (e) {
|
||||
const file = e.target.files[0];
|
||||
$(document).on("click", "#user_avatar_block .set_persona_image", function () {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!file) {
|
||||
if (!avatarId) {
|
||||
console.log('no imgfile');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData($("#form_upload_avatar").get(0));
|
||||
|
||||
const dataUrl = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = "/uploaduseravatar";
|
||||
|
||||
if (crop_data !== undefined) {
|
||||
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (data) {
|
||||
if (data.path) {
|
||||
appendUserAvatar(data.path);
|
||||
}
|
||||
crop_data = undefined;
|
||||
},
|
||||
error: (jqXHR, exception) => { },
|
||||
});
|
||||
|
||||
// Will allow to select the same file twice in a row
|
||||
$("#form_upload_avatar").trigger("reset");
|
||||
$("#avatar_upload_overwrite").val(avatarId);
|
||||
$("#avatar_upload_file").trigger('click');
|
||||
});
|
||||
$("#avatar_upload_file").on("change", uploadUserAvatar);
|
||||
|
||||
$(document).on("click", ".bg_example", async function () {
|
||||
//when user clicks on a BG thumbnail...
|
||||
@ -6558,17 +6780,17 @@ $(document).ready(function () {
|
||||
|
||||
$("#form_create").submit(createOrEditCharacter);
|
||||
|
||||
/* $("#delete_button").click(function () {
|
||||
popup_type = "del_ch";
|
||||
callPopup(`
|
||||
$("#delete_button").on('click', function () {
|
||||
popup_type = "del_ch";
|
||||
callPopup(`
|
||||
<h3>Delete the character?</h3>
|
||||
<b>THIS IS PERMANENT!<br><br>
|
||||
THIS WILL ALSO DELETE ALL<br>
|
||||
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
|
||||
);
|
||||
}); */
|
||||
);
|
||||
});
|
||||
|
||||
$("#rm_info_button").click(function () {
|
||||
$("#rm_info_button").on('click', function () {
|
||||
$("#rm_info_avatar").html("");
|
||||
select_rm_characters();
|
||||
});
|
||||
@ -7281,6 +7503,8 @@ $(document).ready(function () {
|
||||
setUserName($('#your_name').val());
|
||||
});
|
||||
|
||||
$("#create_dummy_persona").on('click', createDummyPersona);
|
||||
|
||||
$('#sync_name_button').on('click', async function () {
|
||||
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
|
||||
|
||||
@ -7324,8 +7548,9 @@ $(document).ready(function () {
|
||||
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
||||
$(document).on('click', '.delete_avatar', deleteUserAvatar);
|
||||
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
||||
$(document).on('click', '.set_user_info', setUserInfo);
|
||||
$('#lock_user_name').on('click', lockUserNameToChat);
|
||||
$('#persona_description').on('input', onPersonaDescriptionInput);
|
||||
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
|
||||
|
||||
//**************************CHARACTER IMPORT EXPORT*************************//
|
||||
$("#character_import_button").click(function () {
|
||||
@ -7341,10 +7566,10 @@ $(document).ready(function () {
|
||||
importCharacter(file);
|
||||
}
|
||||
});
|
||||
/* $("#export_button").click(function (e) {
|
||||
$('#export_format_popup').toggle();
|
||||
exportPopper.update();
|
||||
}); */
|
||||
$("#export_button").on('click', function (e) {
|
||||
$('#export_format_popup').toggle();
|
||||
exportPopper.update();
|
||||
});
|
||||
$(document).on('click', '.export_format', async function () {
|
||||
const format = $(this).data('format');
|
||||
|
||||
@ -7426,19 +7651,19 @@ $(document).ready(function () {
|
||||
select_rm_characters();
|
||||
});
|
||||
|
||||
/* $("#dupe_button").click(async function () {
|
||||
$("#dupe_button").click(async function () {
|
||||
|
||||
const body = { avatar_url: characters[this_chid].avatar };
|
||||
const response = await fetch('/dupecharacter', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) {
|
||||
toastr.success("Character Duplicated");
|
||||
getCharacters();
|
||||
}
|
||||
}); */
|
||||
const body = { avatar_url: characters[this_chid].avatar };
|
||||
const response = await fetch('/dupecharacter', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) {
|
||||
toastr.success("Character Duplicated");
|
||||
getCharacters();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () {
|
||||
let file_name = $(this).hasClass('mes_bookmark')
|
||||
@ -7476,12 +7701,15 @@ $(document).ready(function () {
|
||||
$('.drawer-toggle').click(function () {
|
||||
var icon = $(this).find('.drawer-icon');
|
||||
var drawer = $(this).parent().find('.drawer-content');
|
||||
if (drawer.hasClass('resizing')) { return }
|
||||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||||
|
||||
if (!drawerWasOpenAlready) {
|
||||
$('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
|
||||
if (!drawerWasOpenAlready) { //to open the drawer
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", function () {
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
icon.toggleClass('openIcon closedIcon');
|
||||
@ -7489,29 +7717,36 @@ $(document).ready(function () {
|
||||
|
||||
//console.log(targetDrawerID);
|
||||
if (targetDrawerID === 'right-nav-panel') {
|
||||
$(this).closest('.drawer').find('.drawer-content').slideToggle({
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||||
duration: 200,
|
||||
easing: "swing",
|
||||
start: function () {
|
||||
jQuery(this).css('display', 'flex');
|
||||
},
|
||||
complete: function () {
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
$("#rm_print_characters_block").trigger("scroll");
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$(this).closest('.drawer').find('.drawer-content').slideToggle(200, "swing");
|
||||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, "swing", function () {
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} else if (drawerWasOpenAlready) {
|
||||
} else if (drawerWasOpenAlready) { //to close
|
||||
icon.toggleClass('closedIcon openIcon');
|
||||
|
||||
if (pinnedDrawerClicked) {
|
||||
$(drawer).slideToggle(200, "swing");
|
||||
$(drawer).addClass('resizing').slideToggle(200, "swing", function () {
|
||||
$(this).removeClass('resizing');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
|
||||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", function () {
|
||||
$(this).closest('.drawer-content').removeClass('resizing');
|
||||
});
|
||||
}
|
||||
|
||||
drawer.toggleClass('closedDrawer openDrawer');
|
||||
@ -7566,22 +7801,44 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes .avatar', function () {
|
||||
|
||||
console.log(isMobile());
|
||||
console.log($('body').hasClass('waifuMode'));
|
||||
|
||||
if (isMobile() === true && !$('body').hasClass('waifuMode')) {
|
||||
console.debug('saw mobile regular mode, returning');
|
||||
return;
|
||||
} else { console.debug('saw valid env for zoomed display') }
|
||||
|
||||
let thumbURL = $(this).children('img').attr('src');
|
||||
let charsPath = '/characters/'
|
||||
let targetAvatarImg = thumbURL.substring(thumbURL.lastIndexOf("=") + 1);
|
||||
let charname = targetAvatarImg.replace('.png', '');
|
||||
|
||||
let avatarSrc = isDataURL(thumbURL) ? thumbURL : charsPath + targetAvatarImg;
|
||||
console.log(avatarSrc);
|
||||
if ($(this).parent().parent().attr('is_user') == 'true') { //handle user avatars
|
||||
$("#zoomed_avatar").attr('src', thumbURL);
|
||||
} else if ($(this).parent().parent().attr('is_system') == 'true') { //handle system avatars
|
||||
$("#zoomed_avatar").attr('src', thumbURL);
|
||||
} else if ($(this).parent().parent().attr('is_user') == 'false') { //handle char avatars
|
||||
$("#zoomed_avatar").attr('src', avatarSrc);
|
||||
}
|
||||
$('#avatar_zoom_popup').toggle();
|
||||
if ($(`.zoomed_avatar[forChar="${charname}"]`).length) {
|
||||
console.debug('removing container as it already existed')
|
||||
$(`.zoomed_avatar[forChar="${charname}"]`).remove();
|
||||
} else {
|
||||
console.debug('making new container from template')
|
||||
const template = $('#zoomed_avatar_template').html();
|
||||
const newElement = $(template);
|
||||
newElement.attr('forChar', charname);
|
||||
newElement.attr('id', `zoomFor_${charname}`);
|
||||
newElement.find('.drag-grabber').attr('id', `zoomFor_${charname}header`);
|
||||
|
||||
//} else { return; }
|
||||
$('body').append(newElement);
|
||||
if ($(this).parent().parent().attr('is_user') == 'true') { //handle user avatars
|
||||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
|
||||
} else if ($(this).parent().parent().attr('is_system') == 'true') { //handle system avatars
|
||||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
|
||||
} else if ($(this).parent().parent().attr('is_user') == 'false') { //handle char avatars
|
||||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', avatarSrc);
|
||||
}
|
||||
loadMovingUIState();
|
||||
$(`.zoomed_avatar[forChar="${charname}"]`).css('display', 'block');
|
||||
dragElement(newElement)
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '#OpenAllWIEntries', function () {
|
||||
@ -7636,18 +7893,19 @@ $(document).ready(function () {
|
||||
case 'renameCharButton':
|
||||
renameCharacter();
|
||||
break;
|
||||
case 'dupe_button':
|
||||
/*case 'dupe_button':
|
||||
DupeChar();
|
||||
break;
|
||||
case 'export_button':
|
||||
$('#export_format_popup').toggle();
|
||||
exportPopper.update();
|
||||
break;
|
||||
*/
|
||||
case 'import_character_info':
|
||||
await importEmbeddedWorldInfo();
|
||||
saveCharacterDebounced();
|
||||
break;
|
||||
case 'delete_button':
|
||||
/*case 'delete_button':
|
||||
popup_type = "del_ch";
|
||||
callPopup(`
|
||||
<h3>Delete the character?</h3>
|
||||
@ -7655,7 +7913,7 @@ $(document).ready(function () {
|
||||
THIS WILL ALSO DELETE ALL<br>
|
||||
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
|
||||
);
|
||||
break;
|
||||
break;*/
|
||||
}
|
||||
$("#char-management-dropdown").prop('selectedIndex', 0);
|
||||
});
|
||||
@ -7713,6 +7971,55 @@ $(document).ready(function () {
|
||||
restoreCaretPosition($(this).get(0), caretPosition);
|
||||
});
|
||||
|
||||
$('#external_import_button').on('click', async () => {
|
||||
const html = `<h3>Enter the URL of the content to import</h3>
|
||||
Supported sources:<br>
|
||||
<ul class="justifyLeft">
|
||||
<li>Chub characters (direct link or id)<br>Example: <tt>lorebooks/bartleby/example-lorebook</tt></li>
|
||||
<li>Chub lorebooks (direct link or id)<br>Example: <tt>Anonymous/example-character</tt></li>
|
||||
<li>More coming soon...</li>
|
||||
<ul>`
|
||||
const input = await callPopup(html, 'input');
|
||||
|
||||
if (!input) {
|
||||
console.debug('Custom content import cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = input.trim();
|
||||
console.debug('Custom content import started', url);
|
||||
|
||||
const request = await fetch('/import_custom', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
|
||||
if (!request.ok) {
|
||||
toastr.info(request.statusText, 'Custom content import failed');
|
||||
console.error('Custom content import failed', request.status, request.statusText);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await request.blob();
|
||||
const customContentType = request.headers.get('X-Custom-Content-Type');
|
||||
const fileName = request.headers.get('Content-Disposition').split('filename=')[1].replace(/"/g, '');
|
||||
const file = new File([data], fileName, { type: data.type });
|
||||
|
||||
switch (customContentType) {
|
||||
case 'character':
|
||||
processDroppedFiles([file]);
|
||||
break;
|
||||
case 'lorebook':
|
||||
await importWorldInfo(file);
|
||||
break;
|
||||
default:
|
||||
toastr.warn('Unknown content type');
|
||||
console.error('Unknown content type', customContentType);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const $dropzone = $(document.body);
|
||||
|
||||
$dropzone.on('dragover', (event) => {
|
||||
|
@ -11,8 +11,8 @@ import {
|
||||
is_send_press,
|
||||
getTokenCount,
|
||||
menu_type,
|
||||
|
||||
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
} from "../script.js";
|
||||
|
||||
|
||||
@ -287,18 +287,18 @@ export function RA_CountCharTokens() {
|
||||
} else { console.debug("RA_TC -- no valid char found, closing."); }
|
||||
}
|
||||
// display the counted tokens
|
||||
if (count_tokens < 1024 && perm_tokens < 1024) {
|
||||
//display normal if both counts are under 1024
|
||||
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
|
||||
if (count_tokens < tokenLimit && perm_tokens < tokenLimit) {
|
||||
$("#result_info").html(`<small>${count_tokens} Tokens (${perm_tokens} Permanent)</small>`);
|
||||
} else {
|
||||
$("#result_info").html(`
|
||||
<div class="flex-container flexFlowColumn alignitemscenter">
|
||||
<div class="flex-container alignitemscenter">
|
||||
<div class="flex-container flexnowrap flexNoGap">
|
||||
<small class="flex-container flexnowrap flexNoGap">
|
||||
<div class="neutral_warning">${count_tokens}</div> Tokens (<div class="neutral_warning">${perm_tokens}</div><div> Permanent)</div>
|
||||
</small>
|
||||
</div>
|
||||
<div id="chartokenwarning" class="menu_button whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
|
||||
<div id="chartokenwarning" class="menu_button margin0 whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
|
||||
</div>`);
|
||||
} //warn if either are over 1024
|
||||
}
|
||||
@ -473,60 +473,97 @@ function OpenNavPanels() {
|
||||
|
||||
|
||||
// Make the DIV element draggable:
|
||||
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"));
|
||||
|
||||
|
||||
// SECOND UPDATE AIMING FOR MUTATIONS ONLY
|
||||
|
||||
export 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:
|
||||
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
|
||||
var height, width, top, left;
|
||||
var elmntName = elmnt.attr('id');
|
||||
const elmntNameEscaped = $.escapeSelector(elmntName);
|
||||
const elmntHeader = $(`#${elmntNameEscaped}header`);
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
dragMouseDown(e);
|
||||
});
|
||||
} else {
|
||||
// otherwise, move the DIV from anywhere inside the DIV, b:
|
||||
elmnt.onmousedown = dragMouseDown;
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
const target = mutations[0].target;
|
||||
if (!$(target).is(':visible')
|
||||
|| $(target).hasClass('resizing')
|
||||
|| Number((String(target.height).replace('px', ''))) < 50
|
||||
|| Number((String(target.width).replace('px', ''))) < 50
|
||||
|| isMobile() === true
|
||||
) { return }
|
||||
|
||||
const style = getComputedStyle(target);
|
||||
console.log(style.top, style.left)
|
||||
height = target.offsetHeight;
|
||||
width = target.offsetWidth;
|
||||
top = parseInt(style.top);
|
||||
left = parseInt(style.left);
|
||||
|
||||
/* console.log(`
|
||||
height=${height},
|
||||
width=${width},
|
||||
top=${top},
|
||||
left=${left}`); */
|
||||
|
||||
if (!power_user.movingUIState[elmntName]) {
|
||||
power_user.movingUIState[elmntName] = {};
|
||||
}
|
||||
|
||||
power_user.movingUIState[elmntName].top = top;
|
||||
power_user.movingUIState[elmntName].left = left;
|
||||
power_user.movingUIState[elmntName].width = width;
|
||||
power_user.movingUIState[elmntName].height = height;
|
||||
power_user.movingUIState[elmntName].right = 'unset';
|
||||
power_user.movingUIState[elmntName].bottom = 'unset';
|
||||
power_user.movingUIState[elmntName].margin = 'unset';
|
||||
saveSettingsDebounced();
|
||||
saveSettingsDebounced();
|
||||
|
||||
|
||||
// Check if the element header exists and set the listener on the grabber
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
dragMouseDown(e);
|
||||
});
|
||||
} else {
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
|
||||
function dragMouseDown(e) {
|
||||
//console.log(e);
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX; //mouse X at click
|
||||
pos4 = e.clientY; //mouse Y at click
|
||||
document.onmouseup = closeDragElement;
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag;
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
pos3 = e.clientX; //mouse X at click
|
||||
pos4 = e.clientY; //mouse Y at click
|
||||
}
|
||||
$(document).on('mouseup', closeDragElement);
|
||||
$(document).on('mousemove', elementDrag);
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
//disable scrollbars when dragging to prevent jitter
|
||||
$("body").css("overflow", "hidden");
|
||||
if (!power_user.movingUIState[elmntName]) {
|
||||
power_user.movingUIState[elmntName] = {};
|
||||
}
|
||||
|
||||
|
||||
//get window size
|
||||
let winWidth = window.innerWidth;
|
||||
let winHeight = window.innerHeight;
|
||||
|
||||
//get necessary data for calculating element footprint
|
||||
let draggableHeight = parseInt(getComputedStyle(elmnt).getPropertyValue('height').slice(0, -2));
|
||||
let draggableWidth = parseInt(getComputedStyle(elmnt).getPropertyValue('width').slice(0, -2));
|
||||
let draggableTop = parseInt(getComputedStyle(elmnt).getPropertyValue('top').slice(0, -2));
|
||||
let draggableLeft = parseInt(getComputedStyle(elmnt).getPropertyValue('left').slice(0, -2));
|
||||
let sheldWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sheldWidth').slice(0, -2));
|
||||
let sheldWidth = parseInt($('html').css('--sheldWidth').slice(0, -2));
|
||||
let topBarFirstX = (winWidth - sheldWidth) / 2;
|
||||
let topBarLastX = topBarFirstX + sheldWidth;
|
||||
let maxX = (width + left);
|
||||
let maxY = (height + top);
|
||||
|
||||
//set the lowest and most-right pixel the element touches
|
||||
let maxX = (draggableWidth + draggableLeft);
|
||||
let maxY = (draggableHeight + draggableTop);
|
||||
|
||||
// calculate the new cursor position:
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
|
||||
@ -535,88 +572,54 @@ export function dragElement(elmnt) {
|
||||
pos3 = e.clientX; //new mouse X
|
||||
pos4 = e.clientY; //new mouse Y
|
||||
|
||||
elmnt.setAttribute('data-dragged', 'true');
|
||||
elmnt.attr('data-dragged', 'true');
|
||||
|
||||
//fix over/underflows:
|
||||
|
||||
setTimeout(function () {
|
||||
if (elmnt.offsetTop < 40) {
|
||||
/* console.log('6'); */
|
||||
if (maxX > topBarFirstX && maxX < topBarLastX) {
|
||||
/* console.log('maxX inside topBar!'); */
|
||||
elmnt.style.top = "42px";
|
||||
}
|
||||
if (elmnt.offsetLeft < topBarLastX && elmnt.offsetLeft > topBarFirstX) {
|
||||
/* console.log('offsetLeft inside TopBar!'); */
|
||||
elmnt.style.top = "42px";
|
||||
}
|
||||
if (elmnt.offset().top < 40) {
|
||||
if (maxX > topBarFirstX && maxX < topBarLastX) {
|
||||
elmnt.css('top', '42px');
|
||||
}
|
||||
|
||||
if (elmnt.offsetTop - pos2 <= 0) {
|
||||
/* console.log('1'); */
|
||||
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
|
||||
elmnt.style.top = "0px";
|
||||
if (elmnt.offset().left < topBarLastX && elmnt.offset().left > topBarFirstX) {
|
||||
elmnt.css('top', '42px');
|
||||
}
|
||||
|
||||
if (elmnt.offsetLeft - pos1 <= 0) {
|
||||
/* console.log('2'); */
|
||||
//prevent moving out of window left
|
||||
elmnt.style.left = "0px";
|
||||
}
|
||||
if (elmnt.offset().top - pos2 <= 0) {
|
||||
elmnt.css('top', '0px');
|
||||
}
|
||||
if (elmnt.offset().left - pos1 <= 0) {
|
||||
elmnt.css('left', '0px');
|
||||
}
|
||||
if (maxX >= winWidth) {
|
||||
elmnt.css('left', elmnt.offset().left - 10 + "px");
|
||||
}
|
||||
if (maxY >= winHeight) {
|
||||
elmnt.css('top', elmnt.offset().top - 10 + "px");
|
||||
if (elmnt.offset().top - pos2 <= 40) {
|
||||
elmnt.css('top', '20px');
|
||||
}
|
||||
}
|
||||
elmnt.css('left', (elmnt.offset().left - pos1) + "px");
|
||||
elmnt.css("top", (elmnt.offset().top - pos2) + "px");
|
||||
elmnt.css('margin', 'unset');
|
||||
|
||||
if (maxX >= winWidth) {
|
||||
/* console.log('3'); */
|
||||
//bounce off right
|
||||
elmnt.style.left = elmnt.offsetLeft - 10 + "px";
|
||||
}
|
||||
|
||||
if (maxY >= winHeight) {
|
||||
/* console.log('4'); */
|
||||
//bounce off bottom
|
||||
elmnt.style.top = elmnt.offsetTop - 10 + "px";
|
||||
if (elmnt.offsetTop - pos2 <= 40) {
|
||||
/* console.log('5'); */
|
||||
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
|
||||
/* console.log('caught Y bounce to <40Y top'); */
|
||||
elmnt.style.top = "20px";
|
||||
}
|
||||
}
|
||||
// if no problems, set element's new position
|
||||
/* console.log('7'); */
|
||||
|
||||
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
|
||||
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}
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${elmnt.style.left}
|
||||
Y: ${elmnt.style.top}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
|
||||
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
|
||||
`); */
|
||||
|
||||
|
||||
|
||||
}, 50)
|
||||
|
||||
/* console.log("left/top: " + (elmnt.offsetLeft - pos1) + "/" + (elmnt.offsetTop - pos2) +
|
||||
", win: " + winWidth + "/" + winHeight +
|
||||
", max X / Y: " + maxX + " / " + maxY); */
|
||||
|
||||
/*
|
||||
console.log(`
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${$(elmnt).css('left')}
|
||||
Y: ${$(elmnt).css('top')}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
|
||||
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
|
||||
`);
|
||||
*/
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
// stop moving when mouse button is released:
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
//revert scrolling to normal after drag to allow recovery of vastly misplaced elements
|
||||
$("body").css("overflow", "auto");
|
||||
$(document).off('mouseup', closeDragElement);
|
||||
$(document).off('mousemove', elementDrag);
|
||||
$("body").css("overflow", "");
|
||||
// Clear the "data-dragged" attribute
|
||||
elmnt.attr('data-dragged', 'false');
|
||||
|
||||
}
|
||||
}
|
||||
@ -626,7 +629,22 @@ export function dragElement(elmnt) {
|
||||
$("document").ready(function () {
|
||||
|
||||
// initial status check
|
||||
setTimeout(RA_checkOnlineStatus, 100);
|
||||
setTimeout(() => {
|
||||
if (isMobile === false) {
|
||||
dragElement($("#sheld"));
|
||||
dragElement($("#left-nav-panel"));
|
||||
dragElement($("#right-nav-panel"));
|
||||
dragElement($("#WorldInfo"));
|
||||
dragElement($("#floatingPrompt"));
|
||||
}
|
||||
RA_checkOnlineStatus
|
||||
}
|
||||
, 100);
|
||||
|
||||
|
||||
|
||||
|
||||
//$('div').on('resize', saveMovingUIState());
|
||||
|
||||
// read the state of AutoConnect and AutoLoadChat.
|
||||
$(AutoConnectCheckbox).prop("checked", LoadLocalBool("AutoConnectEnabled"));
|
||||
|
@ -120,7 +120,7 @@ $(document).ready(function () {
|
||||
<div class="background_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Backgrounds</b>
|
||||
<b>Chat Backgrounds</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"display_name": "Character Backgrounds",
|
||||
"display_name": "Chat Backgrounds",
|
||||
"loading_order": 7,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
|
@ -10,7 +10,7 @@ import { selected_group } from "../../group-chats.js";
|
||||
import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { getCharaFilename, debounce } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
@ -222,6 +222,8 @@ function loadSettings() {
|
||||
export function setFloatingPrompt() {
|
||||
const context = getContext();
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
console.debug('setFloatingPrompt: Not in a chat. Skipping.');
|
||||
shouldWIAddPrompt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -243,6 +245,7 @@ export function setFloatingPrompt() {
|
||||
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
|
||||
context.setExtensionPrompt(MODULE_NAME, '');
|
||||
$('#extension_floating_counter').text('(disabled)');
|
||||
shouldWIAddPrompt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -702,7 +702,6 @@ jQuery(async () => {
|
||||
<option value="date">Sort memories by date</option>
|
||||
<option value="distance">Sort memories by relevance</option>
|
||||
</select>
|
||||
<label for="chromadb_keep_context">How many original chat messages to keep: (<span id="chromadb_keep_context_value"></span>) messages</label>
|
||||
<label for="chromadb_keep_context"><small>How many original chat messages to keep: (<span id="chromadb_keep_context_value"></span>) messages</small></label>
|
||||
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
|
||||
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
updateMessageBlock,
|
||||
} from "../../../script.js";
|
||||
import { extension_settings, getContext } from "../../extensions.js";
|
||||
import { secret_state, writeSecret } from "../../secrets.js";
|
||||
|
||||
const autoModeOptions = {
|
||||
NONE: 'none',
|
||||
@ -134,6 +135,14 @@ const languageCodes = {
|
||||
'Zulu': 'zu',
|
||||
};
|
||||
|
||||
const KEY_REQUIRED = ['deepl'];
|
||||
|
||||
function showKeyButton() {
|
||||
const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider);
|
||||
$("#translate_key_button").toggle(providerRequiresKey);
|
||||
$("#translate_key_button").toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
for (const key in defaultSettings) {
|
||||
if (!extension_settings.translate.hasOwnProperty(key)) {
|
||||
@ -144,6 +153,7 @@ function loadSettings() {
|
||||
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', true);
|
||||
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', true);
|
||||
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', true);
|
||||
showKeyButton();
|
||||
}
|
||||
|
||||
async function translateImpersonate(text) {
|
||||
@ -186,18 +196,39 @@ async function translateProviderGoogle(text, lang) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
async function translateProviderDeepl(text, lang) {
|
||||
if (!secret_state.deepl) {
|
||||
throw new Error('No DeepL API key');
|
||||
}
|
||||
|
||||
const response = await fetch('/deepl_translate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.text();
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
async function translate(text, lang) {
|
||||
try {
|
||||
switch (extension_settings.translate.provider) {
|
||||
case 'google':
|
||||
return await translateProviderGoogle(text, lang);
|
||||
case 'deepl':
|
||||
return await translateProviderDeepl(text, lang);
|
||||
default:
|
||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||
return text;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error('Failed to translate message');
|
||||
toastr.error(String(error), 'Failed to translate message');
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,9 +362,13 @@ jQuery(() => {
|
||||
<option value="both">Translate both</option>
|
||||
</select>
|
||||
<label for="translation_provider">Provider</label>
|
||||
<select id="translation_provider" name="provider">
|
||||
<option value="google">Google</option>
|
||||
<select>
|
||||
<div class="flex-container gap5px flexnowrap marginBot5">
|
||||
<select id="translation_provider" name="provider" class="margin0">
|
||||
<option value="google">Google</option>
|
||||
<option value="deepl">DeepL</option>
|
||||
<select>
|
||||
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
||||
</div>
|
||||
<label for="translation_target_language">Target Language</label>
|
||||
<select id="translation_target_language" name="target_language"></select>
|
||||
<div id="translation_clear" class="menu_button">
|
||||
@ -364,6 +399,7 @@ jQuery(() => {
|
||||
});
|
||||
$('#translation_provider').on('change', (event) => {
|
||||
extension_settings.translate.provider = event.target.value;
|
||||
showKeyButton();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#translation_target_language').on('change', (event) => {
|
||||
@ -371,6 +407,17 @@ jQuery(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$(document).on('click', '.mes_translate', onMessageTranslateClick);
|
||||
$('#translate_key_button').on('click', async () => {
|
||||
const optionText = $('#translation_provider option:selected').text();
|
||||
const key = await callPopup(`<h3>${optionText} API Key</h3>`, 'input');
|
||||
|
||||
if (key == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeSecret(extension_settings.translate.provider, key);
|
||||
toastr.success('API Key saved');
|
||||
});
|
||||
|
||||
loadSettings();
|
||||
|
||||
|
@ -2,6 +2,5 @@
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@ -225,12 +225,10 @@ async function showKudos() {
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
|
||||
let hordeModelSelectScrollTop = null;
|
||||
|
||||
$("#horde_model").on('mousedown change', async function (e) {
|
||||
//desktop-only routine for multi-select without CTRL
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
/*if (deviceInfo.device.type === 'desktop') {
|
||||
let hordeModelSelectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
@ -238,7 +236,7 @@ jQuery(function () {
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = hordeModelSelectScrollTop;
|
||||
}
|
||||
}*/
|
||||
horde_settings.models = $('#horde_model').val();
|
||||
console.log('Updated Horde models', horde_settings.models);
|
||||
});
|
||||
@ -265,4 +263,14 @@ jQuery(function () {
|
||||
|
||||
$("#horde_refresh").on("click", getHordeModels);
|
||||
$("#horde_kudos").on("click", showKudos);
|
||||
|
||||
// Not needed on mobile
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
$('#horde_model').select2({
|
||||
width: '100%',
|
||||
placeholder: 'Select Horde models',
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@ -927,16 +927,15 @@ function getTokenizerModel() {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('claude')) {
|
||||
return turboTokenizer;
|
||||
return 'claude';
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('GPT-NeoXT')) {
|
||||
return 'gpt2';
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a Claude tokenizer for JS yet. Turbo 3.5 should be able to handle this.
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return turboTokenizer;
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
// Default to Turbo 3.5
|
||||
|
@ -12,12 +12,13 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
printCharacters,
|
||||
name1,
|
||||
name2,
|
||||
replaceCurrentChat,
|
||||
setCharacterId
|
||||
} from "../script.js";
|
||||
import { favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { favsToHotswap, isMobile } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
selected_group,
|
||||
@ -27,6 +28,7 @@ import { registerSlashCommand } from "./slash-commands.js";
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
loadMovingUIState,
|
||||
collapseNewlines,
|
||||
playMessageSound,
|
||||
sortGroupMembers,
|
||||
@ -77,6 +79,13 @@ const send_on_enter_options = {
|
||||
ENABLED: 1,
|
||||
}
|
||||
|
||||
export const persona_description_positions = {
|
||||
BEFORE_CHAR: 0,
|
||||
AFTER_CHAR: 1,
|
||||
TOP_AN: 2,
|
||||
BOTTOM_AN: 3,
|
||||
}
|
||||
|
||||
let power_user = {
|
||||
tokenizer: tokenizers.CLASSIC,
|
||||
token_padding: 64,
|
||||
@ -101,6 +110,7 @@ let power_user = {
|
||||
chat_display: chat_styles.DEFAULT,
|
||||
sheld_width: sheld_width.DEFAULT,
|
||||
never_resize_avatars: false,
|
||||
show_card_avatar_urls: false,
|
||||
play_message_sound: false,
|
||||
play_sound_unfocused: true,
|
||||
auto_save_msg_edits: false,
|
||||
@ -122,6 +132,7 @@ let power_user = {
|
||||
|
||||
waifuMode: false,
|
||||
movingUI: false,
|
||||
movingUIState: {},
|
||||
noShadows: false,
|
||||
theme: 'Default (Dark) 1.7.1',
|
||||
|
||||
@ -160,6 +171,10 @@ let power_user = {
|
||||
|
||||
personas: {},
|
||||
default_persona: null,
|
||||
persona_descriptions: {},
|
||||
|
||||
persona_description: '',
|
||||
persona_description_position: persona_description_positions.BEFORE_CHAR,
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
@ -279,9 +294,15 @@ function toggleWaifu() {
|
||||
}
|
||||
|
||||
function switchWaifuMode() {
|
||||
//console.log(`switching waifu to ${power_user.waifuMode}`);
|
||||
$("body").toggleClass("waifuMode", power_user.waifuMode);
|
||||
$("#waifuMode").prop("checked", power_user.waifuMode);
|
||||
if (isMobile() && !$('body').hasClass('waifuMode')) {
|
||||
console.debug('saw mobile regular mode, removing ZoomedAvatars');
|
||||
if ($('.zoomed_avatar[forChar]').length) {
|
||||
$('.zoomed_avatar[forChar]').remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
scrollChatToBottom();
|
||||
}
|
||||
|
||||
@ -575,6 +596,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#play_message_sound").prop("checked", power_user.play_message_sound);
|
||||
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
|
||||
$("#never_resize_avatars").prop("checked", power_user.never_resize_avatars);
|
||||
$("#show_card_avatar_urls").prop("checked", power_user.show_card_avatar_urls);
|
||||
$("#auto_save_msg_edits").prop("checked", power_user.auto_save_msg_edits);
|
||||
$("#allow_name1_display").prop("checked", power_user.allow_name1_display);
|
||||
$("#allow_name2_display").prop("checked", power_user.allow_name2_display);
|
||||
@ -621,6 +643,31 @@ function loadPowerUserSettings(settings, data) {
|
||||
loadInstructMode();
|
||||
loadMaxContextUnlocked();
|
||||
switchWaifuMode();
|
||||
loadMovingUIState();
|
||||
|
||||
//console.log(power_user)
|
||||
}
|
||||
|
||||
function loadMovingUIState() {
|
||||
if (isMobile() === false && power_user.movingUIState) {
|
||||
for (var elmntName of Object.keys(power_user.movingUIState)) {
|
||||
var elmntState = power_user.movingUIState[elmntName];
|
||||
try {
|
||||
var elmnt = $('#' + $.escapeSelector(elmntName));
|
||||
if (elmnt.length) {
|
||||
console.debug(`loading state for ${elmntName}`)
|
||||
elmnt.css(elmntState);
|
||||
} else {
|
||||
console.debug(`skipping ${elmntName} because it doesn't exist in the DOM`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug(`error occurred while processing ${elmntName}: ${err}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.debug('skipping movingUI state load for mobile')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loadMaxContextUnlocked() {
|
||||
@ -893,21 +940,26 @@ function resetMovablePanels() {
|
||||
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 = '';
|
||||
document.getElementById("expression-holder").style.right = '';
|
||||
document.getElementById("expression-holder").style.bottom = '';
|
||||
document.getElementById("expression-holder").style.height = '';
|
||||
document.getElementById("expression-holder").style.width = '';
|
||||
document.getElementById("expression-holder").style.margin = '';
|
||||
if ($("#expression-holder")) {
|
||||
document.getElementById("expression-holder").style.top = '';
|
||||
document.getElementById("expression-holder").style.left = '';
|
||||
document.getElementById("expression-holder").style.right = '';
|
||||
document.getElementById("expression-holder").style.bottom = '';
|
||||
document.getElementById("expression-holder").style.height = '';
|
||||
document.getElementById("expression-holder").style.width = '';
|
||||
document.getElementById("expression-holder").style.margin = '';
|
||||
}
|
||||
|
||||
if ($(".zoomed_avatar")) {
|
||||
$(".zoomed_avatar").css('top', '');
|
||||
$(".zoomed_avatar").css('left', '');
|
||||
$(".zoomed_avatar").css('right', '');
|
||||
$(".zoomed_avatar").css('bottom', '');
|
||||
$(".zoomed_avatar").css('width', '');
|
||||
$(".zoomed_avatar").css('height', '');
|
||||
$(".zoomed_avatar").css('margin', '');
|
||||
}
|
||||
|
||||
document.getElementById("avatar_zoom_popup").style.top = '';
|
||||
document.getElementById("avatar_zoom_popup").style.left = '';
|
||||
document.getElementById("avatar_zoom_popup").style.right = '';
|
||||
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 = '';
|
||||
@ -917,7 +969,17 @@ function resetMovablePanels() {
|
||||
document.getElementById("WorldInfo").style.width = '';
|
||||
document.getElementById("WorldInfo").style.margin = '';
|
||||
|
||||
document.getElementById("floatingPrompt").style.top = '';
|
||||
document.getElementById("floatingPrompt").style.left = '';
|
||||
document.getElementById("floatingPrompt").style.right = '';
|
||||
document.getElementById("floatingPrompt").style.bottom = '';
|
||||
document.getElementById("floatingPrompt").style.height = '';
|
||||
document.getElementById("floatingPrompt").style.width = '';
|
||||
document.getElementById("floatingPrompt").style.margin = '';
|
||||
|
||||
$('*[data-dragged="true"]').removeAttr('data-dragged');
|
||||
power_user.movingUIState = {}
|
||||
saveSettingsDebounced();
|
||||
eventSource.emit(event_types.MOVABLE_PANELS_RESET);
|
||||
}
|
||||
|
||||
@ -1159,6 +1221,11 @@ $(document).ready(() => {
|
||||
power_user.never_resize_avatars = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$("#show_card_avatar_urls").on('input', function () {
|
||||
power_user.show_card_avatar_urls = !!$(this).prop('checked');
|
||||
printCharacters();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#play_message_sound").on('input', function () {
|
||||
power_user.play_message_sound = !!$(this).prop('checked');
|
||||
|
2
public/scripts/select2.min.js
vendored
Normal file
2
public/scripts/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -457,9 +457,9 @@ export function isDataURL(str) {
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
export function getCharaFilename() {
|
||||
export function getCharaFilename(chid) {
|
||||
const context = getContext();
|
||||
const fileName = context.characters[context.characterId].avatar;
|
||||
const fileName = context.characters[chid ?? context.characterId].avatar;
|
||||
|
||||
if (fileName) {
|
||||
return fileName.replace(/\.[^/.]+$/, "")
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters } from "../script.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer } from "./utils.js";
|
||||
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type } from "../script.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, delay, getCharaFilename } from "./utils.js";
|
||||
import { getContext } from "./extensions.js";
|
||||
import { metadata_keys, shouldWIAddPrompt } from "./extensions/floating-prompt/index.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./extensions/floating-prompt/index.js";
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { deviceInfo } from "./RossAscends-mods.js";
|
||||
|
||||
export {
|
||||
world_info,
|
||||
@ -25,7 +26,8 @@ const world_info_insertion_strategy = {
|
||||
global_first: 2,
|
||||
};
|
||||
|
||||
let world_info = null;
|
||||
let world_info = {};
|
||||
let selected_world_info = [];
|
||||
let world_names;
|
||||
let world_info_depth = 2;
|
||||
let world_info_budget = 25;
|
||||
@ -34,7 +36,10 @@ let world_info_case_sensitive = false;
|
||||
let world_info_match_whole_words = false;
|
||||
let world_info_character_strategy = world_info_insertion_strategy.evenly;
|
||||
const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), 1000);
|
||||
const saveSettingsDebounced = debounce(() => saveSettings(), 1000);
|
||||
const saveSettingsDebounced = debounce(() => {
|
||||
Object.assign(world_info, { globalSelect: selected_world_info })
|
||||
saveSettings()
|
||||
}, 1000);
|
||||
const sortFn = (a, b) => b.order - a.order;
|
||||
|
||||
const world_info_position = {
|
||||
@ -77,6 +82,19 @@ function setWorldInfoSettings(settings, data) {
|
||||
world_info_budget = 25;
|
||||
}
|
||||
|
||||
// Reset selected world from old string and delete old keys
|
||||
// TODO: Remove next release
|
||||
const existingWorldInfo = settings.world_info;
|
||||
if (typeof existingWorldInfo === "string") {
|
||||
delete settings.world_info;
|
||||
selected_world_info = [existingWorldInfo];
|
||||
} else if (Array.isArray(existingWorldInfo)) {
|
||||
delete settings.world_info;
|
||||
selected_world_info = existingWorldInfo;
|
||||
}
|
||||
|
||||
world_info = settings.world_info ?? {}
|
||||
|
||||
$("#world_info_depth_counter").text(world_info_depth);
|
||||
$("#world_info_depth").val(world_info_depth);
|
||||
|
||||
@ -92,22 +110,22 @@ function setWorldInfoSettings(settings, data) {
|
||||
|
||||
world_names = data.world_names?.length ? data.world_names : [];
|
||||
|
||||
if (settings.world_info != undefined) {
|
||||
if (world_names.includes(settings.world_info)) {
|
||||
world_info = settings.world_info;
|
||||
}
|
||||
// Add to existing selected WI if it exists
|
||||
selected_world_info = selected_world_info.concat(settings.world_info?.globalSelect?.filter((e) => world_names.includes(e)) ?? []);
|
||||
|
||||
if (world_names.length > 0) {
|
||||
$("#world_info").empty();
|
||||
}
|
||||
|
||||
world_names.forEach((item, i) => {
|
||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
||||
$("#world_info").append(`<option value='${i}'${selected_world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
|
||||
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
||||
// preselect world if saved
|
||||
if (item == world_info) {
|
||||
$("#world_info").val(i).trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
$("#world_editor_select").trigger("change");
|
||||
|
||||
// Update settings
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// World Info Editor
|
||||
@ -160,7 +178,7 @@ async function updateWorldInfoList() {
|
||||
$("#world_editor_select").find('option[value!=""]').remove();
|
||||
|
||||
world_names.forEach((item, i) => {
|
||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
||||
$("#world_info").append(`<option value='${i}'${selected_world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
|
||||
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
||||
});
|
||||
}
|
||||
@ -170,6 +188,14 @@ function hideWorldEditor() {
|
||||
displayWorldEntries(null, null);
|
||||
}
|
||||
|
||||
function getWIElement(name) {
|
||||
const wiElement = $("#world_info").children().filter(function () {
|
||||
return $(this).text().toLowerCase() === name.toLowerCase()
|
||||
});
|
||||
|
||||
return wiElement;
|
||||
}
|
||||
|
||||
function nullWorldInfo() {
|
||||
toastr.info("Create or import a new World Info file first.", "World Info is not set", { timeOut: 10000, preventDuplicates: true });
|
||||
}
|
||||
@ -224,7 +250,21 @@ function displayWorldEntries(name, data) {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteWorldInfo(name, world_info);
|
||||
const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(name));
|
||||
if (existingCharLores && existingCharLores.length > 0) {
|
||||
existingCharLores.forEach((charLore) => {
|
||||
const tempCharLore = charLore.extraBooks.filter((e) => e !== name);
|
||||
if (tempCharLore.length === 0) {
|
||||
world_info.charLore.splice(charLore, 1);
|
||||
} else {
|
||||
charLore.extraBooks = tempCharLore;
|
||||
}
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// Selected world_info automatically refreshes
|
||||
await deleteWorldInfo(name);
|
||||
});
|
||||
|
||||
// Check if a sortable instance exists
|
||||
@ -256,7 +296,7 @@ function displayWorldEntries(name, data) {
|
||||
await saveWorldInfo(name, data, true);
|
||||
}
|
||||
});
|
||||
$("#world_popup_entries_list").disableSelection();
|
||||
//$("#world_popup_entries_list").disableSelection();
|
||||
}
|
||||
|
||||
function setOriginalDataValue(data, uid, key, value) {
|
||||
@ -582,19 +622,26 @@ async function renameWorldInfo(name, data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectNewName = null;
|
||||
if (oldName === world_info) {
|
||||
console.debug("Renaming current world info");
|
||||
world_info = newName;
|
||||
selectNewName = newName;
|
||||
}
|
||||
else {
|
||||
console.debug("Renaming non-current world info");
|
||||
selectNewName = world_info;
|
||||
}
|
||||
const entryPreviouslySelected = selected_world_info.findIndex((e) => e === oldName);
|
||||
|
||||
await saveWorldInfo(newName, data, true);
|
||||
await deleteWorldInfo(oldName, selectNewName);
|
||||
await deleteWorldInfo(oldName);
|
||||
|
||||
const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(oldName));
|
||||
if (existingCharLores && existingCharLores.length > 0) {
|
||||
existingCharLores.forEach((charLore) => {
|
||||
const tempCharLore = charLore.extraBooks.filter((e) => e !== oldName);
|
||||
tempCharLore.push(newName);
|
||||
charLore.extraBooks = tempCharLore;
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
if (entryPreviouslySelected !== -1) {
|
||||
const wiElement = getWIElement(newName);
|
||||
wiElement.prop("selected", true);
|
||||
$("#world_info").trigger('change');
|
||||
}
|
||||
|
||||
const selectedIndex = world_names.indexOf(newName);
|
||||
if (selectedIndex !== -1) {
|
||||
@ -602,7 +649,7 @@ async function renameWorldInfo(name, data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteWorldInfo(worldInfoName, selectWorldName) {
|
||||
async function deleteWorldInfo(worldInfoName) {
|
||||
if (!world_names.includes(worldInfoName)) {
|
||||
return;
|
||||
}
|
||||
@ -614,16 +661,22 @@ async function deleteWorldInfo(worldInfoName, selectWorldName) {
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await updateWorldInfoList();
|
||||
|
||||
const selectedIndex = world_names.indexOf(selectWorldName);
|
||||
if (selectedIndex !== -1) {
|
||||
$("#world_info").val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
$("#world_info").val("").trigger('change');
|
||||
const existingWorldIndex = selected_world_info.findIndex((e) => e === worldInfoName);
|
||||
if (existingWorldIndex !== -1) {
|
||||
selected_world_info.splice(existingWorldIndex, 1);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
await updateWorldInfoList();
|
||||
$('#world_editor_select').trigger('change');
|
||||
|
||||
if ($('#character_world').val() === worldInfoName) {
|
||||
$('#character_world').val('').trigger('change');
|
||||
setWorldInfoButtonClass(undefined, false);
|
||||
if (menu_type != 'create') {
|
||||
saveCharacterDebounced();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -663,16 +716,13 @@ async function createNewWorldInfo(worldInfoName) {
|
||||
return;
|
||||
}
|
||||
|
||||
world_info = worldInfoName;
|
||||
await saveWorldInfo(world_info, worldInfoTemplate, true);
|
||||
await saveWorldInfo(worldInfoName, worldInfoTemplate, true);
|
||||
await updateWorldInfoList();
|
||||
|
||||
const selectedIndex = world_names.indexOf(worldInfoName);
|
||||
if (selectedIndex !== -1) {
|
||||
$("#world_info").val(selectedIndex).trigger('change');
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
$("#world_info").val("").trigger('change');
|
||||
hideWorldEditor();
|
||||
}
|
||||
}
|
||||
@ -683,40 +733,53 @@ function transformString(str) {
|
||||
}
|
||||
|
||||
async function getCharacterLore() {
|
||||
const name = characters[this_chid]?.data?.extensions?.world;
|
||||
const character = characters[this_chid];
|
||||
const name = character?.name;
|
||||
let worldsToSearch = new Set();
|
||||
|
||||
if (!name) {
|
||||
const baseWorldName = character?.data?.extensions?.world;
|
||||
if (baseWorldName) {
|
||||
worldsToSearch.add(baseWorldName);
|
||||
} else {
|
||||
console.debug(`Character ${name}'s base world could not be found or is empty! Skipping...`)
|
||||
return [];
|
||||
}
|
||||
|
||||
if (name === world_info) {
|
||||
console.debug(`Character ${characters[this_chid]?.name} world info is the same as global: ${name}. Skipping...`);
|
||||
return [];
|
||||
// TODO: Maybe make the utility function not use the window context?
|
||||
const fileName = getCharaFilename(this_chid);
|
||||
const extraCharLore = world_info.charLore?.find((e) => e.name === fileName);
|
||||
if (extraCharLore) {
|
||||
worldsToSearch = new Set([...worldsToSearch, ...extraCharLore.extraBooks]);
|
||||
}
|
||||
|
||||
if (!world_names.includes(name)) {
|
||||
console.log(`Character ${characters[this_chid]?.name} world info does not exist: ${name}`);
|
||||
return [];
|
||||
let entries = [];
|
||||
for (const worldName of worldsToSearch) {
|
||||
if (selected_world_info.includes(worldName)) {
|
||||
console.debug(`Character ${name}'s world ${worldName} is already activated in global world info! Skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await loadWorldInfoData(worldName);
|
||||
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
||||
entries = entries.concat(newEntries);
|
||||
}
|
||||
|
||||
const data = await loadWorldInfoData(name);
|
||||
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
||||
console.debug(`Character ${characters[this_chid]?.name} lore (${name}) has ${entries.length} world info entries`);
|
||||
console.debug(`Character ${characters[this_chid]?.name} lore (${baseWorldName}) has ${entries.length} world info entries`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function getGlobalLore() {
|
||||
if (!world_info) {
|
||||
if (!selected_world_info) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!world_names.includes(world_info)) {
|
||||
console.log(`Global ${characters[this_chid]?.name} world info does not exist: ${world_info}`);
|
||||
return [];
|
||||
let entries = [];
|
||||
for (const worldName of selected_world_info) {
|
||||
const data = await loadWorldInfoData(worldName);
|
||||
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
||||
entries = entries.concat(newEntries);
|
||||
}
|
||||
|
||||
const data = await loadWorldInfoData(world_info);
|
||||
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
||||
console.debug(`Global world info has ${entries.length} entries`);
|
||||
|
||||
return entries;
|
||||
@ -819,11 +882,12 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
const newEntries = [...activatedNow]
|
||||
.sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b));
|
||||
let newContent = "";
|
||||
const textToScanTokens = getTokenCount(textToScan);
|
||||
|
||||
for (const entry of newEntries) {
|
||||
newContent += `${substituteParams(entry.content)}\n`;
|
||||
|
||||
if (getTokenCount(textToScan + newContent) >= budget) {
|
||||
if (textToScanTokens + getTokenCount(newContent) >= budget) {
|
||||
console.debug(`WI budget reached, stopping`);
|
||||
needsToScan = false;
|
||||
break;
|
||||
@ -837,28 +901,39 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
}
|
||||
}
|
||||
|
||||
let worldInfoBefore = "";
|
||||
let worldInfoAfter = "";
|
||||
|
||||
const ANTopInjection = [];
|
||||
const ANBottomInjection = [];
|
||||
// Forward-sorted list of entries for joining
|
||||
const WIBeforeEntries = [];
|
||||
const WIAfterEntries = [];
|
||||
const ANTopEntries = [];
|
||||
const ANBottomEntries = [];
|
||||
|
||||
// Appends from insertion order 999 to 1. Use unshift for this purpose
|
||||
[...allActivatedEntries].sort(sortFn).forEach((entry) => {
|
||||
if (entry.position === world_info_position.before) {
|
||||
worldInfoBefore = `${substituteParams(entry.content)}\n${worldInfoBefore}`;
|
||||
} else if (entry.position === world_info_position.after) {
|
||||
worldInfoAfter = `${substituteParams(entry.content)}\n${worldInfoAfter}`;
|
||||
} else if (entry.position === world_info_position.ANTop) {
|
||||
ANTopInjection.push(entry.content);
|
||||
} else if (entry.position === world_info_position.ANBottom) {
|
||||
ANBottomInjection.push(entry.content);
|
||||
switch (entry.position) {
|
||||
case world_info_position.before:
|
||||
WIBeforeEntries.unshift(substituteParams(entry.content));
|
||||
break;
|
||||
case world_info_position.after:
|
||||
WIAfterEntries.unshift(substituteParams(entry.content));
|
||||
break;
|
||||
case world_info_position.ANTop:
|
||||
ANTopEntries.unshift(entry.content);
|
||||
break;
|
||||
case world_info_position.ANBottom:
|
||||
ANBottomEntries.unshift(entry.content);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const worldInfoBefore = `${WIBeforeEntries.join("\n")}\n`
|
||||
const worldInfoAfter = `${WIAfterEntries.join("\n")}\n`
|
||||
|
||||
if (shouldWIAddPrompt) {
|
||||
const originalAN = context.extensionPrompts['2_floating_prompt'].value;
|
||||
const ANWithWI = `\n${ANTopInjection.join("\n")} \n${originalAN} \n${ANBottomInjection.reverse().join("\n")}`
|
||||
context.setExtensionPrompt('2_floating_prompt', ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
const originalAN = context.extensionPrompts[NOTE_MODULE_NAME].value;
|
||||
const ANWithWI = `${ANTopEntries.join("\n")}\n${originalAN}\n${ANBottomEntries.join("\n")}`
|
||||
context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
}
|
||||
|
||||
return { worldInfoBefore, worldInfoAfter };
|
||||
@ -993,6 +1068,48 @@ function convertCharacterBook(characterBook) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function setWorldInfoButtonClass(chid, forceValue = undefined) {
|
||||
if (forceValue !== undefined) {
|
||||
$('#set_character_world, #world_button').toggleClass('world_set', forceValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const world = characters[chid]?.data?.extensions?.world;
|
||||
const worldSet = Boolean(world && world_names.includes(world));
|
||||
$('#set_character_world, #world_button').toggleClass('world_set', worldSet);
|
||||
}
|
||||
|
||||
export function checkEmbeddedWorld(chid) {
|
||||
$('#import_character_info').hide();
|
||||
|
||||
if (chid === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (characters[chid]?.data?.character_book) {
|
||||
$('#import_character_info').data('chid', chid).show();
|
||||
|
||||
// Only show the alert once per character
|
||||
const checkKey = `AlertWI_${characters[chid].avatar}`;
|
||||
const worldName = characters[chid]?.data?.extensions?.world;
|
||||
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
|
||||
toastr.info(
|
||||
'To import and use it, select "Import Embedded World Info" in the Options dropdown menu on the character panel.',
|
||||
`${characters[chid].name} has an embedded World/Lorebook`,
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, positionClass: 'toast-top-center' },
|
||||
);
|
||||
localStorage.setItem(checkKey, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function importEmbeddedWorldInfo() {
|
||||
const chid = $('#import_character_info').data('chid');
|
||||
|
||||
@ -1021,40 +1138,148 @@ export async function importEmbeddedWorldInfo() {
|
||||
if (newIndex >= 0) {
|
||||
$("#world_editor_select").val(newIndex).trigger('change');
|
||||
}
|
||||
|
||||
setWorldInfoButtonClass(chid, true);
|
||||
}
|
||||
|
||||
function onWorldInfoChange(_, text) {
|
||||
let selectedWorld;
|
||||
if (_ !== '__notSlashCommand__') { //if it's a slash command
|
||||
if (text !== undefined) { //and args are provided
|
||||
let slashInputWorld = text.toLowerCase();
|
||||
$("#world_info").find(`option`).filter(function () {
|
||||
return $(this).text().toLowerCase() === slashInputWorld;
|
||||
}).prop('selected', true); //matches arg with worldnames and selects; if none found, unsets world
|
||||
let setWorldName = $("#world_info").find(":selected").text(); //only for toastr display
|
||||
toastr.success(`Active world: ${setWorldName}`);
|
||||
selectedWorld = $("#world_info").find(":selected").val();
|
||||
} else { //if no args, unset world
|
||||
toastr.success('Deselected World')
|
||||
let selectedWorlds;
|
||||
if (_ !== '__notSlashCommand__') { // if it's a slash command
|
||||
if (text !== undefined) { // and args are provided
|
||||
const slashInputSplitText = text.trim().toLowerCase().split(",");
|
||||
|
||||
slashInputSplitText.forEach((worldName) => {
|
||||
const wiElement = getWIElement(worldName);
|
||||
if (wiElement.length > 0) {
|
||||
wiElement.prop("selected", true);
|
||||
toastr.success(`Activated world: ${wiElement.text()}`);
|
||||
} else {
|
||||
toastr.error(`No world found named: ${worldName}`);
|
||||
}
|
||||
})
|
||||
} else { // if no args, unset all worlds
|
||||
toastr.success('Deactivated all worlds');
|
||||
selected_world_info = [];
|
||||
$("#world_info").val("");
|
||||
}
|
||||
} else { //if it's a pointer selection
|
||||
selectedWorld = $("#world_info").find(":selected").val();
|
||||
}
|
||||
world_info = null;
|
||||
if (selectedWorld !== "") {
|
||||
const worldIndex = Number(selectedWorld);
|
||||
world_info = !isNaN(worldIndex) ? world_names[worldIndex] : null;
|
||||
let tempWorldInfo = [];
|
||||
let selectedWorlds = $("#world_info").val().map((e) => Number(e)).filter((e) => !isNaN(e));
|
||||
if (selectedWorlds.length > 0) {
|
||||
selectedWorlds.forEach((worldIndex) => {
|
||||
const existingWorldName = world_names[worldIndex];
|
||||
if (existingWorldName) {
|
||||
tempWorldInfo.push(existingWorldName);
|
||||
} else {
|
||||
const wiElement = getWIElement(existingWorldName);
|
||||
wiElement.prop("selected", false);
|
||||
toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
selected_world_info = tempWorldInfo;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
export async function importWorldInfo(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', file);
|
||||
|
||||
try {
|
||||
let jsonData;
|
||||
|
||||
if (file.name.endsWith('.png')) {
|
||||
const buffer = new Uint8Array(await getFileBuffer(file));
|
||||
jsonData = extractDataFromPng(buffer, 'naidata');
|
||||
} else {
|
||||
// File should be a JSON file
|
||||
jsonData = await parseJsonFile(file);
|
||||
}
|
||||
|
||||
if (jsonData === undefined || jsonData === null) {
|
||||
toastr.error(`File is not valid: ${file.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert Novel Lorebook
|
||||
if (jsonData.lorebookVersion !== undefined) {
|
||||
console.log('Converting Novel Lorebook');
|
||||
formData.append('convertedData', JSON.stringify(convertNovelLorebook(jsonData)));
|
||||
}
|
||||
|
||||
// Convert Agnai Memory Book
|
||||
if (jsonData.kind === 'memory') {
|
||||
console.log('Converting Agnai Memory Book');
|
||||
formData.append('convertedData', JSON.stringify(convertAgnaiMemoryBook(jsonData)));
|
||||
}
|
||||
|
||||
// Convert Risu Lorebook
|
||||
if (jsonData.type === 'risu') {
|
||||
console.log('Converting Risu Lorebook');
|
||||
formData.append('convertedData', JSON.stringify(convertRisuLorebook(jsonData)));
|
||||
}
|
||||
} catch (error) {
|
||||
toastr.error(`Error parsing file: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importworldinfo",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.name) {
|
||||
await updateWorldInfoList();
|
||||
|
||||
const newIndex = world_names.indexOf(data.name);
|
||||
if (newIndex >= 0) {
|
||||
$("#world_editor_select").val(newIndex).trigger('change');
|
||||
}
|
||||
|
||||
toastr.info(`World Info "${data.name}" imported successfully!`);
|
||||
}
|
||||
},
|
||||
error: (jqXHR, exception) => { },
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
|
||||
$(document).ready(function () {
|
||||
registerSlashCommand('world', onWorldInfoChange, [], "– sets active World, or unsets if no args provided", true, true);
|
||||
})
|
||||
$("#world_info").on('change', async function () { onWorldInfoChange('__notSlashCommand__') });
|
||||
|
||||
let selectScrollTop = null;
|
||||
|
||||
$("#world_info").on('mousedown change', async function (e) {
|
||||
// If there's no world names, don't do anything
|
||||
if (world_names.length === 0) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
selectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = selectScrollTop;
|
||||
}
|
||||
|
||||
onWorldInfoChange('__notSlashCommand__');
|
||||
});
|
||||
|
||||
//**************************WORLD INFO IMPORT EXPORT*************************//
|
||||
$("#world_import_button").on('click', function () {
|
||||
@ -1064,81 +1289,12 @@ jQuery(() => {
|
||||
$("#world_import_file").on("change", async function (e) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData($("#form_world_import").get(0));
|
||||
|
||||
try {
|
||||
let jsonData;
|
||||
|
||||
if (file.name.endsWith('.png')) {
|
||||
const buffer = new Uint8Array(await getFileBuffer(file));
|
||||
jsonData = extractDataFromPng(buffer, 'naidata');
|
||||
} else {
|
||||
// File should be a JSON file
|
||||
jsonData = await parseJsonFile(file);
|
||||
}
|
||||
|
||||
if (jsonData === undefined || jsonData === null) {
|
||||
toastr.error(`File is not valid: ${file.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert Novel Lorebook
|
||||
if (jsonData.lorebookVersion !== undefined) {
|
||||
console.log('Converting Novel Lorebook');
|
||||
formData.append('convertedData', JSON.stringify(convertNovelLorebook(jsonData)));
|
||||
}
|
||||
|
||||
// Convert Agnai Memory Book
|
||||
if (jsonData.kind === 'memory') {
|
||||
console.log('Converting Agnai Memory Book');
|
||||
formData.append('convertedData', JSON.stringify(convertAgnaiMemoryBook(jsonData)));
|
||||
}
|
||||
|
||||
// Convert Risu Lorebook
|
||||
if (jsonData.type === 'risu') {
|
||||
console.log('Converting Risu Lorebook');
|
||||
formData.append('convertedData', JSON.stringify(convertRisuLorebook(jsonData)));
|
||||
}
|
||||
} catch (error) {
|
||||
toastr.error(`Error parsing file: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importworldinfo",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.name) {
|
||||
await updateWorldInfoList();
|
||||
|
||||
const newIndex = world_names.indexOf(data.name);
|
||||
if (newIndex >= 0) {
|
||||
$("#world_editor_select").val(newIndex).trigger('change');
|
||||
}
|
||||
|
||||
toastr.info(`World Info "${data.name}" imported successfully!`);
|
||||
}
|
||||
},
|
||||
error: (jqXHR, exception) => { },
|
||||
});
|
||||
await importWorldInfo(file);
|
||||
|
||||
// Will allow to select the same file twice in a row
|
||||
$("#form_world_import").trigger("reset");
|
||||
});
|
||||
|
||||
$("#world_cross").click(() => {
|
||||
hideWorldEditor();
|
||||
});
|
||||
|
||||
$("#world_create_button").on('click', async () => {
|
||||
const tempName = getFreeWorldName();
|
||||
const finalName = await callPopup("<h3>Create a new World Info?</h3>Enter a name for the new file:", "input", tempName);
|
||||
@ -1189,5 +1345,27 @@ jQuery(() => {
|
||||
$('#world_info_character_strategy').on('change', function () {
|
||||
world_info_character_strategy = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
})
|
||||
});
|
||||
|
||||
$('#world_button').on('click', async function () {
|
||||
const chid = $('#set_character_world').data('chid');
|
||||
|
||||
if (chid) {
|
||||
const worldName = characters[chid]?.data?.extensions?.world;
|
||||
const hasEmbed = checkEmbeddedWorld(chid);
|
||||
if (worldName && world_names.includes(worldName)) {
|
||||
if (!$('#WorldInfo').is(':visible')) {
|
||||
$('#WIDrawerIcon').trigger('click');
|
||||
}
|
||||
const index = world_names.indexOf(worldName);
|
||||
$("#world_editor_select").val(index).trigger('change');
|
||||
} else if (hasEmbed) {
|
||||
await importEmbeddedWorldInfo();
|
||||
saveCharacterDebounced();
|
||||
}
|
||||
else {
|
||||
$('#char-management-dropdown').val($('#set_character_world').val()).trigger('change');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
305
public/style.css
305
public/style.css
@ -216,6 +216,7 @@ table.responsiveTable {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mes_text strong em,
|
||||
.mes_text strong,
|
||||
.mes_text h2,
|
||||
.mes_text h1 {
|
||||
@ -702,6 +703,7 @@ hr {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-style: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.last_mes .mesAvatarWrapper {
|
||||
@ -769,6 +771,27 @@ hr {
|
||||
box-shadow: 0 0 5px var(--black50a);
|
||||
}
|
||||
|
||||
.character_select .avatar,
|
||||
body.big-avatars .character_select .avatar {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
/*
|
||||
.character_select .avatar img {
|
||||
flex: unset;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
width: unset;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
,
|
||||
body.big-avatars .character_select .avatar img {
|
||||
min-width: 60px;
|
||||
aspect-ratio: unset;
|
||||
}
|
||||
|
||||
*/
|
||||
body.no-hotswap .hotswap {
|
||||
display: none !important;
|
||||
}
|
||||
@ -784,13 +807,16 @@ body.no-timestamps .timestamp {
|
||||
body.big-avatars .avatar {
|
||||
width: 60px;
|
||||
height: 90px;
|
||||
/* width: unset; */
|
||||
border-style: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* align-self: unset; */
|
||||
overflow: visible;
|
||||
border-radius: 10px;
|
||||
flex: 1
|
||||
}
|
||||
|
||||
body.big-avatars #user_avatar_block .avatar,
|
||||
@ -806,8 +832,8 @@ body.big-avatars #user_avatar_block .avatar img {
|
||||
}
|
||||
|
||||
body.big-avatars .avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 60px;
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border: 1px solid var(--black30a);
|
||||
@ -888,7 +914,7 @@ select {
|
||||
}
|
||||
|
||||
#rm_ch_create_block textarea {
|
||||
min-height: 175px;
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
.margin-bot-10px,
|
||||
@ -927,7 +953,6 @@ select {
|
||||
}
|
||||
|
||||
#character_cross,
|
||||
#world_cross,
|
||||
#select_chat_cross {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
@ -1012,6 +1037,7 @@ input[type="file"] {
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#right-nav-panel-tabs .right_menu_button,
|
||||
@ -1122,6 +1148,7 @@ input[type="file"] {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
font-size: calc(var(--mainFontSize) * 1.25);
|
||||
}
|
||||
|
||||
.selected-right-tab {
|
||||
@ -1284,13 +1311,14 @@ select option:not(:checked) {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
border-radius: 7px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#character_search_bar {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
/* padding-left: 0.75em; */
|
||||
height: fit-content;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
input[type=search]::-webkit-search-cancel-button {
|
||||
@ -1326,6 +1354,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.ch_avatar_url {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.character_select .avatar {
|
||||
align-self: center;
|
||||
}
|
||||
@ -1372,6 +1404,10 @@ body.big-avatars .ch_description {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.alignSelfStart {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.gap5px {
|
||||
gap: 5px !important;
|
||||
}
|
||||
@ -1491,7 +1527,7 @@ body.big-avatars .ch_description {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
grid-template-rows:
|
||||
[avatar] min-content [hr] min-content [search block] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto [hidden] min-content;
|
||||
[avatar] min-content [hr] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto [hidden] min-content;
|
||||
}
|
||||
|
||||
.avatar_div {
|
||||
@ -1499,10 +1535,21 @@ body.big-avatars .ch_description {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0px;
|
||||
/* margin-bottom: 6px; */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* #avatar_div_div.avatar img {
|
||||
height: 90%;
|
||||
width: unset;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
body.big-avatars #avatar_div_div.avatar img {
|
||||
height: 90%;
|
||||
width: unset;
|
||||
aspect-ratio: 2 / 3;
|
||||
}
|
||||
*/
|
||||
#user-settings-block h4,
|
||||
#user-settings-block h3 {
|
||||
margin: 5px 0;
|
||||
@ -1584,7 +1631,7 @@ body.big-avatars .ch_description {
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -1702,6 +1749,7 @@ grammarly-extension {
|
||||
#result_info {
|
||||
font-size: calc(var(--mainFontSize) - 0.1rem);
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
@ -1822,7 +1870,7 @@ grammarly-extension {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
height: 35px;
|
||||
height: 32px;
|
||||
filter: grayscale(0.5);
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
@ -2100,22 +2148,6 @@ grammarly-extension {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#world_cross {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#world_logo {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#world_popup h5 {
|
||||
color: var(--grey70);
|
||||
}
|
||||
@ -2184,13 +2216,6 @@ input[type='checkbox']:not(#nav-toggle):not(#rm_button_panel_pin):not(#lm_button
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.option_select_right_menu {
|
||||
width: 284px;
|
||||
margin-bottom: 35px;
|
||||
color: var(--white70a);
|
||||
background-color: var(--black30a);
|
||||
}
|
||||
|
||||
#user_avatar_block {
|
||||
display: flex;
|
||||
grid-gap: 10px;
|
||||
@ -2358,6 +2383,12 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
#char-management-dropdown,
|
||||
#tagInput {
|
||||
height: 32px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.nice-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@ -2749,12 +2780,6 @@ h5 {
|
||||
filter: drop-shadow(0 0 2px red);
|
||||
}
|
||||
|
||||
#advanced_book_logo {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#export_character_div {
|
||||
display: grid;
|
||||
grid-template-columns: 340px auto;
|
||||
@ -2806,6 +2831,7 @@ h5 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: 10px;
|
||||
width: fit-content;
|
||||
min-width: 0;
|
||||
@ -3191,6 +3217,10 @@ body .ui-widget-content li:hover {
|
||||
background-color: var(--white30a);
|
||||
}
|
||||
|
||||
.group_select .avatar {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.group_select .group_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@ -3730,6 +3760,13 @@ label[for="extensions_autoconnect"] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.menu_button_icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/*------------ TOP SIDE SETTINGS ----------------*/
|
||||
|
||||
#top-settings-holder {
|
||||
@ -3739,10 +3776,10 @@ label[for="extensions_autoconnect"] {
|
||||
max-width: var(--sheldWidth);
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
grid-template-columns: 10% 10% 10% 10% 10% 10% 10% 10%;
|
||||
grid-template-columns: 10% 10% 10% 10% 10% 10% 10% 10% 10%;
|
||||
z-index: 3000;
|
||||
position: relative;
|
||||
grid-gap: 2%;
|
||||
grid-gap: 1%;
|
||||
|
||||
}
|
||||
|
||||
@ -3820,6 +3857,10 @@ label[for="extensions_autoconnect"] {
|
||||
flex-basis: calc((var(--sheldWidth) / 4) - 16px);
|
||||
}
|
||||
|
||||
.drawer33pWidth {
|
||||
flex-basis: calc((var(--sheldWidth) / 3) - 16px);
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
@ -3958,7 +3999,6 @@ toolcool-color-picker {
|
||||
width: 30% !important;
|
||||
}
|
||||
|
||||
|
||||
.justifyLeft {
|
||||
text-align: start;
|
||||
justify-content: left;
|
||||
@ -3970,6 +4010,14 @@ toolcool-color-picker {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.justifyContentSpaceAround {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justifyContentFlexEnd {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.spaceEvenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
@ -4246,7 +4294,7 @@ body.movingUI .drag-grabber {
|
||||
body.movingUI #sheld,
|
||||
body.movingUI .drawer-content,
|
||||
body.movingUI #expression-holder,
|
||||
body.movingUI #avatar_zoom_popup,
|
||||
body.movingUI .zoomed_avatar,
|
||||
body.movingUI #floatingPrompt {
|
||||
resize: both;
|
||||
}
|
||||
@ -4273,7 +4321,7 @@ body.noShadows * {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
#avatar_zoom_popup {
|
||||
.zoomed_avatar {
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
@ -4289,7 +4337,7 @@ body.noShadows * {
|
||||
aspect-ratio: 2 / 3;
|
||||
}
|
||||
|
||||
body.waifuMode #avatar_zoom_popup {
|
||||
body.waifuMode .zoomed_avatar {
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
@ -4308,7 +4356,7 @@ body.waifuMode #avatar_zoom_popup {
|
||||
aspect-ratio: 2 / 3;
|
||||
}
|
||||
|
||||
#zoomed_avatar {
|
||||
.zoomed_avatar img {
|
||||
border: 1px solid var(--white50a);
|
||||
border-radius: 20px;
|
||||
height: 100%;
|
||||
@ -4382,7 +4430,7 @@ body.waifuMode #avatar_zoom_popup {
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
#avatar_zoom_popup {
|
||||
.zoomed_avatar {
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
@ -4401,10 +4449,15 @@ body.waifuMode #avatar_zoom_popup {
|
||||
aspect-ratio: 2 / 3;
|
||||
}
|
||||
|
||||
.world_entry_thin_controls {
|
||||
.world_entry_thin_controls,
|
||||
#persona-management-block {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#WIMultiSelector {
|
||||
align-self: normal;
|
||||
}
|
||||
|
||||
.WIEntryContentAndMemo {
|
||||
flex-flow: column;
|
||||
}
|
||||
@ -4573,7 +4626,7 @@ body.waifuMode #avatar_zoom_popup {
|
||||
}
|
||||
|
||||
#rm_ch_create_block textarea {
|
||||
max-height: 180px;
|
||||
max-height: 190px;
|
||||
}
|
||||
|
||||
#talkativeness_hint span {
|
||||
@ -4584,6 +4637,10 @@ body.waifuMode #avatar_zoom_popup {
|
||||
flex-basis: max(calc(100% / 4 - 10px), 190px);
|
||||
}
|
||||
|
||||
.drawer33pWidth {
|
||||
flex-basis: max(calc(100% / 3 - 10px), 190px);
|
||||
}
|
||||
|
||||
.expression-holder {
|
||||
display: none;
|
||||
}
|
||||
@ -4614,13 +4671,14 @@ body.waifuMode #avatar_zoom_popup {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
body:not(.waifuMode) #avatar_zoom_popup {
|
||||
z-index: 999;
|
||||
body:not(.waifuMode) .zoomed_avatar {
|
||||
display: none;
|
||||
/* z-index: 999;
|
||||
width: fit-content;
|
||||
max-height: calc(60svh - 60px);
|
||||
max-height: calc(60svh - 60px); */
|
||||
}
|
||||
|
||||
body.waifuMode #avatar_zoom_popup {
|
||||
body.waifuMode .zoomed_avatar {
|
||||
width: fit-content;
|
||||
max-height: calc(60svh - 60px);
|
||||
max-width: 90svw;
|
||||
@ -4645,14 +4703,33 @@ body.waifuMode #avatar_zoom_popup {
|
||||
body.waifuMode img.expression {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.tag.excluded:after {
|
||||
top: unset;
|
||||
bottom: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
.drawer25pWidth {
|
||||
flex-basis: max(calc(100% / 2 - 10px), 180px);
|
||||
}
|
||||
|
||||
.drawer33pWidth {
|
||||
flex-basis: max(calc(100% / 2 - 10px), 180px);
|
||||
}
|
||||
|
||||
.BGSampleTitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tag.excluded:after {
|
||||
top: unset;
|
||||
bottom: unset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*this part only only applies to iOS devices*/
|
||||
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
@ -4725,3 +4802,127 @@ body.waifuMode #avatar_zoom_popup {
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
|
||||
/* Customize the Select2 container */
|
||||
.select2-container {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
/* Customize the dropdown */
|
||||
.select2-dropdown {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border: 1px solid var(--white30a) !important;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px black;
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
|
||||
color: var(--SmartThemeBodyColor);
|
||||
z-index: 4000;
|
||||
}
|
||||
|
||||
.select2-selection__clear {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
|
||||
padding: revert;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__display {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* Customize the search input */
|
||||
.select2-search__field {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
/* Customize the selected option */
|
||||
.select2-selection--single {
|
||||
border: 1px solid var(--SmartThemeShadowColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
}
|
||||
|
||||
/* Customize the selected option text */
|
||||
.select2-selection__rendered {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
/* Customize the option list item */
|
||||
.select2-results__option {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice {
|
||||
border-radius: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-sizing: border-box;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black30a);
|
||||
border-color: var(--white50a);
|
||||
font-size: calc(var(--mainFontSize) - 5%);
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.select2-results .select2-results__option--selectable {
|
||||
background-color: unset;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
opacity: 0.5;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Customize the hovered option list item */
|
||||
.select2-results .select2-results__option--highlighted.select2-results__option--selectable {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: unset;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Customize the option list item */
|
||||
.select2-results__option {
|
||||
padding-left: 30px;
|
||||
/* Add some padding to make room for the checkbox */
|
||||
}
|
||||
|
||||
/* Add the custom checkbox */
|
||||
.select2-results__option:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 50%;
|
||||
margin-top: -7px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid var(--white30a);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Add the custom checkbox checkmark */
|
||||
.select2-results__option--selected.select2-results__option:before {
|
||||
content: '\2713';
|
||||
font-weight: bold;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
347
server.js
347
server.js
@ -128,10 +128,13 @@ let response_getstatus;
|
||||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
const { SentencePieceProcessor, cleanText } = require("sentencepiece-js");
|
||||
const { Tokenizer } = require('@mlc-ai/web-tokenizers');
|
||||
const CHARS_PER_TOKEN = 3.35;
|
||||
|
||||
let spp_llama;
|
||||
let spp_nerd;
|
||||
let spp_nerd_v2;
|
||||
let claude_tokenizer;
|
||||
|
||||
async function loadSentencepieceTokenizer(modelPath) {
|
||||
try {
|
||||
@ -147,7 +150,7 @@ async function loadSentencepieceTokenizer(modelPath) {
|
||||
async function countSentencepieceTokens(spp, text) {
|
||||
// Fallback to strlen estimation
|
||||
if (!spp) {
|
||||
return Math.ceil(text.length / 3.35);
|
||||
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
let cleaned = cleanText(text);
|
||||
@ -156,9 +159,36 @@ async function countSentencepieceTokens(spp, text) {
|
||||
return ids.length;
|
||||
}
|
||||
|
||||
async function loadClaudeTokenizer(modelPath) {
|
||||
try {
|
||||
const arrayBuffer = fs.readFileSync(modelPath).buffer;
|
||||
const instance = await Tokenizer.fromJSON(arrayBuffer);
|
||||
return instance;
|
||||
} catch (error) {
|
||||
console.error("Claude tokenizer failed to load: " + modelPath, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function countClaudeTokens(tokenizer, messages) {
|
||||
const convertedPrompt = convertClaudePrompt(messages, false, false);
|
||||
|
||||
// Fallback to strlen estimation
|
||||
if (!tokenizer) {
|
||||
return Math.ceil(convertedPrompt.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
const count = tokenizer.encode(convertedPrompt).length;
|
||||
return count;
|
||||
}
|
||||
|
||||
const tokenizersCache = {};
|
||||
|
||||
function getTokenizerModel(requestModel) {
|
||||
if (requestModel.includes('claude')) {
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
if (requestModel.includes('gpt-4-32k')) {
|
||||
return 'gpt-4-32k';
|
||||
}
|
||||
@ -226,6 +256,7 @@ const directories = {
|
||||
extensions: 'public/scripts/extensions',
|
||||
instruct: 'public/instruct',
|
||||
context: 'public/context',
|
||||
backups: 'backups/',
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
@ -728,6 +759,12 @@ function convertToV2(char) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function unsetFavFlag(char) {
|
||||
const _ = require('lodash');
|
||||
_.set(char, 'fav', false);
|
||||
_.set(char, 'data.extensions.fav', false);
|
||||
}
|
||||
|
||||
function readFromV2(char) {
|
||||
const _ = require('lodash');
|
||||
if (_.isUndefined(char.data)) {
|
||||
@ -1049,19 +1086,19 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
|
||||
async function tryReadImage(img_url, crop) {
|
||||
try {
|
||||
let rawImg = await jimp.read(img_url);
|
||||
let final_width = rawImg.bitmap.width, final_height = rawImg.bitmap.height
|
||||
let final_width = rawImg.bitmap.width, final_height = rawImg.bitmap.height
|
||||
|
||||
// Apply crop if defined
|
||||
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
|
||||
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
|
||||
// Apply standard resize if requested
|
||||
if (crop.want_resize) {
|
||||
final_width = AVATAR_WIDTH
|
||||
final_height = AVATAR_HEIGHT
|
||||
}
|
||||
// Apply standard resize if requested
|
||||
if (crop.want_resize) {
|
||||
final_width = AVATAR_WIDTH
|
||||
final_height = AVATAR_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
const image = await rawImg.cover(final_width, final_height).getBufferAsync(jimp.MIME_PNG);
|
||||
const image = await rawImg.cover(final_width, final_height).getBufferAsync(jimp.MIME_PNG);
|
||||
return image;
|
||||
}
|
||||
// If it's an unsupported type of image (APNG) - just read the file as buffer
|
||||
@ -1739,6 +1776,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (jsonData.spec !== undefined) {
|
||||
console.log('importing from v2 json');
|
||||
importRisuSprites(jsonData);
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
||||
let char = JSON.stringify(jsonData);
|
||||
@ -1812,6 +1851,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (jsonData.spec !== undefined) {
|
||||
console.log('Found a v2 character file.');
|
||||
importRisuSprites(jsonData);
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
let char = JSON.stringify(jsonData);
|
||||
charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
@ -2223,7 +2264,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
|
||||
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
|
||||
|
||||
const filename = `${Date.now()}.png`;
|
||||
const filename = request.body.overwrite_name ?? `${Date.now()}.png`;
|
||||
const pathToNewFile = path.join(directories.avatars, filename);
|
||||
fs.writeFileSync(pathToNewFile, image);
|
||||
fs.rmSync(pathToUpload);
|
||||
@ -2870,6 +2911,12 @@ app.post("/openai_bias", jsonParser, async function (request, response) {
|
||||
let result = {};
|
||||
|
||||
const model = getTokenizerModel(String(request.query.model || ''));
|
||||
|
||||
// no bias for claude
|
||||
if (model == 'claude') {
|
||||
return response.send(result);
|
||||
}
|
||||
|
||||
const tokenizer = getTiktokenTokenizer(model);
|
||||
|
||||
for (const entry of request.body) {
|
||||
@ -2942,7 +2989,7 @@ app.post("/deletepreset_openai", jsonParser, function (request, response) {
|
||||
});
|
||||
|
||||
// Prompt Conversion script taken from RisuAI by @kwaroran (GPLv3).
|
||||
function convertClaudePrompt(messages) {
|
||||
function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix) {
|
||||
// Claude doesn't support message names, so we'll just add them to the message content.
|
||||
for (const message of messages) {
|
||||
if (message.name && message.role !== "system") {
|
||||
@ -2972,7 +3019,16 @@ function convertClaudePrompt(messages) {
|
||||
break
|
||||
}
|
||||
return prefix + v.content;
|
||||
}).join('') + '\n\nAssistant: ';
|
||||
}).join('');
|
||||
|
||||
if (addHumanPrefix) {
|
||||
requestPrompt = "\n\nHuman: " + requestPrompt;
|
||||
}
|
||||
|
||||
if (addAssistantPostfix) {
|
||||
requestPrompt = requestPrompt + '\n\nAssistant: ';
|
||||
}
|
||||
|
||||
return requestPrompt;
|
||||
}
|
||||
|
||||
@ -2993,14 +3049,14 @@ async function sendClaudeRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages);
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
console.log('Claude request:', requestPrompt);
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
method: "POST",
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
prompt: "\n\nHuman: " + requestPrompt,
|
||||
prompt: requestPrompt,
|
||||
model: request.body.model,
|
||||
max_tokens_to_sample: request.body.max_tokens,
|
||||
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
|
||||
@ -3166,15 +3222,20 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai = response) {
|
||||
if (!request.body) return response_tokenize_openai.sendStatus(400);
|
||||
|
||||
let num_tokens = 0;
|
||||
const model = getTokenizerModel(String(request.query.model || ''));
|
||||
|
||||
if (model == 'claude') {
|
||||
num_tokens = countClaudeTokens(claude_tokenizer, request.body);
|
||||
return response_tokenize_openai.send({ "token_count": num_tokens });
|
||||
}
|
||||
|
||||
const tokensPerName = model.includes('gpt-4') ? 1 : -1;
|
||||
const tokensPerMessage = model.includes('gpt-4') ? 3 : 4;
|
||||
const tokensPadding = 3;
|
||||
|
||||
const tokenizer = getTiktokenTokenizer(model);
|
||||
|
||||
let num_tokens = 0;
|
||||
for (const msg of request.body) {
|
||||
num_tokens += tokensPerMessage;
|
||||
for (const [key, value] of Object.entries(msg)) {
|
||||
@ -3275,6 +3336,7 @@ const setupTasks = async function () {
|
||||
|
||||
console.log(`SillyTavern ${version.pkgVersion}` + (version.gitBranch ? ` '${version.gitBranch}' (${version.gitRevision})` : ''));
|
||||
|
||||
backupSettings();
|
||||
migrateSecrets();
|
||||
ensurePublicDirectoriesExist();
|
||||
await ensureThumbnailCache();
|
||||
@ -3282,10 +3344,11 @@ const setupTasks = async function () {
|
||||
// Colab users could run the embedded tool
|
||||
if (!is_colab) await convertWebp();
|
||||
|
||||
[spp_llama, spp_nerd, spp_nerd_v2] = await Promise.all([
|
||||
[spp_llama, spp_nerd, spp_nerd_v2, claude_tokenizer] = await Promise.all([
|
||||
loadSentencepieceTokenizer('src/sentencepiece/tokenizer.model'),
|
||||
loadSentencepieceTokenizer('src/sentencepiece/nerdstash.model'),
|
||||
loadSentencepieceTokenizer('src/sentencepiece/nerdstash_v2.model'),
|
||||
loadClaudeTokenizer('src/claude.json'),
|
||||
]);
|
||||
|
||||
console.log('Launching...');
|
||||
@ -3365,6 +3428,41 @@ async function convertWebp() {
|
||||
}
|
||||
}
|
||||
|
||||
function backupSettings() {
|
||||
const MAX_BACKUPS = 25;
|
||||
|
||||
function generateTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(directories.backups)) {
|
||||
fs.mkdirSync(directories.backups);
|
||||
}
|
||||
|
||||
const backupFile = path.join(directories.backups, `settings_${generateTimestamp()}.json`);
|
||||
fs.copyFileSync(SETTINGS_FILE, backupFile);
|
||||
|
||||
let files = fs.readdirSync(directories.backups);
|
||||
if (files.length > MAX_BACKUPS) {
|
||||
files = files.map(f => path.join(directories.backups, f));
|
||||
files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
||||
|
||||
fs.rmSync(files[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Could not backup settings file', err);
|
||||
}
|
||||
}
|
||||
|
||||
function ensurePublicDirectoriesExist() {
|
||||
for (const dir of Object.values(directories)) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
@ -3381,6 +3479,7 @@ const SECRET_KEYS = {
|
||||
POE: 'api_key_poe',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
DEEPL: 'deepl',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
@ -3631,6 +3730,53 @@ app.post('/google_translate', jsonParser, async (request, response) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/deepl_translate', jsonParser, async (request, response) => {
|
||||
const key = readSecret(SECRET_KEYS.DEEPL);
|
||||
|
||||
if (!key) {
|
||||
return response.sendStatus(401);
|
||||
}
|
||||
|
||||
const text = request.body.text;
|
||||
const lang = request.body.lang;
|
||||
|
||||
if (!text || !lang) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
console.log('Input text: ' + text);
|
||||
|
||||
const fetch = require('node-fetch').default;
|
||||
const params = new URLSearchParams();
|
||||
params.append('text', text);
|
||||
params.append('target_lang', lang);
|
||||
|
||||
try {
|
||||
const result = await fetch('https://api-free.deepl.com/v2/translate', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `DeepL-Auth-Key ${key}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
return response.sendStatus(result.status);
|
||||
}
|
||||
|
||||
const json = await result.json();
|
||||
console.log('Translated text: ' + json.translations[0].text);
|
||||
|
||||
return response.send(json.translations[0].text);
|
||||
} catch (error) {
|
||||
console.log("Translation error: " + error.message);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/novel_tts', jsonParser, async (request, response) => {
|
||||
const token = readSecret(SECRET_KEYS.NOVEL);
|
||||
|
||||
@ -3796,6 +3942,177 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/import_custom', jsonParser, async (request, response) => {
|
||||
if (!request.body.url) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = request.body.url;
|
||||
let result;
|
||||
|
||||
const chubParsed = parseChubUrl(url);
|
||||
|
||||
if (chubParsed?.type === 'character') {
|
||||
console.log('Downloading chub character:', chubParsed.id);
|
||||
result = await downloadChubCharacter(chubParsed.id);
|
||||
}
|
||||
else if (chubParsed?.type === 'lorebook') {
|
||||
console.log('Downloading chub lorebook:', chubParsed.id);
|
||||
result = await downloadChubLorebook(chubParsed.id);
|
||||
}
|
||||
else {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
response.set('Content-Type', result.fileType);
|
||||
response.set('Content-Disposition', `attachment; filename="${result.fileName}"`);
|
||||
response.set('X-Custom-Content-Type', chubParsed?.type);
|
||||
return response.send(result.buffer);
|
||||
} catch (error) {
|
||||
console.log('Importing custom content failed', error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
async function downloadChubLorebook(id) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const result = await fetch('https://api.chub.ai/api/lorebooks/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"fullPath": id,
|
||||
"format": "SILLYTAVERN",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('Failed to download lorebook');
|
||||
}
|
||||
|
||||
const name = id.split('/').pop();
|
||||
const buffer = await result.buffer();
|
||||
const fileName = `${sanitize(name)}.json`;
|
||||
const fileType = result.headers.get('content-type');
|
||||
|
||||
return { buffer, fileName, fileType };
|
||||
}
|
||||
|
||||
async function downloadChubCharacter(id) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const result = await fetch('https://api.chub.ai/api/characters/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"format": "tavern",
|
||||
"fullPath": id,
|
||||
})
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('Failed to download character');
|
||||
}
|
||||
|
||||
const buffer = await result.buffer();
|
||||
const fileName = result.headers.get('content-disposition').split('filename=')[1];
|
||||
const fileType = result.headers.get('content-type');
|
||||
|
||||
return { buffer, fileName, fileType };
|
||||
}
|
||||
|
||||
function parseChubUrl(str) {
|
||||
const splitStr = str.split('/');
|
||||
const length = splitStr.length;
|
||||
|
||||
if (length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domainIndex = splitStr.indexOf('chub.ai');
|
||||
const lastTwo = domainIndex !== -1 ? splitStr.slice(domainIndex + 1) : splitStr;
|
||||
|
||||
const firstPart = lastTwo[0].toLowerCase();
|
||||
|
||||
if (firstPart === 'characters' || firstPart === 'lorebooks') {
|
||||
const type = firstPart === 'characters' ? 'character' : 'lorebook';
|
||||
const id = type === 'character' ? lastTwo.slice(1).join('/') : lastTwo.join('/');
|
||||
return {
|
||||
id: id,
|
||||
type: type
|
||||
};
|
||||
} else if (length === 2) {
|
||||
return {
|
||||
id: lastTwo.join('/'),
|
||||
type: 'character'
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function importRisuSprites(data) {
|
||||
try {
|
||||
const name = data?.data?.name;
|
||||
const risuData = data?.data?.extensions?.risuai;
|
||||
|
||||
// Not a Risu AI character
|
||||
if (!risuData || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let images = [];
|
||||
|
||||
if (Array.isArray(risuData.additionalAssets)) {
|
||||
images = images.concat(risuData.additionalAssets);
|
||||
}
|
||||
|
||||
if (Array.isArray(risuData.emotions)) {
|
||||
images = images.concat(risuData.emotions);
|
||||
}
|
||||
|
||||
// No sprites to import
|
||||
if (images.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sprites folder if it doesn't exist
|
||||
const spritesPath = path.join(directories.characters, name);
|
||||
if (!fs.existsSync(spritesPath)) {
|
||||
fs.mkdirSync(spritesPath);
|
||||
}
|
||||
|
||||
// Path to sprites is not a directory. This should never happen.
|
||||
if (!fs.statSync(spritesPath).isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`RisuAI: Found ${images.length} sprites for ${name}. Writing to disk.`);
|
||||
const files = fs.readdirSync(spritesPath);
|
||||
|
||||
outer: for (const [label, fileBase64] of images) {
|
||||
// Remove existing sprite with the same label
|
||||
for (const file of files) {
|
||||
if (path.parse(file).name === label) {
|
||||
console.log(`RisuAI: The sprite ${label} for ${name} already exists. Skipping.`);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
const filename = label + '.png';
|
||||
const pathToFile = path.join(spritesPath, filename);
|
||||
fs.writeFileSync(pathToFile, fileBase64, { encoding: 'base64' });
|
||||
}
|
||||
|
||||
// Remove additionalAssets and emotions from data (they are now in the sprites folder)
|
||||
delete data.data.extensions.risuai.additionalAssets;
|
||||
delete data.data.extensions.risuai.emotions;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function writeSecret(key, value) {
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
const emptyFile = JSON.stringify({});
|
||||
|
1
src/claude.json
Normal file
1
src/claude.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user