Merge branch 'confirm_delete' of github.com:phiharri/SillyTavern into confirm_delete

This commit is contained in:
phiharri
2023-07-06 21:01:50 +01:00
26 changed files with 1341 additions and 305 deletions

View File

@@ -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">&#9776;</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>

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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();

View File

@@ -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);
});

View File

@@ -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}`);
}
}

View 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>

View 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>

View 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);;
}

View 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();
});

View 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"
}

View 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>

View 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;
}

View File

@@ -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] }),
});
}

View File

@@ -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);

View File

@@ -237,6 +237,9 @@ async function autoJailbreak() {
auto_jailbroken = true;
break;
}
// Purge the conversation if we're not jailbroken
await purgeConversation(-1);
}
}

View File

@@ -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;
});

View File

@@ -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
View 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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -676,3 +676,7 @@ export function uuidv4() {
return v.toString(16);
});
}
export function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}

View File

@@ -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`;

View File

@@ -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
View File

@@ -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() {