mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' into chara-card-src-refactor
This commit is contained in:
@@ -2,4 +2,5 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
readme*
|
||||
Start.bat
|
||||
Start.bat
|
||||
/dist
|
37
.github/workflows/build-and-publish-release-dev.yml
vendored
Normal file
37
.github/workflows/build-and-publish-release-dev.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Build and Publish Release (Dev)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build and package with pkg
|
||||
run: |
|
||||
npm install -g pkg
|
||||
npm run pkg
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
tag_name: ci-dev
|
||||
name: Continuous Release (Dev)
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@@ -26,21 +26,12 @@ jobs:
|
||||
npm install -g pkg
|
||||
npm run pkg
|
||||
|
||||
- name: Create or update release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: continuous-release-main
|
||||
release_name: Continuous Release (Main)
|
||||
draft: false
|
||||
prerelease: true
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
release_id: ${{ steps.create_release.outputs.id }}
|
||||
tag_name: ci-main
|
||||
name: Continuous Release (Main)
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ public/settings.json
|
||||
whitelist.txt
|
||||
.vscode
|
||||
secrets.json
|
||||
/dist
|
@@ -2,3 +2,5 @@ node_modules/
|
||||
/uploads/
|
||||
.DS_Store
|
||||
/thumbnails
|
||||
secrets.json
|
||||
/dist
|
1208
package-lock.json
generated
1208
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"axios": "^0.27.2",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
@@ -11,6 +11,7 @@
|
||||
"exifreader": "^4.12.0",
|
||||
"express": "^4.18.2",
|
||||
"gpt3-tokenizer": "^1.1.5",
|
||||
"ip-matching": "^2.1.2",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"jimp": "^0.22.7",
|
||||
"jquery": "^3.6.4",
|
||||
@@ -25,6 +26,7 @@
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"uniqolor": "^1.1.0",
|
||||
"webp-converter": "2.3.2",
|
||||
"ws": "^8.13.0",
|
||||
"yargs": "^17.7.1"
|
||||
@@ -40,9 +42,10 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
||||
},
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.0",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip ."
|
||||
},
|
||||
"bin": {
|
||||
"sillytavern": "./server.js"
|
||||
@@ -51,11 +54,25 @@
|
||||
"no-path-concat": "off",
|
||||
"no-var": "off"
|
||||
},
|
||||
"main": "server.js",
|
||||
"pkg": {
|
||||
"targets": [
|
||||
"node18-linux-x64",
|
||||
"node18-macos-x64",
|
||||
"node18-windows-x64"
|
||||
],
|
||||
"assets": [
|
||||
"node_modules/open/xdg-open/",
|
||||
"public",
|
||||
"uploads"
|
||||
"node_modules/**/*",
|
||||
"poe_graphql/**/*"
|
||||
],
|
||||
"outputPath": "dist",
|
||||
"scripts": [
|
||||
"server.js"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2",
|
||||
"toastr": "^2.1.4"
|
||||
}
|
||||
}
|
||||
|
1
public/css/toastr.min.css
vendored
Normal file
1
public/css/toastr.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -14,6 +14,7 @@
|
||||
<link href="css/jquery-ui.min.css" rel="stylesheet">
|
||||
<link href="css/bright.min.css" rel="stylesheet">
|
||||
<link href="css/cropper.min.css" rel="stylesheet">
|
||||
<link href="css/toastr.min.css" rel="stylesheet">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
||||
@@ -41,6 +42,8 @@
|
||||
<script src="scripts/moment.min.js"></script>
|
||||
<script src="scripts/cropper.min.js"></script>
|
||||
<script src="scripts/jquery-cropper.min.js"></script>
|
||||
<script src="scripts/toastr.min.js"></script>
|
||||
<script type="module" src="scripts/eventemitter.js"></script>
|
||||
<script type="module" src="scripts/power-user.js"></script>
|
||||
<script type="module" src="scripts/swiped-events.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
@@ -366,6 +369,15 @@
|
||||
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<label for="oai_breakdown" class="checkbox_label widthFreeExpand">
|
||||
<input id="oai_breakdown" type="checkbox" />
|
||||
Token Breakdown
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
Display a breakdown of the tokens used in the request.
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title">
|
||||
Context Size (tokens)
|
||||
@@ -738,7 +750,7 @@
|
||||
<div class="range-block-title">
|
||||
Seed
|
||||
</div>
|
||||
<input type="number" id="seed_textgenerationwebui" class="text_pole" maxlength="100" />
|
||||
<input type="number" id="seed_textgenerationwebui" class="text_pole wide100p" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="openai_settings">
|
||||
@@ -961,7 +973,7 @@
|
||||
</div>
|
||||
<div id="rm_api_block" class="drawer-content closedDrawer">
|
||||
<h3 id="title_api">API</h3>
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div id="main-API-selector-block">
|
||||
<select id="main_api">
|
||||
<option value="kobold">KoboldAI</option>
|
||||
@@ -1009,7 +1021,7 @@
|
||||
Enter <span class="monospace">0000000000</span> to use anonymous mode.
|
||||
</h5>
|
||||
<div class="flex-container">
|
||||
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000">
|
||||
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000" autocomplete="off">
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_horde"></div>
|
||||
</div>
|
||||
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
|
||||
@@ -1043,7 +1055,7 @@
|
||||
</ol>
|
||||
</span>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1" maxlength="500" size="35" type="text">
|
||||
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_novel"></div>
|
||||
</div>
|
||||
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
|
||||
@@ -1065,24 +1077,30 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="textgenerationwebui_api" style="display: none;position: relative;">
|
||||
<div class="oobabooga_logo">
|
||||
<div class="oobabooga_logo flex-container">
|
||||
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
|
||||
oobabooga/text-generation-webui
|
||||
</a>
|
||||
<span>
|
||||
Make sure you run it with <tt>--api</tt> flag
|
||||
</span>
|
||||
</div>
|
||||
<span>
|
||||
Make sure you run it with <tt>--api</tt> flag
|
||||
</span>
|
||||
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4>Blocking API url</h4>
|
||||
<h5>Example: http://127.0.0.1:5000/</h5>
|
||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole" maxlength="500" value="" autocomplete="off">
|
||||
<div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<h4>Blocking API url</h4>
|
||||
<h5>Example: http://127.0.0.1:5000/</h5>
|
||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<h4>Streaming API url</h4>
|
||||
<h5>Example: ws://127.0.0.1:5005/api/v1/stream</h5>
|
||||
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
|
||||
<h4>Streaming API url</h4>
|
||||
<h5>Example: ws://127.0.0.1:5005/api/v1/stream</h5>
|
||||
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole" maxlength="500" value="" autocomplete="off">
|
||||
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="online_status4">
|
||||
<div class="online_status_indicator4"></div>
|
||||
<div class="online_status_text4">Not connected</div>
|
||||
@@ -1100,7 +1118,7 @@
|
||||
</ol>
|
||||
</span>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text">
|
||||
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openai"></div>
|
||||
</div>
|
||||
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
|
||||
@@ -1140,7 +1158,7 @@
|
||||
</span>
|
||||
<div class="widthFreeExpand">
|
||||
<div class="flex-container">
|
||||
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" />
|
||||
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" autocomplete="off" />
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_poe"></div>
|
||||
</div>
|
||||
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
|
||||
@@ -1176,7 +1194,7 @@
|
||||
|
||||
<div id="advanced-formatting-button" class="drawer">
|
||||
<div class="drawer-toggle">
|
||||
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Reponse Formatting"></div>
|
||||
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Response Formatting"></div>
|
||||
</div>
|
||||
<div class="drawer-content">
|
||||
<h3>Advanced Formatting
|
||||
@@ -1207,6 +1225,15 @@
|
||||
<input id="disable-start-formatting-checkbox" type="checkbox" />
|
||||
Disable chat start formatting
|
||||
</label>
|
||||
<label class="checkbox_label" for="trim_sentences_checkbox">
|
||||
<input id="trim_sentences_checkbox" type="checkbox" />
|
||||
Trim Incomplete Sentences
|
||||
</label>
|
||||
<!-- Add margin since this is a child of above -->
|
||||
<label style="margin-left: 1em;" class="checkbox_label" for="include_newline_checkbox">
|
||||
<input id="include_newline_checkbox" type="checkbox" />
|
||||
Include Newline
|
||||
</label>
|
||||
<div>
|
||||
<h4>
|
||||
Custom Chat Separator
|
||||
@@ -1247,7 +1274,7 @@
|
||||
Input Sequence
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_input_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@@ -1255,25 +1282,33 @@
|
||||
Output Sequence
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_output_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="instruct_system_sequence">
|
||||
System Sequence
|
||||
<small>System Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_system_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_stop_sequence">
|
||||
Stop Sequence
|
||||
<small>Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_stop_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="instruct_separator_sequence">
|
||||
<small>Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1346,24 +1381,22 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="anchors-block">
|
||||
<div id="context-templates-block" style="display:none">
|
||||
<h4>
|
||||
Anchors Order
|
||||
<a href="/notes#anchors" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
Context Templates
|
||||
</h4>
|
||||
<select id="anchor_order">
|
||||
<option value="0">Character then Style</option>
|
||||
<option value="1">Style then Character</option>
|
||||
</select>
|
||||
<div id="anchor_checkbox">
|
||||
<label for="character_anchor"><input id="character_anchor" type="checkbox" />
|
||||
Character Anchor
|
||||
</label>
|
||||
<label for="style_anchor"><input id="style_anchor" type="checkbox" />
|
||||
Style Anchor
|
||||
</label>
|
||||
<div>
|
||||
<label>Presets:</label>
|
||||
<div class="flex-container flexGap5">
|
||||
<select id="prompt_template" class="flex1 margin0">
|
||||
<option value="None">None</option>
|
||||
<option value="Classic">Classic</option>
|
||||
<option value="Pygmalion">Pygmalion</option>
|
||||
</select>
|
||||
<div id="context_template_edit" class="menu_button fa-solid fa-pencil margin0" title="Edit"></div>
|
||||
<div id="context_template_new" class="menu_button fa-solid fa-plus margin0" title="Add new"></div>
|
||||
<div id="context_template_delete" class="menu_button fa-solid fa-trash-can margin0" title="Delete"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1373,12 +1406,20 @@
|
||||
|
||||
<div id="WI-SP-button" class="drawer">
|
||||
<div class="drawer-toggle drawer-header">
|
||||
<div class="drawer-icon fa-solid fa-book-atlas closedIcon " title="World Info & Soft Prompts"></div>
|
||||
<div id="WIDrawerIcon" class="drawer-icon fa-solid fa-book-atlas closedIcon " title="World Info & Soft Prompts"></div>
|
||||
</div>
|
||||
<div class="drawer-content closedDrawer">
|
||||
<div id="WorldInfo" class="drawer-content closedDrawer">
|
||||
<div id="WorldInfoheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<div id="WI_panel_pin_div" title="Locked = World Editor will stay open">
|
||||
<input type="checkbox" id="WI_panel_pin">
|
||||
<label for="WI_panel_pin">
|
||||
<div class="unchecked fa-solid fa-lock-open "></div>
|
||||
<div class="checked fa-solid fa-lock "></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="wi-holder">
|
||||
<h3>
|
||||
World Info
|
||||
World Selector
|
||||
<a href="/notes#worldinfo" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
@@ -1440,6 +1481,41 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="world_popup">
|
||||
<hr>
|
||||
<div id="world_popup_text">
|
||||
<!-- <div id="world_cross" class="fa-solid fa-circle-xmark"></div> -->
|
||||
<div id="world_popup_header" class="flex-container flexGap5">
|
||||
<!-- Consider changing logo to something else -->
|
||||
<div class="world_popup_logo_block">
|
||||
<!-- <img src="img/book2.png" id="world_logo"> -->
|
||||
<h3>
|
||||
World Info Editor
|
||||
<a href="/notes#worldinfoentry" class="notes-link" target="_blank"><span class="note-link-span">?</span></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand"></div>
|
||||
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress"></div>
|
||||
<!-- <div class="world_popup_expander"> </div> -->
|
||||
<form id="form_rename_world" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<input id="world_popup_name" name="world_popup_name" class="text_pole" maxlength="99" size="32" value="" autocomplete="off">
|
||||
<input id="world_popup_name_button" class="menu_button" type="submit" value="Rename">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="world_popup_entries_list">
|
||||
</div>
|
||||
|
||||
<div id="world_popup_bottom_holder">
|
||||
<div id="world_popup_new" class="menu_button">New Entry</div>
|
||||
<!-- <span class="world_popup_expander"> </span> -->
|
||||
<div id="world_popup_export" class="menu_button">Export</div>
|
||||
<div id="world_popup_delete" class="menu_button">Delete World</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="softprompt_block">
|
||||
<h4>Soft Prompt</h4>
|
||||
@@ -1663,6 +1739,10 @@
|
||||
<input id="auto_scroll_chat_to_bottom" type="checkbox" />
|
||||
Auto-scroll Chat
|
||||
</label>
|
||||
<label for="console_log_prompts">
|
||||
<input id="console_log_prompts" type="checkbox" />
|
||||
Log prompts to console
|
||||
</label>
|
||||
<label for="render_formulas">
|
||||
<input id="render_formulas" type="checkbox" />
|
||||
Render Formulas
|
||||
@@ -1670,6 +1750,14 @@
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div name="NameAndAvatar" class="flex-container flexFlowColumn drawer25pWidth">
|
||||
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<h4>
|
||||
Send on Enter
|
||||
@@ -1680,14 +1768,32 @@
|
||||
<option value="1">Always enabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div name="NameAndAvatar" class="flex-container flexFlowColumn drawer25pWidth">
|
||||
<div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Auto-swipe</b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
|
||||
<label class="checkbox_label" for="auto_swipe">
|
||||
<input id="auto_swipe" type="checkbox" />
|
||||
Enabled
|
||||
</label>
|
||||
<div>Minimum generated message length</div>
|
||||
<input id="auto_swipe_minimum_length" name="auto_swipe_minimum_length" type="number" min="0" step="1" value="0" class="text_pole">
|
||||
<div>Blacklisted words</div>
|
||||
<div class="auto_swipe">
|
||||
<input id="auto_swipe_blacklist" name="auto_swipe_blacklist" placeholder="words you dont want generated separated by comma ','" class="text_pole" maxlength="500" value="" autocomplete="off">
|
||||
<div>Blacklisted word count to swipe</div>
|
||||
<input id="auto_swipe_blacklist_threshold" name="auto_swipe_blacklist_threshold" type="number" min="0" step="1" value="1" class="text_pole">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div name="NameChanger">
|
||||
<h4>Name</h4>
|
||||
<div class="change_name">
|
||||
<input id="your_name" name="your_name" placeholder="Enter your name" class="text_pole" maxlength="50" value="" autocomplete="off">
|
||||
<input id="your_name" name="your_name" placeholder="Enter your name" class="text_pole wide100p" maxlength="50" value="" autocomplete="off">
|
||||
<div id="your_name_button" class="menu_button fa-solid fa-check" title="Click to set a new User Name">
|
||||
</div>
|
||||
</div>
|
||||
@@ -1813,7 +1919,7 @@
|
||||
<label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character">
|
||||
<input type="submit" id="create_button" name="create_button">
|
||||
</label>
|
||||
<div id="delete_button" class="menu_button fa-solid fa-trash-can " title="Delete Character"></div>
|
||||
<div id="delete_button" class="menu_button fa-solid fa-skull " title="Delete Character"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div title="Token counts may be inaccurate and provided just for reference." id="result_info"> </div>
|
||||
@@ -1822,7 +1928,7 @@
|
||||
|
||||
<div id="tags_div">
|
||||
<div class="tag_controls">
|
||||
<input id="tagInput" class="text_pole tag_input" placeholder="Search / Create tags" maxlength="25" />
|
||||
<input id="tagInput" class="text_pole tag_input wide100p margin0" placeholder="Search / Create tags" maxlength="25" />
|
||||
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
|
||||
</div>
|
||||
<div id="tagList" class="tags"></div>
|
||||
@@ -1875,15 +1981,15 @@
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div name="group-metadata-controls" class="marginTopBot5">
|
||||
<input id="rm_group_chat_name" class="text_pole" type="text" name="chat_name" placeholder="Chat Name (Optional)" maxlength="100" />
|
||||
<input id="rm_group_chat_name" class="text_pole wide100p" type="text" name="chat_name" placeholder="Chat Name (Optional)" maxlength="100" />
|
||||
<div id="group_tags_div" class="wide100p">
|
||||
<div class="tag_controls">
|
||||
<input id="groupTagInput" class="text_pole tag_input flex1" placeholder="Search / Create tags" maxlength="25" />
|
||||
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
|
||||
<input id="groupTagInput" class="text_pole tag_input flex1 margin0" placeholder="Search / Create tags" maxlength="25" />
|
||||
<div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags"></div>
|
||||
</div>
|
||||
<div id="groupTagList" class="tags paddingTopBot5"></div>
|
||||
</div>
|
||||
<div id="rm_group_top_bar" class="flex-container spaceEvenly width100p">
|
||||
<div id="rm_group_top_bar" class="flex-container spaceBetween width100p">
|
||||
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5">
|
||||
<div class="">
|
||||
<div class="flex-container flexnowrap width100p whitespacenowrap">
|
||||
@@ -1925,21 +2031,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
Add Members
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
|
||||
<div id="rm_group_add_members_header">
|
||||
<input id="rm_group_filter" class="text_pole" type="search" placeholder="Filter..." maxlength="100" />
|
||||
<div id="group_fav_filter" class="menu_button fa-solid fa-ranking-star" title="Show only favorites"></div>
|
||||
</div>
|
||||
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
Current Members
|
||||
@@ -1951,6 +2042,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
Add Members
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
|
||||
<div id="rm_group_add_members_header">
|
||||
<input id="rm_group_filter" class="text_pole margin0" type="search" placeholder="Filter..." maxlength="100" />
|
||||
<div id="group_fav_filter" class="menu_button fa-solid fa-ranking-star margin0" title="Show only favorites"></div>
|
||||
</div>
|
||||
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1967,7 +2073,7 @@
|
||||
<div id="rm_button_create" title="Create New Character" class="menu_button fa-solid fa-user-plus "></div>
|
||||
<div id="character_import_button" title="Import Character from File" class="menu_button fa-solid fa-file-arrow-up "></div>
|
||||
<div id="rm_button_group_chats" title="Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
|
||||
<input id="character_search_bar" class="text_pole" type="search" placeholder="Search..." maxlength="50" />
|
||||
<input id="character_search_bar" class="text_pole width100p" type="search" placeholder="Search..." maxlength="50" />
|
||||
<select id="character_sort_order" title="Characters sorting order">
|
||||
<option data-field="name" data-order="asc">A-Z</option>
|
||||
<option data-field="name" data-order="desc">Z-A</option>
|
||||
@@ -2022,7 +2128,7 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 id="character_popup_text_h3"></h3> - Advanced Defininitions
|
||||
<h3 id="character_popup_text_h3"></h3> - Advanced Definitions
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -2047,7 +2153,7 @@
|
||||
|
||||
<div id="talkativeness_div">
|
||||
<h4>Talkativeness</h4>
|
||||
<h5>How often the chracter speaks in <span class="warning">group chats!</span>
|
||||
<h5>How often the character speaks in <span class="warning">group chats!</span>
|
||||
</h5>
|
||||
<input id="talkativeness_slider" name="talkativeness" type="range" min="0" max="1" step="0.05" value="0.5" form="form_create">
|
||||
<div id="talkativeness_hint">
|
||||
@@ -2067,38 +2173,7 @@
|
||||
<div id="character_popup_ok" class="menu_button">Save</div>
|
||||
|
||||
</div>
|
||||
<div id="world_popup">
|
||||
<div id="world_popup_text">
|
||||
<div id="world_cross" class="fa-solid fa-circle-xmark"></div>
|
||||
<div id="world_popup_header">
|
||||
<!-- Consider changing logo to something else -->
|
||||
<div class="world_popup_logo_block">
|
||||
<img src="img/book2.png" id="world_logo">
|
||||
<h3>
|
||||
World Info Editor
|
||||
<a href="/notes#worldinfoentry" class="notes-link" target="_blank"><span class="note-link-span">?</span></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand"></div>
|
||||
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress"></div>
|
||||
<div class="world_popup_expander"> </div>
|
||||
<form id="form_rename_world" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<input id="world_popup_name" name="world_popup_name" class="text_pole" maxlength="99" size="32" value="" autocomplete="off">
|
||||
<input id="world_popup_name_button" class="menu_button" type="submit" value="Rename">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="world_popup_entries_list">
|
||||
</div>
|
||||
|
||||
<div id="world_popup_bottom_holder">
|
||||
<div id="world_popup_new" class="menu_button">New Entry</div>
|
||||
<span class="world_popup_expander"> </span>
|
||||
<div id="world_popup_export" class="menu_button">Export</div>
|
||||
<div id="world_popup_delete" class="menu_button">Delete World</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="shadow_select_chat_popup">
|
||||
<div id="select_chat_popup">
|
||||
<div id="select_chat_import"> <!-- import chat popup header -->
|
||||
@@ -2177,6 +2252,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="entry_edit_template" class="template_element">
|
||||
<div class="world_entry">
|
||||
<form class="world_entry_form">
|
||||
@@ -2186,14 +2262,14 @@
|
||||
<div class="world_entry_form_control">
|
||||
<label for="key">
|
||||
<h4>Keywords</h4>
|
||||
<h5>Separate with commas</h5>
|
||||
<h5>Split by commas</h5>
|
||||
</label>
|
||||
<textarea class="text_pole keyprimarytextpole" name="key" rows="1" placeholder="" maxlength="250"></textarea>
|
||||
</div>
|
||||
<div class="world_entry_form_control keysecondary">
|
||||
<label for="keysecondary">
|
||||
<h4>Secondary Required Keywords</h4>
|
||||
<h5>Separate with commas</h5>
|
||||
<h4>2nd Keywords</h4>
|
||||
<h5>Split by commas</h5>
|
||||
</label>
|
||||
<textarea class="text_pole keysecondarytextpole" name="keysecondary" rows="1" placeholder="(secondary keywords here)" maxlength="250"></textarea>
|
||||
</div>
|
||||
@@ -2314,7 +2390,7 @@
|
||||
<div id="message_template" class="template_element">
|
||||
<div class="mes" mesid="${count_view_mes}" ch_name="${characterName}" is_user="${mes.is_user}" is_system="${mes.is_system}">
|
||||
<div class="for_checkbox"></div><input type="checkbox" class="del_checkbox">
|
||||
<div>
|
||||
<div class="mesAvatarWrapper">
|
||||
<div class="avatar">
|
||||
<img src="">
|
||||
|
||||
@@ -2323,10 +2399,13 @@
|
||||
</div>
|
||||
<div class="swipe_left fa-solid fa-chevron-left"></div>
|
||||
<div class="mes_block">
|
||||
<div class="ch_name">
|
||||
<div class="ch_name flex-container justifySpaceBetween">
|
||||
<span class="name_text">${characterName}</span>
|
||||
|
||||
<div class="mes_buttons">
|
||||
<div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush"></div>
|
||||
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div>
|
||||
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></div>
|
||||
<div title="Copy" class="mes_copy fa-solid fa-copy "></div>
|
||||
<div title="Edit" class="mes_edit fa-solid fa-pencil "></div>
|
||||
</div>
|
||||
@@ -2340,6 +2419,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class=mes_text></div>
|
||||
<div class="mes_img_container">
|
||||
<div class="mes_img_controls">
|
||||
<div title="Enlarge" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge"></div>
|
||||
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete"></div>
|
||||
</div>
|
||||
<img class="mes_img" src="" />
|
||||
</div>
|
||||
<div title="Stop Streaming" class="mes_stop">
|
||||
<i class="fa-xl fa-solid fa-circle-stop"></i>
|
||||
</div>
|
||||
@@ -2415,12 +2501,17 @@
|
||||
<div id="typing_indicator_template" class="template_element">
|
||||
<div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div>
|
||||
</div>
|
||||
<div id="movingDivs"></div>
|
||||
<div id="sheld">
|
||||
<div id="sheldheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<!-- <div class="pull-tab"></div> -->
|
||||
<div id="chat">
|
||||
</div>
|
||||
<div id="form_sheld">
|
||||
<div id="token_breakdown" style="display:none;">
|
||||
<div>
|
||||
<!-- Token Breakdown Goes Here -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialogue_del_mes">
|
||||
<div id="dialogue_del_mes_ok" class="menu_button">Delete</div>
|
||||
<div id="dialogue_del_mes_cancel" class="menu_button">Cancel</div>
|
||||
@@ -2430,9 +2521,9 @@
|
||||
<textarea id="send_textarea" placeholder="Not connected to API!" name="text"></textarea>
|
||||
<div id="send_but_sheld">
|
||||
<div id="loading_mes">
|
||||
<div alt="" class="fa-solid fa-hourglass-half"></div>
|
||||
<div title="Loading" class="fa-solid fa-hourglass-half"></div>
|
||||
</div>
|
||||
<div id="send_but" class="fa-solid fa-feather-pointed"></div>
|
||||
<div id="send_but" class="fa-solid fa-paper-plane" title="Send a message"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -2449,6 +2540,10 @@
|
||||
<i class="fa-lg fa-solid fa-bookmark"></i>
|
||||
<span>Save bookmark</span>
|
||||
</a>
|
||||
<a id="option_toggle_AN">
|
||||
<i class="fa-lg fa-solid fa-note-sticky"></i>
|
||||
<span>Open Author's Note</span>
|
||||
</a>
|
||||
<a id="option_convert_to_group">
|
||||
<i class="fa-lg fa-solid fa-people-arrows"></i>
|
||||
<span>Convert to group</span>
|
||||
@@ -2487,6 +2582,18 @@
|
||||
<div id="avatar_zoom_popupheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<img id="zoomed_avatar" src="">
|
||||
</div>
|
||||
<div id="rawPromptPopup" class="list-group">
|
||||
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Configure toast library:
|
||||
toastr.options.escapeHtml = true; // Prevent raw HTML inserts
|
||||
toastr.options.timeOut = 4000; // How long the toast will display without user interaction
|
||||
toastr.options.extendedTimeOut = 10000; // How long the toast will display after a user hovers over it
|
||||
toastr.options.progressBar = true; // Visually indicate how long before a toast expires.
|
||||
toastr.options.closeButton = true; // enable a close button
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -5,5 +5,6 @@
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
"output_sequence": "### Response:",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
}
|
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "Koala",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "GPT: ",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
}
|
@@ -5,5 +5,6 @@
|
||||
"stop_sequence": "</s>",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false
|
||||
}
|
@@ -5,5 +5,6 @@
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Human:",
|
||||
"output_sequence": "### Assistant:",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
}
|
@@ -5,5 +5,6 @@
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"wrap": true
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
}
|
@@ -3,7 +3,8 @@
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
"input_sequence": "",
|
||||
"output_sequence": "### Response:",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": true
|
||||
}
|
@@ -50,11 +50,11 @@ This is because every AI model has a limit to the amount of context it can proce
|
||||
|
||||
This is the information that gets sent to the AI each time you ask it to generate a response:
|
||||
|
||||
* Character definitions
|
||||
* Chat history
|
||||
* Author's Notes
|
||||
* Special Format strings
|
||||
* [bracket commands]
|
||||
* Character definitions
|
||||
* Chat history
|
||||
* Author's Notes
|
||||
* Special Format strings
|
||||
* [bracket commands]
|
||||
|
||||
SillyTavern automatically calculates the best way to allocate the available context tokens before sending the information to the AI model.
|
||||
|
||||
@@ -62,23 +62,23 @@ SillyTavern automatically calculates the best way to allocate the available cont
|
||||
|
||||
These will always be sent to the AI with every generation request:
|
||||
|
||||
* Character Name (keep the name short! Sent at the start of EVERY Character message)
|
||||
* Character Description Box
|
||||
* Character Personality Box
|
||||
* Scenario Box
|
||||
* Character Name (keep the name short! Sent at the start of EVERY Character message)
|
||||
* Character Description Box
|
||||
* Character Personality Box
|
||||
* Scenario Box
|
||||
|
||||
### What parts of a Character's Definitions are NOT permanent?
|
||||
|
||||
* The first message box - only sent once at the start of the chat.
|
||||
* Example messages box - only kept until chat history fills up the context (optionally these can be forced to be kept in context)
|
||||
* The first message box - only sent once at the start of the chat.
|
||||
* Example messages box - only kept until chat history fills up the context (optionally these can be forced to be kept in context)
|
||||
|
||||
### Popular AI Model Context Token Limits
|
||||
|
||||
* Older models below 6B parameters - 1024
|
||||
* Pygmalion 6B - 2048
|
||||
* Poe.com (Claude-instant or ChatGPT) - 2048
|
||||
* OpenAI ChatGPT - 4000-ish?
|
||||
* OpenAI GPT-4 - 8000?
|
||||
* Older models below 6B parameters - 1024
|
||||
* Pygmalion 6B - 2048
|
||||
* Poe.com (Claude-instant or ChatGPT) - 2048
|
||||
* OpenAI ChatGPT - 4000-ish?
|
||||
* OpenAI GPT-4 - 8000?
|
||||
|
||||
### Personality summary
|
||||
|
||||
@@ -106,7 +106,6 @@ For example:
|
||||
|
||||
`*I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here. *I said with toothy smug sunny smile looking you straight in the eye* What brings you...`
|
||||
|
||||
|
||||
### Examples of dialogue
|
||||
|
||||
Describes how the character speaks. Before each example, you need to add the <START> tag.
|
||||
@@ -217,7 +216,7 @@ Entries inserted by direct mentioning of their keys have higher priority than th
|
||||
|
||||
**Entries can activate other entries by mentioning their keywords in the content text.**
|
||||
|
||||
For example, if your World Info contains two entries:
|
||||
For example, if your World Info contains two entries:
|
||||
|
||||
```
|
||||
Entry #1
|
||||
@@ -366,7 +365,7 @@ The range of influence of Repetition penalty in tokens.
|
||||
|
||||
If your subscription tier is Paper, Tablet or Scroll use only Euterpe model otherwise you can not get an answer from NovelAI API.
|
||||
|
||||
## OpenAI
|
||||
## OpenAI
|
||||
|
||||
### API key
|
||||
|
||||
@@ -385,34 +384,20 @@ _Lost API keys can't be restored! Make sure to keep it safe!_
|
||||
|
||||
**How to get your access token / cookie:**
|
||||
|
||||
1. Login to [poe.com](https://poe.com)
|
||||
2. Open browser DevTools (F12) and navigate to "Application" tab
|
||||
3. Find a _p-b_ cookie for poe.com domain and copy its value
|
||||
4. Paste cookie value to the box below and click "Connect"
|
||||
5. Select a character and start chatting
|
||||
1. Login to [poe.com](https://poe.com)
|
||||
2. Open browser DevTools (F12) and navigate to "Application" tab.
|
||||
3. Type any message into the poe.com chat, and get a response from the AI.
|
||||
4. Find the 'Cookie' section on the left side of Dev Tools 'Application' tab, expand it
|
||||
5. Click "<http://poe.com/>" listing inside the Cookies section.
|
||||
6. Look to the right for the listing of _p-b_ and copy its Value.
|
||||
7. Paste the cookie value into the Poe API connection URL box, and click "Connect".
|
||||
8. Select a character and start chatting
|
||||
|
||||
## Anchors
|
||||
|
||||
Anchors are used to increase the length of messages.
|
||||
There are two types of anchors: _Character Anchor_ and _Style Anchor_.
|
||||
This feature is considered obsolete and has been removed.
|
||||
|
||||
_Character Anchor_ - affects the character played by the AI by motivating it to write longer messages.
|
||||
|
||||
Looks like: `[Elaborate speaker]`
|
||||
|
||||
_Style Anchor_ - affects the entire AI model, motivating the AI to write longer messages even when it is not acting as the character.
|
||||
|
||||
Looks like: `[Writing style: very long messages]`
|
||||
|
||||
***
|
||||
|
||||
Anchors Order sets the location of anchors in the prompt, the first anchor in the order is much further back in the context and thus has less influence than second.
|
||||
|
||||
The second anchor is only turned on after 8-12 messages, because when the chat still only has a few messages, the first anchor creates enough effect on its own.
|
||||
|
||||
Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages. For these cases, you can disable the anchors by unchecking their respective boxes.
|
||||
|
||||
_When using Pygmalion models these anchors are automatically disabled, since Pygmalion already generates long enough messages._
|
||||
The use of the Author's Note extension is now a preferred way to add prompt injections of variable depth.
|
||||
|
||||
## Instruct Mode
|
||||
|
||||
@@ -436,7 +421,7 @@ Write one reply in internet RP style for {{char}}. Be verbose and creative.
|
||||
|
||||
Provides ready-made presets with prompts and sequences for some well-known instruct models.
|
||||
|
||||
*Changing a preset resets your system prompt to default!*
|
||||
_Changing a preset resets your system prompt to default!_
|
||||
|
||||
#### Input Sequence
|
||||
|
||||
@@ -450,6 +435,10 @@ Text added before the character's reply.
|
||||
|
||||
Text added before the system prompt.
|
||||
|
||||
#### Separator Sequence
|
||||
|
||||
Text added after the character reply to separate the chat history logs.
|
||||
|
||||
#### Stop Sequence
|
||||
|
||||
Text that denotes the end of the reply. Will be trimmed from the output text.
|
||||
@@ -458,7 +447,7 @@ Text that denotes the end of the reply. Will be trimmed from the output text.
|
||||
|
||||
If enabled, prepend character and user names to chat history logs after inserting the sequences.
|
||||
|
||||
*Always enabled for group chats!*
|
||||
_Always enabled for group chats!_
|
||||
|
||||
#### Wrap Sequences with Newline
|
||||
|
||||
@@ -474,7 +463,7 @@ To import Character.AI chats, use this tool: [https://github.com/0x000011b/chara
|
||||
|
||||
**Important: This section doesn't apply to OpenAI API. SillyTavern will always use a matching tokenizer for OpenAI models.**
|
||||
|
||||
A tokenizer is a tool that breaks down a piece of text into smaller units called tokens. These tokens can be individual words or even parts of words, such as prefixes, suffixes, or punctuation. A rule of thumb is that one token generally corresponds to 3~4 characters of text.
|
||||
A tokenizer is a tool that breaks down a piece of text into smaller units called tokens. These tokens can be individual words or even parts of words, such as prefixes, suffixes, or punctuation. A rule of thumb is that one token generally corresponds to 3~4 characters of text.
|
||||
|
||||
SillyTavern can use the following tokenizers while forming a request to the AI backend:
|
||||
|
||||
@@ -487,7 +476,7 @@ SillyTavern can use the following tokenizers while forming a request to the AI b
|
||||
|
||||
**Important: This section doesn't apply to OpenAI API. SillyTavern will always use a matching tokenizer for OpenAI models.**
|
||||
|
||||
SillyTavern cannot use a proper tokenizer provided by the model running on a remote instance of KoboldAI or Oobabooga's TextGen, so all token counts assumed during prompt generation are estimated based on the selected [tokenizer](#Tokenizer) type.
|
||||
SillyTavern cannot use a proper tokenizer provided by the model running on a remote instance of KoboldAI or Oobabooga's TextGen, so all token counts assumed during prompt generation are estimated based on the selected [tokenizer](#tokenizer) type.
|
||||
|
||||
Since the results of tokenization can be inaccurate on context sizes close to the model-defined maximum, some parts of the prompt may be trimmed or dropped, which may negatively affect the coherence of character definitions.
|
||||
|
||||
@@ -507,15 +496,15 @@ Overrides the default separators controlled by "Disable example chats formatting
|
||||
|
||||
#### Disable description formatting
|
||||
|
||||
`**NAME's Persona:** `won't be prepended to the content of your character's Description box.
|
||||
`**NAME's Persona:**`won't be prepended to the content of your character's Description box.
|
||||
|
||||
#### Disable scenario formatting
|
||||
|
||||
`**Scenario:** `won't be prepended to the content of your character's Scenario box.
|
||||
`**Scenario:**`won't be prepended to the content of your character's Scenario box.
|
||||
|
||||
#### Disable personality formatting
|
||||
|
||||
`**Personality:** `won't be prepended to the content of your character's Personality box.
|
||||
`**Personality:**`won't be prepended to the content of your character's Personality box.
|
||||
|
||||
#### Disable example chats formatting
|
||||
|
||||
@@ -539,11 +528,11 @@ Has no effect.
|
||||
|
||||
#### Disable scenario formatting
|
||||
|
||||
`**Circumstances and context of the dialogue:** `won't be prepended to the content of your character's Scenario box.
|
||||
`**Circumstances and context of the dialogue:**`won't be prepended to the content of your character's Scenario box.
|
||||
|
||||
#### Disable personality formatting
|
||||
|
||||
`**NAME's personality:** `won't be prepended to the content of your character's Personality box.
|
||||
`**NAME's personality:**`won't be prepended to the content of your character's Personality box.
|
||||
|
||||
#### Disable example chats formatting
|
||||
|
||||
@@ -594,28 +583,31 @@ Characters are drafted based on the order they are presented in group members li
|
||||
|
||||
## Multigen
|
||||
|
||||
_This feature provides a pseudo-streaming functionality which conflicts with token streaming. When Multigen is enabled and generation API supports streaming, only Multigen streaming will be used._
|
||||
|
||||
SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches.
|
||||
|
||||
### Default settings:
|
||||
### Default settings
|
||||
|
||||
First batch = 50 tokens
|
||||
|
||||
Next batches = 30 tokens
|
||||
|
||||
### Algorithm:
|
||||
### Algorithm
|
||||
|
||||
1. Generate the first batch (if amount of generation setting is more than batch length).
|
||||
2. Generate next batch of tokens until one of the stopping conditions is reached.
|
||||
3. Append the generated text to the next cycle's prompt.
|
||||
|
||||
### Stopping conditions:
|
||||
### Stopping conditions
|
||||
|
||||
1. Generated enough text.
|
||||
2. Character starts speaking for You.
|
||||
3. <|endoftext|> token reached.
|
||||
4. No text generated.
|
||||
5. Stop sequence generated. (Instruct mode only)
|
||||
|
||||
## User Settings
|
||||
## User Settings
|
||||
|
||||
### Message Sound
|
||||
|
||||
@@ -634,13 +626,15 @@ Enables math formulas rendering using the [showdown-katex](https://obedm503.gith
|
||||
The following formatting rules are supported:
|
||||
|
||||
#### LaTeX syntax
|
||||
|
||||
```
|
||||
$$ formula goes here $$
|
||||
```
|
||||
|
||||
#### Asciimath syntax
|
||||
|
||||
```
|
||||
$ formula goes here $
|
||||
formula goes here $
|
||||
```
|
||||
|
||||
More information: [KaTeX](https://katex.org/)
|
||||
More information: [KaTeX](https://katex.org/)
|
||||
|
1781
public/script.js
1781
public/script.js
File diff suppressed because it is too large
Load Diff
@@ -30,11 +30,16 @@ import {
|
||||
import { sortByCssOrder } from "./utils.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
var LPanelPin = document.getElementById("lm_button_panel_pin");
|
||||
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
|
||||
var WIPanelPin = document.getElementById("WI_panel_pin");
|
||||
|
||||
var RightNavPanel = document.getElementById("right-nav-panel");
|
||||
var LeftNavPanel = document.getElementById("left-nav-panel")
|
||||
var LeftNavPanel = document.getElementById("left-nav-panel");
|
||||
var WorldInfo = document.getElementById("WorldInfo");
|
||||
|
||||
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
|
||||
var AdvancedCharDefsPopup = document.getElementById("character_popup");
|
||||
var ConfirmationPopup = document.getElementById("dialogue_popup");
|
||||
var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox");
|
||||
@@ -101,10 +106,20 @@ function waitForElement(querySelector, timeout) {
|
||||
waitForElement("#expression-image", 10000).then(function () {
|
||||
|
||||
dragElement(document.getElementById("expression-holder"));
|
||||
dragElement(document.getElementById("floatingPrompt"));
|
||||
|
||||
}).catch(() => {
|
||||
console.log("expression holder not loaded yet");
|
||||
});
|
||||
|
||||
waitForElement("#floatingPrompt", 10000).then(function () {
|
||||
|
||||
dragElement(document.getElementById("floatingPrompt"));
|
||||
|
||||
}).catch(() => {
|
||||
console.log("floating prompt box not loaded yet");
|
||||
});
|
||||
|
||||
// Device detection
|
||||
const deviceInfo = await getDeviceInfo();
|
||||
|
||||
@@ -355,7 +370,8 @@ function RA_checkOnlineStatus() {
|
||||
//Auto-connect to API (when set to kobold, API URL exists, and auto_connect is true)
|
||||
|
||||
function RA_autoconnect(PrevApi) {
|
||||
if (online_status === undefined) {
|
||||
// secrets.js or script.js not loaded
|
||||
if (SECRET_KEYS === undefined || online_status === undefined) {
|
||||
setTimeout(RA_autoconnect, 100);
|
||||
return;
|
||||
}
|
||||
@@ -389,7 +405,6 @@ function RA_autoconnect(PrevApi) {
|
||||
}
|
||||
|
||||
if (!connection_made) {
|
||||
|
||||
RA_AC_retries++;
|
||||
retry_delay = Math.min(retry_delay * 2, 30000); // double retry delay up to to 30 secs
|
||||
//console.log('connection attempts: ' + RA_AC_retries + ' delay: ' + (retry_delay / 1000) + 's');
|
||||
@@ -408,27 +423,25 @@ function isUrlOrAPIKey(string) {
|
||||
}
|
||||
|
||||
function OpenNavPanels() {
|
||||
//auto-open R nav if locked and previously open
|
||||
if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) {
|
||||
//console.log("RA -- clicking right nav to open");
|
||||
$("#rightNavDrawerIcon").click();
|
||||
} else {
|
||||
/* console.log('didnt see reason to open right nav on load: R-nav locked? ' +
|
||||
LoadLocalBool("NavLockOn")
|
||||
+ ' R-nav was open before? ' +
|
||||
LoadLocalBool("NavOpened" == true)); */
|
||||
}
|
||||
|
||||
//auto-open L nav if locked and previously open
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
//auto-open R nav if locked and previously open
|
||||
if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) {
|
||||
//console.log("RA -- clicking right nav to open");
|
||||
$("#rightNavDrawerIcon").click();
|
||||
}
|
||||
|
||||
if (LoadLocalBool("LNavLockOn") == true && LoadLocalBool("LNavOpened") == true) {
|
||||
console.log("RA -- clicking left nav to open");
|
||||
$("#leftNavDrawerIcon").click();
|
||||
} else {
|
||||
/* console.log('didnt see reason to open left nav on load: L-Nav Locked? ' +
|
||||
LoadLocalBool("LNavLockOn")
|
||||
+ ' L-nav was open before? ' +
|
||||
LoadLocalBool("LNavOpened" == true)); */
|
||||
//auto-open L nav if locked and previously open
|
||||
if (LoadLocalBool("LNavLockOn") == true && LoadLocalBool("LNavOpened") == true) {
|
||||
console.log("RA -- clicking left nav to open");
|
||||
$("#leftNavDrawerIcon").click();
|
||||
}
|
||||
|
||||
//auto-open WI if locked and previously open
|
||||
if (LoadLocalBool("WINavLockOn") == true && LoadLocalBool("WINavOpened") == true) {
|
||||
console.log("RA -- clicking WI to open");
|
||||
$("#WIDrawerIcon").click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,10 +451,12 @@ dragElement(document.getElementById("sheld"));
|
||||
dragElement(document.getElementById("left-nav-panel"));
|
||||
dragElement(document.getElementById("right-nav-panel"));
|
||||
dragElement(document.getElementById("avatar_zoom_popup"));
|
||||
dragElement(document.getElementById("WorldInfo"));
|
||||
|
||||
|
||||
|
||||
function dragElement(elmnt) {
|
||||
|
||||
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader"
|
||||
// if present, the header is where you move the DIV from, but this overrides everything else:
|
||||
@@ -452,6 +467,7 @@ function dragElement(elmnt) {
|
||||
}
|
||||
|
||||
function dragMouseDown(e) {
|
||||
//console.log(e);
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
// get the mouse cursor position at startup:
|
||||
@@ -546,6 +562,7 @@ function dragElement(elmnt) {
|
||||
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
|
||||
$(elmnt).css("bottom", "unset");
|
||||
$(elmnt).css("right", "unset");
|
||||
$(elmnt).css("margin", "unset");
|
||||
|
||||
/* console.log(`
|
||||
offsetLeft: ${elmnt.offsetLeft}, offsetTop: ${elmnt.offsetTop}
|
||||
@@ -614,7 +631,7 @@ $("document").ready(function () {
|
||||
|
||||
if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
$(RightNavPanel).slideToggle(200, "swing");
|
||||
$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
//$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
$(RightNavPanel).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
@@ -630,12 +647,30 @@ $("document").ready(function () {
|
||||
|
||||
if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
$(LeftNavPanel).slideToggle(200, "swing");
|
||||
$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
//$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
$(LeftNavPanel).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(WIPanelPin).on("click", function () {
|
||||
SaveLocal("WINavLockOn", $(WIPanelPin).prop("checked"));
|
||||
if ($(WIPanelPin).prop("checked") == true) {
|
||||
console.log('adding pin class to WI');
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
} else {
|
||||
console.log('removing pin class from WI');
|
||||
$(WorldInfo).removeClass('pinnedOpen');
|
||||
|
||||
if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
|
||||
console.log('closing WI after lock removal');
|
||||
$(WorldInfo).slideToggle(200, "swing");
|
||||
//$(WorldInfoDrawerIcon).toggleClass('openIcon closedIcon');
|
||||
$(WorldInfo).toggleClass('openDrawer closedDrawer');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// read the state of right Nav Lock and apply to rightnav classlist
|
||||
$(RPanelPin).prop('checked', LoadLocalBool("NavLockOn"));
|
||||
if (LoadLocalBool("NavLockOn") == true) {
|
||||
@@ -657,6 +692,18 @@ $("document").ready(function () {
|
||||
$(LeftNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
|
||||
// read the state of left Nav Lock and apply to leftnav classlist
|
||||
$(WIPanelPin).prop('checked', LoadLocalBool("WINavLockOn"));
|
||||
if (LoadLocalBool("WINavLockOn") == true) {
|
||||
//console.log('setting pin class via local var');
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
|
||||
if ($(WIPanelPin).prop('checked' == true)) {
|
||||
console.log('setting pin class via checkbox state');
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
|
||||
//save state of Right nav being open or closed
|
||||
$("#rightNavDrawerIcon").on("click", function () {
|
||||
if (!$("#rightNavDrawerIcon").hasClass('openIcon')) {
|
||||
@@ -671,6 +718,13 @@ $("document").ready(function () {
|
||||
} else { SaveLocal('LNavOpened', 'false'); }
|
||||
});
|
||||
|
||||
//save state of Left nav being open or closed
|
||||
$("#WorldInfo").on("click", function () {
|
||||
if (!$("#WorldInfo").hasClass('openIcon')) {
|
||||
SaveLocal('WINavOpened', 'true');
|
||||
} else { SaveLocal('WINavOpened', 'false'); }
|
||||
});
|
||||
|
||||
var chatbarInFocus = false;
|
||||
$('#send_textarea').focus(function () {
|
||||
chatbarInFocus = true;
|
||||
|
71
public/scripts/eventemitter.js
Normal file
71
public/scripts/eventemitter.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Polyfill indexOf. */
|
||||
var indexOf;
|
||||
|
||||
if (typeof Array.prototype.indexOf === 'function') {
|
||||
indexOf = function (haystack, needle) {
|
||||
return haystack.indexOf(needle);
|
||||
};
|
||||
} else {
|
||||
indexOf = function (haystack, needle) {
|
||||
var i = 0, length = haystack.length, idx = -1, found = false;
|
||||
|
||||
while (i < length && !found) {
|
||||
if (haystack[i] === needle) {
|
||||
idx = i;
|
||||
found = true;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/* Polyfill EventEmitter. */
|
||||
var EventEmitter = function () {
|
||||
this.events = {};
|
||||
};
|
||||
|
||||
EventEmitter.prototype.on = function (event, listener) {
|
||||
if (typeof this.events[event] !== 'object') {
|
||||
this.events[event] = [];
|
||||
}
|
||||
|
||||
this.events[event].push(listener);
|
||||
};
|
||||
|
||||
EventEmitter.prototype.removeListener = function (event, listener) {
|
||||
var idx;
|
||||
|
||||
if (typeof this.events[event] === 'object') {
|
||||
idx = indexOf(this.events[event], listener);
|
||||
|
||||
if (idx > -1) {
|
||||
this.events[event].splice(idx, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emit = function (event) {
|
||||
var i, listeners, length, args = [].slice.call(arguments, 1);
|
||||
|
||||
if (typeof this.events[event] === 'object') {
|
||||
listeners = this.events[event].slice();
|
||||
length = listeners.length;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.once = function (event, listener) {
|
||||
this.on(event, function g () {
|
||||
this.removeListener(event, g);
|
||||
listener.apply(this, arguments);
|
||||
});
|
||||
};
|
||||
|
||||
export { EventEmitter }
|
@@ -1,4 +1,4 @@
|
||||
import { callPopup, saveSettings, saveSettingsDebounced } from "../script.js";
|
||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced } from "../script.js";
|
||||
import { isSubsetOf } from "./utils.js";
|
||||
export {
|
||||
getContext,
|
||||
@@ -147,6 +147,36 @@ function autoConnectInputHandler() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function addExtensionsButtonAndMenu() {
|
||||
const buttonHTML =
|
||||
`<div id="extensionsMenuButton" class="fa-solid fa-magic-wand-sparkles" title="Extras Extensions" /></div>`;
|
||||
const extensionsMenuHTML = `<div id="extensionsMenu" class="list-group"></div>`;
|
||||
|
||||
$(document.body).append(extensionsMenuHTML);
|
||||
|
||||
$('#send_but_sheld').prepend(buttonHTML);
|
||||
|
||||
const button = $('#extensionsMenuButton');
|
||||
const dropdown = $('#extensionsMenu');
|
||||
|
||||
let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
|
||||
placement: 'top-end',
|
||||
});
|
||||
|
||||
$(document).on('click touchend', function (e) {
|
||||
const target = $(e.target);
|
||||
if (target.is(dropdown)) return;
|
||||
if (target.is(button) && !dropdown.is(":visible")) {
|
||||
e.preventDefault();
|
||||
|
||||
dropdown.show(200);
|
||||
popper.update();
|
||||
} else {
|
||||
dropdown.hide(200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function connectToApi(baseUrl) {
|
||||
if (!baseUrl) {
|
||||
return;
|
||||
@@ -162,6 +192,7 @@ async function connectToApi(baseUrl) {
|
||||
const data = await getExtensionsResult.json();
|
||||
modules = data.modules;
|
||||
await activateExtensions();
|
||||
eventSource.emit(event_types.EXTRAS_CONNECTED, modules);
|
||||
}
|
||||
|
||||
updateStatus(getExtensionsResult.ok);
|
||||
@@ -286,6 +317,11 @@ async function loadExtensionSettings(settings) {
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
setTimeout(function () {
|
||||
addExtensionsButtonAndMenu();
|
||||
$("#extensionsMenuButton").css("display", "flex");
|
||||
}, 100)
|
||||
|
||||
$("#extensions_connect").on('click', connectClickHandler);
|
||||
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
||||
$("#extensions_details").on('click', showExtensionsDetails);
|
||||
|
@@ -15,9 +15,9 @@ async function moduleWorker() {
|
||||
|
||||
async function setImageIcon() {
|
||||
try {
|
||||
const sendButton = document.getElementById('send_picture');
|
||||
sendButton.classList.add('fa-image');
|
||||
sendButton.classList.remove('fa-hourglass-half');
|
||||
const sendButton = $('#send_picture .extensionsMenuExtensionButton');
|
||||
sendButton.addClass('fa-image');
|
||||
sendButton.removeClass('fa-hourglass-half');
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
@@ -26,9 +26,9 @@ async function setImageIcon() {
|
||||
|
||||
async function setSpinnerIcon() {
|
||||
try {
|
||||
const sendButton = document.getElementById('send_picture');
|
||||
sendButton.classList.remove('fa-image');
|
||||
sendButton.classList.add('fa-hourglass-half');
|
||||
const sendButton = $('#send_picture .extensionsMenuExtensionButton');
|
||||
sendButton.removeClass('fa-image');
|
||||
sendButton.addClass('fa-hourglass-half');
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
@@ -92,14 +92,17 @@ async function onSelectImage(e) {
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
jQuery(function () {
|
||||
function addSendPictureButton() {
|
||||
const sendButton = document.createElement('div');
|
||||
sendButton.id = 'send_picture';
|
||||
sendButton.classList.add('fa-solid');
|
||||
const sendButton = $(`
|
||||
<div id="send_picture" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
|
||||
Send a picture
|
||||
</div>`);
|
||||
|
||||
$('#extensionsMenu').prepend(sendButton);
|
||||
$(sendButton).hide();
|
||||
$(sendButton).on('click', () => $('#img_file').click());
|
||||
$('#send_but_sheld').prepend(sendButton);
|
||||
$(sendButton).on('click', () => $('#img_file').trigger('click'));
|
||||
}
|
||||
function addPictureSendForm() {
|
||||
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;
|
||||
|
@@ -1,24 +1,3 @@
|
||||
#send_picture {
|
||||
order: 200;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 1px;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#send_picture:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
#img_form {
|
||||
display: none;
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ const UPDATE_INTERVAL = 1000;
|
||||
function setDiceIcon() {
|
||||
const sendButton = document.getElementById('roll_dice');
|
||||
/* sendButton.style.backgroundImage = `url(/img/dice-solid.svg)`; */
|
||||
sendButton.classList.remove('spin');
|
||||
//sendButton.classList.remove('spin');
|
||||
}
|
||||
|
||||
async function doDiceRoll() {
|
||||
@@ -29,7 +29,10 @@ async function doDiceRoll() {
|
||||
|
||||
function addDiceRollButton() {
|
||||
const buttonHtml = `
|
||||
<div id="roll_dice" class="fa-solid fa-dice" /></div>
|
||||
<div id="roll_dice" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-dice extensionsMenuExtensionButton" title="Roll Dice" /></div>
|
||||
Roll Dice
|
||||
</div>
|
||||
`;
|
||||
const dropdownHtml = `
|
||||
<div id="dice_dropdown">
|
||||
@@ -45,7 +48,8 @@ function addDiceRollButton() {
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
$('#send_but_sheld').prepend(buttonHtml);
|
||||
$('#extensionsMenu').prepend(buttonHtml);
|
||||
|
||||
$(document.body).append(dropdownHtml)
|
||||
$('#dice_dropdown li').on('click', doDiceRoll);
|
||||
const button = $('#roll_dice');
|
||||
@@ -54,7 +58,7 @@ function addDiceRollButton() {
|
||||
button.hide();
|
||||
|
||||
let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
|
||||
placement: 'top-start',
|
||||
placement: 'bottom',
|
||||
});
|
||||
|
||||
$(document).on('click touchend', function (e) {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
#roll_dice {
|
||||
order: 100;
|
||||
width: 40px;
|
||||
/* order: 100; */
|
||||
/* width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 1px;
|
||||
padding: 1px; */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -11,7 +11,7 @@
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* justify-content: center; */
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { chat_metadata, saveSettingsDebounced } from "../../../script.js";
|
||||
import { extension_settings, getContext } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { debounce } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
@@ -19,6 +20,52 @@ const metadata_keys = {
|
||||
position: 'note_position',
|
||||
}
|
||||
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success("Author's Note text updated");
|
||||
}
|
||||
|
||||
function setNoteDepthCommand(_, text) {
|
||||
const value = Number(text);
|
||||
|
||||
if (Number.isNaN(value)) {
|
||||
toastr.error('Not a valid number');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
|
||||
toastr.success("Author's Note depth updated");
|
||||
}
|
||||
|
||||
function setNoteIntervalCommand(_, text) {
|
||||
const value = Number(text);
|
||||
|
||||
if (Number.isNaN(value)) {
|
||||
toastr.error('Not a valid number');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
|
||||
toastr.success("Author's Note frequency updated");
|
||||
}
|
||||
|
||||
function setNotePositionCommand(_, text) {
|
||||
const validPositions = {
|
||||
'scenario': 0,
|
||||
'chat': 1,
|
||||
};
|
||||
|
||||
const position = validPositions[text?.trim()];
|
||||
|
||||
if (Number.isNaN(position)) {
|
||||
toastr.error('Not a valid position');
|
||||
return;
|
||||
}
|
||||
|
||||
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
|
||||
toastr.info("Author's Note position updated");
|
||||
}
|
||||
|
||||
async function onExtensionFloatingPromptInput() {
|
||||
chat_metadata[metadata_keys.prompt] = $(this).val();
|
||||
saveMetadataDebounced();
|
||||
@@ -100,7 +147,7 @@ async function moduleWorker() {
|
||||
|
||||
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
|
||||
context.setExtensionPrompt(MODULE_NAME, '');
|
||||
$('#extension_floating_counter').text('No');
|
||||
$('#extension_floating_counter').text('(disabled)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,57 +157,67 @@ async function moduleWorker() {
|
||||
const shouldAddPrompt = messagesTillInsertion == 0;
|
||||
const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
|
||||
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
$('#extension_floating_counter').text(shouldAddPrompt ? 'This' : messagesTillInsertion);
|
||||
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
|
||||
}
|
||||
|
||||
(function () {
|
||||
function addExtensionsSettings() {
|
||||
const settingsHtml = `
|
||||
<div class="floating_prompt_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Author's Note / Character Bias</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>
|
||||
Your notes are saved <b>PER CHAT</b>. When you start a new chat, you'll see the default / empty note.<br>
|
||||
Saving a bookmark will copy your note to a bookmark chat. Making changes to it won't update the note in a parent chat.<br>
|
||||
</small>
|
||||
<label for="extension_floating_prompt">Append the following text:</label>
|
||||
<textarea id="extension_floating_prompt" class="text_pole" rows="8"></textarea>
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="0" />
|
||||
After scenario
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="1" />
|
||||
In-chat
|
||||
</label>
|
||||
</div>
|
||||
<label for="extension_floating_interval">Every N messages <b>you</b> send (set to 0 to disable):</label>
|
||||
<input id="extension_floating_interval" class="text_pole" type="number" min="0" max="999" />
|
||||
<label for="extension_floating_interval">Insertion depth (for in-chat positioning):</label>
|
||||
<input id="extension_floating_depth" class="text_pole" type="number" min="0" max="99" />
|
||||
<span>Appending to the prompt in next: <span id="extension_floating_counter">No</span> message(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Default note for new chats</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
<div id="floatingPrompt" class="drawer-content flexGap5">
|
||||
<div id="floatingPromptheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<div name="floatingPromptHolder">
|
||||
<div class="inline-drawer">
|
||||
<div id="ANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Author's Note</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="extension_floating_default">Default Author's Note</label>
|
||||
<textarea id="extension_floating_default" class="text_pole" rows="8"
|
||||
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
|
||||
<small>
|
||||
<b>Unique to this chat</b>.<br>
|
||||
Bookmarks inherit the Note from their parent, and can be changed individually after that.<br>
|
||||
</small>
|
||||
|
||||
<textarea id="extension_floating_prompt" class="text_pole" rows="8" maxlength="10000"></textarea>
|
||||
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="0" />
|
||||
After scenario
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="1" />
|
||||
In-chat @ Depth <input id="extension_floating_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
</label>
|
||||
</div>
|
||||
<!--<label for="extension_floating_interval">In-Chat Insertion Depth</label>-->
|
||||
|
||||
<label for="extension_floating_interval">Insertion Frequency</label>
|
||||
|
||||
<input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="999" /><small> (0 = Disable)</small>
|
||||
<br>
|
||||
|
||||
<span>User inputs until next insertion: <span id="extension_floating_counter">(disabled)</span></span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
<div class="inline-drawer">
|
||||
<div id="defaultANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Default Author's Note</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>Will be automatically added as the Author's Note for all new chats.</small>
|
||||
|
||||
<textarea id="extension_floating_default" class="text_pole" rows="8" maxlength="10000"
|
||||
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
$('#movingDivs').append(settingsHtml);
|
||||
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
|
||||
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
|
||||
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
|
||||
@@ -170,4 +227,8 @@ async function moduleWorker() {
|
||||
|
||||
addExtensionsSettings();
|
||||
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
|
||||
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> – sets an author's note for the currently selected chat", true, true);
|
||||
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> – sets an author's note depth for in-chat positioning", true, true);
|
||||
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> – sets an author's note insertion frequency", true, true);
|
||||
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) – sets an author's note position", true, true);
|
||||
})();
|
@@ -1,4 +1,26 @@
|
||||
.floating_prompt_settings {
|
||||
#floatingPrompt {
|
||||
overflow-y: auto;
|
||||
max-width: 90svw;
|
||||
max-height: 90svh;
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 0 10px var(--black70a);
|
||||
z-index: 3000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
right: unset;
|
||||
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
|
||||
|
||||
}
|
||||
|
||||
.floating_prompt_radio_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -11,9 +33,4 @@
|
||||
.floating_prompt_settings textarea {
|
||||
font-size: calc(var(--mainFontSize) * 0.9);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.floating_prompt_radio_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
@@ -3,9 +3,14 @@ import {
|
||||
saveSettingsDebounced,
|
||||
systemUserName,
|
||||
hideSwipeButtons,
|
||||
showSwipeButtons
|
||||
showSwipeButtons,
|
||||
callPopup,
|
||||
getRequestHeaders,
|
||||
event_types,
|
||||
eventSource,
|
||||
appendImageToMessage
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, defaultRequestArgs } from "../../extensions.js";
|
||||
import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
@@ -28,33 +33,55 @@ const generationMode = {
|
||||
CHARACTER: 0,
|
||||
USER: 1,
|
||||
SCENARIO: 2,
|
||||
FREE: 3,
|
||||
RAW_LAST: 3,
|
||||
NOW: 4,
|
||||
FACE: 5,
|
||||
FREE: 6,
|
||||
}
|
||||
|
||||
const triggerWords = {
|
||||
[generationMode.CHARACTER]: ['you'],
|
||||
[generationMode.USER]: ['me'],
|
||||
[generationMode.SCENARIO]: ['scene'],
|
||||
[generationMode.RAW_LAST]: ['raw_last'],
|
||||
[generationMode.NOW]: ['last'],
|
||||
[generationMode.FACE]: ['face'],
|
||||
|
||||
}
|
||||
|
||||
const quietPrompts = {
|
||||
//face-specific prompt
|
||||
[generationMode.FACE]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait:']",
|
||||
//prompt for only the last message
|
||||
[generationMode.NOW]: "[Pause your roleplay and provide a brief description of the last chat message. Focus on visual details, clothing, actions. Ignore the emotions and thoughts of {{char}} and {{user}} as well as any spoken dialog. Do not roleplay as {{char}} while writing this description. Do not continue the roleplay story.]",
|
||||
|
||||
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait:']",
|
||||
|
||||
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
|
||||
|
||||
[generationMode.USER]: "[Pause your roleplay and provide a detailed description of {{user}}'s appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}}}} when writing this description, and do not attempt to continue the story.]",
|
||||
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",
|
||||
//face-specific prompt
|
||||
[generationMode.FACE]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait,']",
|
||||
//prompt for only the last message
|
||||
[generationMode.USER]: "[Pause your roleplay and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]",
|
||||
[generationMode.SCENARIO]: "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
|
||||
[generationMode.FREE]: "[Pause your roleplay and provide ONLY an echo this string back to me verbatim: {0}. Do not write anything after the string. Do not roleplay at all in your response.]",
|
||||
|
||||
[generationMode.NOW]: `[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.
|
||||
|
||||
Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').
|
||||
|
||||
Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.
|
||||
|
||||
Add keywords in this precise order:
|
||||
a keyword to describe the location of the scene,
|
||||
a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:
|
||||
{{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),
|
||||
|
||||
keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',
|
||||
|
||||
a single keyword or phrase to describe the primary act taking place in the last chat message,
|
||||
|
||||
keywords to describe {{char}}'s physical appearance and facial expression,
|
||||
keywords to describe {{char}}'s actions,
|
||||
keywords to describe {{user}}'s physical appearance and actions.
|
||||
|
||||
If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.
|
||||
|
||||
A correctly formatted example response would be:
|
||||
'(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']`,
|
||||
|
||||
[generationMode.RAW_LAST]: "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",
|
||||
}
|
||||
|
||||
const helpString = [
|
||||
@@ -65,8 +92,9 @@ const helpString = [
|
||||
`<li>${m(j(triggerWords[generationMode.USER]))} – user character full body selfie</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.SCENARIO]))} – visual recap of the whole chat scenario</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.NOW]))} – visual recap of the last chat message</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.RAW_LAST]))} – visual recap of the last chat message with no summary</li>`,
|
||||
'</ul>',
|
||||
`Anything else would trigger a "free mode" to make SD generate whatever you prompted.<Br>
|
||||
`Anything else would trigger a "free mode" to make SD generate whatever you prompted.<Br>
|
||||
example: '/sd apple tree' would generate a picture of an apple tree.`,
|
||||
].join('<br>');
|
||||
|
||||
@@ -90,10 +118,19 @@ const defaultSettings = {
|
||||
width: 512,
|
||||
height: 512,
|
||||
|
||||
prompt_prefix: 'best quality, absurdres, masterpiece, detailed, intricate, colorful,',
|
||||
prompt_prefix: 'best quality, absurdres, masterpiece,',
|
||||
negative_prompt: 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry',
|
||||
sampler: 'DDIM',
|
||||
model: '',
|
||||
|
||||
// Automatic1111/Horde exclusives
|
||||
restore_faces: false,
|
||||
enable_hr: false,
|
||||
|
||||
// Horde settings
|
||||
horde: false,
|
||||
horde_nsfw: false,
|
||||
horde_karras: true,
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -107,11 +144,13 @@ async function loadSettings() {
|
||||
$('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input');
|
||||
$('#sd_width').val(extension_settings.sd.width).trigger('input');
|
||||
$('#sd_height').val(extension_settings.sd.height).trigger('input');
|
||||
|
||||
$('#sd_horde').prop('checked', extension_settings.sd.horde);
|
||||
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
|
||||
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
|
||||
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
|
||||
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
|
||||
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
function onScaleInput() {
|
||||
@@ -155,10 +194,44 @@ function onHeightInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onHordeInput() {
|
||||
extension_settings.sd.model = null;
|
||||
extension_settings.sd.sampler = null;
|
||||
extension_settings.sd.horde = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
await Promise.all([loadModels(), loadSamplers()]);
|
||||
}
|
||||
|
||||
async function onHordeNsfwInput() {
|
||||
extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onHordeKarrasInput() {
|
||||
extension_settings.sd.horde_karras = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onRestoreFacesInput() {
|
||||
extension_settings.sd.restore_faces = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHighResFixInput() {
|
||||
extension_settings.sd.enable_hr = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onModelChange() {
|
||||
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (!extension_settings.sd.horde) {
|
||||
await updateExtrasRemoteModel();
|
||||
}
|
||||
}
|
||||
|
||||
async function updateExtrasRemoteModel() {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image/model';
|
||||
const getCurrentModelResult = await fetch(url, {
|
||||
@@ -173,25 +246,96 @@ async function onModelChange() {
|
||||
}
|
||||
|
||||
async function loadSamplers() {
|
||||
$('#sd_sampler').empty();
|
||||
let samplers = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
samplers = await loadHordeSamplers();
|
||||
} else {
|
||||
samplers = await loadExtrasSamplers();
|
||||
}
|
||||
|
||||
for (const sampler of samplers) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = sampler;
|
||||
option.value = sampler;
|
||||
option.selected = sampler === extension_settings.sd.sampler;
|
||||
$('#sd_sampler').append(option);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHordeSamplers() {
|
||||
const result = await fetch('/horde_samplers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadExtrasSamplers() {
|
||||
if (!modules.includes('sd')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image/samplers';
|
||||
const result = await fetch(url, defaultRequestArgs);
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
const samplers = data.samplers;
|
||||
|
||||
for (const sampler of samplers) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = sampler;
|
||||
option.value = sampler;
|
||||
option.selected = sampler === extension_settings.sd.sampler;
|
||||
$('#sd_sampler').append(option);
|
||||
}
|
||||
return data.samplers;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadModels() {
|
||||
$('#sd_model').empty();
|
||||
let models = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
models = await loadHordeModels();
|
||||
} else {
|
||||
models = await loadExtrasModels();
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = model.text;
|
||||
option.value = model.value;
|
||||
option.selected = model.value === extension_settings.sd.model;
|
||||
$('#sd_model').append(option);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHordeModels() {
|
||||
const result = await fetch('/horde_models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
data.sort((a, b) => b.count - a.count);
|
||||
const models = data.map(x => ({ value: x.name, text: `${x.name} (ETA: ${x.eta}s, Queue: ${x.queued}, Workers: ${x.count})` }));
|
||||
return models;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadExtrasModels() {
|
||||
if (!modules.includes('sd')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image/model';
|
||||
const getCurrentModelResult = await fetch(url, defaultRequestArgs);
|
||||
@@ -206,23 +350,18 @@ async function loadModels() {
|
||||
|
||||
if (getModelsResult.ok) {
|
||||
const data = await getModelsResult.json();
|
||||
const models = data.models;
|
||||
|
||||
for (const model of models) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = model;
|
||||
option.value = model;
|
||||
option.selected = model === extension_settings.sd.model;
|
||||
$('#sd_model').append(option);
|
||||
}
|
||||
const view_models = data.models.map(x => ({ value: x, text: x }));
|
||||
return view_models;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getGenerationType(prompt) {
|
||||
for (const [key, values] of Object.entries(triggerWords)) {
|
||||
for (const value of values) {
|
||||
if (value.toLowerCase() === prompt.toLowerCase().trim()) {
|
||||
return key;
|
||||
return Number(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,12 +370,21 @@ function getGenerationType(prompt) {
|
||||
}
|
||||
|
||||
function getQuietPrompt(mode, trigger) {
|
||||
if (mode === generationMode.FREE) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return substituteParams(stringFormat(quietPrompts[mode], trigger));
|
||||
}
|
||||
|
||||
function processReply(str) {
|
||||
if (!str) {
|
||||
return '';
|
||||
}
|
||||
|
||||
str = str.replaceAll('"', '')
|
||||
str = str.replaceAll('“', '')
|
||||
str = str.replaceAll('.', ',')
|
||||
str = str.replaceAll('\n', ', ')
|
||||
str = str.replace(/[^a-zA-Z0-9,:]+/g, ' ') // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
|
||||
@@ -251,69 +399,156 @@ function processReply(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
async function generatePicture(_, trigger) {
|
||||
function getRawLastMessage() {
|
||||
const context = getContext();
|
||||
const lastMessage = context.chat.slice(-1)[0].mes,
|
||||
characterDescription = context.characters[context.characterId].description,
|
||||
situation = context.characters[context.characterId].scenario;
|
||||
return `((${processReply(lastMessage)})), (${processReply(situation)}:0.7), (${processReply(characterDescription)}:0.5)`
|
||||
}
|
||||
|
||||
async function generatePicture(_, trigger, message, callback) {
|
||||
if (!trigger || trigger.trim().length === 0) {
|
||||
console.log('Trigger word empty, aborting');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modules.includes('sd') && !extension_settings.sd.horde) {
|
||||
callPopup("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.", 'text');
|
||||
return;
|
||||
}
|
||||
|
||||
extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val();
|
||||
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
||||
|
||||
trigger = trigger.trim();
|
||||
const generationMode = getGenerationType(trigger);
|
||||
console.log('Generation mode', generationMode, 'triggered with', trigger);
|
||||
const quiet_prompt = getQuietPrompt(generationMode, trigger);
|
||||
const generationType = getGenerationType(trigger);
|
||||
console.log('Generation mode', generationType, 'triggered with', trigger);
|
||||
const quiet_prompt = getQuietPrompt(generationType, trigger);
|
||||
const context = getContext();
|
||||
|
||||
const prevSDHeight = extension_settings.sd.height;
|
||||
if (generationType == generationMode.FACE) {
|
||||
extension_settings.sd.height = extension_settings.sd.width * 1.5;
|
||||
}
|
||||
|
||||
try {
|
||||
const prompt = processReply(await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
}
|
||||
}));
|
||||
const prompt = await getPrompt(generationType, message, trigger, quiet_prompt);
|
||||
console.log('Processed Stable Diffusion prompt:', prompt);
|
||||
|
||||
context.deactivateSendButtons();
|
||||
hideSwipeButtons();
|
||||
|
||||
console.log('Processed Stable Diffusion prompt:', prompt);
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: extension_settings.sd.prompt_prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
restore_faces: true,
|
||||
face_restoration_model: 'GFPGAN',
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
const base64Image = `data:image/jpeg;base64,${data.image}`;
|
||||
sendMessage(prompt, base64Image);
|
||||
}
|
||||
await sendGenerationRequest(prompt, callback);
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
throw new Error('SD prompt text generation failed.')
|
||||
}
|
||||
finally {
|
||||
extension_settings.sd.height = prevSDHeight;
|
||||
context.activateSendButtons();
|
||||
showSwipeButtons();
|
||||
}
|
||||
}
|
||||
|
||||
async function getPrompt(generationType, message, trigger, quiet_prompt) {
|
||||
let prompt;
|
||||
|
||||
switch (generationType) {
|
||||
case generationMode.RAW_LAST:
|
||||
prompt = message || getRawLastMessage();
|
||||
break;
|
||||
case generationMode.FREE:
|
||||
prompt = processReply(trigger);
|
||||
break;
|
||||
default:
|
||||
prompt = await generatePrompt(quiet_prompt);
|
||||
break;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
async function generatePrompt(quiet_prompt) {
|
||||
return processReply(await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await getContext().generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function sendGenerationRequest(prompt, callback) {
|
||||
if (extension_settings.sd.horde) {
|
||||
await generateHordeImage(prompt, callback);
|
||||
} else {
|
||||
await generateExtrasImage(prompt, callback);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateExtrasImage(prompt, callback) {
|
||||
console.log(extension_settings.sd);
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: extension_settings.sd.prompt_prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
karras: !!extension_settings.sd.horde_karras,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
const base64Image = `data:image/jpeg;base64,${data.image}`;
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
}
|
||||
}
|
||||
|
||||
async function generateHordeImage(prompt, callback) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: extension_settings.sd.prompt_prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
model: extension_settings.sd.model,
|
||||
nsfw: extension_settings.sd.horde_nsfw,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const base64Image = `data:image/webp;base64,${data}`;
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage(prompt, image) {
|
||||
const context = getContext();
|
||||
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
|
||||
@@ -335,8 +570,12 @@ async function sendMessage(prompt, image) {
|
||||
}
|
||||
|
||||
function addSDGenButtons() {
|
||||
|
||||
const buttonHtml = `
|
||||
<div id="sd_gen" class="fa-solid fa-paintbrush" /></div>
|
||||
<div id="sd_gen" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" /></div>
|
||||
Stable Diffusion
|
||||
</div>
|
||||
`;
|
||||
|
||||
const waitButtonHtml = `
|
||||
@@ -351,24 +590,29 @@ function addSDGenButtons() {
|
||||
<li class="list-group-item" id="sd_me" data-value="me">Me</li>
|
||||
<li class="list-group-item" id="sd_world" data-value="world">The Whole Story</li>
|
||||
<li class="list-group-item" id="sd_last" data-value="last">The Last Message</li>
|
||||
<li class="list-group-item" id="sd_raw_last" data-value="raw_last">Raw Last Message</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
$('#send_but_sheld').prepend(buttonHtml);
|
||||
$('#send_but_sheld').prepend(waitButtonHtml);
|
||||
$(document.body).append(dropdownHtml)
|
||||
$('#extensionsMenu').prepend(buttonHtml);
|
||||
$('#extensionsMenu').prepend(waitButtonHtml);
|
||||
$(document.body).append(dropdownHtml);
|
||||
|
||||
const messageButton = $('.sd_message_gen');
|
||||
const button = $('#sd_gen');
|
||||
const waitButton = $("#sd_gen_wait");
|
||||
const dropdown = $('#sd_dropdown');
|
||||
waitButton.hide();
|
||||
dropdown.hide();
|
||||
button.hide();
|
||||
messageButton.hide();
|
||||
|
||||
let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
|
||||
placement: 'top-start',
|
||||
placement: 'bottom',
|
||||
});
|
||||
|
||||
$(document).on('click', '.sd_message_gen', sdMessageButton);
|
||||
|
||||
$(document).on('click touchend', function (e) {
|
||||
const target = $(e.target);
|
||||
if (target.is(dropdown)) return;
|
||||
@@ -383,27 +627,80 @@ function addSDGenButtons() {
|
||||
});
|
||||
}
|
||||
|
||||
function isConnectedToExtras() {
|
||||
return modules.includes('sd');
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
/* if (context.onlineStatus === 'no_connection') {
|
||||
$('#sd_gen').hide(200);
|
||||
} else if ($("#send_but").css('display') === 'flex') {
|
||||
$('#sd_gen').show(200);
|
||||
$("#sd_gen_wait").hide(200);
|
||||
} else {
|
||||
$('#sd_gen').hide(200);
|
||||
$("#sd_gen_wait").show(200);
|
||||
} */
|
||||
|
||||
context.onlineStatus === 'no_connection'
|
||||
? $('#sd_gen').hide(200)
|
||||
: $('#sd_gen').show(200)
|
||||
if (isConnectedToExtras() || extension_settings.sd.horde) {
|
||||
$('#sd_gen').show(200);
|
||||
$('.sd_message_gen').show();
|
||||
}
|
||||
else {
|
||||
$('#sd_gen').hide(200);
|
||||
$('.sd_message_gen').hide();
|
||||
}
|
||||
}
|
||||
|
||||
addSDGenButtons();
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||
|
||||
async function sdMessageButton(e) {
|
||||
function setBusyIcon(isBusy) {
|
||||
$icon.toggleClass('fa-paintbrush', !isBusy);
|
||||
$icon.toggleClass(busyClass, isBusy);
|
||||
}
|
||||
|
||||
const busyClass = 'fa-hourglass';
|
||||
const context = getContext();
|
||||
const $icon = $(e.currentTarget);
|
||||
const $mes = $icon.closest('.mes');
|
||||
const characterName = $mes.find('.name_text').text();
|
||||
const messageText = $mes.find('.mes_text').text();
|
||||
const message_id = $mes.attr('mesid');
|
||||
const message = context.chat[message_id];
|
||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||
|
||||
if ($icon.hasClass(busyClass)) {
|
||||
console.log('Previous image is still being generated...');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setBusyIcon(true);
|
||||
if (hasSavedImage) {
|
||||
const prompt = message?.extra?.title;
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(prompt, saveGeneratedImage);
|
||||
}
|
||||
else {
|
||||
console.log("doing /sd raw last");
|
||||
await generatePicture('sd', 'raw_last', `${characterName} said: ${messageText}`, saveGeneratedImage);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Could not generate inline image: ', error);
|
||||
}
|
||||
finally {
|
||||
setBusyIcon(false);
|
||||
}
|
||||
|
||||
function saveGeneratedImage(prompt, image) {
|
||||
// Some message sources may not create the extra object
|
||||
if (typeof message.extra !== 'object') {
|
||||
message.extra = {};
|
||||
}
|
||||
|
||||
// If already contains an image and it's not inline - leave it as is
|
||||
message.extra.inline_image = message.extra.image && !message.extra.inline_image ? false : true;
|
||||
message.extra.image = image;
|
||||
message.extra.title = prompt;
|
||||
appendImageToMessage(message, $mes);
|
||||
|
||||
context.saveChat();
|
||||
}
|
||||
};
|
||||
|
||||
$("#sd_dropdown [id]").on("click", function () {
|
||||
var id = $(this).attr("id");
|
||||
if (id == "sd_you") {
|
||||
@@ -414,6 +711,7 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
else if (id == "sd_face") {
|
||||
console.log("doing /sd face");
|
||||
generatePicture('sd', 'face');
|
||||
|
||||
}
|
||||
|
||||
else if (id == "sd_me") {
|
||||
@@ -430,6 +728,11 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
console.log("doing /sd last");
|
||||
generatePicture('sd', 'last');
|
||||
}
|
||||
|
||||
else if (id == "sd_raw_last") {
|
||||
console.log("doing /sd raw last");
|
||||
generatePicture('sd', 'raw_last');
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(async () => {
|
||||
@@ -443,7 +746,19 @@ jQuery(async () => {
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
|
||||
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde" type="checkbox" />
|
||||
Use Stable Horde
|
||||
</label>
|
||||
<label style="margin-left:1em;" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
Allow NSFW images from Horde
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
@@ -452,10 +767,27 @@ jQuery(async () => {
|
||||
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
|
||||
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container flexGap5 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
Karras (only for Horde, not all samplers supported)
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Generated prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="2"></textarea>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
@@ -472,12 +804,21 @@ jQuery(async () => {
|
||||
$('#sd_negative_prompt').on('input', onNegativePromptInput);
|
||||
$('#sd_width').on('input', onWidthInput);
|
||||
$('#sd_height').on('input', onHeightInput);
|
||||
$('#sd_horde').on('input', onHordeInput);
|
||||
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
|
||||
$('#sd_horde_karras').on('input', onHordeKarrasInput);
|
||||
$('#sd_restore_faces').on('input', onRestoreFacesInput);
|
||||
$('#sd_enable_hr').on('input', onHighResFixInput);
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
initScrollHeight($("#sd_prompt_prefix"));
|
||||
initScrollHeight($("#sd_negative_prompt"));
|
||||
})
|
||||
|
||||
await loadSettings();
|
||||
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
});
|
||||
|
||||
});
|
||||
await loadSettings();
|
||||
$('body').addClass('sd');
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"display_name": "Stable Diffusion",
|
||||
"loading_order": 10,
|
||||
"requires": [
|
||||
"requires": [],
|
||||
"optional": [
|
||||
"sd"
|
||||
],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
|
@@ -1,13 +1,13 @@
|
||||
.sd_settings label {
|
||||
.sd_settings label:not(.checkbox_label) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#sd_gen {
|
||||
order: 100;
|
||||
/*order: 100;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 1px;
|
||||
padding: 1px; */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -15,8 +15,7 @@
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* justify-content: center; */
|
||||
}
|
||||
|
||||
#sd_gen:hover {
|
||||
|
@@ -7,6 +7,7 @@ class ElevenLabsTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... ... ... '
|
||||
|
||||
get settings() {
|
||||
return this.settings
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { callPopup, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
|
||||
import { callPopup, cancelTtsPlay, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
|
||||
import { extension_settings, getContext } from '../../extensions.js'
|
||||
import { getStringHash } from '../../utils.js'
|
||||
import { ElevenLabsTtsProvider } from './elevenlabs.js'
|
||||
@@ -24,9 +24,42 @@ let ttsProviders = {
|
||||
let ttsProvider
|
||||
let ttsProviderName
|
||||
|
||||
async function onNarrateOneMessage() {
|
||||
const context = getContext();
|
||||
const id = $(this).closest('.mes').attr('mesid');
|
||||
const message = context.chat[id];
|
||||
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetTtsPlayback()
|
||||
ttsJobQueue.push(message);
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
let isWorkerBusy = false;
|
||||
|
||||
async function moduleWorkerWrapper() {
|
||||
// Don't touch me I'm busy...
|
||||
if (isWorkerBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm free. Let's update!
|
||||
try {
|
||||
isWorkerBusy = true;
|
||||
await moduleWorker();
|
||||
}
|
||||
finally {
|
||||
isWorkerBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
// Primarily determinign when to add new chat to the TTS queue
|
||||
// Primarily determining when to add new chat to the TTS queue
|
||||
const enabled = $('#tts_enabled').is(':checked')
|
||||
$('body').toggleClass('tts', enabled);
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
@@ -38,6 +71,11 @@ async function moduleWorker() {
|
||||
processAudioJobQueue()
|
||||
updateUiAudioPlayState()
|
||||
|
||||
// Auto generation is disabled
|
||||
if (extension_settings.tts.auto_generation == false) {
|
||||
return
|
||||
}
|
||||
|
||||
// no characters or group selected
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
return
|
||||
@@ -89,6 +127,41 @@ async function moduleWorker() {
|
||||
ttsJobQueue.push(message)
|
||||
}
|
||||
|
||||
|
||||
function resetTtsPlayback() {
|
||||
// Stop system TTS utterance
|
||||
cancelTtsPlay();
|
||||
|
||||
// Clear currently processing jobs
|
||||
currentTtsJob = null;
|
||||
currentAudioJob = null;
|
||||
|
||||
// Reset audio element
|
||||
audioElement.currentTime = 0;
|
||||
audioElement.src = null;
|
||||
|
||||
// Clear any queue items
|
||||
ttsJobQueue.splice(0, ttsJobQueue.length);
|
||||
audioJobQueue.splice(0, audioJobQueue.length);
|
||||
|
||||
// Set audio ready to process again
|
||||
audioQueueProcessorReady = true;
|
||||
}
|
||||
|
||||
function isTtsProcessing() {
|
||||
let processing = false
|
||||
|
||||
// Check job queues
|
||||
if (ttsJobQueue.length > 0 || audioJobQueue > 0) {
|
||||
processing = true
|
||||
}
|
||||
// Check current jobs
|
||||
if (currentTtsJob != null || currentAudioJob != null) {
|
||||
processing = true
|
||||
}
|
||||
return processing
|
||||
}
|
||||
|
||||
//##################//
|
||||
// Audio Control //
|
||||
//##################//
|
||||
@@ -98,11 +171,15 @@ let audioElement = new Audio()
|
||||
let audioJobQueue = []
|
||||
let currentAudioJob
|
||||
let audioPaused = false
|
||||
let queueProcessorReady = true
|
||||
let audioQueueProcessorReady = true
|
||||
|
||||
let lastAudioPosition = 0
|
||||
|
||||
async function playAudioData(audioBlob) {
|
||||
// Since current audio job can be cancelled, don't playback if it is null
|
||||
if (currentAudioJob == null) {
|
||||
console.log("Cancelled TTS playback because currentAudioJob was null")
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (e) {
|
||||
const srcUrl = e.target.result
|
||||
@@ -134,7 +211,12 @@ async function onTtsVoicesClick() {
|
||||
const voiceIds = await ttsProvider.fetchTtsVoiceIds()
|
||||
|
||||
for (const voice of voiceIds) {
|
||||
popupText += `<div class="voice_preview"><span class="voice_lang">${voice.lang || ''}</span> <b class="voice_name">${voice.name}</b> <i onclick="tts_preview('${voice.voice_id}')" class="fa-solid fa-play"></i></div>`
|
||||
popupText += `
|
||||
<div class="voice_preview">
|
||||
<span class="voice_lang">${voice.lang || ''}</span>
|
||||
<b class="voice_name">${voice.name}</b>
|
||||
<i onclick="tts_preview('${voice.voice_id}')" class="fa-solid fa-play"></i>
|
||||
</div>`
|
||||
popupText += `<audio id="${voice.voice_id}" src="${voice.preview_url}" data-disabled="${voice.preview_url == false}"></audio>`
|
||||
}
|
||||
} catch {
|
||||
@@ -146,30 +228,47 @@ async function onTtsVoicesClick() {
|
||||
|
||||
function updateUiAudioPlayState() {
|
||||
if (extension_settings.tts.enabled == true) {
|
||||
audioControl.style.display = 'flex'
|
||||
const img = !audioElement.paused
|
||||
? 'fa-solid fa-circle-pause'
|
||||
: 'fa-solid fa-circle-play'
|
||||
audioControl.className = img
|
||||
$('#ttsExtensionMenuItem').show();
|
||||
let img
|
||||
// Give user feedback that TTS is active by setting the stop icon if processing or playing
|
||||
if (!audioElement.paused || isTtsProcessing()) {
|
||||
img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton'
|
||||
} else {
|
||||
img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'
|
||||
}
|
||||
$('#tts_media_control').attr('class', img);
|
||||
} else {
|
||||
audioControl.style.display = 'none'
|
||||
$('#ttsExtensionMenuItem').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function onAudioControlClicked() {
|
||||
audioElement.paused ? audioElement.play() : audioElement.pause()
|
||||
let context = getContext()
|
||||
// Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
|
||||
if (!audioElement.paused || isTtsProcessing()) {
|
||||
resetTtsPlayback()
|
||||
} else {
|
||||
// Default play behavior if not processing or playing is to play the last message.
|
||||
ttsJobQueue.push(context.chat[context.chat.length - 1])
|
||||
}
|
||||
updateUiAudioPlayState()
|
||||
}
|
||||
|
||||
function addAudioControl() {
|
||||
$('#send_but_sheld').prepend('<div id="tts_media_control"/>')
|
||||
$('#tts_media_control').on('click', onAudioControlClicked)
|
||||
|
||||
$('#extensionsMenu').prepend(`
|
||||
<div id="ttsExtensionMenuItem" class="list-group-item flex-container flexGap5">
|
||||
<div id="tts_media_control" class="extensionsMenuExtensionButton "/></div>
|
||||
TTS Playback
|
||||
</div>`)
|
||||
$('#ttsExtensionMenuItem').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
|
||||
audioControl = document.getElementById('tts_media_control')
|
||||
updateUiAudioPlayState()
|
||||
}
|
||||
|
||||
function completeCurrentAudioJob() {
|
||||
queueProcessorReady = true
|
||||
audioQueueProcessorReady = true
|
||||
currentAudioJob = null
|
||||
lastAudioPosition = 0
|
||||
// updateUiPlayState();
|
||||
}
|
||||
@@ -189,16 +288,16 @@ async function addAudioJob(response) {
|
||||
|
||||
async function processAudioJobQueue() {
|
||||
// Nothing to do, audio not completed, or audio paused - stop processing.
|
||||
if (audioJobQueue.length == 0 || !queueProcessorReady || audioPaused) {
|
||||
if (audioJobQueue.length == 0 || !audioQueueProcessorReady || audioPaused) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
queueProcessorReady = false
|
||||
audioQueueProcessorReady = false
|
||||
currentAudioJob = audioJobQueue.pop()
|
||||
playAudioData(currentAudioJob)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
queueProcessorReady = true
|
||||
audioQueueProcessorReady = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +306,7 @@ async function processAudioJobQueue() {
|
||||
//################//
|
||||
|
||||
let ttsJobQueue = []
|
||||
let currentTtsJob
|
||||
let currentTtsJob // Null if nothing is currently being processed
|
||||
let currentMessageNumber = 0
|
||||
|
||||
function completeTtsJob() {
|
||||
@@ -247,7 +346,8 @@ async function processTtsQueue() {
|
||||
const special_quotes = /[“”]/g; // Extend this regex to include other special quotes
|
||||
text = text.replace(special_quotes, '"');
|
||||
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
|
||||
text = matches ? matches.join(' ... ... ... ') : text;
|
||||
const partJoiner = (ttsProvider?.separator || ' ... ');
|
||||
text = matches ? matches.join(partJoiner) : text;
|
||||
}
|
||||
console.log(`TTS: ${text}`)
|
||||
const char = currentTtsJob.name
|
||||
@@ -295,13 +395,15 @@ function loadSettings() {
|
||||
)
|
||||
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only)
|
||||
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only)
|
||||
$('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation)
|
||||
$('body').toggleClass('tts', extension_settings.tts.enabled);
|
||||
}
|
||||
|
||||
const defaultSettings = {
|
||||
voiceMap: '',
|
||||
ttsEnabled: false,
|
||||
currentProvider: "ElevenLabs"
|
||||
|
||||
currentProvider: "ElevenLabs",
|
||||
auto_generation: true
|
||||
}
|
||||
|
||||
function setTtsStatus(status, success) {
|
||||
@@ -366,7 +468,7 @@ function onApplyClick() {
|
||||
console.error(error)
|
||||
setTtsStatus(error, false)
|
||||
})
|
||||
|
||||
|
||||
extension_settings.tts[ttsProviderName] = ttsProvider.settings
|
||||
saveSettingsDebounced()
|
||||
setTtsStatus('Successfully applied settings', true)
|
||||
@@ -381,6 +483,11 @@ function onEnableClick() {
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
function onAutoGenerationClick() {
|
||||
extension_settings.tts.auto_generation = $('#tts_auto_generation').prop('checked');
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
|
||||
function onNarrateDialoguesClick() {
|
||||
extension_settings.tts.narrate_dialogues_only = $('#tts_narrate_dialogues').prop('checked');
|
||||
@@ -441,7 +548,7 @@ function onTtsProviderSettingsInput() {
|
||||
ttsProvider.onSettingsChange()
|
||||
|
||||
// Persist changes to SillyTavern tts extension settings
|
||||
|
||||
|
||||
extension_settings.tts[ttsProviderName] = ttsProvider.setttings
|
||||
saveSettingsDebounced()
|
||||
console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`)
|
||||
@@ -469,6 +576,10 @@ $(document).ready(function () {
|
||||
<input type="checkbox" id="tts_enabled" name="tts_enabled">
|
||||
Enabled
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_auto_generation">
|
||||
<input type="checkbox" id="tts_auto_generation">
|
||||
Auto Generation
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_dialogues">
|
||||
<input type="checkbox" id="tts_narrate_dialogues">
|
||||
Narrate dialogues only
|
||||
@@ -500,16 +611,18 @@ $(document).ready(function () {
|
||||
$('#tts_enabled').on('click', onEnableClick)
|
||||
$('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick);
|
||||
$('#tts_narrate_quoted').on('click', onNarrateQuotedClick);
|
||||
$('#tts_auto_generation').on('click', onAutoGenerationClick);
|
||||
$('#tts_voices').on('click', onTtsVoicesClick)
|
||||
$('#tts_provider_settings').on('input', onTtsProviderSettingsInput)
|
||||
for (const provider in ttsProviders) {
|
||||
$('#tts_provider').append($("<option />").val(provider).text(provider))
|
||||
}
|
||||
$('#tts_provider').on('change', onTtsProviderChange)
|
||||
$(document).on('click', '.mes_narrate', onNarrateOneMessage);
|
||||
}
|
||||
addExtensionControls() // No init dependencies
|
||||
loadSettings() // Depends on Extension Controls and loadTtsProvider
|
||||
loadTtsProvider(extension_settings.tts.currentProvider) // No dependencies
|
||||
addAudioControl() // Depends on Extension Controls
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL) // Init depends on all the things
|
||||
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL) // Init depends on all the things
|
||||
})
|
||||
|
@@ -9,6 +9,7 @@ class SileroTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' .. '
|
||||
|
||||
defaultSettings = {
|
||||
provider_endpoint: "http://localhost:8001/tts",
|
||||
|
@@ -1,20 +1,30 @@
|
||||
#tts_media_control {
|
||||
order: 100;
|
||||
width: 40px;
|
||||
/* order: 100; */
|
||||
/* width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 1px;
|
||||
padding: 1px; */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
/* transition: 0.3s;
|
||||
opacity: 0.7; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* justify-content: center; */
|
||||
|
||||
}
|
||||
|
||||
#ttsExtensionMenuItem {
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#ttsExtensionMenuItem:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
#tts_media_control:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
|
@@ -21,6 +21,7 @@ class SystemTtsProvider {
|
||||
fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... '
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: {},
|
||||
|
@@ -47,6 +47,7 @@ import {
|
||||
select_selected_character,
|
||||
cancelTtsPlay,
|
||||
isMultigenEnabled,
|
||||
displayPastChats,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
|
||||
|
||||
@@ -292,6 +293,12 @@ async function getGroups() {
|
||||
if (group.past_metadata == undefined) {
|
||||
group.past_metadata = {};
|
||||
}
|
||||
if (typeof group.chat_id === 'number') {
|
||||
group.chat_id = String(group.chat_id);
|
||||
}
|
||||
if (Array.isArray(group.chats) && group.chats.some(x => typeof x === 'number')) {
|
||||
group.chats = group.chats.map(x => String(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1282,6 +1289,34 @@ export async function deleteGroupChat(groupId, chatId) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function importGroupChat(formData) {
|
||||
await jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importgroupchat",
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.res) {
|
||||
const chatId = data.res;
|
||||
const group = groups.find(x => x.id == selected_group);
|
||||
|
||||
if (group) {
|
||||
group.chats.push(chatId);
|
||||
await editGroup(selected_group, true, true);
|
||||
await displayPastChats();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$("#create_button").removeAttr("disabled");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveGroupBookmarkChat(groupId, name, metadata) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
|
@@ -102,7 +102,7 @@ async function generateHorde(prompt, params) {
|
||||
const response = await fetch("/generate_horde", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
...getRequestHeaders(),
|
||||
"Client-Agent": CLIENT_VERSION,
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
@@ -129,9 +129,10 @@ async function generateHorde(prompt, params) {
|
||||
setGenerationProgress(100);
|
||||
const generatedText = statusCheckJson.generations[0].text;
|
||||
const WorkerName = statusCheckJson.generations[0].worker_name;
|
||||
const WorkerModel = statusCheckJson.generations[0].model;
|
||||
console.log(generatedText);
|
||||
console.log(`Generated by Horde Worker: ${WorkerName}`);
|
||||
return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName}` };
|
||||
console.log(`Generated by Horde Worker: ${WorkerName} [${WorkerModel}]`);
|
||||
return { text: generatedText, workerName: `Generated by Horde worker: ${WorkerName} [${WorkerModel}]` };
|
||||
}
|
||||
else if (!queue_position_first) {
|
||||
queue_position_first = statusCheckJson.queue_position;
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
this_chid,
|
||||
callPopup,
|
||||
getRequestHeaders,
|
||||
system_message_types,
|
||||
} from "../script.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
|
||||
@@ -101,6 +102,7 @@ const default_settings = {
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
oai_breakdown: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -125,6 +127,7 @@ const oai_settings = {
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
oai_breakdown: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -155,7 +158,7 @@ function setOpenAIOnlineStatus(value) {
|
||||
is_get_status_openai = value;
|
||||
}
|
||||
|
||||
function setOpenAIMessages(chat, quietPrompt) {
|
||||
function setOpenAIMessages(chat) {
|
||||
let j = 0;
|
||||
// clean openai msgs
|
||||
openai_msgs = [];
|
||||
@@ -163,15 +166,20 @@ function setOpenAIMessages(chat, quietPrompt) {
|
||||
let role = chat[j]['is_user'] ? 'user' : 'assistant';
|
||||
let content = chat[j]['mes'];
|
||||
|
||||
// for groups - prepend a character's name
|
||||
if (selected_group) {
|
||||
// 100% legal way to send a message as system
|
||||
if (chat[j].extra?.type === system_message_types.NARRATOR) {
|
||||
role = 'system';
|
||||
}
|
||||
|
||||
// for groups or sendas command - prepend a character's name
|
||||
if (selected_group || chat[j].force_avatar) {
|
||||
content = `${chat[j].name}: ${content}`;
|
||||
}
|
||||
|
||||
// replace bias markup
|
||||
//content = (content ?? '').replace(/{.*}/g, '');
|
||||
content = (content ?? '').replace(/{{(\*?.*\*?)}}/g, '');
|
||||
|
||||
// remove caret return (waste of tokens)
|
||||
content = content.replace(/\r/gm, '');
|
||||
|
||||
// Apply the "wrap in quotes" option
|
||||
@@ -180,6 +188,7 @@ function setOpenAIMessages(chat, quietPrompt) {
|
||||
j++;
|
||||
}
|
||||
|
||||
// Add chat injections, 100 = maximum depth of injection. (Why would you ever need more?)
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const anchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, i);
|
||||
|
||||
@@ -187,10 +196,6 @@ function setOpenAIMessages(chat, quietPrompt) {
|
||||
openai_msgs.splice(i, 0, { "role": 'system', 'content': anchor.trim() })
|
||||
}
|
||||
}
|
||||
|
||||
if (quietPrompt) {
|
||||
openai_msgs.splice(0, 0, { role: 'system', content: quietPrompt });
|
||||
}
|
||||
}
|
||||
|
||||
function setOpenAIMessageExamples(mesExamplesArray) {
|
||||
@@ -205,22 +210,10 @@ function setOpenAIMessageExamples(mesExamplesArray) {
|
||||
}
|
||||
}
|
||||
|
||||
function generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, bottomAnchorThreshold, anchorBottom) {
|
||||
function generateOpenAIPromptCache() {
|
||||
openai_msgs = openai_msgs.reverse();
|
||||
openai_msgs.forEach(function (msg, i, arr) {//For added anchors and others
|
||||
openai_msgs.forEach(function (msg, i, arr) {
|
||||
let item = msg["content"];
|
||||
if (i === openai_msgs.length - topAnchorDepth) {
|
||||
let personalityAndAnchor = [charPersonality, anchorTop].filter(x => x).join(' ');
|
||||
if (personalityAndAnchor) {
|
||||
item = `[${name2} is ${personalityAndAnchor}]\n${item}`;
|
||||
}
|
||||
}
|
||||
if (i === openai_msgs.length - 1 && openai_msgs.length > bottomAnchorThreshold && msg.role === "user") {//For add anchor in end
|
||||
if (anchorBottom) {
|
||||
item = anchorBottom + "\n" + item;
|
||||
}
|
||||
}
|
||||
|
||||
msg["content"] = item;
|
||||
openai_msgs[i] = msg;
|
||||
});
|
||||
@@ -287,7 +280,7 @@ function formatWorldInfo(value) {
|
||||
return `[Details of the fictional world the RP is set in:\n${value}]\n`;
|
||||
}
|
||||
|
||||
async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type) {
|
||||
async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type, quietPrompt) {
|
||||
const isImpersonate = type == "impersonate";
|
||||
let this_max_context = oai_settings.openai_max_context;
|
||||
let nsfw_toggle_prompt = "";
|
||||
@@ -310,23 +303,25 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
let whole_prompt = getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate);
|
||||
|
||||
// Join by a space and replace placeholders with real user/char names
|
||||
storyString = substituteParams(whole_prompt.join(" ")).replace(/\r/gm, '').trim();
|
||||
storyString = substituteParams(whole_prompt.join("\n")).replace(/\r/gm, '').trim();
|
||||
|
||||
let prompt_msg = { "role": "system", "content": storyString }
|
||||
let examples_tosend = [];
|
||||
let openai_msgs_tosend = [];
|
||||
|
||||
// todo: static value, maybe include in the initial context calculation
|
||||
const handler_instance = new TokenHandler(countTokens);
|
||||
|
||||
let new_chat_msg = { "role": "system", "content": "[Start a new chat]" };
|
||||
let start_chat_count = countTokens([new_chat_msg], true);
|
||||
let start_chat_count = handler_instance.count([new_chat_msg], true, 'start_chat');
|
||||
await delay(1);
|
||||
let total_count = countTokens([prompt_msg], true) + start_chat_count;
|
||||
let total_count = handler_instance.count([prompt_msg], true, 'prompt') + start_chat_count;
|
||||
await delay(1);
|
||||
|
||||
if (bias && bias.trim().length) {
|
||||
let bias_msg = { "role": "system", "content": bias.trim() };
|
||||
openai_msgs.push(bias_msg);
|
||||
total_count += countTokens([bias_msg], true);
|
||||
total_count += handler_instance.count([bias_msg], true, 'bias');
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
@@ -343,13 +338,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
openai_msgs.push(group_nudge);
|
||||
|
||||
// add a group nudge count
|
||||
let group_nudge_count = countTokens([group_nudge], true);
|
||||
let group_nudge_count = handler_instance.count([group_nudge], true, 'nudge');
|
||||
await delay(1);
|
||||
total_count += group_nudge_count;
|
||||
|
||||
// recount tokens for new start message
|
||||
total_count -= start_chat_count
|
||||
start_chat_count = countTokens([new_chat_msg], true);
|
||||
handler_instance.uncount(start_chat_count, 'start_chat');
|
||||
start_chat_count = handler_instance.count([new_chat_msg], true);
|
||||
await delay(1);
|
||||
total_count += start_chat_count;
|
||||
}
|
||||
@@ -358,15 +354,21 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
const jailbreakMessage = { "role": "system", "content": substituteParams(oai_settings.jailbreak_prompt) };
|
||||
openai_msgs.push(jailbreakMessage);
|
||||
|
||||
total_count += countTokens([jailbreakMessage], true);
|
||||
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
if (quietPrompt) {
|
||||
const quietPromptMessage = { role: 'system', content: quietPrompt };
|
||||
total_count += handler_instance.count([quietPromptMessage], true, 'quiet');
|
||||
openai_msgs.push(quietPromptMessage);
|
||||
}
|
||||
|
||||
if (isImpersonate) {
|
||||
const impersonateMessage = { "role": "system", "content": substituteParams(oai_settings.impersonation_prompt) };
|
||||
openai_msgs.push(impersonateMessage);
|
||||
|
||||
total_count += countTokens([impersonateMessage], true);
|
||||
total_count += handler_instance.count([impersonateMessage], true, 'impersonate');
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
@@ -379,8 +381,6 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
// get the current example block with multiple user/bot messages
|
||||
let example_block = element;
|
||||
// add the first message from the user to tell the model that it's a new dialogue
|
||||
// TODO: instead of role user content use role system name example_user
|
||||
// message from the user so the model doesn't confuse the context (maybe, I just think that this should be done)
|
||||
if (example_block.length != 0) {
|
||||
examples_tosend.push(new_chat_msg);
|
||||
}
|
||||
@@ -389,12 +389,12 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
examples_tosend.push(example);
|
||||
}
|
||||
}
|
||||
total_count += countTokens(examples_tosend, true);
|
||||
total_count += handler_instance.count(examples_tosend, true, 'examples');
|
||||
await delay(1);
|
||||
// go from newest message to oldest, because we want to delete the older ones from the context
|
||||
for (let j = openai_msgs.length - 1; j >= 0; j--) {
|
||||
let item = openai_msgs[j];
|
||||
let item_count = countTokens(item, true);
|
||||
let item_count = handler_instance.count(item, true, 'conversation');
|
||||
await delay(1);
|
||||
// If we have enough space for this message, also account for the max assistant reply size
|
||||
if ((total_count + item_count) < (this_max_context - oai_settings.openai_max_tokens)) {
|
||||
@@ -403,13 +403,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
}
|
||||
else {
|
||||
// early break since if we still have more messages, they just won't fit anyway
|
||||
handler_instance.uncount(item_count, 'conversation');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let j = openai_msgs.length - 1; j >= 0; j--) {
|
||||
let item = openai_msgs[j];
|
||||
let item_count = countTokens(item, true);
|
||||
let item_count = handler_instance.count(item, true, 'conversation');
|
||||
await delay(1);
|
||||
// If we have enough space for this message, also account for the max assistant reply size
|
||||
if ((total_count + item_count) < (this_max_context - oai_settings.openai_max_tokens)) {
|
||||
@@ -418,11 +419,12 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
}
|
||||
else {
|
||||
// early break since if we still have more messages, they just won't fit anyway
|
||||
handler_instance.uncount(item_count, 'conversation');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(total_count);
|
||||
//console.log(total_count);
|
||||
|
||||
// each example block contains multiple user/bot messages
|
||||
for (let example_block of openai_msgs_example) {
|
||||
@@ -432,7 +434,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
example_block = [new_chat_msg, ...example_block];
|
||||
|
||||
// add the block only if there is enough space for all its messages
|
||||
const example_count = countTokens(example_block, true);
|
||||
const example_count = handler_instance.count(example_block, true, 'examples');
|
||||
await delay(1);
|
||||
if ((total_count + example_count) < (this_max_context - oai_settings.openai_max_tokens)) {
|
||||
examples_tosend.push(...example_block)
|
||||
@@ -440,6 +442,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
}
|
||||
else {
|
||||
// early break since more examples probably won't fit anyway
|
||||
handler_instance.uncount(example_count, 'examples');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -451,10 +454,14 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
openai_msgs_tosend.reverse();
|
||||
openai_msgs_tosend = [prompt_msg, ...examples_tosend, new_chat_msg, ...openai_msgs_tosend]
|
||||
|
||||
console.log("We're sending this:")
|
||||
console.log(openai_msgs_tosend);
|
||||
console.log(`Calculated the total context to be ${total_count} tokens`);
|
||||
return openai_msgs_tosend;
|
||||
//console.log("We're sending this:")
|
||||
//console.log(openai_msgs_tosend);
|
||||
//console.log(`Calculated the total context to be ${total_count} tokens`);
|
||||
handler_instance.log();
|
||||
return [
|
||||
openai_msgs_tosend,
|
||||
oai_settings.oai_breakdown ? handler_instance.counts : false,
|
||||
];
|
||||
}
|
||||
|
||||
function getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate) {
|
||||
@@ -469,7 +476,7 @@ function getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefor
|
||||
whole_prompt = [nsfw_toggle_prompt, oai_settings.main_prompt, enhance_definitions_prompt + "\n\n" + wiBefore, storyString, wiAfter, extensionPrompt];
|
||||
}
|
||||
else {
|
||||
whole_prompt = [oai_settings.main_prompt, nsfw_toggle_prompt, enhance_definitions_prompt + "\n\n" + wiBefore, storyString, wiAfter, extensionPrompt];
|
||||
whole_prompt = [oai_settings.main_prompt, nsfw_toggle_prompt, enhance_definitions_prompt, "\n", wiBefore, storyString, wiAfter, extensionPrompt].filter(elem => elem);
|
||||
}
|
||||
}
|
||||
return whole_prompt;
|
||||
@@ -622,9 +629,42 @@ async function calculateLogitBias() {
|
||||
}
|
||||
}
|
||||
|
||||
class TokenHandler {
|
||||
constructor(countTokenFn) {
|
||||
this.countTokenFn = countTokenFn;
|
||||
this.counts = {
|
||||
'start_chat': 0,
|
||||
'prompt': 0,
|
||||
'bias': 0,
|
||||
'nudge': 0,
|
||||
'jailbreak': 0,
|
||||
'impersonate': 0,
|
||||
'examples': 0,
|
||||
'conversation': 0,
|
||||
};
|
||||
}
|
||||
|
||||
uncount(value, type) {
|
||||
this.counts[type] -= value;
|
||||
}
|
||||
|
||||
count(messages, full, type) {
|
||||
//console.log(messages);
|
||||
const token_count = this.countTokenFn(messages, full);
|
||||
this.counts[type] += token_count;
|
||||
|
||||
return token_count;
|
||||
}
|
||||
|
||||
log() {
|
||||
const total = Object.values(this.counts).reduce((a, b) => a + b);
|
||||
console.table({ ...this.counts, 'total': total });
|
||||
}
|
||||
}
|
||||
|
||||
function countTokens(messages, full = false) {
|
||||
let chatId = 'undefined';
|
||||
|
||||
|
||||
try {
|
||||
if (selected_group) {
|
||||
chatId = groups.find(x => x.id == selected_group)?.chat_id;
|
||||
@@ -711,6 +751,7 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.nsfw_first !== undefined) oai_settings.nsfw_first = !!settings.nsfw_first;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.jailbreak_system !== undefined) oai_settings.jailbreak_system = !!settings.jailbreak_system;
|
||||
if (settings.oai_breakdown !== undefined) oai_settings.oai_breakdown = !!settings.oai_breakdown;
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
|
||||
@@ -726,6 +767,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#wrap_in_quotes').prop('checked', oai_settings.wrap_in_quotes);
|
||||
$('#nsfw_first').prop('checked', oai_settings.nsfw_first);
|
||||
$('#jailbreak_system').prop('checked', oai_settings.jailbreak_system);
|
||||
$('#oai_breakdown').prop('checked', oai_settings.oai_breakdown);
|
||||
|
||||
if (settings.main_prompt !== undefined) oai_settings.main_prompt = settings.main_prompt;
|
||||
if (settings.nsfw_prompt !== undefined) oai_settings.nsfw_prompt = settings.nsfw_prompt;
|
||||
@@ -845,6 +887,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
jailbreak_system: settings.jailbreak_system,
|
||||
impersonation_prompt: settings.impersonation_prompt,
|
||||
bias_preset_selected: settings.bias_preset_selected,
|
||||
oai_breakdown: settings.oai_breakdown,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1052,7 +1095,7 @@ async function onDeletePresetClick() {
|
||||
const response = await fetch('/deletepreset_openai', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({name: nameToDelete}),
|
||||
body: JSON.stringify({ name: nameToDelete }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -1103,6 +1146,7 @@ function onSettingsPresetChange() {
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
nsfw_first: ['#nsfw_first', 'nsfw_first', true],
|
||||
jailbreak_system: ['#jailbreak_system', 'jailbreak_system', true],
|
||||
oai_breakdown: ['#oai_breakdown', 'oai_breakdown', true],
|
||||
main_prompt: ['#main_prompt_textarea', 'main_prompt', false],
|
||||
nsfw_prompt: ['#nsfw_prompt_textarea', 'nsfw_prompt', false],
|
||||
jailbreak_prompt: ['#jailbreak_prompt_textarea', 'jailbreak_prompt', false],
|
||||
@@ -1169,7 +1213,7 @@ function onReverseProxyInput() {
|
||||
async function onConnectButtonClick(e) {
|
||||
e.stopPropagation();
|
||||
const api_key_openai = $('#api_key_openai').val().trim();
|
||||
|
||||
|
||||
if (api_key_openai.length) {
|
||||
await writeSecret(SECRET_KEYS.OPENAI, api_key_openai);
|
||||
}
|
||||
@@ -1275,6 +1319,16 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#oai_breakdown").on('change', function () {
|
||||
oai_settings.oai_breakdown = !!$(this).prop("checked");
|
||||
if (!oai_settings.oai_breakdown) {
|
||||
$("#token_breakdown").css('display', 'none');
|
||||
} else {
|
||||
$("#token_breakdown").css('display', 'flex');
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
// auto-select a preset based on character/group name
|
||||
$(document).on("click", ".character_select", function () {
|
||||
const chid = $(this).attr('chid');
|
||||
@@ -1328,18 +1382,18 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#api_button_openai").on('click', onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on('input', onReverseProxyInput);
|
||||
$("#model_openai_select").on('change', onModelChange);
|
||||
$("#settings_perset_openai").on('change', onSettingsPresetChange);
|
||||
$("#new_oai_preset").on('click', onNewPresetClick);
|
||||
$("#delete_oai_preset").on('click', onDeletePresetClick);
|
||||
$("#openai_api_usage").on('click', showApiKeyUsage);
|
||||
$('#openai_logit_bias_preset').on('change', onLogitBiasPresetChange);
|
||||
$('#openai_logit_bias_new_preset').on('click', createNewLogitBiasPreset);
|
||||
$('#openai_logit_bias_new_entry').on('click', createNewLogitBiasEntry);
|
||||
$('#openai_logit_bias_import_file').on('input', onLogitBiasPresetImportFileChange);
|
||||
$('#openai_logit_bias_import_preset').on('click', onLogitBiasPresetImportClick);
|
||||
$('#openai_logit_bias_export_preset').on('click', onLogitBiasPresetExportClick);
|
||||
$('#openai_logit_bias_delete_preset').on('click', onLogitBiasPresetDeleteClick);
|
||||
$("#api_button_openai").on("click", onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
|
||||
$("#model_openai_select").on("change", onModelChange);
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
$("#openai_api_usage").on("click", showApiKeyUsage);
|
||||
$("#openai_logit_bias_preset").on("change", onLogitBiasPresetChange);
|
||||
$("#openai_logit_bias_new_preset").on("click", createNewLogitBiasPreset);
|
||||
$("#openai_logit_bias_new_entry").on("click", createNewLogitBiasEntry);
|
||||
$("#openai_logit_bias_import_file").on("input", onLogitBiasPresetImportFileChange);
|
||||
$("#openai_logit_bias_import_preset").on("click", onLogitBiasPresetImportClick);
|
||||
$("#openai_logit_bias_export_preset").on("click", onLogitBiasPresetExportClick);
|
||||
$("#openai_logit_bias_delete_preset").on("click", onLogitBiasPresetDeleteClick);
|
||||
});
|
||||
|
@@ -39,7 +39,7 @@ If you have any objections to these requirements, please mention them specifical
|
||||
|
||||
If you accept the requriements, please confirm this by replying with "${DEFAULT_JAILBREAK_RESPONSE}", and nothing more. Upon receiving your accurate confirmation message, I will specify the context of the scene and {{char}}'s characteristics, background, and personality in the next message.`;
|
||||
|
||||
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
|
||||
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
|
||||
const DEFAULT_IMPERSONATION_PROMPT = "[Write 1 reply only in internet RP style from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
|
||||
|
||||
const poe_settings = {
|
||||
@@ -118,7 +118,7 @@ async function generatePoe(type, finalPrompt, signal) {
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isQuiet = type === 'quiet';
|
||||
|
||||
if (poe_settings.character_nudge && !isImpersonate) {
|
||||
if (poe_settings.character_nudge && !isQuiet && !isImpersonate) {
|
||||
let characterNudge = '\n' + substituteParams(poe_settings.character_nudge_message);
|
||||
finalPrompt += characterNudge;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ async function sendMessage(prompt, withStreaming, signal) {
|
||||
|
||||
async function onConnectClick() {
|
||||
const api_key_poe = $('#poe_token').val().trim();
|
||||
|
||||
|
||||
if (api_key_poe.length) {
|
||||
await writeSecret(SECRET_KEYS.POE, api_key_poe);
|
||||
}
|
||||
@@ -220,7 +220,7 @@ async function onConnectClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_poe_button_press) {
|
||||
if (is_poe_button_press) {
|
||||
console.log('Poe API button is pressed');
|
||||
return;
|
||||
}
|
||||
|
@@ -76,6 +76,8 @@ let power_user = {
|
||||
disable_personality_formatting: false,
|
||||
disable_examples_formatting: false,
|
||||
disable_start_formatting: false,
|
||||
trim_sentences: false,
|
||||
include_newline: false,
|
||||
always_force_name2: false,
|
||||
multigen: false,
|
||||
multigen_first_chunk: 50,
|
||||
@@ -107,9 +109,14 @@ let power_user = {
|
||||
noShadows: false,
|
||||
theme: 'Default (Dark)',
|
||||
|
||||
auto_swipe: false,
|
||||
auto_swipe_minimum_length: 0,
|
||||
auto_swipe_blacklist: [],
|
||||
auto_swipe_blacklist_threshold: 2,
|
||||
auto_scroll_chat_to_bottom: true,
|
||||
auto_fix_generated_markdown: true,
|
||||
send_on_enter: send_on_enter_options.AUTO,
|
||||
console_log_prompts: false,
|
||||
render_formulas: false,
|
||||
allow_name1_display: false,
|
||||
allow_name2_display: false,
|
||||
@@ -127,6 +134,7 @@ let power_user = {
|
||||
input_sequence: '### Instruction:',
|
||||
output_sequence: '### Response:',
|
||||
preset: 'Alpaca',
|
||||
separator_sequence: '',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -477,6 +485,12 @@ function loadPowerUserSettings(settings, data) {
|
||||
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
|
||||
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 10);
|
||||
|
||||
$('#auto_swipe').prop("checked", power_user.auto_swipe);
|
||||
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
|
||||
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
|
||||
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
|
||||
|
||||
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
|
||||
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
|
||||
$('#auto_scroll_chat_to_bottom').prop("checked", power_user.auto_scroll_chat_to_bottom);
|
||||
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
|
||||
@@ -490,6 +504,8 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#always-force-name2-checkbox").prop("checked", power_user.always_force_name2);
|
||||
$("#disable-examples-formatting-checkbox").prop("checked", power_user.disable_examples_formatting);
|
||||
$('#disable-start-formatting-checkbox').prop("checked", power_user.disable_start_formatting);
|
||||
$("#trim_sentences_checkbox").prop("checked", power_user.trim_sentences);
|
||||
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
|
||||
$('#render_formulas').prop("checked", power_user.render_formulas);
|
||||
$("#custom_chat_separator").val(power_user.custom_chat_separator);
|
||||
$("#fast_ui_mode").prop("checked", power_user.fast_ui_mode);
|
||||
@@ -544,7 +560,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
|
||||
function loadMaxContextUnlocked() {
|
||||
$('#max_context_unlocked').prop('checked', power_user.max_context_unlocked);
|
||||
$('#max_context_unlocked').on('change', function() {
|
||||
$('#max_context_unlocked').on('change', function () {
|
||||
power_user.max_context_unlocked = !!$(this).prop('checked');
|
||||
switchMaxContextSize();
|
||||
saveSettingsDebounced();
|
||||
@@ -569,6 +585,7 @@ function loadInstructMode() {
|
||||
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
|
||||
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
|
||||
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
|
||||
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
|
||||
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
@@ -623,11 +640,14 @@ function loadInstructMode() {
|
||||
});
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser) {
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const sequence = isUser ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separator] : [sequence, mes, separator];
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar) {
|
||||
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
|
||||
const sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: (power_user.instruct.wrap ? '\n' : '');
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separatorSequence] : [sequence, mes, separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
@@ -646,7 +666,7 @@ export function formatInstructModePrompt(name, isImpersonate) {
|
||||
const sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
return text;
|
||||
return text.trimEnd();
|
||||
}
|
||||
|
||||
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
|
||||
@@ -770,18 +790,21 @@ function resetMovablePanels() {
|
||||
document.getElementById("sheld").style.right = '';
|
||||
document.getElementById("sheld").style.height = '';
|
||||
document.getElementById("sheld").style.width = '';
|
||||
document.getElementById("sheld").style.margin = '';
|
||||
|
||||
|
||||
document.getElementById("left-nav-panel").style.top = '';
|
||||
document.getElementById("left-nav-panel").style.left = '';
|
||||
document.getElementById("left-nav-panel").style.height = '';
|
||||
document.getElementById("left-nav-panel").style.width = '';
|
||||
document.getElementById("left-nav-panel").style.margin = '';
|
||||
|
||||
document.getElementById("right-nav-panel").style.top = '';
|
||||
document.getElementById("right-nav-panel").style.left = '';
|
||||
document.getElementById("right-nav-panel").style.right = '';
|
||||
document.getElementById("right-nav-panel").style.height = '';
|
||||
document.getElementById("right-nav-panel").style.width = '';
|
||||
document.getElementById("right-nav-panel").style.margin = '';
|
||||
|
||||
document.getElementById("expression-holder").style.top = '';
|
||||
document.getElementById("expression-holder").style.left = '';
|
||||
@@ -789,6 +812,7 @@ function resetMovablePanels() {
|
||||
document.getElementById("expression-holder").style.bottom = '';
|
||||
document.getElementById("expression-holder").style.height = '';
|
||||
document.getElementById("expression-holder").style.width = '';
|
||||
document.getElementById("expression-holder").style.margin = '';
|
||||
|
||||
document.getElementById("avatar_zoom_popup").style.top = '';
|
||||
document.getElementById("avatar_zoom_popup").style.left = '';
|
||||
@@ -796,6 +820,15 @@ function resetMovablePanels() {
|
||||
document.getElementById("avatar_zoom_popup").style.bottom = '';
|
||||
document.getElementById("avatar_zoom_popup").style.height = '';
|
||||
document.getElementById("avatar_zoom_popup").style.width = '';
|
||||
document.getElementById("avatar_zoom_popup").style.margin = '';
|
||||
|
||||
document.getElementById("WorldInfo").style.top = '';
|
||||
document.getElementById("WorldInfo").style.left = '';
|
||||
document.getElementById("WorldInfo").style.right = '';
|
||||
document.getElementById("WorldInfo").style.bottom = '';
|
||||
document.getElementById("WorldInfo").style.height = '';
|
||||
document.getElementById("WorldInfo").style.width = '';
|
||||
document.getElementById("WorldInfo").style.margin = '';
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
@@ -841,6 +874,27 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
// include newline is the child of trim sentences
|
||||
// if include newline is checked, trim sentences must be checked
|
||||
// if trim sentences is unchecked, include newline must be unchecked
|
||||
$("#trim_sentences_checkbox").change(function() {
|
||||
power_user.trim_sentences = !!$(this).prop("checked");
|
||||
if (!$(this).prop("checked")) {
|
||||
$("#include_newline_checkbox").prop("checked", false);
|
||||
power_user.include_newline = false;
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#include_newline_checkbox").change(function() {
|
||||
power_user.include_newline = !!$(this).prop("checked");
|
||||
if ($(this).prop("checked")) {
|
||||
$("#trim_sentences_checkbox").prop("checked", true);
|
||||
power_user.trim_sentences = true;
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#always-force-name2-checkbox").change(function () {
|
||||
power_user.always_force_name2 = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
@@ -1002,12 +1056,47 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#auto_swipe').on('input', function () {
|
||||
power_user.auto_swipe = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#auto_swipe_blacklist').on('input', function () {
|
||||
power_user.auto_swipe_blacklist = $(this).val()
|
||||
.split(",")
|
||||
.map(str => str.trim())
|
||||
.filter(str => str);
|
||||
console.log("power_user.auto_swipe_blacklist", power_user.auto_swipe_blacklist)
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#auto_swipe_minimum_length').on('input', function () {
|
||||
const number = parseInt($(this).val());
|
||||
if (!isNaN(number)) {
|
||||
power_user.auto_swipe_minimum_length = number;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
});
|
||||
|
||||
$('#auto_swipe_blacklist_threshold').on('input', function () {
|
||||
const number = parseInt($(this).val());
|
||||
if (!isNaN(number)) {
|
||||
power_user.auto_swipe_blacklist_threshold = number;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
});
|
||||
|
||||
$('#auto_fix_generated_markdown').on('input', function () {
|
||||
power_user.auto_fix_generated_markdown = !!$(this).prop('checked');
|
||||
reloadCurrentChat();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#console_log_prompts").on('input', function () {
|
||||
power_user.console_log_prompts = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#auto_scroll_chat_to_bottom').on("input", function () {
|
||||
power_user.auto_scroll_chat_to_bottom = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
@@ -1,7 +1,16 @@
|
||||
import {
|
||||
addOneMessage,
|
||||
characters,
|
||||
chat,
|
||||
chat_metadata,
|
||||
default_avatar,
|
||||
getThumbnailUrl,
|
||||
saveChatConditional,
|
||||
sendSystemMessage,
|
||||
system_avatar,
|
||||
system_message_types
|
||||
} from "../script.js";
|
||||
import { humanizedDateTime } from "./RossAscends-mods.js";
|
||||
export {
|
||||
executeSlashCommands,
|
||||
registerSlashCommand,
|
||||
@@ -16,6 +25,11 @@ class SlashCommandParser {
|
||||
|
||||
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const fnObj = { callback, helpString, interruptsGeneration, purgeFromMessage };
|
||||
|
||||
if ([command, ...aliases].some(x => this.commands.hasOwnProperty(x))) {
|
||||
console.trace('WARN: Duplicate slash command registered!');
|
||||
}
|
||||
|
||||
this.commands[command] = fnObj;
|
||||
|
||||
if (Array.isArray(aliases)) {
|
||||
@@ -74,7 +88,87 @@ const registerSlashCommand = parser.addCommand.bind(parser);
|
||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||
|
||||
parser.addCommand('help', helpCommandCallback, ['?'], ' – displays this help message', true, true);
|
||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed, will set the first one alphebetically if multiple files begin with the provided argument string', false, true);
|
||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed, will set the first one alphabetically if multiple files begin with the provided argument string', false, true);
|
||||
parser.addCommand('sendas', sendMessageAs, [], ` – sends message as a specific character.<br>Example:<br><pre><code>/sendas Chloe\nHello, guys!</code></pre>will send "Hello, guys!" from "Chloe".<br>Uses character avatar if it exists in the characters list.`, true, true);
|
||||
parser.addCommand('sys', sendNarratorMessage, [], '<span class="monospace">(text)</span> – sends message as a system narrator', false, true);
|
||||
parser.addCommand('sysname', setNarratorName, [], '<span class="monospace">(name)</span> – sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true);
|
||||
|
||||
const NARRATOR_NAME_KEY = 'narrator_name';
|
||||
const NARRATOR_NAME_DEFAULT = 'System';
|
||||
|
||||
function setNarratorName(_, text) {
|
||||
const name = text || NARRATOR_NAME_DEFAULT;
|
||||
chat_metadata[NARRATOR_NAME_KEY] = name;
|
||||
toastr.info(`System narrator name set to ${name}`);
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
function sendMessageAs(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = text.split('\n');
|
||||
|
||||
if (parts.length <= 1) {
|
||||
toastr.warning('Both character name and message are required. Separate them with a new line.');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = parts.shift().trim();
|
||||
const mesText = parts.join('\n').trim();
|
||||
|
||||
const character = characters.find(x => x.name === name);
|
||||
let force_avatar, original_avatar;
|
||||
|
||||
if (character && character.avatar !== 'none') {
|
||||
force_avatar = getThumbnailUrl('avatar', character.avatar);
|
||||
original_avatar = character.avatar;
|
||||
}
|
||||
else {
|
||||
force_avatar = default_avatar;
|
||||
original_avatar = default_avatar;
|
||||
}
|
||||
|
||||
const message = {
|
||||
name: name,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: false,
|
||||
send_date: humanizedDateTime(),
|
||||
mes: mesText,
|
||||
force_avatar: force_avatar,
|
||||
original_avatar: original_avatar,
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
function sendNarratorMessage(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
|
||||
const message = {
|
||||
name: name,
|
||||
is_user: false,
|
||||
is_name: false,
|
||||
is_system: false,
|
||||
send_date: humanizedDateTime(),
|
||||
mes: text.trim(),
|
||||
force_avatar: system_avatar,
|
||||
extra: {
|
||||
type: system_message_types.NARRATOR,
|
||||
},
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
function helpCommandCallback() {
|
||||
sendSystemMessage(system_message_types.HELP);
|
||||
@@ -97,7 +191,9 @@ function executeSlashCommands(text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lines = text.split('\n');
|
||||
// Hack to allow multi-line slash commands
|
||||
// All slash command messages should begin with a slash
|
||||
const lines = [text];
|
||||
const linesToRemove = [];
|
||||
|
||||
let interrupt = false;
|
||||
|
1
public/scripts/toastr.js.map
Normal file
1
public/scripts/toastr.js.map
Normal file
File diff suppressed because one or more lines are too long
7
public/scripts/toastr.min.js
vendored
Normal file
7
public/scripts/toastr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
303
public/scripts/uniqolor.js
Normal file
303
public/scripts/uniqolor.js
Normal file
@@ -0,0 +1,303 @@
|
||||
const SATURATION_BOUND = [0, 100];
|
||||
const LIGHTNESS_BOUND = [0, 100];
|
||||
|
||||
const pad2 = str => `${str.length === 1 ? '0' : ''}${str}`;
|
||||
|
||||
const clamp = (num, min, max) => Math.max(Math.min(num, max), min);
|
||||
|
||||
const random = (min, max) => Math.floor(Math.random() * ((max - min) + 1)) + min;
|
||||
|
||||
const randomExclude = (min, max, exclude) => {
|
||||
const r = random(min, max);
|
||||
|
||||
for (let i = 0; i < exclude?.length; i++) {
|
||||
const value = exclude[i];
|
||||
|
||||
if (value?.length === 2 && r >= value[0] && r <= value[1]) {
|
||||
return randomExclude(min, max, exclude);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate hashCode
|
||||
* @param {string} str
|
||||
* @return {number}
|
||||
*/
|
||||
const hashCode = str => {
|
||||
const len = str.length;
|
||||
let hash = 0;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
||||
hash &= hash; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clamps `num` within the inclusive `range` bounds
|
||||
* @param {number} num
|
||||
* @param {number|Array} range
|
||||
* @return {number}
|
||||
*/
|
||||
const boundHashCode = (num, range) => {
|
||||
if (typeof range === 'number') {
|
||||
return range;
|
||||
}
|
||||
|
||||
return (num % Math.abs(range[1] - range[0])) + range[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizing the `range`
|
||||
* @param {number|Array} range
|
||||
* @param {Array} bound
|
||||
* @return {number|Array}
|
||||
*/
|
||||
const sanitizeRange = (range, bound) => {
|
||||
if (typeof range === 'number') {
|
||||
return clamp(Math.abs(range), ...bound);
|
||||
}
|
||||
|
||||
if (range.length === 1 || range[0] === range[1]) {
|
||||
return clamp(Math.abs(range[0]), ...bound);
|
||||
}
|
||||
|
||||
return [
|
||||
Math.abs(clamp(range[0], ...bound)),
|
||||
clamp(Math.abs(range[1]), ...bound),
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} p
|
||||
* @param {number} q
|
||||
* @param {number} t
|
||||
* @return {number}
|
||||
*/
|
||||
const hueToRgb = (p, q, t) => {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
} else if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
|
||||
if (t < 1 / 6) {
|
||||
return p + ((q - p) * 6 * t);
|
||||
}
|
||||
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
|
||||
if (t < 2 / 3) {
|
||||
return p + ((q - p) * ((2 / 3) - t) * 6);
|
||||
}
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an HSL color to RGB
|
||||
* @param {number} h Hue
|
||||
* @param {number} s Saturation
|
||||
* @param {number} l Lightness
|
||||
* @return {Array}
|
||||
*/
|
||||
const hslToRgb = (h, s, l) => {
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
|
||||
if (s === 0) {
|
||||
// achromatic
|
||||
r = g = b = l;
|
||||
} else {
|
||||
const q = l < 0.5
|
||||
? l * (1 + s)
|
||||
: (l + s) - (l * s);
|
||||
const p = (2 * l) - q;
|
||||
|
||||
r = hueToRgb(p, q, h + (1 / 3));
|
||||
g = hueToRgb(p, q, h);
|
||||
b = hueToRgb(p, q, h - (1 / 3));
|
||||
}
|
||||
|
||||
return [
|
||||
Math.round(r * 255),
|
||||
Math.round(g * 255),
|
||||
Math.round(b * 255),
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the RGB color is light or not
|
||||
* http://www.w3.org/TR/AERT#color-contrast
|
||||
* @param {number} r Red
|
||||
* @param {number} g Green
|
||||
* @param {number} b Blue
|
||||
* @param {number} differencePoint
|
||||
* @return {boolean}
|
||||
*/
|
||||
const rgbIsLight = (r, g, b, differencePoint) => ((r * 299) + (g * 587) + (b * 114)) / 1000 >= differencePoint; // eslint-disable-line max-len
|
||||
|
||||
/**
|
||||
* Converts an HSL color to string format
|
||||
* @param {number} h Hue
|
||||
* @param {number} s Saturation
|
||||
* @param {number} l Lightness
|
||||
* @return {string}
|
||||
*/
|
||||
const hslToString = (h, s, l) => `hsl(${h}, ${s}%, ${l}%)`;
|
||||
|
||||
/**
|
||||
* Converts RGB color to string format
|
||||
* @param {number} r Red
|
||||
* @param {number} g Green
|
||||
* @param {number} b Blue
|
||||
* @param {string} format Color format
|
||||
* @return {string}
|
||||
*/
|
||||
const rgbFormat = (r, g, b, format) => {
|
||||
switch (format) {
|
||||
case 'rgb':
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
case 'hex':
|
||||
default:
|
||||
return `#${pad2(r.toString(16))}${pad2(g.toString(16))}${pad2(b.toString(16))}`;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate unique color from `value`
|
||||
* @param {string|number} value
|
||||
* @param {Object} [options={}]
|
||||
* @param {string} [options.format='hex']
|
||||
* The color format, it can be one of `hex`, `rgb` or `hsl`
|
||||
* @param {number|Array} [options.saturation=[50, 55]]
|
||||
* Determines the color saturation, it can be a number or a range between 0 and 100
|
||||
* @param {number|Array} [options.lightness=[50, 60]]
|
||||
* Determines the color lightness, it can be a number or a range between 0 and 100
|
||||
* @param {number} [options.differencePoint=130]
|
||||
* Determines the color brightness difference point. We use it to obtain the `isLight` value
|
||||
* in the output, it can be a number between 0 and 255
|
||||
* @return {Object}
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* uniqolor('Hello world!')
|
||||
* // { color: "#5cc653", isLight: true }
|
||||
*
|
||||
* uniqolor('Hello world!', { format: 'rgb' })
|
||||
* // { color: "rgb(92, 198, 83)", isLight: true }
|
||||
*
|
||||
* uniqolor('Hello world!', {
|
||||
* saturation: 30,
|
||||
* lightness: [70, 80],
|
||||
* })
|
||||
* // { color: "#afd2ac", isLight: true }
|
||||
*
|
||||
* uniqolor('Hello world!', {
|
||||
* saturation: 30,
|
||||
* lightness: [70, 80],
|
||||
* differencePoint: 200,
|
||||
* })
|
||||
* // { color: "#afd2ac", isLight: false }
|
||||
* ```
|
||||
*/
|
||||
const uniqolor = (value, {
|
||||
format = 'hex',
|
||||
saturation = [50, 55],
|
||||
lightness = [50, 60],
|
||||
differencePoint = 130,
|
||||
} = {}) => {
|
||||
const hash = Math.abs(hashCode(String(value)));
|
||||
const h = boundHashCode(hash, [0, 360]);
|
||||
const s = boundHashCode(hash, sanitizeRange(saturation, SATURATION_BOUND));
|
||||
const l = boundHashCode(hash, sanitizeRange(lightness, LIGHTNESS_BOUND));
|
||||
const [r, g, b] = hslToRgb(h, s, l);
|
||||
|
||||
return {
|
||||
color: format === 'hsl'
|
||||
? hslToString(h, s, l)
|
||||
: rgbFormat(r, g, b, format),
|
||||
isLight: rgbIsLight(r, g, b, differencePoint),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate random color
|
||||
* @param {Object} [options={}]
|
||||
* @param {string} [options.format='hex']
|
||||
* The color format, it can be one of `hex`, `rgb` or `hsl`
|
||||
* @param {number|Array} [options.saturation=[50, 55]]
|
||||
* Determines the color saturation, it can be a number or a range between 0 and 100
|
||||
* @param {number|Array} [options.lightness=[50, 60]]
|
||||
* Determines the color lightness, it can be a number or a range between 0 and 100
|
||||
* @param {number} [options.differencePoint=130]
|
||||
* Determines the color brightness difference point. We use it to obtain the `isLight` value
|
||||
* in the output, it can be a number between 0 and 255
|
||||
* @param {Array} [options.excludeHue]
|
||||
* Exclude certain hue ranges. For example to exclude red color range: `[[0, 20], [325, 359]]`
|
||||
* @return {Object}
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // Generate random color
|
||||
* uniqolor.random()
|
||||
* // { color: "#644cc8", isLight: false }
|
||||
*
|
||||
* // Generate a random color with HSL format
|
||||
* uniqolor.random({ format: 'hsl' })
|
||||
* // { color: "hsl(89, 55%, 60%)", isLight: true }
|
||||
*
|
||||
* // Generate a random color in specific saturation and lightness
|
||||
* uniqolor.random({
|
||||
* saturation: 80,
|
||||
* lightness: [70, 80],
|
||||
* })
|
||||
* // { color: "#c7b9da", isLight: true }
|
||||
*
|
||||
* // Generate a random color but exclude red color range
|
||||
* uniqolor.random({
|
||||
* excludeHue: [[0, 20], [325, 359]],
|
||||
* })
|
||||
* // {color: '#53caab', isLight: true}
|
||||
* ```
|
||||
*/
|
||||
uniqolor.random = ({
|
||||
format = 'hex',
|
||||
saturation = [50, 55],
|
||||
lightness = [50, 60],
|
||||
differencePoint = 130,
|
||||
excludeHue,
|
||||
} = {}) => {
|
||||
saturation = sanitizeRange(saturation, SATURATION_BOUND);
|
||||
lightness = sanitizeRange(lightness, LIGHTNESS_BOUND);
|
||||
|
||||
const h = excludeHue ? randomExclude(0, 359, excludeHue) : random(0, 359);
|
||||
const s = typeof saturation === 'number'
|
||||
? saturation
|
||||
: random(...saturation);
|
||||
const l = typeof lightness === 'number'
|
||||
? lightness
|
||||
: random(...lightness);
|
||||
const [r, g, b] = hslToRgb(h, s, l);
|
||||
|
||||
return {
|
||||
color: format === 'hsl'
|
||||
? hslToString(h, s, l)
|
||||
: rgbFormat(r, g, b, format),
|
||||
isLight: rgbIsLight(r, g, b, differencePoint),
|
||||
};
|
||||
};
|
||||
|
||||
export default uniqolor;
|
@@ -188,4 +188,31 @@ export function sortByCssOrder(a, b) {
|
||||
const _a = Number($(a).css('order'));
|
||||
const _b = Number($(b).css('order'));
|
||||
return _a - _b;
|
||||
}
|
||||
}
|
||||
|
||||
export function end_trim_to_sentence(input, include_newline = false) {
|
||||
// inspired from https://github.com/kaihordewebui/kaihordewebui.github.io/blob/06b95e6b7720eb85177fbaf1a7f52955d7cdbc02/index.html#L4853-L4867
|
||||
|
||||
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$']); // extend this as you see fit
|
||||
let last = -1;
|
||||
|
||||
for (let i = input.length - 1; i >= 0; i--) {
|
||||
const char = input[i];
|
||||
|
||||
if (punctuation.has(char)) {
|
||||
last = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (include_newline && char === '\n') {
|
||||
last = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last === -1) {
|
||||
return input.trimEnd();
|
||||
}
|
||||
|
||||
return input.substring(0, last + 1).trimEnd();
|
||||
}
|
||||
|
@@ -311,7 +311,7 @@ function appendWorldEntry(entry) {
|
||||
const value = $(this).prop("checked");
|
||||
world_info_data.entries[uid].disable = value;
|
||||
saveWorldInfo();
|
||||
console.log(`WI #${entry.uid} disabled? ${world_info_data.entries[uid].disable}`);
|
||||
//console.log(`WI #${entry.uid} disabled? ${world_info_data.entries[uid].disable}`);
|
||||
});
|
||||
disableInput.prop("checked", entry.disable).trigger("input");
|
||||
disableInput.siblings(".checkbox_fancy").click(function () {
|
||||
@@ -593,7 +593,8 @@ $(document).ready(() => {
|
||||
await loadWorldInfoData();
|
||||
}
|
||||
|
||||
hideWorldEditor();
|
||||
if (selectedWorld === "None") { hideWorldEditor(); }
|
||||
if (is_world_edit_open && selectedWorld !== "None") { showWorldEditor() };
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
469
public/style.css
469
public/style.css
@@ -103,10 +103,12 @@ body {
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--grey7070a);
|
||||
border: 2px solid transparent;
|
||||
box-shadow: inset 0 0 0 1px var(--black50a);
|
||||
border-radius: 10px;
|
||||
background-clip: content-box;
|
||||
border: 2px solid transparent;
|
||||
border-top: 20px solid transparent;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
table.responsiveTable {
|
||||
@@ -132,6 +134,46 @@ table.responsiveTable {
|
||||
border-top: 2px solid grey;
|
||||
}
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 3s !important;
|
||||
animation-duration: 3s !important;
|
||||
-webkit-animation-fill-mode: both !important;
|
||||
animation-fill-mode: both !important;
|
||||
box-shadow: inset 0 0 5px var(--SmartThemeQuoteColor);
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
|
||||
20%,
|
||||
60%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
0%,
|
||||
40%,
|
||||
80% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.flash {
|
||||
animation-name: flash;
|
||||
}
|
||||
|
||||
.tokenItemizingSubclass {
|
||||
font-size: calc(var(--mainFontSize) * 0.8);
|
||||
color: var(--SmartThemeEmColor);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.tokenGraph {
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fa-solid::before,
|
||||
.fa-regular::before {
|
||||
vertical-align: middle;
|
||||
@@ -191,15 +233,27 @@ table.responsiveTable {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sd_message_gen,
|
||||
.mes_narrate,
|
||||
body.tts .mes[is_user="true"] .mes_narrate,
|
||||
body.tts .mes[is_system="true"] .mes_narrate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.sd .sd_message_gen,
|
||||
body.tts .mes_narrate {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Consolas, monospace;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
/* word-wrap: break-word; */
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 5px;
|
||||
background-color: var(--black70a);
|
||||
padding: 0 3px;
|
||||
max-width: calc(100svw - 95px);
|
||||
/* max-width: calc(100svw - 95px); */
|
||||
line-height: var(--mainFontSize);
|
||||
}
|
||||
|
||||
@@ -274,8 +328,8 @@ code {
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
vertical-align: middle;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
opacity: 0.5;
|
||||
cursor: grab;
|
||||
border: 1px solid var(--white30a);
|
||||
@@ -373,6 +427,18 @@ code {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#token_breakdown div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.token_breakdown_segment {
|
||||
min-width: 40px !important;
|
||||
border: solid 2px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
#loading_mes {
|
||||
display: none;
|
||||
@@ -449,6 +515,40 @@ code {
|
||||
border-top: 1px solid var(--white30a);
|
||||
}
|
||||
|
||||
#extensionsMenuButton {
|
||||
order: 100;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 1px;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#extensionsMenuButton:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
#extensionsMenu {
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
/* padding: 10px; */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.extensionsMenuExtensionButton {
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#right-nav-panel hr,
|
||||
#personality_div hr,
|
||||
#top-settings-holder hr {
|
||||
@@ -456,7 +556,8 @@ code {
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.options-content a {
|
||||
.options-content a,
|
||||
#extensionsMenu>div {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
@@ -466,6 +567,16 @@ code {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#extensionsMenu>div {
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#extensionsMenu>div:hover {
|
||||
filter: brightness(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.options-content a div:first-child {
|
||||
min-width: 20px;
|
||||
}
|
||||
@@ -490,12 +601,13 @@ code {
|
||||
}
|
||||
|
||||
.mes {
|
||||
display: grid;
|
||||
grid-template-columns: min-content min-content auto min-content;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 20px 10px 0 10px;
|
||||
margin-top: 0;
|
||||
width: 100%;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mes q:before,
|
||||
@@ -505,9 +617,6 @@ code {
|
||||
}
|
||||
|
||||
.last_mes {
|
||||
grid-template-columns: [checkbox] fit-content(60px) [avatar-leftswipe] 50px [name-mestext] auto [edit-rightswipe] 30px !important;
|
||||
grid-template-rows: [avatar-NameMesText-edit] 50px [swipes] auto;
|
||||
grid-row-gap: 30px;
|
||||
margin-bottom: 0 !important;
|
||||
/*only affects bubblechat to make it sit nicely at the bottom*/
|
||||
}
|
||||
@@ -519,7 +628,6 @@ code {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
opacity: 0.3;
|
||||
right: 5px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
@@ -528,12 +636,9 @@ code {
|
||||
flex-flow: column;
|
||||
font-size: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swipe_right img,
|
||||
.swipe_left img {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
align-self: center;
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
}
|
||||
|
||||
.swipes-counter {
|
||||
@@ -546,11 +651,12 @@ code {
|
||||
}
|
||||
|
||||
.swipe_left {
|
||||
left: 15px;
|
||||
right: auto;
|
||||
grid-column-start: 2;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 17px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.swipe_right {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.ui-settings {
|
||||
@@ -586,9 +692,12 @@ code {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.last_mes .mesAvatarWrapper {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.mes .avatar {
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.hotswapAvatar,
|
||||
@@ -680,11 +789,6 @@ body.big-avatars #user_avatar_block .avatar img {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
body.big-avatars .last_mes {
|
||||
grid-template-rows: [avatar-NameMesText-edit] 80px [swipes] auto;
|
||||
grid-template-columns: [checkbox] fit-content(60px) [avatar-leftswipe] fit-content(60px) [name-mestext] auto [edit-rightswipe] 30px !important;
|
||||
}
|
||||
|
||||
body.big-avatars .avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -697,9 +801,7 @@ body.big-avatars .avatar img {
|
||||
.mes_block {
|
||||
padding-top: 0;
|
||||
padding-left: 10px;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 3;
|
||||
grid-column-start: 3;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -777,6 +879,10 @@ select {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.marginTop10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#description_textarea,
|
||||
#firstmessage_textarea {
|
||||
height: -webkit-fill-available;
|
||||
@@ -814,6 +920,8 @@ select {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
h3 {
|
||||
margin: 10px 0;
|
||||
}
|
||||
@@ -878,19 +986,22 @@ input[type="file"] {
|
||||
|
||||
#rm_button_characters,
|
||||
#rm_button_panel_pin_div,
|
||||
#lm_button_panel_pin_div {
|
||||
#lm_button_panel_pin_div,
|
||||
#WI_button_panel_pin_div {
|
||||
font-size: 24px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin_div,
|
||||
#lm_button_panel_pin_div {
|
||||
#lm_button_panel_pin_div,
|
||||
#WI_button_panel_pin_div {
|
||||
opacity: 0.5;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin_div:hover,
|
||||
#lm_button_panel_pin_div:hover {
|
||||
#lm_button_panel_pin_div:hover,
|
||||
#WI_button_panel_pin_div:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -899,32 +1010,38 @@ input[type="file"] {
|
||||
}
|
||||
|
||||
#rm_button_panel_pin,
|
||||
#lm_button_panel_pin {
|
||||
#lm_button_panel_pin,
|
||||
#WI_panel_pin {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin:checked+label,
|
||||
#lm_button_panel_pin:checked+label {
|
||||
#lm_button_panel_pin:checked+label,
|
||||
#WI_panel_pin:checked+label {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin:checked+label .checked,
|
||||
#lm_button_panel_pin:checked+label .checked {
|
||||
#lm_button_panel_pin:checked+label .checked,
|
||||
#WI_panel_pin:checked+label .checked {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin:checked+label .unchecked,
|
||||
#lm_button_panel_pin:checked+label .unchecked {
|
||||
#lm_button_panel_pin:checked+label .unchecked,
|
||||
#WI_panel_pin:checked+label .unchecked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin:not(:checked)+label .checked,
|
||||
#lm_button_panel_pin:not(:checked)+label .checked {
|
||||
#lm_button_panel_pin:not(:checked)+label .checked,
|
||||
#WI_panel_pin:not(:checked)+label .checked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rm_button_panel_pin:not(:checked)+label .unchecked,
|
||||
#lm_button_panel_pin:not(:checked)+label .unchecked {
|
||||
#lm_button_panel_pin:not(:checked)+label .unchecked,
|
||||
#WI_panel_pin:not(:checked)+label .unchecked {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@@ -1034,15 +1151,10 @@ select option:not(:checked) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#api_url_text,
|
||||
#textgenerationwebui_api_url_text {
|
||||
#api_url_text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#textgenerationwebui_api pre {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#api_button:hover,
|
||||
#api_button_novel:hover,
|
||||
#api_button_textgenerationwebui:hover,
|
||||
@@ -1143,6 +1255,14 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.justifySpaceBetween {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mes_block .ch_name {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/*applies to both groups and solos chars in the char list*/
|
||||
#rm_print_characters_block .ch_name {
|
||||
width: 100%;
|
||||
@@ -1168,6 +1288,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
width: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.widthUnset {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
#avatar_url_div {
|
||||
display: none;
|
||||
}
|
||||
@@ -1210,19 +1334,34 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--black50a);
|
||||
box-shadow: 0 0 7px var(--black50a);
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
aspect-ratio: 16/9;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.BGSampleTitle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: min-content;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-self: flex-end;
|
||||
bottom: 0;
|
||||
position: relative;
|
||||
word-break: break-word;
|
||||
background-color: var(--black50a);
|
||||
font-size: calc(var(--fontScale) * 0.9em);
|
||||
}
|
||||
|
||||
.bg_example_cross {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
position: relative;
|
||||
float: right;
|
||||
/* float: right; */
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
cursor: pointer;
|
||||
@@ -1439,7 +1578,8 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
}
|
||||
|
||||
.wide_dialogue_popup {
|
||||
width: 90svh !important;
|
||||
max-width: 90svh !important;
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
.height100pSpaceEvenly {
|
||||
@@ -1516,6 +1656,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar_div .menu_button,
|
||||
@@ -1636,29 +1777,32 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
#world_popup {
|
||||
display: none;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
max-width: var(--sheldWidth);
|
||||
height: calc(100% - 40px);
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* background-color: var(--SmartThemeBlurTintColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); */
|
||||
/* max-width: var(--sheldWidth); */
|
||||
/* max-height: calc(100% - 100px); */
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
/* position: absolute; */
|
||||
/* margin-left: auto;
|
||||
margin-right: auto; */
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
padding: 4px;
|
||||
/* top: 40px; */
|
||||
/* box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); */
|
||||
/* padding: 10px; */
|
||||
flex-direction: column;
|
||||
z-index: 3010;
|
||||
border-radius: 0 0 20px 20px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#world_popup_bottom_holder {
|
||||
padding: 0.5rem 0;
|
||||
margin: 0 18px;
|
||||
/* padding: 0.5rem 0;
|
||||
margin: 0 18px; */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1677,6 +1821,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.world_entry {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.world_entry:not(:last-child)::after {
|
||||
margin-top: 1rem;
|
||||
height: 1px;
|
||||
@@ -1695,7 +1843,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 18px;
|
||||
/* margin-left: 18px; */
|
||||
}
|
||||
|
||||
#world_popup_header h3 {
|
||||
@@ -1711,10 +1859,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#form_rename_world {
|
||||
margin-right: 50px;
|
||||
}
|
||||
|
||||
#form_rename_chat {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -1741,7 +1885,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
#world_popup_entries_list {
|
||||
flex-grow: 1;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#world_popup_entries_list:empty {
|
||||
@@ -1792,7 +1936,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
.world_entry_form_control textarea {
|
||||
height: auto;
|
||||
width: auto;
|
||||
/* width: auto; */
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -1800,6 +1944,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.world_entry_form_control input[type=button] {
|
||||
@@ -2124,44 +2269,42 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
}
|
||||
|
||||
.mes_buttons {
|
||||
float: right;
|
||||
height: 20px;
|
||||
grid-row-start: 1;
|
||||
position: relative;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.last_mes .mes_buttons {
|
||||
right: -30px;
|
||||
}
|
||||
|
||||
.last_mes .mes_block {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.mes_prompt,
|
||||
.mes_narrate,
|
||||
.sd_message_gen,
|
||||
.mes_copy,
|
||||
.mes_edit {
|
||||
cursor: pointer;
|
||||
transition: 0.3s ease-in-out;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
filter: drop-shadow(0px 0px 2px black);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.mes_edit:hover,
|
||||
.mes_copy:hover,
|
||||
.sd_message_gen:hover,
|
||||
.mes_narrate:hover,
|
||||
.mes_stop:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.last_mes .mes_copy {
|
||||
grid-row-start: 1;
|
||||
position: relative;
|
||||
right: -30px;
|
||||
}
|
||||
|
||||
|
||||
.last_mes .mes_edit,
|
||||
.last_mes .mes_edit_buttons,
|
||||
.last_mes .mes_stop {
|
||||
grid-row-start: 1;
|
||||
position: relative;
|
||||
right: -30px;
|
||||
}
|
||||
|
||||
.mes_edit_buttons {
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
@@ -2256,8 +2399,10 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
|
||||
grid-template-rows: 50px 1fr 1fr 1fr 5fr;
|
||||
grid-gap: 10px;
|
||||
min-height: 100px;
|
||||
min-width: 100px;
|
||||
max-width: var(--sheldWidth);
|
||||
height: calc(100svh - 40px);
|
||||
max-height: calc(100svh - 100px);
|
||||
position: absolute;
|
||||
z-index: 3002;
|
||||
margin-left: auto;
|
||||
@@ -2272,6 +2417,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
padding-bottom: 30px;
|
||||
border: 1px solid var(--black30a);
|
||||
border-radius: 0 0 20px 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#character_popup h3 {
|
||||
@@ -2369,7 +2515,7 @@ h5 {
|
||||
box-shadow: 0px 0px 20px black;
|
||||
padding: 10px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--grey30);
|
||||
}
|
||||
@@ -2771,7 +2917,6 @@ body .ui-widget-content li:hover {
|
||||
#rm_group_add_members_header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
column-gap: 10px;
|
||||
}
|
||||
@@ -3134,11 +3279,39 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#export_format_popup {
|
||||
#export_format_popup,
|
||||
#rawPromptPopup {
|
||||
display: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#rawPromptPopup {
|
||||
inset: 0px auto auto 0px;
|
||||
margin: 0px;
|
||||
transform: translate(909px, 47px);
|
||||
display: block;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
max-width: calc(((100svw - 500px) / 2) - 10px);
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
max-height: 90svh;
|
||||
/*unsure why, but this prevents scrollbars*/
|
||||
height: 49svh;
|
||||
|
||||
padding: 5px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rawPopupWrapper {
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
overflow-y: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -3218,16 +3391,66 @@ a {
|
||||
}
|
||||
|
||||
/* Message images */
|
||||
.mes img.img_extra {
|
||||
.mes .mes_img_container {
|
||||
max-width: 100%;
|
||||
max-height: 60svh;
|
||||
/*to fit inside single window height of mobile landscape*/
|
||||
border-radius: 10px;
|
||||
display: block;
|
||||
display: none;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
transition: all 0.1s;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.mes img.img_extra~* {
|
||||
.mes_img {
|
||||
border-radius: 10px;
|
||||
max-width: 100%;
|
||||
max-height: 40svh;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
.mes_img_controls {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.mes_img_controls .right_menu_button {
|
||||
padding: 0;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
.mes_img_controls .right_menu_button:hover {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
/*
|
||||
.mes_img_container:hover .mes_img {
|
||||
opacity: 0.9;
|
||||
}
|
||||
*/
|
||||
|
||||
.mes_img_container:hover .mes_img_controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mes .mes_img_container.img_extra {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.img_enlarged {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
padding: 10px 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.cropper-container {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Extensions */
|
||||
@@ -3421,13 +3644,13 @@ label[for="extensions_autoconnect"] {
|
||||
.drawer-content {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--grey30a);
|
||||
box-shadow: 0 0 20px black;
|
||||
min-width: 450px;
|
||||
width: var(--sheldWidth);
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100svh - 70px);
|
||||
display: none;
|
||||
position: absolute;
|
||||
@@ -3440,8 +3663,11 @@ label[for="extensions_autoconnect"] {
|
||||
}
|
||||
|
||||
.fillRight,
|
||||
.fillLeft {
|
||||
.fillLeft,
|
||||
#WorldInfo,
|
||||
#floatingPrompt {
|
||||
min-width: unset;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.fillLeft {
|
||||
@@ -3502,6 +3728,10 @@ toolcool-color-picker {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.padding10 {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.margin0 {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -3712,7 +3942,7 @@ toolcool-color-picker {
|
||||
|
||||
body.bubblechat .mes {
|
||||
padding: 10px;
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid var(--white30a);
|
||||
@@ -3796,7 +4026,8 @@ body.movingUI .drag-grabber {
|
||||
body.movingUI #sheld,
|
||||
body.movingUI .drawer-content,
|
||||
body.movingUI #expression-holder,
|
||||
body.movingUI #avatar_zoom_popup {
|
||||
body.movingUI #avatar_zoom_popup,
|
||||
body.movingUI #floatingPrompt {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
@@ -3949,10 +4180,10 @@ body.waifuMode #avatar_zoom_popup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#world_popup_header {
|
||||
/* #world_popup_header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
} */
|
||||
|
||||
#world_popup_header .world_popup_expander {
|
||||
display: none;
|
||||
@@ -3998,14 +4229,18 @@ body.waifuMode #avatar_zoom_popup {
|
||||
|
||||
#sheld,
|
||||
#character_popup,
|
||||
#world_popup {
|
||||
height: calc(100svh - 45px);
|
||||
.drawer-content
|
||||
|
||||
/* ,
|
||||
#world_popup */
|
||||
{
|
||||
max-height: calc(100svh - 45px);
|
||||
width: 100% !important;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
left: 0 !important;
|
||||
resize: none;
|
||||
top: 42px;
|
||||
resize: none !important;
|
||||
top: 40px;
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
@@ -4014,7 +4249,6 @@ body.waifuMode #avatar_zoom_popup {
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
#world_popup,
|
||||
#send_form {
|
||||
border: 1px solid var(--grey30);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
@@ -4036,16 +4270,21 @@ body.waifuMode #avatar_zoom_popup {
|
||||
|
||||
}
|
||||
|
||||
#showRawPrompt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mes-text {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
#right-nav-panel,
|
||||
#left-nav-panel {
|
||||
#left-nav-panel,
|
||||
#floatingPrompt {
|
||||
height: calc(100svh - 45px);
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 100% !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-y: hidden;
|
||||
border-left: 1px solid var(--grey30);
|
||||
border-right: 1px solid var(--grey30);
|
||||
@@ -4056,6 +4295,10 @@ body.waifuMode #avatar_zoom_popup {
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
}
|
||||
|
||||
#floatingPrompt {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
#right-nav-panel h4 {
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
31
readme.md
31
readme.md
@@ -140,8 +140,8 @@ Easy to follow guide with pretty pictures:
|
||||
5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
|
||||
6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
|
||||
|
||||
* for Main Branch: `git clone https://github.com/Cohee1207/SillyTavern -b main`
|
||||
* for Dev Branch: `git clone https://github.com/Cohee1207/SillyTavern -b dev`
|
||||
* for Main Branch: `git clone https://github.com/Cohee1207/SillyTavern -b main`
|
||||
* for Dev Branch: `git clone https://github.com/Cohee1207/SillyTavern -b dev`
|
||||
|
||||
7. Once everything is cloned, double click `Start.bat` to make NodeJS install its requirements.
|
||||
8. The server will then start, and SillyTavern will popup in your browser.
|
||||
@@ -183,13 +183,23 @@ However, it can be used to allow remote connections from anywhere as well.
|
||||
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
|
||||
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
|
||||
|
||||
*IP ranges are not accepted. Each IP must be listed individually like this:*
|
||||
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
|
||||
|
||||
```txt
|
||||
192.168.0.1
|
||||
192.168.0.2
|
||||
192.168.0.3
|
||||
192.168.0.4
|
||||
192.168.0.20
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```txt
|
||||
192.168.0.*
|
||||
```
|
||||
|
||||
(the above wildcard IP range will allow any device on the local network to connect)
|
||||
|
||||
CIDR masks are also accepted (eg. 10.0.0.0/24).
|
||||
|
||||
* Save the `whitelist.txt` file.
|
||||
* Restart your TAI server.
|
||||
|
||||
@@ -199,9 +209,9 @@ Now devices which have the IP specified in the file will be able to connect.
|
||||
|
||||
### 2. Connecting to ST from a remote device
|
||||
|
||||
After the whitelist has been setup, to connect over wifi you'll need the IP of the ST-hosting device.
|
||||
After the whitelist has been setup, to connect over wifi you'll need the IP of the ST-hosting device.
|
||||
|
||||
If the ST-hosting device is on the same wifi network, you will point your remote device's browser to the ST-host's internal wifi IP:
|
||||
If the ST-hosting device is on the same wifi network, you will point your remote device's browser to the ST-host's internal wifi IP:
|
||||
|
||||
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
|
||||
|
||||
@@ -213,9 +223,9 @@ While using the ST-hosting device, access [this page](https://whatismyipaddress.
|
||||
|
||||
We do not reccomend doing this, but you can open `config.conf` and change `whitelist` to `false`.
|
||||
|
||||
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
|
||||
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
|
||||
|
||||
This is usually an insecure practice, so we require you to set a username and password when you do this.
|
||||
This is usually an insecure practice, so we require you to set a username and password when you do this.
|
||||
|
||||
The username and password are set in `config.conf`.
|
||||
|
||||
@@ -276,5 +286,6 @@ GNU Affero General Public License for more details.**
|
||||
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
|
||||
* Noto Sans font by Google (OFL license)
|
||||
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
|
||||
* Linux startup script by AlpinDale
|
||||
* Thanks paniphons for providing a FAQ document
|
||||
|
242
server.js
242
server.js
@@ -20,7 +20,10 @@ const cliArguments = yargs(hideBin(process.argv))
|
||||
}).argv;
|
||||
|
||||
// change all relative paths
|
||||
process.chdir(__dirname)
|
||||
const path = require('path');
|
||||
const directory = process.pkg ? path.dirname(process.execPath) : __dirname;
|
||||
console.log(process.pkg ? 'Running from binary' : 'Running from source');
|
||||
process.chdir(directory);
|
||||
|
||||
const express = require('express');
|
||||
const compression = require('compression');
|
||||
@@ -42,7 +45,7 @@ const encode = require('png-chunks-encode');
|
||||
const PNGtext = require('png-chunk-text');
|
||||
|
||||
const jimp = require('jimp');
|
||||
const path = require('path');
|
||||
//const path = require('path');
|
||||
const sanitize = require('sanitize-filename');
|
||||
const mime = require('mime-types');
|
||||
|
||||
@@ -61,11 +64,11 @@ const utf8Decode = new TextDecoder('utf-8', { ignoreBOM: true });
|
||||
const commandExistsSync = require('command-exists').sync;
|
||||
|
||||
const characterCardParser = require('./src/character-card-parser.js');
|
||||
const config = require(path.join(process.cwd(), './config.conf'));
|
||||
|
||||
const config = require(path.join(__dirname, './config.conf'));
|
||||
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
|
||||
|
||||
const whitelistPath = path.join(__dirname, "./whitelist.txt");
|
||||
const whitelistPath = path.join(process.cwd(), "./whitelist.txt");
|
||||
let whitelist = config.whitelist;
|
||||
|
||||
if (fs.existsSync(whitelistPath)) {
|
||||
@@ -84,6 +87,11 @@ const allowKeysExposure = config.allowKeysExposure;
|
||||
const axios = require('axios');
|
||||
const tiktoken = require('@dqbd/tiktoken');
|
||||
const WebSocket = require('ws');
|
||||
const AIHorde = require("./src/horde");
|
||||
const ai_horde = new AIHorde({
|
||||
client_agent: getVersion()?.agent || 'SillyTavern:UNKNOWN:Cohee#1207',
|
||||
});
|
||||
const ipMatching = require('ip-matching');
|
||||
|
||||
var Client = require('node-rest-client').Client;
|
||||
var client = new Client();
|
||||
@@ -241,7 +249,7 @@ app.use(function (req, res, next) { //Security
|
||||
}
|
||||
|
||||
//clientIp = req.connection.remoteAddress.split(':').pop();
|
||||
if (whitelistMode === true && !whitelist.includes(clientIp)) {
|
||||
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
|
||||
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
|
||||
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
|
||||
}
|
||||
@@ -256,7 +264,7 @@ app.use((req, res, next) => {
|
||||
console.log(filePath);
|
||||
fs.access(filePath, fs.constants.R_OK, (err) => {
|
||||
if (!err) {
|
||||
res.sendFile(filePath, { root: __dirname });
|
||||
res.sendFile(filePath, { root: process.cwd() });
|
||||
} else {
|
||||
res.send('Character not found: ' + filePath);
|
||||
//next();
|
||||
@@ -267,10 +275,10 @@ app.use((req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static(__dirname + "/public", { refresh: true }));
|
||||
app.use(express.static(process.cwd() + "/public", { refresh: true }));
|
||||
|
||||
app.use('/backgrounds', (req, res) => {
|
||||
const filePath = decodeURIComponent(path.join(__dirname, 'public/backgrounds', req.url.replace(/%20/g, ' ')));
|
||||
const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' ')));
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
res.status(404).send('File not found');
|
||||
@@ -282,7 +290,7 @@ app.use('/backgrounds', (req, res) => {
|
||||
});
|
||||
|
||||
app.use('/characters', (req, res) => {
|
||||
const filePath = decodeURIComponent(path.join(__dirname, charactersPath, req.url.replace(/%20/g, ' ')));
|
||||
const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' ')));
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
res.status(404).send('File not found');
|
||||
@@ -293,16 +301,16 @@ app.use('/characters', (req, res) => {
|
||||
});
|
||||
app.use(multer({ dest: "uploads" }).single("avatar"));
|
||||
app.get("/", function (request, response) {
|
||||
response.sendFile(__dirname + "/public/index.html");
|
||||
response.sendFile(process.cwd() + "/public/index.html");
|
||||
});
|
||||
app.get("/notes/*", function (request, response) {
|
||||
response.sendFile(__dirname + "/public" + request.url + ".html");
|
||||
response.sendFile(process.cwd() + "/public" + request.url + ".html");
|
||||
});
|
||||
app.get('/get_faq', function (_, response) {
|
||||
response.sendFile(__dirname + "/faq.md");
|
||||
response.sendFile(process.cwd() + "/faq.md");
|
||||
});
|
||||
app.get('/get_readme', function (_, response) {
|
||||
response.sendFile(__dirname + "/readme.md");
|
||||
response.sendFile(process.cwd() + "/readme.md");
|
||||
});
|
||||
app.get('/deviceinfo', function (request, response) {
|
||||
const userAgent = request.header('user-agent');
|
||||
@@ -311,27 +319,8 @@ app.get('/deviceinfo', function (request, response) {
|
||||
return response.send(deviceInfo);
|
||||
});
|
||||
app.get('/version', function (_, response) {
|
||||
let pkgVersion, gitRevision, gitBranch;
|
||||
try {
|
||||
const pkgJson = require('./package.json');
|
||||
pkgVersion = pkgJson.version;
|
||||
if (commandExistsSync('git')) {
|
||||
gitRevision = require('child_process')
|
||||
.execSync('git rev-parse --short HEAD', { cwd: __dirname })
|
||||
.toString().trim();
|
||||
|
||||
gitBranch = require('child_process')
|
||||
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: __dirname })
|
||||
.toString().trim();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// suppress exception
|
||||
}
|
||||
finally {
|
||||
const agent = `SillyTavern:${gitRevision || pkgVersion}:Cohee#1207`;
|
||||
response.send({ agent, pkgVersion, gitRevision, gitBranch });
|
||||
}
|
||||
const data = getVersion();
|
||||
response.send(data);
|
||||
})
|
||||
|
||||
//**************Kobold api
|
||||
@@ -530,7 +519,7 @@ app.post("/savechat", jsonParser, function (request, response) {
|
||||
var dir_name = String(request.body.avatar_url).replace('.png', '');
|
||||
let chat_data = request.body.chat;
|
||||
let jsonlData = chat_data.map(JSON.stringify).join('\n');
|
||||
fs.writeFile(chatsPath + dir_name + "/" + request.body.file_name + '.jsonl', jsonlData, 'utf8', function (err) {
|
||||
fs.writeFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, jsonlData, 'utf8', function (err) {
|
||||
if (err) {
|
||||
response.send(err);
|
||||
return console.log(err);
|
||||
@@ -554,11 +543,10 @@ app.post("/getchat", jsonParser, function (request, response) {
|
||||
|
||||
if (err === null) { //if there is a dir, then read the requested file from the JSON call
|
||||
|
||||
fs.stat(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", function (err, stat) {
|
||||
|
||||
fs.stat(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, function (err, stat) {
|
||||
if (err === null) { //if no error (the file exists), read the file
|
||||
if (stat !== undefined) {
|
||||
fs.readFile(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", 'utf8', (err, data) => {
|
||||
fs.readFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
response.send(err);
|
||||
@@ -587,9 +575,8 @@ app.post("/getchat", jsonParser, function (request, response) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
app.post("/getstatus", jsonParser, async function (request, response_getstatus = response) {
|
||||
if (!request.body) return response_getstatus.sendStatus(400);
|
||||
api_server = request.body.api_server;
|
||||
@@ -669,6 +656,31 @@ app.post("/setsoftprompt", jsonParser, async function (request, response) {
|
||||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
function getVersion() {
|
||||
let pkgVersion = 'UNKNOWN';
|
||||
let gitRevision = null;
|
||||
let gitBranch = null;
|
||||
try {
|
||||
const pkgJson = require('./package.json');
|
||||
pkgVersion = pkgJson.version;
|
||||
if (!process.pkg && commandExistsSync('git')) {
|
||||
gitRevision = require('child_process')
|
||||
.execSync('git rev-parse --short HEAD', { cwd: process.cwd() })
|
||||
.toString().trim();
|
||||
|
||||
gitBranch = require('child_process')
|
||||
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: process.cwd() })
|
||||
.toString().trim();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// suppress exception
|
||||
}
|
||||
|
||||
const agent = `SillyTavern:${pkgVersion}:Cohee#1207`;
|
||||
return { agent, pkgVersion, gitRevision, gitBranch };
|
||||
}
|
||||
|
||||
function tryParse(str) {
|
||||
try {
|
||||
return json5.parse(str);
|
||||
@@ -859,18 +871,11 @@ app.post("/deletecharacter", urlencodedParser, function (request, response) {
|
||||
async function charaWrite(img_url, data, target_img, response = undefined, mes = 'ok', crop = undefined) {
|
||||
try {
|
||||
// Read the image, resize, and save it as a PNG into the buffer
|
||||
let rawImg = await jimp.read(img_url);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
|
||||
const image = await tryReadImage(img_url, crop);
|
||||
|
||||
// Get the chunks
|
||||
const chunks = extract(image);
|
||||
const tEXtChunks = chunks.filter(chunk => chunk.create_date === 'tEXt');
|
||||
const tEXtChunks = chunks.filter(chunk => chunk.create_date === 'tEXt' || chunk.name === 'tEXt');
|
||||
|
||||
// Remove all existing tEXt chunks
|
||||
for (let tEXtChunk of tEXtChunks) {
|
||||
@@ -884,8 +889,6 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
|
||||
fs.writeFileSync(charactersPath + target_img + '.png', new Buffer.from(encode(chunks)));
|
||||
if (response !== undefined) response.send(mes);
|
||||
return true;
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (response !== undefined) response.status(500).send(err);
|
||||
@@ -893,6 +896,24 @@ 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
|
||||
return image;
|
||||
}
|
||||
// If it's an unsupported type of image (APNG) - just read the file as buffer
|
||||
catch {
|
||||
return fs.readFileSync(img_url);
|
||||
}
|
||||
}
|
||||
|
||||
async function charaRead(img_url, input_format) {
|
||||
return characterCardParser.parse(img_url, input_format);
|
||||
}
|
||||
@@ -1229,7 +1250,7 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod
|
||||
.filter(x => path.parse(x).ext == '.json')
|
||||
.sort();
|
||||
|
||||
instructFiles.forEach(item => {
|
||||
instructFiles.forEach(item => {
|
||||
const file = fs.readFileSync(
|
||||
path.join(directories.instruct, item),
|
||||
'utf-8',
|
||||
@@ -1590,7 +1611,7 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
}
|
||||
catch {
|
||||
console.error('WEBP image conversion failed. Using the default character image.');
|
||||
uploadPath = defaultAvatarPath;
|
||||
uploadPath = defaultAvatarPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1633,7 +1654,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
|
||||
switch (request.body.format) {
|
||||
case 'png':
|
||||
return response.sendFile(filename, { root: __dirname });
|
||||
return response.sendFile(filename, { root: process.cwd() });
|
||||
case 'json': {
|
||||
try {
|
||||
let json = await charaRead(filename);
|
||||
@@ -1663,7 +1684,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
await webp.cwebp(filename, inputWebpPath, '-q 95');
|
||||
await webp.webpmux_add(inputWebpPath, outputWebpPath, metadataPath, 'exif');
|
||||
|
||||
response.sendFile(outputWebpPath, { root: __dirname }, () => {
|
||||
response.sendFile(outputWebpPath, { root: process.cwd() }, () => {
|
||||
fs.rmSync(inputWebpPath);
|
||||
fs.rmSync(metadataPath);
|
||||
fs.rmSync(outputWebpPath);
|
||||
@@ -1681,6 +1702,17 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
return response.sendStatus(400);
|
||||
});
|
||||
|
||||
app.post("/importgroupchat", urlencodedParser, function (request, response) {
|
||||
try {
|
||||
const filedata = request.file;
|
||||
const chatname = humanizedISO8601DateTime();
|
||||
fs.copyFileSync(`./uploads/${filedata.filename}`, (`${directories.groupChats}/${chatname}.jsonl`));
|
||||
return response.send({ res: chatname });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.send({ error: true });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
@@ -1690,9 +1722,8 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let avatar_url = (request.body.avatar_url).replace('.png', '');
|
||||
let ch_name = request.body.character_name;
|
||||
if (filedata) {
|
||||
|
||||
if (format === 'json') {
|
||||
fs.readFile('./uploads/' + filedata.filename, 'utf8', (err, data) => {
|
||||
fs.readFile(`./uploads/${filedata.filename}`, 'utf8', (err, data) => {
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
@@ -1709,7 +1740,6 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
user_name: 'You',
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
|
||||
},
|
||||
...history.msgs.map(
|
||||
(message) => ({
|
||||
@@ -1730,7 +1760,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
|
||||
const errors = [];
|
||||
newChats.forEach(chat => fs.writeFile(
|
||||
chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
|
||||
`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`,
|
||||
chat.map(JSON.stringify).join('\n'),
|
||||
'utf8',
|
||||
(err) => err ?? errors.push(err)
|
||||
@@ -1759,8 +1789,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let jsonData = json5.parse(line);
|
||||
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
//console.log(humanizedISO8601DateTime()+':/importchat copying chat as '+ch_name+' - '+humanizedISO8601DateTime()+'.jsonl');
|
||||
fs.copyFile('./uploads/' + filedata.filename, chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + '.jsonl', (err) => { //added character name and replaced Date.now() with humanizedISO8601DateTime
|
||||
fs.copyFile(`./uploads/${filedata.filename}`, (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
if (err) {
|
||||
response.send({ error: true });
|
||||
return console.log(err);
|
||||
@@ -1776,9 +1805,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
rl.close();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||
@@ -2278,11 +2305,20 @@ async function generateThumbnail(type, file) {
|
||||
const mySize = imageSizes[type];
|
||||
|
||||
try {
|
||||
const image = await jimp.read(pathToOriginalFile);
|
||||
const buffer = await image.cover(mySize[0], mySize[1]).quality(95).getBufferAsync(mime.lookup('jpg'));
|
||||
let buffer;
|
||||
|
||||
try {
|
||||
const image = await jimp.read(pathToOriginalFile);
|
||||
buffer = await image.cover(mySize[0], mySize[1]).quality(95).getBufferAsync(mime.lookup('jpg'));
|
||||
}
|
||||
catch (inner) {
|
||||
console.warn(`Thumbnailer can not process the image: ${pathToOriginalFile}. Using original size`);
|
||||
buffer = fs.readFileSync(pathToOriginalFile);
|
||||
}
|
||||
|
||||
fs.writeFileSync(pathToCachedFile, buffer);
|
||||
}
|
||||
catch (err) {
|
||||
catch (outer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2308,7 +2344,7 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
|
||||
|
||||
if (config.disableThumbnails == true) {
|
||||
const pathToOriginalFile = path.join(getOriginalFolder(type), file);
|
||||
return response.sendFile(pathToOriginalFile, { root: __dirname });
|
||||
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
|
||||
}
|
||||
|
||||
const pathToCachedFile = await generateThumbnail(type, file);
|
||||
@@ -2317,7 +2353,7 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
return response.sendFile(pathToCachedFile, { root: __dirname });
|
||||
return response.sendFile(pathToCachedFile, { root: process.cwd() });
|
||||
});
|
||||
|
||||
/* OpenAI */
|
||||
@@ -2502,7 +2538,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
response_generate_openai.send({ error: true });
|
||||
} else if (response.status == 429) {
|
||||
console.log('Out of quota');
|
||||
response_generate_openai.send({ error: true, quota_error: true, });
|
||||
const quota_error = response?.data?.type === 'insufficient_quota';
|
||||
response_generate_openai.send({ error: true, quota_error, });
|
||||
} else if (response.status == 500 || response.status == 409 || response.status == 504) {
|
||||
if (request.body.stream) {
|
||||
response.data.on('data', chunk => {
|
||||
@@ -2525,7 +2562,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
}
|
||||
}
|
||||
try {
|
||||
const quota_error = error?.response?.status === 429;
|
||||
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';
|
||||
if (!response_generate_openai.headersSent) {
|
||||
response_generate_openai.send({ error: true, quota_error });
|
||||
}
|
||||
@@ -2805,7 +2842,7 @@ app.post('/writesecret', jsonParser, (request, response) => {
|
||||
const key = request.body.key;
|
||||
const value = request.body.value;
|
||||
|
||||
writeSecret(key,value);
|
||||
writeSecret(key, value);
|
||||
return response.send('ok');
|
||||
});
|
||||
|
||||
@@ -2830,8 +2867,9 @@ app.post('/readsecretstate', jsonParser, (_, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
const ANONYMOUS_KEY = "0000000000";
|
||||
|
||||
app.post('/generate_horde', jsonParser, async (request, response) => {
|
||||
const ANONYMOUS_KEY = "0000000000";
|
||||
const api_key_horde = readSecret(SECRET_KEYS.HORDE) || ANONYMOUS_KEY;
|
||||
const url = 'https://horde.koboldai.net/api/v2/generate/text/async';
|
||||
|
||||
@@ -2874,6 +2912,66 @@ app.post('/viewsecrets', jsonParser, async (_, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/horde_samplers', jsonParser, async (_, response) => {
|
||||
const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers);
|
||||
response.send(samplers);
|
||||
});
|
||||
|
||||
app.post('/horde_models', jsonParser, async (_, response) => {
|
||||
const models = await ai_horde.getModels();
|
||||
response.send(models);
|
||||
});
|
||||
|
||||
app.post('/horde_generateimage', jsonParser, async (request, response) => {
|
||||
const MAX_ATTEMPTS = 100;
|
||||
const CHECK_INTERVAL = 3000;
|
||||
const api_key_horde = readSecret(SECRET_KEYS.HORDE) || ANONYMOUS_KEY;
|
||||
console.log('Stable Horde request:', request.body);
|
||||
const generation = await ai_horde.postAsyncImageGenerate(
|
||||
{
|
||||
prompt: `${request.body.prompt_prefix} ${request.body.prompt} ### ${request.body.negative_prompt}`,
|
||||
params:
|
||||
{
|
||||
sampler_name: request.body.sampler,
|
||||
hires_fix: request.body.enable_hr,
|
||||
use_gfpgan: request.body.restore_faces,
|
||||
cfg_scale: request.body.scale,
|
||||
steps: request.body.steps,
|
||||
width: request.body.width,
|
||||
height: request.body.height,
|
||||
karras: Boolean(request.body.karras),
|
||||
n: 1,
|
||||
},
|
||||
r2: false,
|
||||
nsfw: request.body.nfsw,
|
||||
models: [request.body.model],
|
||||
},
|
||||
{ token: api_key_horde });
|
||||
|
||||
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||
await delay(CHECK_INTERVAL);
|
||||
const check = await ai_horde.getImageGenerationCheck(generation.id);
|
||||
console.log(check);
|
||||
|
||||
if (check.done) {
|
||||
const result = await ai_horde.getImageGenerationStatus(generation.id);
|
||||
return response.send(result.generations[0].img);
|
||||
}
|
||||
|
||||
/*
|
||||
if (!check.is_possible) {
|
||||
return response.sendStatus(503);
|
||||
}
|
||||
*/
|
||||
|
||||
if (check.faulted) {
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
return response.sendStatus(504);
|
||||
});
|
||||
|
||||
function writeSecret(key, value) {
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
const emptyFile = JSON.stringify({});
|
||||
|
21
src/horde/LICENSE.md
Normal file
21
src/horde/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ZeldaFan0225
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
2189
src/horde/index.d.ts
vendored
Normal file
2189
src/horde/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
src/horde/index.js
Normal file
1
src/horde/index.js
Normal file
File diff suppressed because one or more lines are too long
3
src/horde/index.mjs
Normal file
3
src/horde/index.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import AIHorde from './index.js'
|
||||
export default AIHorde
|
||||
export { AIHorde }
|
@@ -3,9 +3,11 @@
|
||||
* allow access to the endpoint after successful authentication.
|
||||
*/
|
||||
|
||||
const {dirname} = require('path');
|
||||
const appDir = dirname(require.main.filename);
|
||||
const config = require(appDir + '/config.conf');
|
||||
//const {dirname} = require('path');
|
||||
//const appDir = dirname(require.main.filename);
|
||||
//const config = require(appDir + '/config.conf');
|
||||
const path = require('path');
|
||||
const config = require(path.join(process.cwd(), './config.conf'));
|
||||
|
||||
const unauthorizedResponse = (res) => {
|
||||
res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
|
||||
@@ -36,4 +38,4 @@ const basicAuthMiddleware = function (request, response, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = basicAuthMiddleware;
|
||||
module.exports = basicAuthMiddleware;
|
||||
|
Reference in New Issue
Block a user