mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'confirm_delete' of github.com:phiharri/SillyTavern into confirm_delete
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
<script src="scripts/toastr.min.js"></script>
|
||||
<script src="scripts/fuse.js"></script>
|
||||
<script src="scripts/select2.min.js"></script>
|
||||
<script src="scripts/seedrandom.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>
|
||||
@@ -549,7 +550,7 @@
|
||||
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="50" max="1000">
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block" data-source="openai,claude,windowai">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@@ -1318,7 +1319,7 @@
|
||||
<option value="koboldhorde">KoboldAI Horde</option>
|
||||
<option value="textgenerationwebui">Text Gen WebUI (ooba)</option>
|
||||
<option value="novel">NovelAI</option>
|
||||
<option value="openai">Chat Completion (OpenAI, Claude, Window/OpenRouter)</option>
|
||||
<option value="openai">Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)</option>
|
||||
<option value="poe">Poe</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1477,6 +1478,7 @@
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="windowai">Window AI / OpenRouter</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="scale">Scale</option>
|
||||
</select>
|
||||
<form id="openai_form" data-source="openai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4>OpenAI API key</h4>
|
||||
@@ -1499,17 +1501,29 @@
|
||||
<div>
|
||||
<h4 data-i18n="OpenAI Model">OpenAI Model</h4>
|
||||
<select id="model_openai_select">
|
||||
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
||||
<option value="gpt-3.5-turbo-16k">gpt-3.5-turbo-16k</option>
|
||||
<option value="gpt-3.5-turbo-16k-0613">gpt-3.5-turbo-16k-0613</option>
|
||||
<option value="gpt-3.5-turbo-0613">gpt-3.5-turbo-0613</option>
|
||||
<option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301</option>
|
||||
<option value="gpt-4">gpt-4</option>
|
||||
<option value="gpt-4-0613">gpt-4-0613</option>
|
||||
<option value="gpt-4-0314">gpt-4-0314</option>
|
||||
<option value="gpt-4-32k">gpt-4-32k</option>
|
||||
<option value="gpt-4-32k-0301">gpt-4-32k-0301</option>
|
||||
<option value="gpt-4-32k-0613">gpt-4-32k-0613</option>
|
||||
<optgroup label="GPT-3.5 Turbo">
|
||||
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
||||
<option value="gpt-3.5-turbo-16k">gpt-3.5-turbo-16k</option>
|
||||
<option value="gpt-3.5-turbo-16k-0613">gpt-3.5-turbo-16k-0613</option>
|
||||
<option value="gpt-3.5-turbo-0613">gpt-3.5-turbo-0613</option>
|
||||
<option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301</option>
|
||||
</optgroup>
|
||||
<optgroup label="GPT-4">
|
||||
<option value="gpt-4">gpt-4</option>
|
||||
<option value="gpt-4-0613">gpt-4-0613</option>
|
||||
<option value="gpt-4-0314">gpt-4-0314</option>
|
||||
<option value="gpt-4-32k">gpt-4-32k</option>
|
||||
<option value="gpt-4-32k-0301">gpt-4-32k-0301</option>
|
||||
<option value="gpt-4-32k-0613">gpt-4-32k-0613</option>
|
||||
</optgroup>
|
||||
<optgroup label="Other">
|
||||
<option value="text-davinci-003">text-davinci-003</option>
|
||||
<option value="text-davinci-002">text-davinci-002</option>
|
||||
<option value="text-curie-001">text-curie-001</option>
|
||||
<option value="text-babbage-001">text-babbage-001</option>
|
||||
<option value="text-ada-001">text-ada-001</option>
|
||||
<option value="code-davinci-002">code-davinci-002</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
@@ -1593,6 +1607,21 @@
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<form id="scale_form" data-source="scale" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4>Scale API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_scale" name="api_key_scale" class="text_pole flex1" maxlength="500" value="" autocomplete="off">
|
||||
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_scale"></div>
|
||||
</div>
|
||||
<div data-for="api_key_scale" class="neutral_warning">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<h4>Scale API URL</h4>
|
||||
<input id="api_url_scale" name="api_url_scale" class="text_pole" maxlength="500" value="" autocomplete="off" placeholder="https://dashboard.scale.com/spellbook/api/v2/deploy/xxxxxxx">
|
||||
<!-- Its only purpose is to trigger max context size check -->
|
||||
<select id="model_scale_select" class="displayNone"></select>
|
||||
</form>
|
||||
|
||||
<div class="flex-container flex">
|
||||
<input id="api_button_openai" class="menu_button" type="submit" value="Connect">
|
||||
<input data-source="windowai" id="openrouter_authorize" class="menu_button" type="button" value="Authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">
|
||||
@@ -2347,6 +2376,9 @@
|
||||
<label for="confirm_message_delete"><input id="confirm_message_delete" type="checkbox" />
|
||||
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
|
||||
</label>
|
||||
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
|
||||
<span data-i18n="Spoiler Free Mode">Spolier Free Mode</span>
|
||||
</label>
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
@@ -2428,9 +2460,9 @@
|
||||
<input id="extensions_details" class="alignitemsflexstart menu_button" type="button" value="Manage extensions">
|
||||
</div>
|
||||
</div>
|
||||
<div id="extensions_settings" class="flex1 widthNatural">
|
||||
<div id="extensions_settings" class="flex1 wide50p">
|
||||
</div>
|
||||
<div id="extensions_settings2" class="flex1 widthNatural">
|
||||
<div id="extensions_settings2" class="flex1 wide50p">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -2630,6 +2662,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="firstmessage_textarea" placeholder="This will be the first message from the character that starts every chat." class="marginBot5" name="first_mes" placeholder=""></textarea>
|
||||
<div id="spoiler_free_desc">
|
||||
<div id="creators_notes_div" class="marginBot5 title_restorable">
|
||||
<span data-i18n="Creator's Notes">Creator's Notes</span>
|
||||
<div id="spoiler_free_desc_button" class="menu_button fa-solid fa-eye" title="Show / Hide Description and First Message"></div>
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div id="creator_notes_spoiler" placeholder="Creator's Notes" class="marginBot5" name="creator_notes_spoiler"></div>
|
||||
<!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
|
||||
</div>
|
||||
|
||||
<!-- these divs are invisible and used for server communication purposes -->
|
||||
<div id="hidden-divs">
|
||||
@@ -2775,7 +2818,11 @@
|
||||
<div class="rm_tag_controls">
|
||||
<div class="tags rm_tag_filter"></div>
|
||||
</div>
|
||||
<!-- a div containing a dynamically updated count of characters currently displayed -->
|
||||
<div id="rm_character_count" ></div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div id="rm_print_characters_block"></div>
|
||||
</div>
|
||||
|
||||
@@ -2818,7 +2865,7 @@
|
||||
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<h4>Prompt Overrides <small>(For OpenAI/Claude APIs, Window/OpenRouter, Poe, and Instruct mode)</small></h4>
|
||||
<h4>Prompt Overrides <small>(For OpenAI/Claude/Scale APIs, Window/OpenRouter, Poe, and Instruct mode)</small></h4>
|
||||
|
||||
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
@@ -3092,7 +3139,8 @@
|
||||
<div class="inline-drawer-toggle inline-drawer-header gap5px">
|
||||
<span class="drag-handle">☰</span>
|
||||
<div class="gap5px world_entry_thin_controls wide100p">
|
||||
<div class="world_entry_form_control">
|
||||
|
||||
<div class="world_entry_form_control flex1">
|
||||
<label for="key">
|
||||
<small>
|
||||
<span data-i18n="Keywords">
|
||||
@@ -3111,11 +3159,18 @@
|
||||
</label>
|
||||
<textarea class="text_pole keyprimarytextpole" name="key" rows="1" placeholder="Comma separated (required)" maxlength="1000"></textarea>
|
||||
</div>
|
||||
<div class="world_entry_form_control keysecondary">
|
||||
<div class="flex-container widthFitContent alignItemsFlexEnd">
|
||||
<select name="entryLogicType" class="height32px widthFitContent margin0">
|
||||
<option value="0">AND</option>
|
||||
<option value="1">NOT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="world_entry_form_control keysecondary flex1">
|
||||
<label for="keysecondary">
|
||||
<small>
|
||||
<span data-i18n="Secondary Required Keywords">
|
||||
Secondary Required Keywords
|
||||
Optional Filter
|
||||
</span>
|
||||
</small>
|
||||
<small class="displayNone">
|
||||
@@ -3132,7 +3187,7 @@
|
||||
<div class="inline-drawer-content flex-container">
|
||||
<div class="WIEntryContentAndMemo flex-container">
|
||||
<div class="world_entry_thin_controls flex2">
|
||||
<div class="world_entry_form_control">
|
||||
<div class="world_entry_form_control flex1">
|
||||
<label for="content ">
|
||||
<small>
|
||||
<span data-i18n="Content">
|
||||
@@ -3153,7 +3208,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="world_entry_thin_controls commentContainer">
|
||||
<div class="world_entry_form_control">
|
||||
<div class="world_entry_form_control flex1">
|
||||
<label for="comment">
|
||||
<small>
|
||||
<span data-i18n="Memo/Note">
|
||||
@@ -3192,18 +3247,16 @@
|
||||
<input type="checkbox" name="addMemo">
|
||||
<span data-i18n="Add Memo">Add Memo</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox flex-container alignitemscenter">
|
||||
<input type="checkbox" name="exclude_recursion" />
|
||||
<span data-i18n="Exclude from recursion">
|
||||
Non-recursable
|
||||
</span>
|
||||
</label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <div name="injectioStratBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text ">
|
||||
<!--
|
||||
<div name="injectioStratBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text ">
|
||||
<label for="injectionStrat">Injection:</label>
|
||||
<small>
|
||||
<select name="injectionStrat">
|
||||
@@ -3212,7 +3265,8 @@
|
||||
<option value="2">Disabled</option>
|
||||
</select>
|
||||
</small>
|
||||
</div> -->
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text ">
|
||||
<label for="position">Position:</label>
|
||||
@@ -3307,6 +3361,7 @@
|
||||
<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="Create bookmark" class="mes_create_bookmark fa-regular fa-code-branch"></div>
|
||||
<div title="Copy" class="mes_copy fa-solid fa-copy "></div>
|
||||
</div>
|
||||
<div title="Open bookmark chat" class="mes_bookmark fa-solid fa-bookmark"></div>
|
||||
|
195
public/script.js
195
public/script.js
@@ -101,7 +101,10 @@ import {
|
||||
nai_settings,
|
||||
} from "./scripts/nai-settings.js";
|
||||
|
||||
import { showBookmarksButtons } from "./scripts/bookmarks.js";
|
||||
import {
|
||||
createNewBookmark,
|
||||
showBookmarksButtons
|
||||
} from "./scripts/bookmarks.js";
|
||||
|
||||
import {
|
||||
horde_settings,
|
||||
@@ -162,6 +165,7 @@ import { context_settings, loadContextTemplatesFromSettings } from "./scripts/co
|
||||
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/extensions/floating-prompt/index.js";
|
||||
import { deviceInfo } from "./scripts/RossAscends-mods.js";
|
||||
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@@ -238,6 +242,26 @@ export {
|
||||
// API OBJECT FOR EXTERNAL WIRING
|
||||
window["SillyTavern"] = {};
|
||||
|
||||
// Event source init
|
||||
export const event_types = {
|
||||
EXTRAS_CONNECTED: 'extras_connected',
|
||||
MESSAGE_SWIPED: 'message_swiped',
|
||||
MESSAGE_SENT: 'message_sent',
|
||||
MESSAGE_RECEIVED: 'message_received',
|
||||
MESSAGE_EDITED: 'message_edited',
|
||||
MESSAGE_DELETED: 'message_deleted',
|
||||
IMPERSONATE_READY: 'impersonate_ready',
|
||||
CHAT_CHANGED: 'chat_id_changed',
|
||||
GENERATION_STOPPED: 'generation_stopped',
|
||||
EXTENSIONS_FIRST_LOAD: 'extensions_first_load',
|
||||
SETTINGS_LOADED: 'settings_loaded',
|
||||
SETTINGS_UPDATED: 'settings_updated',
|
||||
GROUP_UPDATED: 'group_updated',
|
||||
MOVABLE_PANELS_RESET: 'movable_panels_reset',
|
||||
}
|
||||
|
||||
export const eventSource = new EventEmitter();
|
||||
|
||||
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
|
||||
hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.innerText } });
|
||||
|
||||
@@ -322,6 +346,7 @@ const system_message_types = {
|
||||
SLASH_COMMANDS: "slash_commands",
|
||||
FORMATTING: "formatting",
|
||||
HOTKEYS: "hotkeys",
|
||||
MACROS: "macros",
|
||||
};
|
||||
|
||||
const extension_prompt_types = {
|
||||
@@ -342,6 +367,7 @@ const system_messages = {
|
||||
<li><a href="javascript:displayHelp('1')">Slash Commands</a> (or <tt>/help slash</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('2')">Formatting</a> (or <tt>/help format</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('3')">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
|
||||
<li><a href="javascript:displayHelp('4')">{{Macros}}</a> (or <tt>/help macros</tt>)</li>
|
||||
</ul>
|
||||
<br><b>Still got questions left? The <a target="_blank" href="https://docs.sillytavern.app/">Official SillyTavern Documentation Website</a> has much more information!</b>`
|
||||
]
|
||||
@@ -396,6 +422,23 @@ const system_messages = {
|
||||
</ul>`
|
||||
]
|
||||
},
|
||||
macros: {
|
||||
name: systemUserName,
|
||||
force_avatar: system_avatar,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
is_name: true,
|
||||
mes: [
|
||||
`System-wide Replacement Macros:
|
||||
<ul>
|
||||
<li><tt>{{user}}</tt> - your current Persona username</li>
|
||||
<li><tt>{{char}}</tt> - the Character's name</li>
|
||||
<li><tt>{{time}}</tt> - the current time</li>
|
||||
<li><tt>{{date}}</tt> - the current date</li>
|
||||
<li><tt>{{random:(args)}}</tt> - returns a random item from the list. (ex: {{random:1,2,3,4}} will return 1 of the 4 numbers at random. Works with text lists too.</li>
|
||||
</ul>`
|
||||
]
|
||||
},
|
||||
welcome:
|
||||
{
|
||||
name: systemUserName,
|
||||
@@ -479,23 +522,6 @@ const system_messages = {
|
||||
},
|
||||
};
|
||||
|
||||
export const event_types = {
|
||||
EXTRAS_CONNECTED: 'extras_connected',
|
||||
MESSAGE_SWIPED: 'message_swiped',
|
||||
MESSAGE_SENT: 'message_sent',
|
||||
MESSAGE_RECEIVED: 'message_received',
|
||||
MESSAGE_EDITED: 'message_edited',
|
||||
MESSAGE_DELETED: 'message_deleted',
|
||||
IMPERSONATE_READY: 'impersonate_ready',
|
||||
CHAT_CHANGED: 'chat_id_changed',
|
||||
GENERATION_STOPPED: 'generation_stopped',
|
||||
SETTINGS_UPDATED: 'settings_updated',
|
||||
GROUP_UPDATED: 'group_updated',
|
||||
MOVABLE_PANELS_RESET: 'movable_panels_reset',
|
||||
}
|
||||
|
||||
export const eventSource = new EventEmitter();
|
||||
|
||||
$(document).ajaxError(function myErrorHandler(_, xhr) {
|
||||
if (xhr.status == 403) {
|
||||
toastr.warning("doubleCsrf errors in console are NORMAL in this case. Just reload the page or close this tab.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
|
||||
@@ -916,6 +942,7 @@ async function getCharacters() {
|
||||
}
|
||||
await getGroups();
|
||||
await printCharacters();
|
||||
updateCharacterCount('#rm_print_characters_block > div');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,6 +1136,11 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
|
||||
mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), "");
|
||||
}
|
||||
|
||||
const regexResult = getRegexedString(mes, regex_placement.MD_DISPLAY);
|
||||
if (regexResult) {
|
||||
mes = regexResult;
|
||||
}
|
||||
|
||||
if (power_user.auto_fix_generated_markdown) {
|
||||
mes = fixMarkdown(mes);
|
||||
}
|
||||
@@ -1484,9 +1516,28 @@ function substituteParams(content, _name1, _name2, _original) {
|
||||
content = content.replace(/<BOT>/gi, _name2);
|
||||
content = content.replace(/{{time}}/gi, moment().format('LT'));
|
||||
content = content.replace(/{{date}}/gi, moment().format('LL'));
|
||||
content = randomReplace(content);
|
||||
return content;
|
||||
}
|
||||
|
||||
function randomReplace(input, emptyListPlaceholder = '') {
|
||||
const randomPattern = /{{random:([^}]+)}}/gi;
|
||||
|
||||
return input.replace(randomPattern, (match, listString) => {
|
||||
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
||||
|
||||
if (list.length === 0) {
|
||||
return emptyListPlaceholder;
|
||||
}
|
||||
|
||||
var rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
|
||||
//const randomIndex = Math.floor(Math.random() * list.length);
|
||||
return list[randomIndex];
|
||||
});
|
||||
}
|
||||
|
||||
function getStoppingStrings(isImpersonate, addSpace) {
|
||||
const charString = `\n${name2}:`;
|
||||
const youString = `\nYou:`;
|
||||
@@ -1588,7 +1639,7 @@ export function extractMessageBias(message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const forbiddenMatches = ['user', 'char', 'time', 'date'];
|
||||
const forbiddenMatches = ['user', 'char', 'time', 'date', 'random'];
|
||||
const found = [];
|
||||
const rxp = /\{\{([\s\S]+?)\}\}/gm;
|
||||
//const rxp = /{([^}]+)}/g;
|
||||
@@ -1597,7 +1648,12 @@ export function extractMessageBias(message) {
|
||||
while ((curMatch = rxp.exec(message))) {
|
||||
const match = curMatch[1].trim();
|
||||
|
||||
if (forbiddenMatches.includes(match)) {
|
||||
// Ignore random pattern matches
|
||||
if (/^random:.+/i.test(match)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (forbiddenMatches.includes(match.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1715,7 +1771,7 @@ function appendToStoryString(value, prefix) {
|
||||
}
|
||||
|
||||
function isStreamingEnabled() {
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai)
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE)
|
||||
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_settings.can_use_streaming)
|
||||
|| (main_api == 'novel' && nai_settings.streaming_novel)
|
||||
|| (main_api == 'poe' && poe_settings.streaming)
|
||||
@@ -2866,6 +2922,11 @@ export function replaceBiasMarkup(str) {
|
||||
}
|
||||
|
||||
export async function sendMessageAsUser(textareaText, messageBias) {
|
||||
const regexResult = getRegexedString(textareaText, regex_placement.USER_INPUT);
|
||||
if (regexResult) {
|
||||
textareaText = regexResult;
|
||||
}
|
||||
|
||||
chat[chat.length] = {};
|
||||
chat[chat.length - 1]['name'] = name1;
|
||||
chat[chat.length - 1]['is_user'] = true;
|
||||
@@ -3446,11 +3507,21 @@ function extractMessageFromData(data) {
|
||||
}
|
||||
|
||||
function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences = false) {
|
||||
// Append the user bias first before trimming anything else
|
||||
if (power_user.user_prompt_bias && power_user.user_prompt_bias.length !== 0) {
|
||||
// Add the prompt bias before anything else
|
||||
if (
|
||||
power_user.user_prompt_bias &&
|
||||
!isImpersonate &&
|
||||
power_user.user_prompt_bias.length !== 0
|
||||
) {
|
||||
getMessage = substituteParams(power_user.user_prompt_bias) + getMessage;
|
||||
}
|
||||
|
||||
// Regex uses vars, so add before formatting
|
||||
const regexResult = getRegexedString(getMessage, isImpersonate ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT);
|
||||
if (regexResult) {
|
||||
getMessage = regexResult;
|
||||
}
|
||||
|
||||
if (!displayIncompleteSentences && power_user.trim_sentences) {
|
||||
getMessage = end_trim_to_sentence(getMessage, power_user.include_newline);
|
||||
}
|
||||
@@ -3816,7 +3887,7 @@ async function renamePastChats(newAvatar, newValue) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChat(chat_name, withMetadata) {
|
||||
async function saveChat(chat_name, withMetadata, mesId) {
|
||||
const metadata = { ...chat_metadata, ...(withMetadata || {}) };
|
||||
let file_name = chat_name ?? characters[this_chid].chat;
|
||||
characters[this_chid]['date_last_chat'] = Date.now();
|
||||
@@ -3837,6 +3908,11 @@ async function saveChat(chat_name, withMetadata) {
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
|
||||
? chat.slice(0, parseInt(mesId) + 1)
|
||||
: chat;
|
||||
|
||||
var save_chat = [
|
||||
{
|
||||
user_name: name1,
|
||||
@@ -3844,7 +3920,7 @@ async function saveChat(chat_name, withMetadata) {
|
||||
create_date: chat_create_date,
|
||||
chat_metadata: metadata,
|
||||
},
|
||||
...chat,
|
||||
...trimmed_chat,
|
||||
];
|
||||
return jQuery.ajax({
|
||||
type: "POST",
|
||||
@@ -4768,10 +4844,13 @@ async function getSettings(type) {
|
||||
|
||||
if (data.enable_extensions) {
|
||||
await loadExtensionSettings(settings);
|
||||
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_checked_colab) isColab();
|
||||
|
||||
eventSource.emit(event_types.SETTINGS_LOADED);
|
||||
}
|
||||
|
||||
function selectKoboldGuiPreset() {
|
||||
@@ -4860,13 +4939,34 @@ function setCharacterBlockHeight() {
|
||||
function updateMessage(div) {
|
||||
const mesBlock = div.closest(".mes_block");
|
||||
let text = mesBlock.find(".edit_textarea").val();
|
||||
const mes = chat[this_edit_mes_id];
|
||||
let regexPlacement;
|
||||
if (mes.is_name && !mes.is_user && mes.name !== name2) {
|
||||
regexPlacement = regex_placement.SENDAS;
|
||||
} else if (mes.is_name && !mes.is_user) {
|
||||
regexPlacement = regex_placement.AI_OUTPUT;
|
||||
} else if (mes.is_name && mes.is_user) {
|
||||
regexPlacement = regex_placement.USER_INPUT;
|
||||
} else if (mes.extra?.type === "narrator") {
|
||||
regexPlacement = regex_placement.SYSTEM;
|
||||
}
|
||||
|
||||
const regexResult = getRegexedString(
|
||||
text,
|
||||
regexPlacement,
|
||||
{
|
||||
characterOverride: regexPlacement === regex_placement.SENDAS ? mes.name : undefined
|
||||
}
|
||||
);
|
||||
if (regexResult) {
|
||||
text = regexResult;
|
||||
}
|
||||
|
||||
if (power_user.trim_spaces) {
|
||||
text = text.trim();
|
||||
}
|
||||
|
||||
const bias = extractMessageBias(text);
|
||||
const mes = chat[this_edit_mes_id];
|
||||
mes["mes"] = text;
|
||||
if (mes["swipe_id"] !== undefined) {
|
||||
mes["swipes"][mes["swipe_id"]] = text;
|
||||
@@ -5189,6 +5289,7 @@ export function select_selected_character(chid) {
|
||||
$("#description_textarea").val(characters[chid].description);
|
||||
$("#character_world").val(characters[chid].data?.extensions?.world || '');
|
||||
$("#creator_notes_textarea").val(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
|
||||
$("#creator_notes_spoiler").text(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
|
||||
$("#character_version_textarea").val(characters[chid].data?.character_version || '');
|
||||
$("#system_prompt_textarea").val(characters[chid].data?.system_prompt || '');
|
||||
$("#post_history_instructions_textarea").val(characters[chid].data?.post_history_instructions || '');
|
||||
@@ -5257,6 +5358,7 @@ function select_rm_create() {
|
||||
$("#description_textarea").val(create_save.description);
|
||||
$('#character_world').val(create_save.world);
|
||||
$("#creator_notes_textarea").val(create_save.creator_notes);
|
||||
$("#creator_notes_spoiler").text(create_save.creator_notes);
|
||||
$("#post_history_instructions_textarea").val(create_save.post_history_instructions);
|
||||
$("#system_prompt_textarea").val(create_save.system_prompt);
|
||||
$("#tags_textarea").val(create_save.tags);
|
||||
@@ -5331,7 +5433,7 @@ function onScenarioOverrideRemoveClick() {
|
||||
$(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
|
||||
}
|
||||
|
||||
function callPopup(text, type, inputValue = '') {
|
||||
function callPopup(text, type, inputValue = '', okButton) {
|
||||
if (type) {
|
||||
popup_type = type;
|
||||
}
|
||||
@@ -5339,30 +5441,30 @@ function callPopup(text, type, inputValue = '') {
|
||||
$("#dialogue_popup_cancel").css("display", "inline-block");
|
||||
switch (popup_type) {
|
||||
case "avatarToCrop":
|
||||
$("#dialogue_popup_ok").text("Accept");
|
||||
$("#dialogue_popup_ok").text(okButton ?? "Accept");
|
||||
break;
|
||||
case "text":
|
||||
case "alternate_greeting":
|
||||
case "char_not_selected":
|
||||
$("#dialogue_popup_ok").text("Ok");
|
||||
$("#dialogue_popup_ok").text(okButton ?? "Ok");
|
||||
$("#dialogue_popup_cancel").css("display", "none");
|
||||
break;
|
||||
case "new_chat":
|
||||
case "confirm":
|
||||
$("#dialogue_popup_ok").text("Yes");
|
||||
$("#dialogue_popup_ok").text(okButton ?? "Yes");
|
||||
break;
|
||||
case "del_group":
|
||||
case "rename_chat":
|
||||
case "del_chat":
|
||||
default:
|
||||
$("#dialogue_popup_ok").text("Delete");
|
||||
$("#dialogue_popup_ok").text(okButton ?? "Delete");
|
||||
}
|
||||
|
||||
$("#dialogue_popup_input").val(inputValue);
|
||||
|
||||
if (popup_type == 'input') {
|
||||
$("#dialogue_popup_input").css("display", "block");
|
||||
$("#dialogue_popup_ok").text("Save");
|
||||
$("#dialogue_popup_ok").text(okButton ?? "Save");
|
||||
}
|
||||
else {
|
||||
$("#dialogue_popup_input").css("display", "none");
|
||||
@@ -5960,9 +6062,11 @@ async function createOrEditCharacter(e) {
|
||||
success: async function (html) {
|
||||
if (chat.length === 1 && !selected_group) {
|
||||
var this_ch_mes = default_ch_mes;
|
||||
|
||||
if ($("#firstmessage_textarea").val() != "") {
|
||||
this_ch_mes = $("#firstmessage_textarea").val();
|
||||
}
|
||||
|
||||
if (
|
||||
this_ch_mes !=
|
||||
$.trim(
|
||||
@@ -5973,6 +6077,13 @@ async function createOrEditCharacter(e) {
|
||||
.text()
|
||||
)
|
||||
) {
|
||||
// MARK - kingbri: Regex on character greeting message
|
||||
// May need to be placed somewhere else
|
||||
const regexResult = getRegexedString(this_ch_mes, regex_placement.AI_OUTPUT);
|
||||
if (regexResult) {
|
||||
this_ch_mes = regexResult;
|
||||
}
|
||||
|
||||
clearChat();
|
||||
chat.length = 0;
|
||||
chat[0] = {};
|
||||
@@ -6348,6 +6459,17 @@ const swipe_right = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCharacterCount(characterSelector) {
|
||||
const visibleCharacters = $(characterSelector).filter(":visible");
|
||||
const visibleCharacterCount = visibleCharacters.length;
|
||||
const totalCharacterCount = $(characterSelector).length;
|
||||
|
||||
$("#rm_character_count").text(
|
||||
`(${visibleCharacterCount} / ${totalCharacterCount}) Characters`
|
||||
);
|
||||
console.log("visibleCharacters.length: " + visibleCharacters.length);
|
||||
}
|
||||
|
||||
function updateVisibleDivs(containerSelector, resizecontainer) {
|
||||
var $container = $(containerSelector);
|
||||
var $children = $container.children();
|
||||
@@ -6602,6 +6724,8 @@ $(document).ready(function () {
|
||||
});
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
updateCharacterCount(selector);
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -7886,6 +8010,13 @@ $(document).ready(function () {
|
||||
$("#load_select_chat_div").css("display", "block");
|
||||
});
|
||||
|
||||
$(document).on("click", ".mes_create_bookmark", async function () {
|
||||
var selected_mes_id = $(this).closest(".mes").attr("mesid");
|
||||
if (selected_mes_id !== undefined) {
|
||||
createNewBookmark(selected_mes_id);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", ".mes_stop", function () {
|
||||
if (streamingProcessor) {
|
||||
streamingProcessor.abortController.abort();
|
||||
|
@@ -403,7 +403,12 @@ function RA_autoconnect(PrevApi) {
|
||||
}
|
||||
break;
|
||||
case 'openai':
|
||||
if (secret_state[SECRET_KEYS.OPENAI] || secret_state[SECRET_KEYS.CLAUDE] || oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
if ( (secret_state[SECRET_KEYS.OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|
||||
|| (secret_state[SECRET_KEYS.CLAUDE] && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
|
||||
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && !oai_settings.use_openrouter)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && oai_settings.use_openrouter)
|
||||
) {
|
||||
$("#api_button_openai").click();
|
||||
}
|
||||
break;
|
||||
|
@@ -32,6 +32,7 @@ import {
|
||||
} from "./utils.js";
|
||||
|
||||
export {
|
||||
createNewBookmark,
|
||||
showBookmarksButtons,
|
||||
}
|
||||
|
||||
@@ -123,13 +124,26 @@ function showBookmarksButtons() {
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewBookmark() {
|
||||
async function saveBookmarkMenu() {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Bookmark creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const mesId = chat.length - 1;
|
||||
return createNewBookmark(chat.length - 1);
|
||||
}
|
||||
|
||||
async function createNewBookmark(mesId) {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Bookmark creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (mesId < 0 || mesId >= chat.length) {
|
||||
toastr.warning('Invalid message ID.', 'Bookmark creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMes = chat[mesId];
|
||||
|
||||
if (typeof lastMes.extra !== 'object') {
|
||||
@@ -155,9 +169,9 @@ async function createNewBookmark() {
|
||||
const newMetadata = { main_chat: mainChat };
|
||||
|
||||
if (selected_group) {
|
||||
await saveGroupBookmarkChat(selected_group, name, newMetadata);
|
||||
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
|
||||
} else {
|
||||
await saveChat(name, newMetadata);
|
||||
await saveChat(name, newMetadata, mesId);
|
||||
}
|
||||
|
||||
lastMes.extra['bookmark_link'] = name;
|
||||
@@ -294,7 +308,7 @@ async function convertSoloToGroupChat() {
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#option_new_bookmark').on('click', createNewBookmark);
|
||||
$('#option_new_bookmark').on('click', saveBookmarkMenu);
|
||||
$('#option_back_to_main').on('click', backToMainChat);
|
||||
$('#option_convert_to_group').on('click', convertSoloToGroupChat);
|
||||
});
|
||||
|
@@ -56,6 +56,7 @@ const extension_settings = {
|
||||
caption: {},
|
||||
expressions: {},
|
||||
dice: {},
|
||||
regex: [],
|
||||
tts: {},
|
||||
sd: {},
|
||||
chromadb: {},
|
||||
@@ -414,6 +415,7 @@ async function loadExtensionSettings(settings) {
|
||||
$("#extensions_autoconnect").prop('checked', extension_settings.autoConnect);
|
||||
|
||||
// Activate offline extensions
|
||||
eventSource.emit(event_types.EXTENSIONS_FIRST_LOAD);
|
||||
extensionNames = await discoverExtensions();
|
||||
manifests = await getManifests(extensionNames)
|
||||
await activateExtensions();
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { getCharaFilename, debounce } from "../../utils.js";
|
||||
import { getCharaFilename, debounce, waitUntilCondition } from "../../utils.js";
|
||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||
@@ -339,136 +339,138 @@ function onChatChanged() {
|
||||
$('#extension_floating_default_token_counter').text(tokenCounter3);
|
||||
}
|
||||
|
||||
//for some reason exporting metadata_keys for WI usage caused this to throw errors
|
||||
//"accessing eventSource before initialization"
|
||||
//putting it on a 1ms Timeout solved this.
|
||||
setTimeout(function () {
|
||||
function addExtensionsSettings() {
|
||||
const settingsHtml = `
|
||||
<div id="floatingPrompt" class="drawer-content flexGap5">
|
||||
<div class="panelControlBar flex-container">
|
||||
<div id="floatingPromptheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<div id="ANClose" class="fa-solid fa-circle-xmark"></div>
|
||||
</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>
|
||||
function addExtensionsSettings() {
|
||||
const settingsHtml = `
|
||||
<div id="floatingPrompt" class="drawer-content flexGap5">
|
||||
<div class="panelControlBar flex-container">
|
||||
<div id="floatingPromptheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||
<div id="ANClose" class="fa-solid fa-circle-xmark"></div>
|
||||
</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">
|
||||
<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="extension_token_counter">Tokens: <span id="extension_floating_prompt_token_counter">0</small></div>
|
||||
|
||||
<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, 1 = Always)</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="charaANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Author's Note</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>
|
||||
<b>Unique to this chat</b>.<br>
|
||||
Bookmarks inherit the Note from their parent, and can be changed individually after that.<br>
|
||||
</small>
|
||||
<small>Will be automatically added as the author's note for this character. Will be used in groups, but can't be modified when a group chat is open.</small>
|
||||
|
||||
<textarea id="extension_floating_prompt" class="text_pole" rows="8" maxlength="10000"></textarea>
|
||||
<div class="extension_token_counter">Tokens: <span id="extension_floating_prompt_token_counter">0</small></div>
|
||||
<textarea id="extension_floating_chara" class="text_pole" rows="8" maxlength="10000"
|
||||
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
|
||||
<div class="extension_token_counter">Tokens: <span id="extension_floating_chara_token_counter">0</small></div>
|
||||
|
||||
<label class="checkbox_label" for="extension_use_floating_chara">
|
||||
<input id="extension_use_floating_chara" type="checkbox" />
|
||||
<span data-i18n="Use character author's note">Use character author's note</span>
|
||||
</label>
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_position" value="0" />
|
||||
After scenario
|
||||
<input type="radio" name="extension_floating_char_position" value="0" />
|
||||
Replace Author's Note
|
||||
</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" />
|
||||
<input type="radio" name="extension_floating_char_position" value="1" />
|
||||
Top of Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="2" />
|
||||
Bottom of Author's Note
|
||||
</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, 1 = Always)</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="charaANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character 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 this character. Will be used in groups, but can't be modified when a group chat is open.</small>
|
||||
|
||||
<textarea id="extension_floating_chara" class="text_pole" rows="8" maxlength="10000"
|
||||
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
|
||||
<div class="extension_token_counter">Tokens: <span id="extension_floating_chara_token_counter">0</small></div>
|
||||
|
||||
<label class="checkbox_label" for="extension_use_floating_chara">
|
||||
<input id="extension_use_floating_chara" type="checkbox" />
|
||||
<span data-i18n="Use character author's note">Use character author's note</span>
|
||||
</label>
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="0" />
|
||||
Replace Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="1" />
|
||||
Top of Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="2" />
|
||||
Bottom of Author's Note
|
||||
</label>
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
<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 class="extension_token_counter">Tokens: <span id="extension_floating_default_token_counter">0</small></div>
|
||||
</div>
|
||||
<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 class="extension_token_counter">Tokens: <span id="extension_floating_default_token_counter">0</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const ANButtonHtml = `
|
||||
<a id="option_toggle_AN">
|
||||
<i class="fa-lg fa-solid fa-note-sticky"></i>
|
||||
<span data-i18n="Author's Note">Author's Note</span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
$('#options .options-content').prepend(ANButtonHtml);
|
||||
$('#movingDivs').append(settingsHtml);
|
||||
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
|
||||
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
|
||||
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
|
||||
$('#extension_floating_chara').on('input', onExtensionFloatingCharaPromptInput);
|
||||
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
|
||||
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
||||
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
||||
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
||||
$('#ANClose').on('click', function () {
|
||||
$("#floatingPrompt").transition({
|
||||
opacity: 0,
|
||||
duration: 200,
|
||||
easing: 'ease-in-out',
|
||||
});
|
||||
setTimeout(function () { $('#floatingPrompt').hide() }, 200);
|
||||
})
|
||||
$("#option_toggle_AN").on('click', onANMenuItemClick);
|
||||
}
|
||||
|
||||
addExtensionsSettings();
|
||||
const ANButtonHtml = `
|
||||
<a id="option_toggle_AN">
|
||||
<i class="fa-lg fa-solid fa-note-sticky"></i>
|
||||
<span data-i18n="Author's Note">Author's Note</span>
|
||||
</a>
|
||||
`;
|
||||
|
||||
$('#options .options-content').prepend(ANButtonHtml);
|
||||
$('#movingDivs').append(settingsHtml);
|
||||
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
|
||||
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
|
||||
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
|
||||
$('#extension_floating_chara').on('input', onExtensionFloatingCharaPromptInput);
|
||||
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
|
||||
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
||||
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
||||
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
||||
$('#ANClose').on('click', function () {
|
||||
$("#floatingPrompt").transition({
|
||||
opacity: 0,
|
||||
duration: 200,
|
||||
easing: 'ease-in-out',
|
||||
});
|
||||
setTimeout(function () { $('#floatingPrompt').hide() }, 200);
|
||||
})
|
||||
$("#option_toggle_AN").on('click', onANMenuItemClick);
|
||||
}
|
||||
|
||||
// Inject extension when extensions_activating is fired
|
||||
// Inserts the extension first since it's statically imported
|
||||
jQuery(async () => {
|
||||
await waitUntilCondition(() => eventSource !== undefined);
|
||||
eventSource.on(event_types.EXTENSIONS_FIRST_LOAD, addExtensionsSettings);
|
||||
|
||||
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);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
}, 1);
|
||||
});
|
||||
|
@@ -318,7 +318,12 @@ async function onExportClick() {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
toastr.error('An error occurred while attempting to download the data');
|
||||
//Show the error from the result without the html, only what's in the body paragraph
|
||||
let parser = new DOMParser();
|
||||
let error = await exportResult.text();
|
||||
let doc = parser.parseFromString(error, 'text/html');
|
||||
let errorMessage = doc.querySelector('p').textContent;
|
||||
toastr.error(`An error occurred while attempting to download the data from ChromaDB: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
17
public/scripts/extensions/regex/dropdown.html
Normal file
17
public/scripts/extensions/regex/dropdown.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<div class="regex_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Regex</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span>Open Editor</span>
|
||||
</div>
|
||||
<hr />
|
||||
<label>Saved Scripts</label>
|
||||
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
115
public/scripts/extensions/regex/editor.html
Normal file
115
public/scripts/extensions/regex/editor.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<div id="regex_editor_template">
|
||||
<div class="regex_editor">
|
||||
<h3><strong data-i18n="Regex Editor">Regex Editor</strong>
|
||||
<a href="https://regexr.com/" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<small class="flex-container extensions_info">
|
||||
Regex is a tool to find/replace strings using regular expressions. If you want to learn more, click on the ? next to the title.
|
||||
</small>
|
||||
<hr />
|
||||
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div class="flex1">
|
||||
<label for="regex_script_name" class="title_restorable">
|
||||
<small data-i18n="Script Name">Script Name</small>
|
||||
</label>
|
||||
<div>
|
||||
<input class="regex_script_name text_pole textarea_compact" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="find_regex" class="title_restorable">
|
||||
<small data-i18n="Find Regex">Find Regex</small>
|
||||
</label>
|
||||
<div>
|
||||
<input class="find_regex text_pole textarea_compact" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="regex_replace_string" class="title_restorable">
|
||||
<small data-i18n="Replace With">Replace With</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea
|
||||
class="regex_replace_string text_pole wide100p textarea_compact"
|
||||
placeholder="Use {{match}} to include the matched text from the Find Regex"
|
||||
rows="2"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="regex_trim_strings" class="title_restorable">
|
||||
<small data-i18n="Trim Out">Trim Out</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea
|
||||
class="regex_trim_strings text_pole wide100p textarea_compact"
|
||||
placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter."
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
|
||||
<small>Placement</small>
|
||||
<div>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="replace_position" value="0">
|
||||
<span data-i18n="Author's Note">Markdown Display</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="replace_position" value="1">
|
||||
<span data-i18n="Before Char">User Input</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="replace_position" value="2">
|
||||
<span data-i18n="After Char">AI Output</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="replace_position" value="3">
|
||||
<span data-i18n="Author's Note">/sys Command</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="replace_position" value="4">
|
||||
<span data-i18n="Author's Note">/sendas Command</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
|
||||
<small>Other Options</small>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="disabled" />
|
||||
<span data-i18n="Disabled">Disabled</span>
|
||||
</label>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="run_on_edit" />
|
||||
<span data-i18n="Run On Edit">Run On Edit</span>
|
||||
</label>
|
||||
<label class="checkbox flex-container">
|
||||
<input type="checkbox" name="substitute_regex" />
|
||||
<span data-i18n="Substitute Regex">Substitute Regex</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn alignitemsstart">
|
||||
<small>Replacement Strategy</small>
|
||||
<select name="replace_strategy_select" class="margin0">
|
||||
<option value="0">Replace</option>
|
||||
<option value="1">Overlay</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
163
public/scripts/extensions/regex/engine.js
Normal file
163
public/scripts/extensions/regex/engine.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { substituteParams } from "../../../script.js";
|
||||
import { extension_settings } from "../../extensions.js";
|
||||
export {
|
||||
regex_placement,
|
||||
getRegexedString,
|
||||
runRegexScript
|
||||
}
|
||||
|
||||
const regex_placement = {
|
||||
MD_DISPLAY: 0,
|
||||
USER_INPUT: 1,
|
||||
AI_OUTPUT: 2,
|
||||
SYSTEM: 3,
|
||||
SENDAS: 4
|
||||
}
|
||||
|
||||
const regex_replace_strategy = {
|
||||
REPLACE: 0,
|
||||
OVERLAY: 1
|
||||
}
|
||||
|
||||
// Originally from: https://github.com/IonicaBizau/regex-parser.js/blob/master/lib/index.js
|
||||
function regexFromString(input) {
|
||||
try {
|
||||
// Parse input
|
||||
var m = input.match(/(\/?)(.+)\1([a-z]*)/i);
|
||||
|
||||
// Invalid flags
|
||||
if (m[3] && !/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(m[3])) {
|
||||
return RegExp(input);
|
||||
}
|
||||
|
||||
// Create the regular expression
|
||||
return new RegExp(m[2], m[3]);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Parent function to fetch a regexed version of a raw string
|
||||
function getRegexedString(rawString, placement, { characterOverride } = {}) {
|
||||
if (extension_settings.disabledExtensions.includes("regex") || !rawString || placement === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let finalString;
|
||||
extension_settings.regex.forEach((script) => {
|
||||
if (script.placement.includes(placement)) {
|
||||
finalString = runRegexScript(script, rawString, { characterOverride });
|
||||
}
|
||||
});
|
||||
|
||||
return finalString;
|
||||
}
|
||||
|
||||
// Runs the provided regex script on the given string
|
||||
function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
|
||||
if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) {
|
||||
return;
|
||||
}
|
||||
|
||||
let match;
|
||||
let newString;
|
||||
const findRegex = regexFromString(regexScript.substituteRegex ? substituteParams(regexScript.findRegex) : regexScript.findRegex);
|
||||
|
||||
// The user skill issued. Return with nothing.
|
||||
if (!findRegex) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ((match = findRegex.exec(rawString)) !== null) {
|
||||
const fencedMatch = match[0];
|
||||
const capturedMatch = match[1];
|
||||
|
||||
let trimCapturedMatch;
|
||||
let trimFencedMatch;
|
||||
if (capturedMatch) {
|
||||
const tempTrimCapture = filterString(capturedMatch, regexScript.trimStrings, { characterOverride });
|
||||
trimFencedMatch = fencedMatch.replaceAll(capturedMatch, tempTrimCapture);
|
||||
trimCapturedMatch = tempTrimCapture;
|
||||
} else {
|
||||
trimFencedMatch = filterString(fencedMatch, regexScript.trimStrings, { characterOverride });
|
||||
}
|
||||
|
||||
// TODO: Use substrings for replacement. But not necessary at this time.
|
||||
// A substring is from match.index to match.index + match[0].length or fencedMatch.length
|
||||
const subReplaceString = substituteRegexParams(
|
||||
regexScript.replaceString,
|
||||
trimCapturedMatch ?? trimFencedMatch,
|
||||
{
|
||||
characterOverride,
|
||||
replaceStrategy: regexScript.replaceStrategy ?? regex_replace_strategy.REPLACE
|
||||
}
|
||||
);
|
||||
if (!newString) {
|
||||
newString = rawString.replace(fencedMatch, subReplaceString);
|
||||
} else {
|
||||
newString = newString.replace(fencedMatch, subReplaceString);
|
||||
}
|
||||
|
||||
// If the regex isn't global, break out of the loop
|
||||
if (!findRegex.flags.includes('g')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newString;
|
||||
}
|
||||
|
||||
// Filters anything to trim from the regex match
|
||||
function filterString(rawString, trimStrings, { characterOverride } = {}) {
|
||||
let finalString = rawString;
|
||||
trimStrings.forEach((trimString) => {
|
||||
const subTrimString = substituteParams(trimString, undefined, characterOverride);
|
||||
finalString = finalString.replaceAll(subTrimString, "");
|
||||
});
|
||||
|
||||
return finalString;
|
||||
}
|
||||
|
||||
// Substitutes regex-specific and normal parameters
|
||||
function substituteRegexParams(rawString, regexMatch, { characterOverride, replaceStrategy } = {}) {
|
||||
let finalString = rawString;
|
||||
finalString = substituteParams(finalString, undefined, characterOverride);
|
||||
|
||||
let overlaidMatch = regexMatch;
|
||||
if (replaceStrategy === regex_replace_strategy.OVERLAY) {
|
||||
const splitReplace = finalString.split("{{match}}");
|
||||
|
||||
// There's a prefix
|
||||
if (splitReplace[0]) {
|
||||
const splicedPrefix = spliceSymbols(splitReplace[0], false);
|
||||
overlaidMatch = overlaidMatch.replace(splicedPrefix, "").trim();
|
||||
}
|
||||
|
||||
// There's a suffix
|
||||
if (splitReplace[1]) {
|
||||
const splicedSuffix = spliceSymbols(splitReplace[1], true);
|
||||
overlaidMatch = overlaidMatch.replace(new RegExp(`${splicedSuffix}$`), "").trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Only one match is replaced. This is by design
|
||||
finalString = finalString.replace("{{match}}", overlaidMatch) || finalString.replace("{{match}}", regexMatch);
|
||||
|
||||
return finalString;
|
||||
}
|
||||
|
||||
// Splices symbols and whitespace from the beginning and end of a string
|
||||
// Using a for loop due to sequential ordering
|
||||
function spliceSymbols(rawString, isSuffix) {
|
||||
let offset = 0;
|
||||
|
||||
for (const ch of isSuffix ? rawString.split('').reverse() : rawString) {
|
||||
if (ch.match(/[^\w.,?'!]/)) {
|
||||
offset++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isSuffix ? rawString.substring(0, rawString.length - offset) : rawString.substring(offset);;
|
||||
}
|
194
public/scripts/extensions/regex/index.js
Normal file
194
public/scripts/extensions/regex/index.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import { callPopup, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from "../../../script.js";
|
||||
import { extension_settings } from "../../extensions.js";
|
||||
import { uuidv4, waitUntilCondition } from "../../utils.js";
|
||||
import { regex_placement } from "./engine.js";
|
||||
|
||||
async function saveRegexScript(regexScript, existingScriptIndex) {
|
||||
// If not editing
|
||||
if (existingScriptIndex === -1) {
|
||||
// Is the script name undefined?
|
||||
if (!regexScript.scriptName) {
|
||||
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Does the script name already exist?
|
||||
if (extension_settings.regex.find((e) => e.scriptName === regexScript.scriptName)) {
|
||||
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Does the script name already exist somewhere else?
|
||||
// (If this fails, make it a .filter().map() to index array)
|
||||
const foundIndex = extension_settings.regex.findIndex((e) => e.scriptName === regexScript.scriptName);
|
||||
if (foundIndex !== existingScriptIndex && foundIndex !== -1) {
|
||||
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Is a find regex present?
|
||||
if (regexScript.findRegex.length === 0) {
|
||||
toastr.error(`Could not save regex script: A find regex is required!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is there someplace to place results?
|
||||
if (regexScript.placement.length === 0) {
|
||||
toastr.error(`Could not save regex script: One placement checkbox must be selected!`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingScriptIndex !== -1) {
|
||||
extension_settings.regex[existingScriptIndex] = regexScript;
|
||||
} else {
|
||||
extension_settings.regex.push(regexScript);
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await loadRegexScripts();
|
||||
|
||||
// Markdown is global, so reload the chat.
|
||||
if (regexScript.placement.includes(regex_placement.MD_DISPLAY)) {
|
||||
const currentChatId = getCurrentChatId();
|
||||
if (currentChatId !== undefined && currentChatId !== null) {
|
||||
await reloadCurrentChat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRegexScript({ existingId }) {
|
||||
let scriptName = $(`#${existingId}`).find('.regex_script_name').text();
|
||||
|
||||
const existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === scriptName);
|
||||
if (!existingScriptIndex || existingScriptIndex !== -1) {
|
||||
extension_settings.regex.splice(existingScriptIndex, 1);
|
||||
|
||||
saveSettingsDebounced();
|
||||
await loadRegexScripts();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRegexScripts() {
|
||||
$("#saved_regex_scripts").empty();
|
||||
|
||||
const scriptTemplate = $(await $.get("scripts/extensions/regex/scriptTemplate.html"));
|
||||
|
||||
extension_settings.regex.forEach((script) => {
|
||||
// Have to clone here
|
||||
const scriptHtml = scriptTemplate.clone();
|
||||
scriptHtml.attr('id', uuidv4());
|
||||
scriptHtml.find('.regex_script_name').text(script.scriptName);
|
||||
scriptHtml.find('.edit_existing_regex').on('click', async function() {
|
||||
await onRegexEditorOpenClick(scriptHtml.attr("id"));
|
||||
});
|
||||
scriptHtml.find('.delete_regex').on('click', async function() {
|
||||
await deleteRegexScript({ existingId: scriptHtml.attr("id") });
|
||||
});
|
||||
|
||||
$("#saved_regex_scripts").append(scriptHtml);
|
||||
});
|
||||
}
|
||||
|
||||
async function onRegexEditorOpenClick(existingId) {
|
||||
const editorHtml = $(await $.get("scripts/extensions/regex/editor.html"));
|
||||
|
||||
// If an ID exists, fill in all the values
|
||||
let existingScriptIndex = -1;
|
||||
if (existingId) {
|
||||
const existingScriptName = $(`#${existingId}`).find('.regex_script_name').text();
|
||||
existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === existingScriptName);
|
||||
if (existingScriptIndex !== -1) {
|
||||
const existingScript = extension_settings.regex[existingScriptIndex];
|
||||
if (existingScript.scriptName) {
|
||||
editorHtml.find(`.regex_script_name`).val(existingScript.scriptName);
|
||||
} else {
|
||||
toastr.error("This script doesn't have a name! Please delete it.")
|
||||
return;
|
||||
}
|
||||
|
||||
editorHtml.find(`.find_regex`).val(existingScript.findRegex || "");
|
||||
editorHtml.find(`.regex_replace_string`).val(existingScript.replaceString || "");
|
||||
editorHtml.find(`.regex_trim_strings`).val(existingScript.trimStrings?.join("\n") || []);
|
||||
editorHtml
|
||||
.find(`input[name="disabled"]`)
|
||||
.prop("checked", existingScript.disabled ?? false);
|
||||
editorHtml
|
||||
.find(`input[name="run_on_edit"]`)
|
||||
.prop("checked", existingScript.runOnEdit ?? false);
|
||||
editorHtml
|
||||
.find(`input[name="substitute_regex"]`)
|
||||
.prop("checked", existingScript.substituteRegex ?? false);
|
||||
editorHtml
|
||||
.find(`select[name="replace_strategy_select"]`)
|
||||
.val(existingScript.replaceStrategy ?? 0);
|
||||
|
||||
existingScript.placement.forEach((element) => {
|
||||
editorHtml
|
||||
.find(`input[name="replace_position"][value="${element}"]`)
|
||||
.prop("checked", true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
editorHtml
|
||||
.find(`input[name="run_on_edit"]`)
|
||||
.prop("checked", true);
|
||||
|
||||
editorHtml
|
||||
.find(`input[name="replace_position"][value="0"]`)
|
||||
.prop("checked", true);
|
||||
}
|
||||
|
||||
const popupResult = await callPopup(editorHtml, "confirm", undefined, "Save");
|
||||
if (popupResult) {
|
||||
const newRegexScript = {
|
||||
scriptName: editorHtml.find(".regex_script_name").val(),
|
||||
findRegex: editorHtml.find(".find_regex").val(),
|
||||
replaceString: editorHtml.find(".regex_replace_string").val(),
|
||||
trimStrings: editorHtml.find(".regex_trim_strings").val().split("\n").filter((e) => e.length !== 0) || [],
|
||||
placement:
|
||||
editorHtml
|
||||
.find(`input[name="replace_position"]`)
|
||||
.filter(":checked")
|
||||
.map(function() { return parseInt($(this).val()) })
|
||||
.get()
|
||||
.filter((e) => e !== NaN) || [],
|
||||
disabled:
|
||||
editorHtml
|
||||
.find(`input[name="disabled"]`)
|
||||
.prop("checked"),
|
||||
runOnEdit:
|
||||
editorHtml
|
||||
.find(`input[name="run_on_edit"]`)
|
||||
.prop("checked"),
|
||||
substituteRegex:
|
||||
editorHtml
|
||||
.find(`input[name="substitute_regex"]`)
|
||||
.prop("checked"),
|
||||
replaceStrategy:
|
||||
parseInt(editorHtml
|
||||
.find(`select[name="replace_strategy_select"]`)
|
||||
.find(`:selected`)
|
||||
.val()) ?? 0
|
||||
};
|
||||
|
||||
saveRegexScript(newRegexScript, existingScriptIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for loading in sequence with other extensions
|
||||
// NOTE: Always puts extension at the top of the list, but this is fine since it's static
|
||||
jQuery(async () => {
|
||||
// Manually disable the extension since static imports auto-import the JS file
|
||||
if (extension_settings.disabledExtensions.includes("regex")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsHtml = await $.get("scripts/extensions/regex/dropdown.html");
|
||||
$("#extensions_settings2").append(settingsHtml);
|
||||
$("#open_regex_editor").on("click", function() {
|
||||
onRegexEditorOpenClick(false);
|
||||
});
|
||||
|
||||
await loadRegexScripts();
|
||||
});
|
11
public/scripts/extensions/regex/manifest.json
Normal file
11
public/scripts/extensions/regex/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Regex",
|
||||
"loading_order": 1,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "kingbri",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
11
public/scripts/extensions/regex/scriptTemplate.html
Normal file
11
public/scripts/extensions/regex/scriptTemplate.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="regex-script-label flex-container flexnowrap">
|
||||
<div class="regex_script_name flexGrow overflow-hidden"></div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<div class="edit_existing_regex menu_button">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</div>
|
||||
<div class="delete_regex menu_button">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
public/scripts/extensions/regex/style.css
Normal file
20
public/scripts/extensions/regex/style.css
Normal file
@@ -0,0 +1,20 @@
|
||||
.regex_settings .menu_button {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.regex-script-container {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.regex-script-label {
|
||||
align-items: center;
|
||||
border: 1px solid rgba(128, 128, 128, 0.5);
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
}
|
@@ -1234,7 +1234,7 @@ function filterGroupMembers() {
|
||||
$("#rm_group_add_members .group_member").removeClass('hiddenBySearch');
|
||||
} else {
|
||||
$("#rm_group_add_members .group_member").each(function () {
|
||||
const isValidSearch = $(this).children(".ch_name").text().toLowerCase().includes(searchValue);
|
||||
const isValidSearch = $(this).find(".ch_name").text().toLowerCase().includes(searchValue);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
}
|
||||
@@ -1440,7 +1440,7 @@ export async function importGroupChat(formData) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveGroupBookmarkChat(groupId, name, metadata) {
|
||||
export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
if (!group) {
|
||||
@@ -1450,12 +1450,16 @@ export async function saveGroupBookmarkChat(groupId, name, metadata) {
|
||||
group.past_metadata[name] = { ...chat_metadata, ...(metadata || {}) };
|
||||
group.chats.push(name);
|
||||
|
||||
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
|
||||
? chat.slice(0, parseInt(mesId) + 1)
|
||||
: chat;
|
||||
|
||||
await editGroup(groupId, true);
|
||||
|
||||
await fetch("/savegroupchat", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ id: name, chat: [...chat] }),
|
||||
body: JSON.stringify({ id: name, chat: [...trimmed_chat] }),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -81,11 +81,12 @@ const default_bias_presets = {
|
||||
]
|
||||
};
|
||||
|
||||
const gpt3_max = 4095;
|
||||
const gpt3_16k_max = 16383;
|
||||
const gpt4_max = 8191;
|
||||
const gpt_neox_max = 2048;
|
||||
const gpt4_32k_max = 32767;
|
||||
const max_2k = 2047;
|
||||
const max_4k = 4095;
|
||||
const max_8k = 8191;
|
||||
const max_16k = 16383;
|
||||
const max_32k = 32767;
|
||||
const scale_max = 7900; // Probably more. Save some for the system prompt defined on Scale site.
|
||||
const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer
|
||||
const claude_100k_max = 99000;
|
||||
@@ -100,6 +101,7 @@ export const chat_completion_sources = {
|
||||
OPENAI: 'openai',
|
||||
WINDOWAI: 'windowai',
|
||||
CLAUDE: 'claude',
|
||||
SCALE: 'scale',
|
||||
};
|
||||
|
||||
const default_settings = {
|
||||
@@ -110,7 +112,7 @@ const default_settings = {
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
openai_max_context: gpt3_max,
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
nsfw_toggle: true,
|
||||
enhance_definitions: false,
|
||||
@@ -134,6 +136,7 @@ const default_settings = {
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
use_openrouter: false,
|
||||
api_url_scale: '',
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -144,7 +147,7 @@ const oai_settings = {
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
openai_max_context: gpt3_max,
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
nsfw_toggle: true,
|
||||
enhance_definitions: false,
|
||||
@@ -168,6 +171,7 @@ const oai_settings = {
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
use_openrouter: false,
|
||||
api_url_scale: '',
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -668,6 +672,8 @@ function getChatCompletionModel() {
|
||||
return oai_settings.openai_model;
|
||||
case chat_completion_sources.WINDOWAI:
|
||||
return oai_settings.windowai_model;
|
||||
case chat_completion_sources.SCALE:
|
||||
return '';
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||
}
|
||||
@@ -679,14 +685,12 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
signal = new AbortController().signal;
|
||||
}
|
||||
|
||||
if (oai_settings.reverse_proxy) {
|
||||
validateReverseProxy();
|
||||
}
|
||||
|
||||
let logit_bias = {};
|
||||
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
|
||||
const isOpenRouter = oai_settings.use_openrouter && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI;
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai;
|
||||
const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
|
||||
const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('text-') || oai_settings.openai_model.startsWith('code-'));
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai && !isScale;
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
@@ -713,12 +717,28 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
"top_k": parseFloat(oai_settings.top_k_openai),
|
||||
"max_tokens": oai_settings.openai_max_tokens,
|
||||
"stream": stream,
|
||||
"reverse_proxy": oai_settings.reverse_proxy,
|
||||
"logit_bias": logit_bias,
|
||||
"use_claude": isClaude,
|
||||
"use_openrouter": isOpenRouter,
|
||||
};
|
||||
|
||||
// Proxy is only supported for Claude and OpenAI
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) {
|
||||
validateReverseProxy();
|
||||
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
|
||||
}
|
||||
|
||||
if (isClaude) {
|
||||
generate_data['use_claude'] = true;
|
||||
}
|
||||
|
||||
if (isOpenRouter) {
|
||||
generate_data['use_openrouter'] = true;
|
||||
}
|
||||
|
||||
if (isScale) {
|
||||
generate_data['use_scale'] = true;
|
||||
generate_data['api_url_scale'] = oai_settings.api_url_scale;
|
||||
}
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
const response = await fetch(generate_url, {
|
||||
method: 'POST',
|
||||
@@ -785,7 +805,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
throw new Error(data);
|
||||
}
|
||||
|
||||
return data.choices[0]["message"]["content"];
|
||||
return !isTextCompletion ? data.choices[0]["message"]["content"] : data.choices[0]["text"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,7 +813,7 @@ function getStreamingReply(getMessage, data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
getMessage = data.completion || "";
|
||||
} else {
|
||||
getMessage += data.choices[0]?.delta?.content || data.choices[0]?.message?.content || "";
|
||||
getMessage += data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || "";
|
||||
}
|
||||
return getMessage;
|
||||
}
|
||||
@@ -943,6 +963,11 @@ export function getTokenizerModel() {
|
||||
return oai_settings.openai_model;
|
||||
}
|
||||
|
||||
// Assuming no one would use it for different models.. right?
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
return 'gpt-4';
|
||||
}
|
||||
|
||||
const turboTokenizer = 'gpt-3.5-turbo'
|
||||
// Select correct tokenizer for WindowAI proxies
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
@@ -1006,6 +1031,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.use_openrouter = settings.use_openrouter ?? default_settings.use_openrouter;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
|
||||
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
|
||||
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
|
||||
@@ -1016,6 +1042,7 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.jailbreak_system !== undefined) oai_settings.jailbreak_system = !!settings.jailbreak_system;
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
|
||||
$('#model_openai_select').val(oai_settings.openai_model);
|
||||
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
|
||||
@@ -1102,7 +1129,7 @@ async function getStatusOpen() {
|
||||
return resultCheckStatusOpen();
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
let status = 'Unable to verify key; press "Test Message" to validate.';
|
||||
setOnlineStatus(status);
|
||||
return resultCheckStatusOpen();
|
||||
@@ -1211,6 +1238,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
|
||||
wi_format: settings.wi_format,
|
||||
stream_openai: settings.stream_openai,
|
||||
api_url_scale: settings.api_url_scale,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1561,6 +1589,7 @@ function onSettingsPresetChange() {
|
||||
wi_format: ['#wi_format_textarea', 'wi_format', false],
|
||||
stream_openai: ['#stream_toggle', 'stream_openai', true],
|
||||
use_openrouter: ['#use_openrouter', 'use_openrouter', true],
|
||||
api_url_scale: ['#api_url_scale', 'api_url_scale', false],
|
||||
};
|
||||
|
||||
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
|
||||
@@ -1597,6 +1626,16 @@ async function onModelChange() {
|
||||
oai_settings.openai_model = value;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', scale_max);
|
||||
}
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
@@ -1632,26 +1671,26 @@ async function onModelChange() {
|
||||
$('#openai_max_context').attr('max', claude_max);
|
||||
}
|
||||
else if (value.includes('gpt-3.5-turbo-16k')) {
|
||||
$('#openai_max_context').attr('max', gpt3_16k_max);
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
}
|
||||
else if (value.includes('gpt-3.5')) {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
}
|
||||
else if (value.includes('gpt-4-32k')) {
|
||||
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
}
|
||||
else if (value.includes('gpt-4')) {
|
||||
$('#openai_max_context').attr('max', gpt4_max);
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else if (value.includes('palm-2')) {
|
||||
$('#openai_max_context').attr('max', palm2_max);
|
||||
}
|
||||
else if (value.includes('GPT-NeoXT')) {
|
||||
$('#openai_max_context').attr('max', gpt_neox_max);
|
||||
$('#openai_max_context').attr('max', max_2k);
|
||||
}
|
||||
else {
|
||||
// default to gpt-3 (4095 tokens)
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
@@ -1671,17 +1710,23 @@ async function onModelChange() {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
else if (value == 'gpt-4' || value == 'gpt-4-0314' || value == 'gpt-4-0613') {
|
||||
$('#openai_max_context').attr('max', gpt4_max);
|
||||
else if (['gpt-4', 'gpt-4-0314', 'gpt-4-0613'].includes(value)) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else if (value == 'gpt-4-32k' || value == 'gpt-4-32k-0314' || value == 'gpt-4-32k-0613') {
|
||||
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||
else if (['gpt-4-32k', 'gpt-4-32k-0314', 'gpt-4-32k-0613'].includes(value)) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
}
|
||||
else if (value == 'gpt-3.5-turbo-16k' || value == 'gpt-3.5-turbo-16k-0613') {
|
||||
$('#openai_max_context').attr('max', gpt3_16k_max);
|
||||
else if (['gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613'].includes(value)) {
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
}
|
||||
else if (value == 'code-davinci-002') {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else if (['text-curie-001', 'text-babbage-001', 'text-ada-001'].includes(value)) {
|
||||
$('#openai_max_context').attr('max', max_2k);
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
@@ -1737,6 +1782,24 @@ async function onConnectButtonClick(e) {
|
||||
return await getStatusOpen();
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
const api_key_scale = $('#api_key_scale').val().trim();
|
||||
|
||||
if (api_key_scale.length) {
|
||||
await writeSecret(SECRET_KEYS.SCALE, api_key_scale);
|
||||
}
|
||||
|
||||
if (!oai_settings.api_url_scale) {
|
||||
console.log('No API URL saved for Scale');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.SCALE]) {
|
||||
console.log('No secret key saved for Scale');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
const api_key_claude = $('#api_key_claude').val().trim();
|
||||
|
||||
@@ -1781,6 +1844,9 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
$('#model_windowai_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
$('#model_scale_select').trigger('change');
|
||||
}
|
||||
|
||||
$('[data-source]').each(function () {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
@@ -2013,11 +2079,17 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#api_url_scale').on('input', function () {
|
||||
oai_settings.api_url_scale = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#api_button_openai").on("click", onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
|
||||
$("#model_openai_select").on("change", onModelChange);
|
||||
$("#model_claude_select").on("change", onModelChange);
|
||||
$("#model_windowai_select").on("change", onModelChange);
|
||||
$("#model_scale_select").on("change", onModelChange);
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
|
@@ -237,6 +237,9 @@ async function autoJailbreak() {
|
||||
auto_jailbroken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Purge the conversation if we're not jailbroken
|
||||
await purgeConversation(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -317,6 +317,32 @@ function switchWaifuMode() {
|
||||
scrollChatToBottom();
|
||||
}
|
||||
|
||||
function switchSpoilerMode() {
|
||||
if (power_user.spoiler_free_mode) {
|
||||
$("#description_div").hide();
|
||||
$("#description_textarea").hide();
|
||||
$("#firstmessage_textarea").hide();
|
||||
$("#first_message_div").hide();
|
||||
$("#spoiler_free_desc").show();
|
||||
}
|
||||
else {
|
||||
$("#description_div").show();
|
||||
$("#description_textarea").show();
|
||||
$("#firstmessage_textarea").show();
|
||||
$("#first_message_div").show();
|
||||
$("#spoiler_free_desc").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function peekSpoilerMode() {
|
||||
$("#description_div").toggle();
|
||||
$("#description_textarea").toggle();
|
||||
$("#firstmessage_textarea").toggle();
|
||||
$("#first_message_div").toggle();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function switchMovingUI() {
|
||||
const movingUI = localStorage.getItem(storage_keys.movingUI);
|
||||
power_user.movingUI = movingUI === null ? false : movingUI == "true";
|
||||
@@ -634,6 +660,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true);
|
||||
$("#import_card_tags").prop("checked", power_user.import_card_tags);
|
||||
$("#confirm_message_delete").prop("checked", power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
|
||||
$("#spoiler_free_mode").prop("checked", power_user.spoiler_free_mode);
|
||||
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
|
||||
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
|
||||
$("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting);
|
||||
@@ -707,6 +734,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
loadInstructMode();
|
||||
loadMaxContextUnlocked();
|
||||
switchWaifuMode();
|
||||
switchSpoilerMode();
|
||||
loadMovingUIState();
|
||||
|
||||
//console.log(power_user)
|
||||
@@ -1140,7 +1168,7 @@ function setAvgBG() {
|
||||
.attr('src')
|
||||
.replace(/^url\(['"]?/, '')
|
||||
.replace(/['"]?\)$/, '');
|
||||
|
||||
|
||||
const userAvatar = new Image()
|
||||
userAvatar.src = $("#user_avatar_block .avatar.selected img")
|
||||
.attr('src')
|
||||
@@ -1159,7 +1187,7 @@ function setAvgBG() {
|
||||
.replace('rgb', '')
|
||||
.replace('(', '[')
|
||||
.replace(')', ']'); //[50, 120, 200, 1]; // Example background color
|
||||
const backgroundColorArray = JSON.parse(backgroundColorString) //[200, 200, 200, 1]
|
||||
const backgroundColorArray = JSON.parse(backgroundColorString) //[200, 200, 200, 1]
|
||||
console.log(backgroundColorArray)
|
||||
$("#main-text-color-picker").attr('color', getReadableTextColor(backgroundColorArray));
|
||||
console.log($("#main-text-color-picker").attr('color')); // Output: 'rgba(0, 47, 126, 1)'
|
||||
@@ -1171,7 +1199,7 @@ function setAvgBG() {
|
||||
//console.log(rgb);
|
||||
$("#bot-mes-blur-tint-color-picker").attr('color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')');
|
||||
}
|
||||
|
||||
|
||||
userAvatar.onload = function () {
|
||||
var rgb = getAverageRGB(userAvatar);
|
||||
//console.log(`average color of the user avatar is:`);
|
||||
@@ -1274,16 +1302,16 @@ function setAvgBG() {
|
||||
//this version keeps BG and main text in same hue
|
||||
/* function getReadableTextColor(rgb) {
|
||||
const [r, g, b] = rgb;
|
||||
|
||||
|
||||
// Convert RGB to HSL
|
||||
const rgbToHsl = (r, g, b) => {
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const d = max - min;
|
||||
const l = (max + min) / 2;
|
||||
|
||||
|
||||
if (d === 0) return [0, 0, l];
|
||||
|
||||
|
||||
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
const h = (() => {
|
||||
switch (max) {
|
||||
@@ -1295,16 +1323,16 @@ function setAvgBG() {
|
||||
return (r - g) / d + 4;
|
||||
}
|
||||
})() / 6;
|
||||
|
||||
|
||||
return [h, s, l];
|
||||
};
|
||||
const [h, s, l] = rgbToHsl(r / 255, g / 255, b / 255);
|
||||
|
||||
|
||||
// Calculate appropriate text color based on background color
|
||||
const targetLuminance = l > 0.5 ? 0.2 : 0.8;
|
||||
const targetSaturation = s > 0.5 ? s - 0.2 : s + 0.2;
|
||||
const [rNew, gNew, bNew] = hslToRgb(h, targetSaturation, targetLuminance);
|
||||
|
||||
|
||||
// Return the text color in RGBA format
|
||||
return `rgba(${rNew.toFixed(0)}, ${gNew.toFixed(0)}, ${bNew.toFixed(0)}, 1)`;
|
||||
}*/
|
||||
@@ -1793,6 +1821,17 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#spoiler_free_mode').on('input', function () {
|
||||
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
||||
switchSpoilerMode();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#spoiler_free_desc_button').on('click', function () {
|
||||
peekSpoilerMode();
|
||||
$(this).toggleClass('fa-eye fa-eye-slash');
|
||||
});
|
||||
|
||||
$(window).on('focus', function () {
|
||||
browser_has_focus = true;
|
||||
});
|
||||
|
@@ -7,6 +7,7 @@ export const SECRET_KEYS = {
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -16,6 +17,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
|
||||
[SECRET_KEYS.SCALE]: '#api_key_scale',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
|
1
public/scripts/seedrandom.min.js
vendored
Normal file
1
public/scripts/seedrandom.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);
|
@@ -22,6 +22,7 @@ import {
|
||||
} from "../script.js";
|
||||
import { humanizedDateTime } from "./RossAscends-mods.js";
|
||||
import { resetSelectedGroup } from "./group-chats.js";
|
||||
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
|
||||
import { chat_styles, power_user } from "./power-user.js";
|
||||
export {
|
||||
executeSlashCommands,
|
||||
@@ -218,14 +219,18 @@ async function sendMessageAs(_, text) {
|
||||
}
|
||||
|
||||
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();
|
||||
let mesText = parts.join('\n').trim();
|
||||
const regexResult = getRegexedString(mesText, regex_placement.SENDAS, { characterOverride: name });
|
||||
if (regexResult) {
|
||||
mesText = regexResult;
|
||||
}
|
||||
|
||||
// Messages that do nothing but set bias will be hidden from the context
|
||||
const bias = extractMessageBias(mesText);
|
||||
const isSystem = replaceBiasMarkup(mesText).trim().length === 0;
|
||||
@@ -268,6 +273,11 @@ async function sendNarratorMessage(_, text) {
|
||||
return;
|
||||
}
|
||||
|
||||
const regexResult = getRegexedString(text, regex_placement.SYSTEM);
|
||||
if (regexResult) {
|
||||
text = regexResult;
|
||||
}
|
||||
|
||||
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
|
||||
// Messages that do nothing but set bias will be hidden from the context
|
||||
const bias = extractMessageBias(text);
|
||||
@@ -333,6 +343,10 @@ function helpCommandCallback(_, type) {
|
||||
case '3':
|
||||
sendSystemMessage(system_message_types.HOTKEYS);
|
||||
break;
|
||||
case 'macros':
|
||||
case '4':
|
||||
sendSystemMessage(system_message_types.MACROS);
|
||||
break;
|
||||
default:
|
||||
sendSystemMessage(system_message_types.HELP);
|
||||
break;
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
menu_type,
|
||||
updateVisibleDivs,
|
||||
getCharacters,
|
||||
updateCharacterCount,
|
||||
} from "../script.js";
|
||||
|
||||
import { selected_group } from "./group-chats.js";
|
||||
@@ -82,6 +83,7 @@ function applyFavFilter(characterSelector) {
|
||||
}
|
||||
|
||||
});
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
@@ -94,6 +96,7 @@ function filterByGroups(characterSelector) {
|
||||
$(characterSelector).each((_, element) => {
|
||||
$(element).toggleClass('hiddenByGroup', displayGroupsOnly && !$(element).hasClass('group_select'));
|
||||
});
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
@@ -355,6 +358,7 @@ function onTagFilterClick(listElement, characterSelector) {
|
||||
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
$(characterSelector).each((_, element) => applyFilterToElement(tagIds, excludedTagIds, element));
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
|
@@ -676,3 +676,7 @@ export function uuidv4() {
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type } from "../script.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, delay, getCharaFilename } from "./utils.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, delay, getCharaFilename, deepClone } from "./utils.js";
|
||||
import { getContext } from "./extensions.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./extensions/floating-prompt/index.js";
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
@@ -363,6 +363,24 @@ function appendWorldEntry(name, data, entry) {
|
||||
keyInput.val(entry.key.join(",")).trigger("input");
|
||||
initScrollHeight(keyInput);
|
||||
|
||||
// logic AND/NOT
|
||||
const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
|
||||
selectiveLogicDropdown.data("uid", entry.uid);
|
||||
|
||||
selectiveLogicDropdown.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = Number($(this).val());
|
||||
console.debug(`logic for ${entry.uid} set to ${value}`)
|
||||
data.entries[uid].selectiveLogic = !isNaN(value) ? value : 0;
|
||||
setOriginalDataValue(data, uid, "selectiveLogic", data.entries[uid].selectiveLogic);
|
||||
saveWorldInfo(name, data);
|
||||
|
||||
});
|
||||
template
|
||||
.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
|
||||
.prop("selected", true)
|
||||
.trigger("input");
|
||||
|
||||
// keysecondary
|
||||
const keySecondaryInput = template.find('textarea[name="keysecondary"]');
|
||||
keySecondaryInput.data("uid", entry.uid);
|
||||
@@ -465,6 +483,7 @@ function appendWorldEntry(name, data, entry) {
|
||||
value ? keysecondary.show() : keysecondary.hide();
|
||||
|
||||
});
|
||||
//forced on, ignored if empty
|
||||
selectiveInput.prop("checked", true /* entry.selective */).trigger("input");
|
||||
selectiveInput.parent().hide();
|
||||
|
||||
@@ -548,6 +567,7 @@ function appendWorldEntry(name, data, entry) {
|
||||
|
||||
probabilityInput.val(data.entries[uid].probability).trigger("input");
|
||||
});
|
||||
//forced on, 100% by default
|
||||
probabilityToggle.prop("checked", true /* entry.useProbability */).trigger("input");
|
||||
probabilityToggle.parent().hide();
|
||||
|
||||
@@ -632,14 +652,15 @@ function createWorldInfoEntry(name, data) {
|
||||
comment: "",
|
||||
content: "",
|
||||
constant: false,
|
||||
selective: false,
|
||||
selective: true,
|
||||
selectiveLogic: 0,
|
||||
addMemo: false,
|
||||
order: 100,
|
||||
position: 0,
|
||||
disable: false,
|
||||
excludeRecursion: false,
|
||||
probability: null,
|
||||
useProbability: false,
|
||||
probability: 100,
|
||||
useProbability: true,
|
||||
};
|
||||
const newUid = getFreeWorldEntryUid(data);
|
||||
|
||||
@@ -877,7 +898,8 @@ async function getSortedEntries() {
|
||||
|
||||
console.debug(`Sorted ${entries.length} world lore entries using strategy ${world_info_character_strategy}`);
|
||||
|
||||
return entries;
|
||||
// Need to deep clone the entries to avoid modifying the cached data
|
||||
return deepClone(entries);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
@@ -919,31 +941,56 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
}
|
||||
|
||||
if (entry.constant) {
|
||||
entry.content = substituteParams(entry.content)
|
||||
activatedNow.add(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(entry.key) && entry.key.length) {
|
||||
if (Array.isArray(entry.key) && entry.key.length) { //check for keywords existing
|
||||
primary: for (let key of entry.key) {
|
||||
const substituted = substituteParams(key);
|
||||
console.debug(`${entry.uid}: ${substituted}`)
|
||||
if (substituted && matchKeys(textToScan, substituted.trim())) {
|
||||
console.debug(`${entry.uid}: got primary match`)
|
||||
//selective logic begins
|
||||
if (
|
||||
entry.selective &&
|
||||
Array.isArray(entry.keysecondary) &&
|
||||
entry.keysecondary.length
|
||||
entry.selective && //all entries are selective now
|
||||
Array.isArray(entry.keysecondary) && //always true
|
||||
entry.keysecondary.length //ignore empties
|
||||
) {
|
||||
console.debug(`uid:${entry.uid}: checking logic: ${entry.selectiveLogic}`)
|
||||
secondary: for (let keysecondary of entry.keysecondary) {
|
||||
const secondarySubstituted = substituteParams(keysecondary);
|
||||
if (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) {
|
||||
activatedNow.add(entry);
|
||||
break secondary;
|
||||
console.debug(`uid:${entry.uid}: filtering ${secondarySubstituted}`)
|
||||
//AND operator
|
||||
if (entry.selectiveLogic === 0) {
|
||||
console.debug('saw AND logic, checking..')
|
||||
if (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) {
|
||||
console.log(`activating entry ${entry.uid} with AND found`)
|
||||
activatedNow.add(entry);
|
||||
break secondary;
|
||||
}
|
||||
}
|
||||
//NOT operator
|
||||
if (entry.selectiveLogic === 1) {
|
||||
console.debug(`uid ${entry.uid}: checking NOT logic for ${secondarySubstituted}`)
|
||||
if (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) {
|
||||
console.debug(`uid ${entry.uid}: canceled; filtered out by ${secondarySubstituted}`)
|
||||
break primary;
|
||||
} else {
|
||||
console.debug(`${entry.uid}: activated; passed NOT filter`)
|
||||
activatedNow.add(entry);
|
||||
break secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
//handle cases where secondary is empty
|
||||
} else {
|
||||
console.debug(`uid ${entry.uid}: activated without filter logic`)
|
||||
activatedNow.add(entry);
|
||||
break primary;
|
||||
}
|
||||
}
|
||||
} else { console.debug('no active entries for logic checks yet') }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -954,15 +1001,16 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
let newContent = "";
|
||||
const textToScanTokens = getTokenCount(allActivatedText);
|
||||
const probabilityChecksBefore = failedProbabilityChecks.size;
|
||||
|
||||
console.debug(`-- PROBABILITY CHECKS BEGIN --`)
|
||||
for (const entry of newEntries) {
|
||||
const rollValue = Math.random() * 100;
|
||||
|
||||
|
||||
if (entry.useProbability && rollValue > entry.probability) {
|
||||
console.debug(`WI entry ${entry.key} failed probability check, skipping`);
|
||||
console.debug(`WI entry ${entry.uid} ${entry.key} failed probability check, skipping`);
|
||||
failedProbabilityChecks.add(entry);
|
||||
continue;
|
||||
}
|
||||
} else { console.debug(`uid:${entry.uid} passed probability check, inserting to prompt`) }
|
||||
|
||||
newContent += `${substituteParams(entry.content)}\n`;
|
||||
|
||||
|
@@ -1159,6 +1159,12 @@ input[type="file"] {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
#rm_character_count {
|
||||
padding: 5px;
|
||||
font-size: calc(var(--mainFontSize) * .8);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#rm_print_characters_block {
|
||||
/* padding: 5px 0; */
|
||||
overflow-y: auto;
|
||||
@@ -1408,6 +1414,10 @@ body.big-avatars .ch_description {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.alignItemsFlexEnd {
|
||||
align-items: flex-end !important;
|
||||
}
|
||||
|
||||
.alignSelfStart {
|
||||
align-self: start;
|
||||
}
|
||||
@@ -1885,6 +1895,10 @@ grammarly-extension {
|
||||
background-color: var(--white30a);
|
||||
}
|
||||
|
||||
.height32px {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2127,9 +2141,9 @@ grammarly-extension {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.world_entry_thin_controls>div {
|
||||
/* .world_entry_thin_controls>div {
|
||||
flex: 1;
|
||||
}
|
||||
} */
|
||||
|
||||
.world_entry_form_control label h4 {
|
||||
margin-bottom: 0;
|
||||
@@ -2470,6 +2484,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
|
||||
.mes_buttons .mes_edit,
|
||||
.mes_buttons .mes_bookmark,
|
||||
.mes_buttons .mes_create_bookmark,
|
||||
.extraMesButtonsHint,
|
||||
.tagListHint,
|
||||
.extraMesButtons div {
|
||||
@@ -2481,6 +2496,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
|
||||
.mes_buttons .mes_edit:hover,
|
||||
.mes_buttons .mes_bookmark:hover,
|
||||
.mes_buttons .mes_create_bookmark:hover,
|
||||
.extraMesButtonsHint:hover,
|
||||
.tagListHint:hover,
|
||||
.extraMesButtons div:hover {
|
||||
@@ -3997,6 +4013,16 @@ toolcool-color-picker {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alignitemsstart {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.padding5 {
|
||||
padding: 5px;
|
||||
}
|
||||
@@ -4060,6 +4086,10 @@ toolcool-color-picker {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justifyContentFlexStart {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justifyContentFlexEnd {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -4087,6 +4117,10 @@ toolcool-color-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wide50p {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.wide50px {
|
||||
width: 50px;
|
||||
}
|
||||
@@ -5058,4 +5092,5 @@ body.waifuMode .zoomed_avatar {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
197
server.js
197
server.js
@@ -201,6 +201,10 @@ function getTokenizerModel(requestModel) {
|
||||
return 'gpt-3.5-turbo';
|
||||
}
|
||||
|
||||
if (requestModel.startsWith('text-') || requestModel.startsWith('code-')) {
|
||||
return requestModel;
|
||||
}
|
||||
|
||||
// default
|
||||
return 'gpt-3.5-turbo';
|
||||
}
|
||||
@@ -1790,9 +1794,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('importing from v1 json');
|
||||
jsonData.name = sanitize(jsonData.name);
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
png_name = getPngName(jsonData.name);
|
||||
let char = {
|
||||
"name": jsonData.name,
|
||||
@@ -1815,9 +1819,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
||||
console.log('importing from gradio json');
|
||||
jsonData.char_name = sanitize(jsonData.char_name);
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
png_name = getPngName(jsonData.char_name);
|
||||
let char = {
|
||||
"name": jsonData.char_name,
|
||||
@@ -1872,9 +1876,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('Found a v1 character file.');
|
||||
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
|
||||
let char = {
|
||||
"name": jsonData.name,
|
||||
@@ -3023,6 +3027,22 @@ app.post("/deletepreset_openai", jsonParser, function (request, response) {
|
||||
return response.send({ error: true });
|
||||
});
|
||||
|
||||
function convertChatMLPrompt(messages) {
|
||||
const messageStrings = [];
|
||||
messages.forEach(m => {
|
||||
if (m.role === 'system' && m.name === undefined) {
|
||||
messageStrings.push("System: " + m.content);
|
||||
}
|
||||
else if (m.role === 'system' && m.name !== undefined) {
|
||||
messageStrings.push(m.name + ": " + m.content);
|
||||
}
|
||||
else {
|
||||
messageStrings.push(m.role + ": " + m.content);
|
||||
}
|
||||
});
|
||||
return messageStrings.join("\n");
|
||||
}
|
||||
|
||||
// Prompt Conversion script taken from RisuAI by @kwaroran (GPLv3).
|
||||
function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix) {
|
||||
// Claude doesn't support message names, so we'll just add them to the message content.
|
||||
@@ -3067,6 +3087,54 @@ function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix) {
|
||||
return requestPrompt;
|
||||
}
|
||||
|
||||
async function sendScaleRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const api_url = new URL(request.body.api_url_scale).toString();
|
||||
const api_key_scale = readSecret(SECRET_KEYS.SCALE);
|
||||
|
||||
if (!api_key_scale) {
|
||||
return response.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
const requestPrompt = convertChatMLPrompt(request.body.messages);
|
||||
console.log('Scale request:', requestPrompt);
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const generateResponse = await fetch(api_url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ input: { input: requestPrompt } }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Basic ${api_key_scale}`,
|
||||
},
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
if (!generateResponse.ok) {
|
||||
console.log(`Scale API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
|
||||
return response.status(generateResponse.status).send({ error: true });
|
||||
}
|
||||
|
||||
const generateResponseJson = await generateResponse.json();
|
||||
console.log('Scale response:', generateResponseJson);
|
||||
|
||||
const reply = { choices: [{ "message": { "content": generateResponseJson.output, } }] };
|
||||
return response.send(reply);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (!response.headersSent) {
|
||||
return response.status(500).send({ error: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sendClaudeRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
@@ -3149,11 +3217,15 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
return sendClaudeRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
if (request.body.use_scale) {
|
||||
return sendScaleRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
|
||||
if (request.body.use_openrouter == false) {
|
||||
if (!request.body.use_openrouter) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
@@ -3168,23 +3240,27 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
return response_generate_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
const isTextCompletion = request.body.model.startsWith('text-') || request.body.model.startsWith('code-');
|
||||
const textPrompt = isTextCompletion ? convertChatMLPrompt(request.body.messages) : '';
|
||||
const endpointUrl = isTextCompletion ? `${api_url}/completions` : `${api_url}/chat/completions`;
|
||||
|
||||
const controller = new AbortController();
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
console.log(request.body);
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: api_url + '/chat/completions',
|
||||
url: endpointUrl,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + api_key_openai,
|
||||
...headers,
|
||||
},
|
||||
data: {
|
||||
"messages": request.body.messages,
|
||||
"messages": isTextCompletion === false ? request.body.messages : undefined,
|
||||
"prompt": isTextCompletion === true ? textPrompt : undefined,
|
||||
"model": request.body.model,
|
||||
"temperature": request.body.temperature,
|
||||
"max_tokens": request.body.max_tokens,
|
||||
@@ -3198,17 +3274,22 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (request.body.stream)
|
||||
config.responseType = 'stream';
|
||||
console.log(config.data);
|
||||
|
||||
if (request.body.stream) {
|
||||
config.responseType = 'stream';
|
||||
}
|
||||
|
||||
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 1000) {
|
||||
try {
|
||||
const response = await axios(config);
|
||||
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
if (response.status <= 299) {
|
||||
if (request.body.stream) {
|
||||
console.log("Streaming request in progress")
|
||||
console.log('Streaming request in progress');
|
||||
response.data.pipe(response_generate_openai);
|
||||
response.data.on('end', function () {
|
||||
console.log("Streaming request finished");
|
||||
response.data.on('end', () => {
|
||||
console.log('Streaming request finished');
|
||||
response_generate_openai.end();
|
||||
});
|
||||
} else {
|
||||
@@ -3216,54 +3297,37 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
console.log(response.data);
|
||||
console.log(response.data?.choices[0]?.message);
|
||||
}
|
||||
} else if (response.status == 400) {
|
||||
console.log('Validation error');
|
||||
response_generate_openai.send({ error: true });
|
||||
} else if (response.status == 401) {
|
||||
console.log('Access Token is incorrect');
|
||||
response_generate_openai.send({ error: true });
|
||||
} else if (response.status == 402) {
|
||||
console.log('An active subscription is required to access this endpoint');
|
||||
response_generate_openai.send({ error: true });
|
||||
} else if (response.status == 429) {
|
||||
console.log('Out of quota');
|
||||
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 => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
} else {
|
||||
console.log(response.data);
|
||||
}
|
||||
response_generate_openai.send({ error: true });
|
||||
} else {
|
||||
handleErrorResponse(response, response_generate_openai, request);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
if (error.response) {
|
||||
if (request.body.stream) {
|
||||
error.response.data.on('data', chunk => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
} else {
|
||||
console.log(error.response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429 && retries > 0) {
|
||||
console.log('Out of quota, retrying...');
|
||||
setTimeout(() => {
|
||||
makeRequest(config, response_generate_openai, request, retries - 1);
|
||||
}, timeout);
|
||||
} else {
|
||||
handleError(error, response_generate_openai, request);
|
||||
}
|
||||
try {
|
||||
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 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (!response_generate_openai.headersSent) {
|
||||
return response_generate_openai.send({ error: true });
|
||||
}
|
||||
} finally {
|
||||
response_generate_openai.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleErrorResponse(response, response_generate_openai, request) {
|
||||
if (response.status >= 400 && response.status <= 504) {
|
||||
console.log('Error occurred:', response.status, response.data);
|
||||
response_generate_openai.send({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error, response_generate_openai, request) {
|
||||
console.error('Error:', error.message);
|
||||
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: quota_error });
|
||||
}
|
||||
}
|
||||
|
||||
makeRequest(config, response_generate_openai, request);
|
||||
});
|
||||
|
||||
app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai = response) {
|
||||
@@ -3528,6 +3592,7 @@ const SECRET_KEYS = {
|
||||
CLAUDE: 'api_key_claude',
|
||||
DEEPL: 'deepl',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
|
Reference in New Issue
Block a user