mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of github.com:Cohee1207/SillyTavern into dev
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
about: "Create a report to help us improve. PAY ATTENTION: Support requests for extenal programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
|
@ -16,6 +16,7 @@ Method 1 - GIT
|
||||
We always recommend users install using 'git'. Here's why:
|
||||
|
||||
When you have installed via `git clone`, all you have to do to update is type `git pull` in a command line in the ST folder.
|
||||
Alternatively, if the command prompt gives you problems (and you have GitHub Desktop installed), you can use the 'Repository' menu and select 'Pull'.
|
||||
The updates are applied automatically and safely.
|
||||
|
||||
Method 2 - ZIP
|
||||
|
@ -10,6 +10,19 @@
|
||||
"SillyTavern community Discord (support and discussion): https://discord.gg/RZdyAEUPvj"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title <-- Tap this if you run on Mobile { display-mode: \"form\" }\n",
|
||||
"#Taken from KoboldAI colab\n",
|
||||
"%%html\n",
|
||||
"<b>Press play on the audio player to keep the tab alive. (Uses only 13MB of data)</b><br/>\n",
|
||||
"<audio src=\"https://henk.tech/colabkobold/silence.m4a\" controls>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@ -42,9 +55,13 @@
|
||||
"#@markdown Enables Silero text-to-speech module\n",
|
||||
"extras_enable_sd = True #@param {type:\"boolean\"}\n",
|
||||
"#@markdown Enables SD picture generation\n",
|
||||
"SD_Model = \"ckpt/anything-v4.5-vae-swapped\" #@param [ \"ckpt/anything-v4.5-vae-swapped\", \"ckpt/sd15\" ]\n",
|
||||
"SD_Model = \"ckpt/anything-v4.5-vae-swapped\" #@param [ \"ckpt/anything-v4.5-vae-swapped\", \"hakurei/waifu-diffusion\", \"philz1337/clarity\", \"prompthero/openjourney\", \"ckpt/sd15\", \"stabilityai/stable-diffusion-2-1-base\" ]\n",
|
||||
"#@markdown * ckpt/anything-v4.5-vae-swapped - anime style model\n",
|
||||
"#@markdown * hakurei/waifu-diffusion - anime style model\n",
|
||||
"#@markdown * philz1337/clarity - realistic style model\n",
|
||||
"#@markdown * prompthero/openjourney - midjourney style model\n",
|
||||
"#@markdown * ckpt/sd15 - base SD 1.5\n",
|
||||
"#@markdown * stabilityai/stable-diffusion-2-1-base - base SD 2.1\n",
|
||||
"\n",
|
||||
"import subprocess\n",
|
||||
"\n",
|
||||
@ -78,6 +95,7 @@
|
||||
"%cd /\n",
|
||||
"!git clone https://github.com/Cohee1207/SillyTavern-extras\n",
|
||||
"%cd /SillyTavern-extras\n",
|
||||
"!git clone https://github.com/Cohee1207/tts_samples\n",
|
||||
"!npm install -g localtunnel\n",
|
||||
"!pip install -r requirements-complete.txt\n",
|
||||
"!pip install tensorflow==2.11\n",
|
||||
|
12
config.conf
12
config.conf
@ -8,7 +8,17 @@ const disableThumbnails = false; //Disables the generation of thumbnails, opting
|
||||
const autorun = true; //Autorun in the browser. true/false
|
||||
const enableExtensions = true; //Enables support for TavernAI-extras project
|
||||
const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine.
|
||||
const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend.
|
||||
|
||||
module.exports = {
|
||||
port, whitelist, whitelistMode, basicAuthMode, basicAuthUser, autorun, enableExtensions, listen, disableThumbnails
|
||||
port,
|
||||
whitelist,
|
||||
whitelistMode,
|
||||
basicAuthMode,
|
||||
basicAuthUser,
|
||||
autorun,
|
||||
enableExtensions,
|
||||
listen,
|
||||
disableThumbnails,
|
||||
allowKeysExposure,
|
||||
};
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
|
@ -40,7 +40,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
||||
},
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
|
@ -1005,10 +1005,14 @@
|
||||
Adjust response length to worker capabilities
|
||||
</label>
|
||||
<h4>API key</h4>
|
||||
<h5>Get it here: <a target="_blank" href="https://horde.koboldai.net/register">Register</a>
|
||||
<h5>Get it here: <a target="_blank" href="https://horde.koboldai.net/register">Register</a><br>
|
||||
Enter <span class="monospace">0000000000</span> to use anonymous mode.
|
||||
</h5>
|
||||
<input id="horde_api_key" name="horde_api_key" class="text_pole" maxlength="500" type="text" placeholder="0000000000">
|
||||
<div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div>
|
||||
<div class="flex-container">
|
||||
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000">
|
||||
<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>
|
||||
<h4 class="horde_model_title">
|
||||
Model
|
||||
<div id="horde_refresh" title="Refresh models" class="right_menu_button">
|
||||
@ -1038,8 +1042,11 @@
|
||||
<li>Enter it in the box below:</li>
|
||||
</ol>
|
||||
</span>
|
||||
<input id="api_key_novel" name="api_key_novel" class="text_pole" maxlength="500" size="35" type="text">
|
||||
<div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1" maxlength="500" size="35" type="text">
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_novel"></div>
|
||||
</div>
|
||||
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
|
||||
<input id="api_button_novel" class="menu_button" type="submit" value="Connect">
|
||||
<div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
<h4>Novel AI Model
|
||||
@ -1092,8 +1099,11 @@
|
||||
<li>Enter it in the box below:</li>
|
||||
</ol>
|
||||
</span>
|
||||
<input id="api_key_openai" name="api_key_openai" class="text_pole" maxlength="500" value="" type="text">
|
||||
<div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text">
|
||||
<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>
|
||||
<input id="api_button_openai" class="menu_button" type="submit" value="Connect">
|
||||
<div id="api_loading_openai" class=" api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||
</form>
|
||||
@ -1129,8 +1139,11 @@
|
||||
</ol>
|
||||
</span>
|
||||
<div class="widthFreeExpand">
|
||||
<input id="poe_token" class="text_pole" type="text" maxlength="100" />
|
||||
<div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div>
|
||||
<div class="flex-container">
|
||||
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<input id="poe_connect" class="menu_button" type="button" value="Connect" />
|
||||
@ -1152,9 +1165,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" />
|
||||
Auto-connect to Last Server
|
||||
</label>
|
||||
<div class="flex-container alignitemscenter spaceBetween wide100p">
|
||||
<label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" />
|
||||
Auto-connect to Last Server
|
||||
</label>
|
||||
<a id="viewSecrets" href="javascript:void(0);">View hidden API keys</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1440,7 +1456,10 @@
|
||||
<div class="drawer-icon fa-solid fa-face-smile closedIcon" title="User Settings"></div>
|
||||
</div>
|
||||
<div id="user-settings-block" class="drawer-content closedDrawer">
|
||||
<h3>User Settings</h3>
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||
<h3>User Settings</h3>
|
||||
<div id="version_display"></div>
|
||||
</div>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div name="UI Customization" class="flex-container drawer25pWidth">
|
||||
<div class="ui-settings">
|
||||
@ -2027,7 +2046,7 @@
|
||||
<hr>
|
||||
<h4>Personality summary</h4>
|
||||
<h5>A brief description of the personality <a href="/notes#personalitysummary" class="notes-link" target="_blank"><span class="note-link-span">?</span></a></h5>
|
||||
<textarea id="personality_textarea" name="personality" placeholder="" form="form_create" class="text_pole" autocomplete="off" rows="2"></textarea>
|
||||
<textarea id="personality_textarea" name="personality" placeholder="" form="form_create" class="text_pole" autocomplete="off" rows="2" maxlength="20000"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="scenario_div">
|
||||
@ -2037,7 +2056,7 @@
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</h5>
|
||||
<textarea id="scenario_pole" name="scenario" class="text_pole" maxlength="9999" value="" autocomplete="off" form="form_create" rows="2"></textarea>
|
||||
<textarea id="scenario_pole" name="scenario" class="text_pole" maxlength="20000" value="" autocomplete="off" form="form_create" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="talkativeness_div">
|
||||
@ -2057,7 +2076,7 @@
|
||||
<h4>Examples of dialogue</h4>
|
||||
<h5>Forms a personality more clearly <a href="/notes#examplesofdialogue" class="notes-link" target="_blank"><span class="note-link-span">?</span></a></h5>
|
||||
</div>
|
||||
<textarea id="mes_example_textarea" name="mes_example" placeholder="" form="form_create"></textarea>
|
||||
<textarea id="mes_example_textarea" name="mes_example" placeholder="" form="form_create" maxlength="20000"></textarea>
|
||||
</div>
|
||||
<div id="character_popup_ok" class="menu_button">Save</div>
|
||||
|
||||
@ -2322,6 +2341,7 @@
|
||||
<span class="name_text">${characterName}</span>
|
||||
|
||||
<div class="mes_buttons">
|
||||
<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>
|
||||
@ -2427,7 +2447,7 @@
|
||||
<div id="loading_mes">
|
||||
<div alt="" class="fa-solid fa-hourglass-half"></div>
|
||||
</div>
|
||||
<div id="send_but" class="fa-solid fa-feather-pointed"></div>
|
||||
<div id="send_but" class="fa-solid fa-feather-pointed" title="Send a message"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Alpaca",
|
||||
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "Koala",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "GPT: ",
|
||||
"wrap": true
|
||||
"wrap": false
|
||||
}
|
9
public/instruct/Vicuna 1.0.json
Normal file
9
public/instruct/Vicuna 1.0.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Vicuna 1.0",
|
||||
"system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Human:",
|
||||
"output_sequence": "### Assistant:",
|
||||
"wrap": true
|
||||
}
|
9
public/instruct/Vicuna 1.1.json
Normal file
9
public/instruct/Vicuna 1.1.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Vicuna 1.1",
|
||||
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"wrap": true
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "WizardLM",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
|
@ -416,7 +416,53 @@ _When using Pygmalion models these anchors are automatically disabled, since Pyg
|
||||
|
||||
## Instruct Mode
|
||||
|
||||
_This section is under construction. Please check later._
|
||||
Instruct Mode allows you to adjust the prompting for instruction-following models, such as Alpaca, Metharme, WizardLM, etc.
|
||||
|
||||
**This is not supported for OpenAI API.**
|
||||
|
||||
### Instruct Mode Settings
|
||||
|
||||
#### System Prompt
|
||||
|
||||
Added to the beginning of each prompt. Should define the instructions for the model to follow.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
Write one reply in internet RP style for {{char}}. Be verbose and creative.
|
||||
```
|
||||
|
||||
#### Presets
|
||||
|
||||
Provides ready-made presets with prompts and sequences for some well-known instruct models.
|
||||
|
||||
*Changing a preset resets your system prompt to default!*
|
||||
|
||||
#### Input Sequence
|
||||
|
||||
Text added before the user's input.
|
||||
|
||||
#### Output Sequence
|
||||
|
||||
Text added before the character's reply.
|
||||
|
||||
#### System Sequence
|
||||
|
||||
Text added before the system prompt.
|
||||
|
||||
#### Stop Sequence
|
||||
|
||||
Text that denotes the end of the reply. Will be trimmed from the output text.
|
||||
|
||||
#### Include Names
|
||||
|
||||
If enabled, prepend character and user names to chat history logs after inserting the sequences.
|
||||
|
||||
*Always enabled for group chats!*
|
||||
|
||||
#### Wrap Sequences with Newline
|
||||
|
||||
Each sequence text will be wrapped with newline characters when inserted to the prompt. Required for Alpaca and its derivatives.
|
||||
|
||||
## Chat import
|
||||
|
||||
|
@ -22,6 +22,7 @@ You definitely installed via git, so just 'git pull' inside the SillyTavern dire
|
||||
We always recommend users install using 'git'. Here's why:
|
||||
|
||||
When you have installed via 'git clone', all you have to do to update is type 'git pull' in a command line in the ST folder.
|
||||
Alternatively, if the command prompt gives you problems (and you have GitHub Desktop installed), you can use the 'Repository' menu and select 'Pull'.
|
||||
The updates are applied automatically and safely.
|
||||
|
||||
### Method 2 - ZIP
|
||||
|
386
public/script.js
386
public/script.js
@ -107,7 +107,7 @@ import {
|
||||
} from "./scripts/poe.js";
|
||||
|
||||
import { debounce, delay, restoreCaretPosition, saveCaretPosition } from "./scripts/utils.js";
|
||||
import { extension_settings, loadExtensionSettings } from "./scripts/extensions.js";
|
||||
import { extension_settings, getContext, loadExtensionSettings } from "./scripts/extensions.js";
|
||||
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js";
|
||||
import {
|
||||
tag_map,
|
||||
@ -203,6 +203,9 @@ hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.in
|
||||
let converter;
|
||||
reloadMarkdownProcessor();
|
||||
|
||||
// array for prompt token calculations
|
||||
let itemizedPrompts = [];
|
||||
|
||||
/* let bg_menu_toggle = false; */
|
||||
export const systemUserName = "SillyTavern System";
|
||||
let default_user_name = "You";
|
||||
@ -283,9 +286,8 @@ const system_messages = {
|
||||
mes: [
|
||||
'Hi there! The following chat formatting commands are supported:',
|
||||
'<ol>',
|
||||
'<li><tt>*text*</tt> – format the actions that your character does</li>',
|
||||
'<li><tt>{{text}}</tt> – set the behavioral bias for the AI character</li>',
|
||||
'<li><tt>{{}}</tt> – cancel a previously set bias</li>',
|
||||
'<li><tt>{{text}}</tt> – sets a permanent behavioral bias for the AI</li>',
|
||||
'<li><tt>{{}}</tt> – removes any active character bias</li>',
|
||||
'</ol>',
|
||||
].join('')
|
||||
},
|
||||
@ -296,22 +298,24 @@ const system_messages = {
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
mes: [
|
||||
'<h2>Welcome to SillyTavern!</h2>',
|
||||
'<h2>Welcome to <span id="version_display_welcome">SillyTavern</span>!</h2>',
|
||||
'<div id="version_display_welcome"></div>',
|
||||
'<h3>Want to Update to the latest version?</h3>',
|
||||
"Read the <a href='/notes/update.html' target='_blank'>instructions here</a>. Also located in your installation's base folder",
|
||||
'<hr class="sysHR">',
|
||||
'<h3>In order to begin chatting:</h3>',
|
||||
'<ol>',
|
||||
'<li>Connect to one of the supported generation APIs (the plug icon)</li>',
|
||||
'<li>Create or pick a character from the list (the top-right namecard icon)</li>',
|
||||
'</ol>',
|
||||
"<h3>Running on Colab and can't get an answer from the AI or getting Out of Memory errors?</h3>",
|
||||
'Set a lower Context Size in AI generation settings (leftmost icon).<br>Values in range of 1400-1600 Tokens would be the safest choice.',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Where to download more characters?</h3>',
|
||||
'<i>(Not endorsed, your discretion is advised)</i>',
|
||||
'<ol>',
|
||||
'<li><a target="_blank" href="https://discord.gg/pygmalionai">Pygmalion AI Discord</a></li>',
|
||||
'<li><a target="_blank" href="https://www.characterhub.org/">CharacterHub (NSFW)</a></li>',
|
||||
'</ol>',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Where can I get help?</h3>',
|
||||
'Before going any further, check out the following resources:',
|
||||
'<ol>',
|
||||
@ -322,6 +326,7 @@ const system_messages = {
|
||||
'<li><a target="_blank" href="https://docs.alpindale.dev/">Pygmalion AI Docs</a></li>',
|
||||
'</ol>',
|
||||
'Type <tt>/?</tt> in any chat to get help on message formatting commands.',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Still have questions or suggestions left?</h3>',
|
||||
'<a target="_blank" href="https://discord.gg/RZdyAEUPvj">SillyTavern Community Discord</a>',
|
||||
'<br/>',
|
||||
@ -386,7 +391,16 @@ $(document).ajaxError(function myErrorHandler(_, xhr) {
|
||||
async function getClientVersion() {
|
||||
try {
|
||||
const response = await fetch('/version');
|
||||
CLIENT_VERSION = await response.text();
|
||||
const data = await response.json();
|
||||
CLIENT_VERSION = data.agent;
|
||||
let displayVersion = `SillyTavern ${data.pkgVersion}`;
|
||||
|
||||
if (data.gitRevision && data.gitBranch) {
|
||||
displayVersion += ` '${data.gitBranch}' (${data.gitRevision})`;
|
||||
}
|
||||
|
||||
$('#version_display').text(displayVersion);
|
||||
$('#version_display_welcome').text(displayVersion);
|
||||
} catch (err) {
|
||||
console.log("Couldn't get client version", err);
|
||||
}
|
||||
@ -551,13 +565,13 @@ $.ajaxPrefilter((options, originalOptions, xhr) => {
|
||||
///// initialization protocol ////////
|
||||
$.get("/csrf-token").then(async (data) => {
|
||||
token = data.token;
|
||||
sendSystemMessage(system_message_types.WELCOME);
|
||||
await readSecretState();
|
||||
await getClientVersion();
|
||||
await getSettings("def");
|
||||
await getUserAvatars();
|
||||
await getCharacters();
|
||||
await getBackgrounds();
|
||||
await getUserAvatars();
|
||||
sendSystemMessage(system_message_types.WELCOME);
|
||||
});
|
||||
|
||||
function checkOnlineStatus() {
|
||||
@ -751,8 +765,8 @@ function printCharacters() {
|
||||
|
||||
printTags();
|
||||
printGroups();
|
||||
favsToHotswap();
|
||||
sortCharactersList();
|
||||
favsToHotswap();
|
||||
}
|
||||
|
||||
async function getCharacters() {
|
||||
@ -1115,6 +1129,27 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
|
||||
|
||||
if (isSystem) {
|
||||
newMessage.find(".mes_edit").hide();
|
||||
newMessage.find(".mes_prompt").hide(); //dont'd need prompt display for sys messages
|
||||
}
|
||||
|
||||
// don't need prompt butons for user messages
|
||||
if (params.isUser === true) {
|
||||
newMessage.find(".mes_prompt").hide();
|
||||
}
|
||||
|
||||
//shows or hides the Prompt display button
|
||||
let mesIdToFind = Number(newMessage.attr('mesId'));
|
||||
if (itemizedPrompts.length !== 0) {
|
||||
for (var i = 0; i < itemizedPrompts.length; i++) {
|
||||
if (itemizedPrompts[i].mesId === mesIdToFind) {
|
||||
newMessage.find(".mes_prompt").show();
|
||||
} else {
|
||||
console.log('no cache found for mesID, hiding prompt button and continuing search');
|
||||
newMessage.find(".mes_prompt").hide();
|
||||
}
|
||||
}
|
||||
} else { //hide all when prompt cache is empty
|
||||
$(".mes_prompt").hide();
|
||||
}
|
||||
|
||||
newMessage.find('.avatar img').on('error', function () {
|
||||
@ -1327,11 +1362,13 @@ function cleanGroupMessage(getMessage) {
|
||||
}
|
||||
|
||||
function getAllExtensionPrompts() {
|
||||
return substituteParams(Object
|
||||
const value = Object
|
||||
.values(extension_prompts)
|
||||
.filter(x => x.value)
|
||||
.map(x => x.value.trim())
|
||||
.join('\n'));
|
||||
.join('\n');
|
||||
|
||||
return value.length ? substituteParams(value) : '';
|
||||
}
|
||||
|
||||
function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
|
||||
@ -1347,13 +1384,15 @@ function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
|
||||
if (extension_prompt.length && !extension_prompt.endsWith(separator)) {
|
||||
extension_prompt = extension_prompt + separator;
|
||||
}
|
||||
extension_prompt = substituteParams(extension_prompt);
|
||||
if (extension_prompt.length) {
|
||||
extension_prompt = substituteParams(extension_prompt);
|
||||
}
|
||||
return extension_prompt;
|
||||
}
|
||||
|
||||
function baseChatReplace(value, name1, name2) {
|
||||
if (value !== undefined && value.length > 0) {
|
||||
value = substituteParams(value, is_pygmalion ? "You:" : name1, name2);
|
||||
value = substituteParams(value, is_pygmalion ? "You" : name1, name2);
|
||||
|
||||
if (power_user.collapse_newlines) {
|
||||
value = collapseNewlines(value);
|
||||
@ -1370,9 +1409,10 @@ function appendToStoryString(value, prefix) {
|
||||
}
|
||||
|
||||
function isStreamingEnabled() {
|
||||
return (main_api == 'openai' && oai_settings.stream_openai)
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai)
|
||||
|| (main_api == 'poe' && poe_settings.streaming)
|
||||
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming);
|
||||
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
|
||||
&& !isMultigenEnabled(); // Multigen has a quasi-streaming mode which breaks the real streaming
|
||||
}
|
||||
|
||||
class StreamingProcessor {
|
||||
@ -1585,7 +1625,15 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
|
||||
const isImpersonate = type == "impersonate";
|
||||
const isInstruct = power_user.instruct.enabled;
|
||||
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
||||
|
||||
// Name for the multigen prefix
|
||||
const magName = isImpersonate ? (is_pygmalion ? 'You' : name1) : name2;
|
||||
|
||||
if (isInstruct) {
|
||||
message_already_generated = formatInstructModePrompt(magName, isImpersonate);
|
||||
} else {
|
||||
message_already_generated = `${magName}: `;
|
||||
}
|
||||
|
||||
const interruptedByCommand = processCommands($("#send_textarea").val(), type);
|
||||
|
||||
@ -1819,6 +1867,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
this_max_context = Number(max_context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Adjust token limit for Horde
|
||||
let adjustedParams;
|
||||
if (main_api == 'kobold' && horde_settings.use_horde && (horde_settings.auto_adjust_context_length || horde_settings.auto_adjust_response_length)) {
|
||||
@ -1834,11 +1884,12 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
// Extension added strings
|
||||
const allAnchors = getAllExtensionPrompts();
|
||||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
|
||||
let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
|
||||
|
||||
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
|
||||
|
||||
// hack for regeneration of the first message
|
||||
@ -2006,8 +2057,16 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
const isBottom = j === mesSend.length - 1;
|
||||
mesSendString += mesSend[j];
|
||||
|
||||
// Add quiet generation prompt at depth 0
|
||||
if (isBottom && quiet_prompt && quiet_prompt.length) {
|
||||
const name = is_pygmalion ? 'You' : name1;
|
||||
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, true) : `\n${name}: ${quiet_prompt}`;
|
||||
mesSendString += quietAppend;
|
||||
}
|
||||
|
||||
if (isInstruct && isBottom && tokens_already_generated === 0) {
|
||||
mesSendString += formatInstructModePrompt(isImpersonate);
|
||||
const name = isImpersonate ? (is_pygmalion ? 'You' : name1) : name2;
|
||||
mesSendString += formatInstructModePrompt(name, isImpersonate);
|
||||
}
|
||||
|
||||
if (!isInstruct && isImpersonate && isBottom && tokens_already_generated === 0) {
|
||||
@ -2084,7 +2143,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
mesSendString = '<START>\n' + mesSendString;
|
||||
//mesSendString = mesSendString; //This edit simply removes the first "<START>" that is prepended to all context prompts
|
||||
}
|
||||
let finalPromt = worldInfoBefore +
|
||||
let finalPromt =
|
||||
worldInfoBefore +
|
||||
storyString +
|
||||
worldInfoAfter +
|
||||
afterScenarioAnchor +
|
||||
@ -2093,6 +2153,33 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
generatedPromtCache +
|
||||
promptBias;
|
||||
|
||||
//set array object for prompt token itemization of this message
|
||||
let thisPromptBits = {
|
||||
mesId: count_view_mes,
|
||||
worldInfoBefore: worldInfoBefore,
|
||||
allAnchors: allAnchors,
|
||||
summarizeString: (extension_prompts['1_memory']?.value || ''),
|
||||
authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''),
|
||||
worldInfoString: worldInfoString,
|
||||
storyString: storyString,
|
||||
worldInfoAfter: worldInfoAfter,
|
||||
afterScenarioAnchor: afterScenarioAnchor,
|
||||
examplesString: examplesString,
|
||||
mesSendString: mesSendString,
|
||||
generatedPromtCache: generatedPromtCache,
|
||||
promptBias: promptBias,
|
||||
finalPromt: finalPromt,
|
||||
charDescription: charDescription,
|
||||
charPersonality: charPersonality,
|
||||
scenarioText: scenarioText,
|
||||
promptBias: promptBias,
|
||||
storyString: storyString,
|
||||
this_max_context: this_max_context,
|
||||
padding: power_user.token_padding
|
||||
}
|
||||
|
||||
itemizedPrompts.push(thisPromptBits);
|
||||
|
||||
if (zeroDepthAnchor && zeroDepthAnchor.length) {
|
||||
if (!isMultigenEnabled() || tokens_already_generated == 0) {
|
||||
const trimBothEnds = !force_name2 && !is_pygmalion;
|
||||
@ -2110,11 +2197,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
}
|
||||
|
||||
// Add quiet generation prompt at depth 0
|
||||
if (quiet_prompt && quiet_prompt.length) {
|
||||
finalPromt += `\n${quiet_prompt}`;
|
||||
}
|
||||
|
||||
finalPromt = finalPromt.replace(/\r/gm, '');
|
||||
|
||||
if (power_user.collapse_newlines) {
|
||||
@ -2229,11 +2311,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
hideSwipeButtons();
|
||||
let getMessage = await streamingProcessor.generate();
|
||||
|
||||
// Cohee: Basically a dead-end code... (disabled by isStreamingEnabled)
|
||||
// I wasn't able to get multigen working with real streaming
|
||||
// consistently without screwing the interim prompting
|
||||
if (isMultigenEnabled()) {
|
||||
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
|
||||
tokens_already_generated += this_amount_gen;
|
||||
message_already_generated += getMessage;
|
||||
promptBias = '';
|
||||
if (!streamingProcessor.isStopped && shouldContinueMultigen(getMessage)) {
|
||||
if (!streamingProcessor.isStopped && shouldContinueMultigen(getMessage, isImpersonate)) {
|
||||
streamingProcessor.isFinished = false;
|
||||
runGenerate(getMessage);
|
||||
console.log('returning to make generate again');
|
||||
@ -2265,16 +2350,23 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
|
||||
let this_mes_is_name;
|
||||
({ this_mes_is_name, getMessage } = extractNameFromMessage(getMessage, force_name2, isImpersonate));
|
||||
if (tokens_already_generated == 0) {
|
||||
console.log("New message");
|
||||
({ type, getMessage } = saveReply(type, getMessage, this_mes_is_name, title));
|
||||
}
|
||||
else {
|
||||
console.log("Should append message");
|
||||
({ type, getMessage } = saveReply('append', getMessage, this_mes_is_name, title));
|
||||
|
||||
if (!isImpersonate) {
|
||||
if (tokens_already_generated == 0) {
|
||||
console.log("New message");
|
||||
({ type, getMessage } = saveReply(type, getMessage, this_mes_is_name, title));
|
||||
}
|
||||
else {
|
||||
console.log("Should append message");
|
||||
({ type, getMessage } = saveReply('append', getMessage, this_mes_is_name, title));
|
||||
}
|
||||
} else {
|
||||
let chunk = cleanUpMessage(message_already_generated, true);
|
||||
let extract = extractNameFromMessage(chunk, force_name2, isImpersonate);
|
||||
$('#send_textarea').val(extract.getMessage).trigger('input');
|
||||
}
|
||||
|
||||
if (shouldContinueMultigen(getMessage)) {
|
||||
if (shouldContinueMultigen(getMessage, isImpersonate)) {
|
||||
hideSwipeButtons();
|
||||
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
|
||||
getMessage = message_already_generated;
|
||||
@ -2294,6 +2386,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
if (getMessage.length > 0) {
|
||||
if (isImpersonate) {
|
||||
$('#send_textarea').val(getMessage).trigger('input');
|
||||
generatedPromtCache = "";
|
||||
}
|
||||
else if (type == 'quiet') {
|
||||
resolve(getMessage);
|
||||
@ -2368,6 +2461,147 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
//console.log('generate ending');
|
||||
} //generate ends
|
||||
|
||||
function promptItemize(itemizedPrompts, requestedMesId) {
|
||||
let incomingMesId = Number(requestedMesId);
|
||||
let thisPromptSet = undefined;
|
||||
|
||||
for (var i = 0; i < itemizedPrompts.length; i++) {
|
||||
if (itemizedPrompts[i].mesId === incomingMesId) {
|
||||
thisPromptSet = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisPromptSet === undefined) {
|
||||
console.log(`couldnt find the right mesId. looked for ${incomingMesId}`);
|
||||
console.log(itemizedPrompts);
|
||||
return null;
|
||||
}
|
||||
|
||||
let finalPromptTokens = getTokenCount(itemizedPrompts[thisPromptSet].finalPromt);
|
||||
let allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors);
|
||||
let summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString);
|
||||
let authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString);
|
||||
let afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
|
||||
let zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
|
||||
let worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
|
||||
let storyStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].storyString);
|
||||
let examplesStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].examplesString);
|
||||
let charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
|
||||
let charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription);
|
||||
let scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText);
|
||||
let promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
|
||||
let mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString)
|
||||
let ActualChatHistoryTokens = mesSendStringTokens - allAnchorsTokens + power_user.token_padding;
|
||||
let thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context;
|
||||
let thisPrompt_padding = itemizedPrompts[thisPromptSet].padding;
|
||||
|
||||
let totalTokensInPrompt =
|
||||
storyStringTokens + //chardefs total
|
||||
worldInfoStringTokens +
|
||||
ActualChatHistoryTokens + //chat history
|
||||
allAnchorsTokens + // AN and/or legacy anchors
|
||||
//afterScenarioAnchorTokens + //only counts if AN is set to 'after scenario'
|
||||
//zeroDepthAnchorTokens + //same as above, even if AN not on 0 depth
|
||||
promptBiasTokens + //{{}}
|
||||
- thisPrompt_padding; //not sure this way of calculating is correct, but the math results in same value as 'finalPromt'
|
||||
|
||||
let storyStringTokensPercentage = ((storyStringTokens / (totalTokensInPrompt + thisPrompt_padding)) * 100).toFixed(2);
|
||||
let ActualChatHistoryTokensPercentage = ((ActualChatHistoryTokens / (totalTokensInPrompt + thisPrompt_padding)) * 100).toFixed(2);
|
||||
let promptBiasTokensPercentage = ((promptBiasTokens / (totalTokensInPrompt + thisPrompt_padding)) * 100).toFixed(2);
|
||||
let worldInfoStringTokensPercentage = ((worldInfoStringTokens / (totalTokensInPrompt + thisPrompt_padding)) * 100).toFixed(2);
|
||||
let allAnchorsTokensPercentage = ((allAnchorsTokens / (totalTokensInPrompt + thisPrompt_padding)) * 100).toFixed(2);
|
||||
let selectedTokenizer = $("#tokenizer").find(':selected').text();
|
||||
callPopup(
|
||||
`
|
||||
<h3>Prompt Itemization</h3>
|
||||
Tokenizer: ${selectedTokenizer}<br>
|
||||
<span class="tokenItemizingSubclass">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
Grey color items may not have been included in the context due to certain prompt format settings.
|
||||
</span>
|
||||
<hr class="sysHR">
|
||||
<div class="justifyLeft">
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flex1 flexFlowColumns flexNoGap wide50p tokenGraph">
|
||||
<div class="wide100p" style="background-color: indianred; height: ${storyStringTokensPercentage}%;"></div>
|
||||
<div class="wide100p" style="background-color: gold; height: ${worldInfoStringTokensPercentage}%;"></div>
|
||||
<div class="wide100p" style="background-color: palegreen; height: ${ActualChatHistoryTokensPercentage}%;"></div>
|
||||
<div class="wide100p" style="background-color: cornflowerblue; height: ${allAnchorsTokensPercentage}%;"></div>
|
||||
<div class="wide100p" style="background-color: mediumpurple; height: ${promptBiasTokensPercentage}%;"></div>
|
||||
</div>
|
||||
<div class="flex-container wide50p">
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: indianred;"> Character Definitions:</div>
|
||||
<div class=""> ${storyStringTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Description: </div>
|
||||
<div class="tokenItemizingSubclass">${charDescriptionTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Personality:</div>
|
||||
<div class="tokenItemizingSubclass"> ${charPersonalityTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Scenario: </div>
|
||||
<div class="tokenItemizingSubclass">${scenarioTextTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class="tokenItemizingSubclass"> ${examplesStringTokens}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: gold;">World Info:</div>
|
||||
<div class="">${worldInfoStringTokens}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;">Chat History:</div>
|
||||
<div class=""> ${ActualChatHistoryTokens}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: cornflowerblue;">Extensions:</div>
|
||||
<div class="">${allAnchorsTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Summarize: </div>
|
||||
<div class="tokenItemizingSubclass">${summarizeStringTokens}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Author's Note:</div>
|
||||
<div class="tokenItemizingSubclass"> ${authorsNoteStringTokens}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: mediumpurple;">{{}} Bias:</div><div class="">${promptBiasTokens}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
<div class="wide100p flex-container flexFlowColumns">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Total Tokens in Prompt:</div><div class=""> ${totalTokensInPrompt}</div>
|
||||
</div>
|
||||
<!-- <div class="flex1">finalPromt:</div><div class=""> ${finalPromptTokens}</div> -->
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Max Context:</div><div class="">${thisPrompt_max_context}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">- Padding:</div><div class=""> ${thisPrompt_padding}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Actual Max Context Allowed:</div><div class="">${thisPrompt_max_context - thisPrompt_padding}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
`, 'text'
|
||||
);
|
||||
}
|
||||
|
||||
function setInContextMessages(lastmsg, type) {
|
||||
$("#chat .mes").removeClass('lastInContext');
|
||||
|
||||
@ -2442,12 +2676,24 @@ function getGenerateUrl() {
|
||||
return generate_url;
|
||||
}
|
||||
|
||||
function shouldContinueMultigen(getMessage) {
|
||||
const nameString = is_pygmalion ? 'You:' : `${name1}:`;
|
||||
return message_already_generated.indexOf(nameString) === -1 && //if there is no 'You:' in the response msg
|
||||
message_already_generated.indexOf('<|endoftext|>') === -1 && //if there is no <endoftext> stamp in the response msg
|
||||
tokens_already_generated < parseInt(amount_gen) && //if the gen'd msg is less than the max response length..
|
||||
getMessage.length > 0; //if we actually have gen'd text at all...
|
||||
function shouldContinueMultigen(getMessage, isImpersonate) {
|
||||
if (power_user.instruct.enabled && power_user.instruct.stop_sequence) {
|
||||
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// stopping name string
|
||||
const nameString = isImpersonate ? `${name2}:` : (is_pygmalion ? 'You:' : `${name1}:`);
|
||||
// if there is no 'You:' in the response msg
|
||||
const doesNotContainName = message_already_generated.indexOf(nameString) === -1;
|
||||
//if there is no <endoftext> stamp in the response msg
|
||||
const isNotEndOfText = message_already_generated.indexOf('<|endoftext|>') === -1;
|
||||
//if the gen'd msg is less than the max response length..
|
||||
const notReachedMax = tokens_already_generated < parseInt(amount_gen);
|
||||
//if we actually have gen'd text at all...
|
||||
const msgHasText = getMessage.length > 0;
|
||||
return doesNotContainName && isNotEndOfText && notReachedMax && msgHasText;
|
||||
}
|
||||
|
||||
function extractNameFromMessage(getMessage, force_name2, isImpersonate) {
|
||||
@ -2563,6 +2809,12 @@ function cleanUpMessage(getMessage, isImpersonate) {
|
||||
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
|
||||
}
|
||||
}
|
||||
if (power_user.instruct.enabled && power_user.instruct.input_sequence && isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||
}
|
||||
if (power_user.instruct.enabled && power_user.instruct.output_sequence && !isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||
}
|
||||
// clean-up group message from excessive generations
|
||||
if (selected_group) {
|
||||
getMessage = cleanGroupMessage(getMessage);
|
||||
@ -3715,6 +3967,7 @@ function select_rm_create() {
|
||||
$("#renameCharButton").css('display', 'none');
|
||||
$("#name_div").removeClass('displayNone');
|
||||
$("#name_div").addClass('displayBlock');
|
||||
updateFavButtonState(false);
|
||||
|
||||
$("#form_create").attr("actiontype", "createcharacter");
|
||||
}
|
||||
@ -3748,7 +4001,6 @@ function updateFavButtonState(state) {
|
||||
$("#fav_checkbox").val(fav_ch_checked);
|
||||
$("#favorite_button").toggleClass('fav_on', fav_ch_checked);
|
||||
$("#favorite_button").toggleClass('fav_off', !fav_ch_checked);
|
||||
|
||||
}
|
||||
|
||||
function callPopup(text, type, inputValue = '') {
|
||||
@ -5311,27 +5563,6 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
/* $("#donation").click(function () {
|
||||
$("#shadow_tips_popup").css("display", "block");
|
||||
$("#shadow_tips_popup").transition({
|
||||
opacity: 1.0,
|
||||
duration: 100,
|
||||
easing: animation_easing,
|
||||
complete: function () { },
|
||||
});
|
||||
}); */
|
||||
|
||||
/* $("#tips_cross").click(function () {
|
||||
$("#shadow_tips_popup").transition({
|
||||
opacity: 0.0,
|
||||
duration: 100,
|
||||
easing: animation_easing,
|
||||
complete: function () {
|
||||
$("#shadow_tips_popup").css("display", "none");
|
||||
},
|
||||
});
|
||||
}); */
|
||||
|
||||
$("#select_chat_cross").click(function () {
|
||||
$("#shadow_select_chat_popup").transition({
|
||||
opacity: 0,
|
||||
@ -5358,16 +5589,16 @@ $(document).ready(function () {
|
||||
try {
|
||||
var edit_mes_id = $(this).closest(".mes").attr("mesid");
|
||||
var text = chat[edit_mes_id]["mes"];
|
||||
navigator.clipboard.writeText(text);
|
||||
const copiedMsg = document.createElement("div");
|
||||
copiedMsg.classList.add('code-copied');
|
||||
copiedMsg.innerText = "Copied!";
|
||||
copiedMsg.style.top = `${event.clientY - 55}px`;
|
||||
copiedMsg.style.left = `${event.clientX - 55}px`;
|
||||
document.body.append(copiedMsg);
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(copiedMsg);
|
||||
}, 1000);
|
||||
navigator.clipboard.writeText(text);
|
||||
const copiedMsg = document.createElement("div");
|
||||
copiedMsg.classList.add('code-copied');
|
||||
copiedMsg.innerText = "Copied!";
|
||||
copiedMsg.style.top = `${event.clientY - 55}px`;
|
||||
copiedMsg.style.left = `${event.clientX - 55}px`;
|
||||
document.body.append(copiedMsg);
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(copiedMsg);
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
}
|
||||
@ -5375,6 +5606,13 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("pointerup", ".mes_prompt", function () {
|
||||
let mesIdForItemization = $(this).closest('.mes').attr('mesId');
|
||||
if (itemizedPrompts.length !== undefined && itemizedPrompts.length !== 0) {
|
||||
promptItemize(itemizedPrompts, mesIdForItemization);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
//********************
|
||||
//***Message Editor***
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder } from "./utils.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
@ -275,7 +276,7 @@ export async function favsToHotswap() {
|
||||
const maxCount = 6;
|
||||
let count = 0;
|
||||
|
||||
$(selector).each(function () {
|
||||
$(selector).sort(sortByCssOrder).each(function () {
|
||||
if ($(this).hasClass('is_fav') && count < maxCount) {
|
||||
const isCharacter = $(this).hasClass('character_select');
|
||||
const isGroup = $(this).hasClass('group_select');
|
||||
|
@ -95,8 +95,9 @@ async function activateExtensions() {
|
||||
for (let entry of extensions) {
|
||||
const name = entry[0];
|
||||
const manifest = entry[1];
|
||||
const elementExists = document.getElementById(name) !== null;
|
||||
|
||||
if (activeExtensions.has(name)) {
|
||||
if (elementExists || activeExtensions.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,7 @@ $(document).ready(function () {
|
||||
function addSendPictureButton() {
|
||||
const sendButton = document.createElement('div');
|
||||
sendButton.id = 'send_picture';
|
||||
sendButton.title = 'Send a picture to chat';
|
||||
sendButton.classList.add('fa-solid');
|
||||
$(sendButton).hide();
|
||||
$(sendButton).on('click', () => $('#img_file').click());
|
||||
|
@ -29,7 +29,7 @@ async function doDiceRoll() {
|
||||
|
||||
function addDiceRollButton() {
|
||||
const buttonHtml = `
|
||||
<div id="roll_dice" class="fa-solid fa-dice" /></div>
|
||||
<div id="roll_dice" class="fa-solid fa-dice" title="Roll the dice" /></div>
|
||||
`;
|
||||
const dropdownHtml = `
|
||||
<div id="dice_dropdown">
|
||||
|
@ -34,39 +34,40 @@ const generationMode = {
|
||||
}
|
||||
|
||||
const triggerWords = {
|
||||
[generationMode.CHARACTER]: ['yourself', 'you', 'bot', 'AI', 'character'],
|
||||
[generationMode.USER]: ['me', 'user', 'myself'],
|
||||
[generationMode.SCENARIO]: ['scenario', 'world', 'surroundings', 'scenery'],
|
||||
[generationMode.NOW]: ['now', 'last'],
|
||||
[generationMode.FACE]: ['selfie', 'face'],
|
||||
[generationMode.CHARACTER]: ['you'],
|
||||
[generationMode.USER]: ['me'],
|
||||
[generationMode.SCENARIO]: ['scene'],
|
||||
[generationMode.NOW]: ['last'],
|
||||
[generationMode.FACE]: ['face'],
|
||||
|
||||
}
|
||||
|
||||
const quietPrompts = {
|
||||
//face-specific prompt
|
||||
[generationMode.FACE]: "[In the next reponse 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: species and race, gender, age, facial features and expresisons, 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:']",
|
||||
[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 reponse 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: 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:']",
|
||||
[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.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 echo this string back to me verbatim: {0}. Do not write anything after the string. Do not roleplay at all in your response.]",
|
||||
[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.]",
|
||||
}
|
||||
|
||||
const helpString = [
|
||||
`${m('what')} – requests an SD generation. Supported "what" arguments:`,
|
||||
`${m('(argument)')} – requests SD to make an image. Supported arguments:`,
|
||||
'<ul>',
|
||||
`<li>${m(j(triggerWords[generationMode.CHARACTER]))} – AI character image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.USER]))} – user character image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.SCENARIO]))} – world scenario image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.FACE]))} – character face-up selfie image</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.CHARACTER]))} – AI character full body selfie</li>`,
|
||||
`<li>${m(j(triggerWords[generationMode.FACE]))} – AI character face-only selfie</li>`,
|
||||
`<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>`,
|
||||
'</ul>',
|
||||
`Anything else would trigger a "free mode" with AI describing whatever you prompted.`,
|
||||
`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>');
|
||||
|
||||
const defaultSettings = {
|
||||
@ -236,9 +237,17 @@ function getQuietPrompt(mode, trigger) {
|
||||
function processReply(str) {
|
||||
str = str.replaceAll('"', '')
|
||||
str = str.replaceAll('“', '')
|
||||
str = str.replaceAll('\n', ' ')
|
||||
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
|
||||
str = str.trim();
|
||||
|
||||
str = str
|
||||
.split(',') // list split by commas
|
||||
.map(x => x.trim()) // trim each entry
|
||||
.filter(x => x) // remove empty entries
|
||||
.join(', '); // join it back with proper spacing
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@ -258,7 +267,7 @@ async function generatePicture(_, trigger) {
|
||||
const prompt = processReply(await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await context.generate('quiet', { resolve, reject, quiet_prompt });
|
||||
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
@ -268,6 +277,8 @@ async function generatePicture(_, trigger) {
|
||||
context.deactivateSendButtons();
|
||||
hideSwipeButtons();
|
||||
|
||||
console.log('Processed Stable Diffusion prompt:', prompt);
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await fetch(url, {
|
||||
@ -294,7 +305,7 @@ async function generatePicture(_, trigger) {
|
||||
sendMessage(prompt, base64Image);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.trace(err);
|
||||
throw new Error('SD prompt text generation failed.')
|
||||
}
|
||||
finally {
|
||||
@ -325,7 +336,7 @@ async function sendMessage(prompt, image) {
|
||||
|
||||
function addSDGenButtons() {
|
||||
const buttonHtml = `
|
||||
<div id="sd_gen" class="fa-solid fa-paintbrush" /></div>
|
||||
<div id="sd_gen" class="fa-solid fa-paintbrush" title="Trigger Stable Diffusion" /></div>
|
||||
`;
|
||||
|
||||
const waitButtonHtml = `
|
||||
@ -411,8 +422,8 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
}
|
||||
|
||||
else if (id == "sd_world") {
|
||||
console.log("doing /sd world");
|
||||
generatePicture('sd', 'world');
|
||||
console.log("doing /sd scene");
|
||||
generatePicture('sd', 'scene');
|
||||
}
|
||||
|
||||
else if (id == "sd_last") {
|
||||
@ -422,7 +433,7 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
});
|
||||
|
||||
jQuery(async () => {
|
||||
getContext().registerSlashCommand('sd', generatePicture, ['picture', 'image'], helpString, true, true);
|
||||
getContext().registerSlashCommand('sd', generatePicture, [], helpString, true, true);
|
||||
|
||||
const settingsHtml = `
|
||||
<div class="sd_settings">
|
||||
|
@ -7,6 +7,7 @@ class ElevenLabsTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... ... ... '
|
||||
|
||||
get settings() {
|
||||
return this.settings
|
||||
|
@ -48,10 +48,8 @@ async function moduleWorker() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat/character/group changed
|
||||
// Chat changed
|
||||
if (
|
||||
(context.groupId && lastGroupId !== context.groupId) ||
|
||||
context.characterId !== lastCharacterId ||
|
||||
context.chatId !== lastChatId
|
||||
) {
|
||||
currentMessageNumber = context.chat.length ? context.chat.length : 0
|
||||
@ -75,6 +73,7 @@ async function moduleWorker() {
|
||||
// We're currently swiping or streaming. Don't generate voice
|
||||
if (
|
||||
message.mes === '...' ||
|
||||
message.mes === '' ||
|
||||
(context.streamingProcessor && !context.streamingProcessor.isFinished)
|
||||
) {
|
||||
return
|
||||
@ -164,7 +163,7 @@ function onAudioControlClicked() {
|
||||
|
||||
function addAudioControl() {
|
||||
$('#send_but_sheld').prepend('<div id="tts_media_control"/>')
|
||||
$('#send_but_sheld').on('click', onAudioControlClicked)
|
||||
$('#tts_media_control').attr('title', 'TTS play/pause').on('click', onAudioControlClicked)
|
||||
audioControl = document.getElementById('tts_media_control')
|
||||
updateUiAudioPlayState()
|
||||
}
|
||||
@ -181,7 +180,7 @@ function completeCurrentAudioJob() {
|
||||
*/
|
||||
async function addAudioJob(response) {
|
||||
const audioData = await response.blob()
|
||||
if (!audioData.type in ['audio/mpeg', 'audio/wav']) {
|
||||
if (!audioData.type in ['audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/wave']) {
|
||||
throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}`
|
||||
}
|
||||
audioJobQueue.push(audioData)
|
||||
@ -240,12 +239,26 @@ async function processTtsQueue() {
|
||||
|
||||
console.debug('New message found, running TTS')
|
||||
currentTtsJob = ttsJobQueue.shift()
|
||||
const text = extension_settings.tts.narrate_dialogues_only
|
||||
? currentTtsJob.mes.replace(/\*[^\*]*?(\*|$)/g, '') // remove asterisks content
|
||||
: currentTtsJob.mes.replaceAll('*', '') // remove just the asterisks
|
||||
let text = extension_settings.tts.narrate_dialogues_only
|
||||
? currentTtsJob.mes.replace(/\*[^\*]*?(\*|$)/g, '').trim() // remove asterisks content
|
||||
: currentTtsJob.mes.replaceAll('*', '').trim() // remove just the asterisks
|
||||
|
||||
if (extension_settings.tts.narrate_quoted_only) {
|
||||
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
|
||||
const partJoiner = (ttsProvider?.separator || ' ... ');
|
||||
text = matches ? matches.join(partJoiner) : text;
|
||||
}
|
||||
console.log(`TTS: ${text}`)
|
||||
const char = currentTtsJob.name
|
||||
|
||||
try {
|
||||
if (!text) {
|
||||
console.warn('Got empty text in TTS queue job.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!voiceMap[char]) {
|
||||
throw `${char} not in voicemap. Configure character in extension settings voice map`
|
||||
}
|
||||
@ -282,6 +295,7 @@ function loadSettings() {
|
||||
extension_settings.tts.enabled
|
||||
)
|
||||
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only)
|
||||
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only)
|
||||
}
|
||||
|
||||
const defaultSettings = {
|
||||
@ -374,6 +388,13 @@ function onNarrateDialoguesClick() {
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
|
||||
function onNarrateQuotedClick() {
|
||||
extension_settings.tts.narrate_quoted_only = $('#tts_narrate_quoted').prop('checked');
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
|
||||
//##############//
|
||||
// TTS Provider //
|
||||
//##############//
|
||||
@ -453,6 +474,10 @@ $(document).ready(function () {
|
||||
<input type="checkbox" id="tts_narrate_dialogues">
|
||||
Narrate dialogues only
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_quoted">
|
||||
<input type="checkbox" id="tts_narrate_quoted">
|
||||
Narrate quoted only
|
||||
</label>
|
||||
</div>
|
||||
<label>Voice Map</label>
|
||||
<textarea id="tts_voice_map" type="text" class="text_pole textarea_compact" rows="4"
|
||||
@ -475,6 +500,7 @@ $(document).ready(function () {
|
||||
$('#tts_apply').on('click', onApplyClick)
|
||||
$('#tts_enabled').on('click', onEnableClick)
|
||||
$('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick);
|
||||
$('#tts_narrate_quoted').on('click', onNarrateQuotedClick);
|
||||
$('#tts_voices').on('click', onTtsVoicesClick)
|
||||
$('#tts_provider_settings').on('input', onTtsProviderSettingsInput)
|
||||
for (const provider in ttsProviders) {
|
||||
|
@ -9,6 +9,7 @@ class SileroTtsProvider {
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' .. '
|
||||
|
||||
defaultSettings = {
|
||||
provider_endpoint: "http://localhost:8001/tts",
|
||||
|
@ -21,6 +21,7 @@ class SystemTtsProvider {
|
||||
fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
|
||||
settings
|
||||
voices = []
|
||||
separator = ' ... '
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: {},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { saveSettingsDebounced, changeMainAPI, callPopup, setGenerationProgress, CLIENT_VERSION, getRequestHeaders } from "../script.js";
|
||||
import { SECRET_KEYS, writeSecret } from "./secrets.js";
|
||||
import { delay } from "./utils.js";
|
||||
|
||||
export {
|
||||
@ -217,5 +218,10 @@ jQuery(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#horde_api_key").on("input", async function () {
|
||||
const key = $(this).val().trim();
|
||||
await writeSecret(SECRET_KEYS.HORDE, key);
|
||||
});
|
||||
|
||||
$("#horde_refresh").on("click", getHordeModels);
|
||||
})
|
@ -9,6 +9,7 @@ import {
|
||||
getRequestHeaders,
|
||||
substituteParams,
|
||||
} from "../script.js";
|
||||
import { favsToHotswap } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
selected_group,
|
||||
@ -632,10 +633,10 @@ function loadInstructMode() {
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser) {
|
||||
const includeNames = power_user.instruct.names || (selected_group && !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];
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separator] : [sequence, mes, separator];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
@ -649,10 +650,11 @@ export function formatInstructStoryString(story) {
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(isImpersonate) {
|
||||
export function formatInstructModePrompt(name, isImpersonate) {
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const text = separator + sequence;
|
||||
const text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -995,6 +997,7 @@ $(document).ready(() => {
|
||||
power_user.sort_order = $(this).find(":selected").data('order');
|
||||
power_user.sort_rule = $(this).find(":selected").data('rule');
|
||||
sortCharactersList();
|
||||
favsToHotswap();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getRequestHeaders } from "../script.js";
|
||||
import { callPopup, getRequestHeaders } from "../script.js";
|
||||
|
||||
export const SECRET_KEYS = {
|
||||
HORDE: 'api_key_horde',
|
||||
@ -14,14 +14,50 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
const key = $(this).data('key');
|
||||
await writeSecret(key, '');
|
||||
secret_state[key] = false;
|
||||
updateSecretDisplay();
|
||||
$(INPUT_MAP[key]).val('');
|
||||
}
|
||||
|
||||
function updateSecretDisplay() {
|
||||
for (const [secret_key, input_selector] of Object.entries(INPUT_MAP)) {
|
||||
const validSecret = !!secret_state[secret_key];
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
$(input_selector).attr('placeholder', placeholder).val('');
|
||||
$(input_selector).attr('placeholder', placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
async function viewSecrets() {
|
||||
const response = await fetch('/viewsecrets', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (response.status == 403) {
|
||||
callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.conf file and restart the SillyTavern server.</p>', 'text');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#dialogue_popup').addClass('wide_dialogue_popup');
|
||||
const data = await response.json();
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('responsiveTable');
|
||||
$(table).append('<thead><th>Key</th><th>Value</th></thead>');
|
||||
|
||||
for (const [key,value] of Object.entries(data)) {
|
||||
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
|
||||
}
|
||||
|
||||
callPopup(table.outerHTML, 'text');
|
||||
}
|
||||
|
||||
export let secret_state = {};
|
||||
|
||||
export async function writeSecret(key, value) {
|
||||
@ -60,3 +96,8 @@ export async function readSecretState() {
|
||||
console.error('Could not read secrets file');
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$('#viewSecrets').on('click', viewSecrets);
|
||||
$(document).on('click', '.clear-api-key', clearSecret);
|
||||
});
|
@ -73,8 +73,8 @@ const parser = new SlashCommandParser();
|
||||
const registerSlashCommand = parser.addCommand.bind(parser);
|
||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||
|
||||
parser.addCommand('help', helpCommandCallback, ['?'], ' – displays a help information', true, true);
|
||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">name</span> – sets a background by file name', false, true);
|
||||
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);
|
||||
|
||||
function helpCommandCallback() {
|
||||
sendSystemMessage(system_message_types.HELP);
|
||||
|
@ -183,3 +183,9 @@ export async function initScrollHeight(element) {
|
||||
$(element).css("height", `${newHeight}px`);
|
||||
//resetScrollHeight(element);
|
||||
}
|
||||
|
||||
export function sortByCssOrder(a, b) {
|
||||
const _a = Number($(a).css('order'));
|
||||
const _b = Number($(b).css('order'));
|
||||
return _a - _b;
|
||||
}
|
@ -109,6 +109,41 @@ body {
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
table.responsiveTable {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.responsiveTable tr {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.responsiveTable,
|
||||
.responsiveTable th,
|
||||
.responsiveTable td {
|
||||
flex: 1;
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
word-break: break-all;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.sysHR {
|
||||
border-top: 2px solid grey;
|
||||
}
|
||||
|
||||
.tokenItemizingSubclass {
|
||||
font-size: calc(var(--mainFontSize) * 0.8);
|
||||
color: var(--SmartThemeEmColor);
|
||||
}
|
||||
|
||||
.tokenGraph {
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--white30a);
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fa-solid::before,
|
||||
.fa-regular::before {
|
||||
vertical-align: middle;
|
||||
@ -191,6 +226,11 @@ code {
|
||||
transition: background-image 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#version_display {
|
||||
padding: 5px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#bg1 {
|
||||
background-image: url('backgrounds/tavern day.jpg');
|
||||
z-index: -2;
|
||||
@ -2093,6 +2133,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
width: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mes_buttons {
|
||||
float: right;
|
||||
height: 20px;
|
||||
@ -2101,6 +2142,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.mes_prompt,
|
||||
.mes_copy,
|
||||
.mes_edit {
|
||||
cursor: pointer;
|
||||
@ -2117,7 +2159,8 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.last_mes .mes_copy {
|
||||
.last_mes .mes_copy,
|
||||
.last_mes .mes_prompt {
|
||||
grid-row-start: 1;
|
||||
position: relative;
|
||||
right: -30px;
|
||||
@ -3516,6 +3559,10 @@ toolcool-color-picker {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.spaceBetween {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.widthNatural {
|
||||
width: unset !important;
|
||||
min-width: unset !important;
|
||||
@ -3665,7 +3712,7 @@ toolcool-color-picker {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#max_context_unlocked:not(:checked) + div {
|
||||
#max_context_unlocked:not(:checked)+div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -3961,6 +4008,7 @@ body.waifuMode #avatar_zoom_popup {
|
||||
|
||||
}
|
||||
|
||||
|
||||
#sheld,
|
||||
#character_popup,
|
||||
#world_popup {
|
||||
@ -3973,6 +4021,11 @@ body.waifuMode #avatar_zoom_popup {
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
#world_popup {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
#world_popup,
|
||||
#send_form {
|
||||
|
87
readme.md
87
readme.md
@ -1,18 +1,23 @@
|
||||
# SillyTavern
|
||||
|
||||
## Based on a fork of TavernAI 1.2.8
|
||||
|
||||
### Brought to you by Cohee, RossAscends and the SillyTavern community
|
||||
|
||||
NOTE: We have added [a FAQ](faq.md) to answer most of your questions and help you get started.
|
||||
|
||||
### What is SillyTavern or TavernAI?
|
||||
|
||||
Tavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
|
||||
|
||||
SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs.
|
||||
|
||||
### What do I need other than Tavern?
|
||||
|
||||
On its own Tavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](faq.md).
|
||||
|
||||
### Do I need a powerful PC to run Tavern?
|
||||
|
||||
Since Tavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful.
|
||||
|
||||
## Mobile support
|
||||
@ -21,13 +26,13 @@ Since Tavern is only a user interface, it has tiny hardware requirements, it wil
|
||||
|
||||
> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**
|
||||
|
||||
https://rentry.org/STAI-Termux
|
||||
<https://rentry.org/STAI-Termux>
|
||||
|
||||
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
|
||||
|
||||
## Questions or suggestions?
|
||||
|
||||
### We now have a community Discord server!
|
||||
### We now have a community Discord server
|
||||
|
||||
Get support, share favorite characters and prompts:
|
||||
|
||||
@ -36,11 +41,13 @@ Get support, share favorite characters and prompts:
|
||||
***
|
||||
|
||||
Get in touch with the developers directly:
|
||||
|
||||
* Discord: Cohee#1207 or RossAscends#1779
|
||||
* Reddit: /u/RossAscends or /u/sillylossy
|
||||
* [Post a GitHub issue](https://github.com/Cohee1207/SillyTavern/issues)
|
||||
|
||||
## This version includes
|
||||
|
||||
* A heavily modified TavernAI 1.2.8 (more than 50% of code rewritten or optimized)
|
||||
* Swipes
|
||||
* Group chats: multi-bot rooms for characters to talk to you or each other
|
||||
@ -54,12 +61,13 @@ Get in touch with the developers directly:
|
||||
* Prompt generation formatting tweaking
|
||||
* webp character card interoperability (PNG is still an internal format)
|
||||
* Extensibility support via [SillyLossy's TAI-extras](https://github.com/Cohee1207/TavernAI-extras) plugins
|
||||
* Author's Note / Character Bias
|
||||
* Character emotional expressions
|
||||
* Auto-Summary of the chat history
|
||||
* Sending images to chat, and the AI interpreting the content.
|
||||
* Author's Note / Character Bias
|
||||
* Character emotional expressions
|
||||
* Auto-Summary of the chat history
|
||||
* Sending images to chat, and the AI interpreting the content.
|
||||
|
||||
## UI Extensions 🚀
|
||||
|
||||
| Name | Description | Required <a href="https://github.com/Cohee1207/TavernAI-extras#modules" target="_blank">Extra Modules</a> | Screenshot |
|
||||
| ---------------- | ---------------------------------| ---------------------------- | ---------- |
|
||||
| Image Captioning | Send a cute picture to your bot!<br><br>Picture select option will appear beside the "Message send" button. | `caption` | <img src="https://user-images.githubusercontent.com/18619528/224161576-ddfc51cd-995e-44ec-bf2d-d2477d603f0c.png" style="max-width:200px" /> |
|
||||
@ -97,12 +105,12 @@ Get in touch with the developers directly:
|
||||
* Nav panel status of open or closed will also be saved across sessions.
|
||||
|
||||
* Customizable chat UI:
|
||||
* Play a sound when a new message arrives
|
||||
* Switch between round or rectangle avatar styles
|
||||
* Have a wider chat window on the desktop
|
||||
* Optional semi-transparent glass-like panels
|
||||
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
|
||||
* Customizable UI background color and blur amount
|
||||
* Play a sound when a new message arrives
|
||||
* Switch between round or rectangle avatar styles
|
||||
* Have a wider chat window on the desktop
|
||||
* Optional semi-transparent glass-like panels
|
||||
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
|
||||
* Customizable UI background color and blur amount
|
||||
|
||||
## Installation
|
||||
|
||||
@ -115,6 +123,27 @@ Get in touch with the developers directly:
|
||||
> DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
|
||||
|
||||
### Windows
|
||||
|
||||
Installing via Git (recommended for easy updating)
|
||||
|
||||
Easy to follow guide with pretty pictures:
|
||||
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
|
||||
|
||||
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||
2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
|
||||
3. Open Windows Explorer (`Win+E`)
|
||||
4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\)
|
||||
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`
|
||||
|
||||
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.
|
||||
|
||||
Installing via zip download
|
||||
|
||||
1. install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||
2. download the zip from this GitHub repo
|
||||
3. unzip it into a folder of your choice
|
||||
@ -122,9 +151,21 @@ Get in touch with the developers directly:
|
||||
5. Once the server has prepared everything for you, it will open a tab in your browser.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Run the `start.sh` script.
|
||||
2. Enjoy.
|
||||
|
||||
## API keys management
|
||||
|
||||
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
|
||||
|
||||
By default they will not be exposed to a frontend after you enter them and reload the page.
|
||||
|
||||
In order to enable viewing your keys by clicking a button in the API block:
|
||||
|
||||
1. Set the value of `allowKeysExposure` to `true` in `config.conf` file.
|
||||
2. Restart the SillyTavern server.
|
||||
|
||||
## Remote connections
|
||||
|
||||
Most often this is for people who want to use SillyTavern on their mobile phones while at home.
|
||||
@ -133,10 +174,13 @@ If you want to enable other devices to connect to your TAI server, open 'config.
|
||||
```
|
||||
const whitelistMode = true;
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```
|
||||
const whitelistMode = false;
|
||||
```
|
||||
|
||||
Save the file.
|
||||
Restart your TAI server.
|
||||
|
||||
@ -156,12 +200,14 @@ The `whitelist` array in `config.conf` will be ignored if `whitelist.txt` exists
|
||||
***Disclaimer: Anyone else who knows your IP address and TAI port number will be able to connect as well***
|
||||
|
||||
To connect over wifi you'll need your PC's local wifi IP address
|
||||
- (For Windows: windows button > type 'cmd.exe' in the search bar> type 'ipconfig' in the console, hit Enter > "IPv4" listing)
|
||||
|
||||
* (For Windows: windows button > type 'cmd.exe' in the search bar> type 'ipconfig' in the console, hit Enter > "IPv4" listing)
|
||||
if you want other people on the internet to connect, check [here](https://whatismyipaddress.com/) for 'IPv4'
|
||||
|
||||
### Still Unable To Connect?
|
||||
|
||||
- Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
|
||||
- Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
|
||||
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
|
||||
|
||||
## Performance issues?
|
||||
|
||||
@ -187,9 +233,10 @@ We're moving to 100% original content only policy, so old background images have
|
||||
|
||||
You can find them archived here:
|
||||
|
||||
https://files.catbox.moe/1xevnc.zip
|
||||
<https://files.catbox.moe/1xevnc.zip>
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
|
||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">
|
||||
|
||||
@ -204,13 +251,13 @@ GNU Affero General Public License for more details.**
|
||||
* Cohee's modifications and derived code: AGPL v3
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
* poe-api client adapted from https://github.com/ading2210/poe-api (GPL v3)
|
||||
* GraphQL files for poe: https://github.com/muharamdani/poe (ISC License)
|
||||
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
|
||||
* poe-api client adapted from <https://github.com/ading2210/poe-api> (GPL v3)
|
||||
* GraphQL files for poe: <https://github.com/muharamdani/poe> (ISC License)
|
||||
* 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)
|
||||
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Linux startup script by AlpinDale
|
||||
* Thanks paniphons for providing a FAQ document
|
||||
|
48
server.js
48
server.js
@ -77,6 +77,7 @@ const whitelistMode = config.whitelistMode;
|
||||
const autorun = config.autorun && !cliArguments.ssl;
|
||||
const enableExtensions = config.enableExtensions;
|
||||
const listen = config.listen;
|
||||
const allowKeysExposure = config.allowKeysExposure;
|
||||
|
||||
const axios = require('axios');
|
||||
const tiktoken = require('@dqbd/tiktoken');
|
||||
@ -308,7 +309,7 @@ app.get('/deviceinfo', function (request, response) {
|
||||
return response.send(deviceInfo);
|
||||
});
|
||||
app.get('/version', function (_, response) {
|
||||
let pkgVersion, gitRevision;
|
||||
let pkgVersion, gitRevision, gitBranch;
|
||||
try {
|
||||
const pkgJson = require('./package.json');
|
||||
pkgVersion = pkgJson.version;
|
||||
@ -316,13 +317,18 @@ app.get('/version', function (_, response) {
|
||||
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 {
|
||||
response.send(`SillyTavern:${gitRevision || pkgVersion}:Cohee#1207`)
|
||||
const agent = `SillyTavern:${gitRevision || pkgVersion}:Cohee#1207`;
|
||||
response.send({ agent, pkgVersion, gitRevision, gitBranch });
|
||||
}
|
||||
})
|
||||
|
||||
@ -522,7 +528,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);
|
||||
@ -546,11 +552,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);
|
||||
@ -579,9 +584,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;
|
||||
@ -1275,7 +1279,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',
|
||||
@ -2808,7 +2812,7 @@ function migrateSecrets() {
|
||||
if (typeof hordeKey === 'string') {
|
||||
console.log('Migrating Horde key...');
|
||||
writeSecret(SECRET_KEYS.HORDE, hordeKey);
|
||||
delete settings.hordeKey;
|
||||
delete settings.horde_settings.api_key;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@ -2841,7 +2845,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');
|
||||
});
|
||||
|
||||
@ -2880,6 +2884,7 @@ app.post('/generate_horde', jsonParser, async (request, response) => {
|
||||
}
|
||||
};
|
||||
|
||||
console.log(args.data);
|
||||
try {
|
||||
const data = await postAsync(url, args);
|
||||
return response.send(data);
|
||||
@ -2888,6 +2893,27 @@ app.post('/generate_horde', jsonParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/viewsecrets', jsonParser, async (_, response) => {
|
||||
if (!allowKeysExposure) {
|
||||
console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.conf is set to true');
|
||||
return response.sendStatus(403);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
console.error('secrets.json does not exist');
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
try {
|
||||
const fileContents = fs.readFileSync(SECRETS_FILE);
|
||||
const secrets = JSON.parse(fileContents);
|
||||
return response.send(secrets);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
function writeSecret(key, value) {
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
const emptyFile = JSON.stringify({});
|
||||
|
Reference in New Issue
Block a user