mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-22 23:18:27 +01:00
Merge branch 'staging' of https://github.com/Tony-sama/SillyTavern into staging
This commit is contained in:
commit
d070336e9c
@ -135,7 +135,7 @@
|
||||
"instruct": {
|
||||
"enabled": false,
|
||||
"preset": "🧙 Roleplay",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -53,8 +53,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2",
|
||||
"toastr": "^2.1.4"
|
||||
"pkg-fetch": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@agnai/sentencepiece-js": {
|
||||
@ -3366,15 +3365,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toastr": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
|
||||
"integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jquery": ">=1.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
|
@ -81,7 +81,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2",
|
||||
"toastr": "^2.1.4"
|
||||
"pkg-fetch": "^3.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -63,42 +63,8 @@
|
||||
|
||||
<link rel="stylesheet" href="css/bg_load.css">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<script>
|
||||
function applyLocale() {
|
||||
const overrideLanguage = localStorage.getItem("language");
|
||||
var language = overrideLanguage || navigator.language || navigator.userLanguage;
|
||||
language = language.toLowerCase();
|
||||
console.log(language)
|
||||
//load the appropriate language file
|
||||
$.getJSON("i18n.json", function (data) {
|
||||
console.log(data)
|
||||
if (data.lang.indexOf(language) < 0) language = "en";
|
||||
console.log(language)
|
||||
//find all the elements with `data-i18n` attribute
|
||||
$("[data-i18n]").each(function () {
|
||||
//read the translation from the language data
|
||||
const keys = $(this).data("i18n").split(';'); // Multi-key entries are ; delimited
|
||||
for (const key of keys) {
|
||||
const attrmatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
|
||||
if (attrmatch) { // attribute-tagged key
|
||||
const locval = data?.[language]?.[attrmatch[2]];
|
||||
if (locval) {
|
||||
$(this).attr(attrmatch[1], locval);
|
||||
}
|
||||
} else { // No attribute tag, treat as 'text'
|
||||
const locval = data?.[language]?.[key];
|
||||
if (locval) {
|
||||
$(this).text(locval);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
$(document).ready(applyLocale);
|
||||
window["applyLocale"] = applyLocale;
|
||||
</script>
|
||||
<script type=module src="script.js"></script>
|
||||
<script type="module" src="scripts/i18n.js"></script>
|
||||
<script type="module" src="script.js"></script>
|
||||
|
||||
<script type="module" src="scripts/world-info.js"></script>
|
||||
<script type="module" src="scripts/group-chats.js"></script>
|
||||
@ -669,7 +635,7 @@
|
||||
Max prompt cost: <span id="openrouter_max_prompt_cost">Unknown</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@ -744,7 +710,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21">
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale">
|
||||
<div class="range-block-title" data-i18n="Top-p">
|
||||
Top P
|
||||
</div>
|
||||
@ -1470,9 +1436,22 @@
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div id="quick-edit-container">
|
||||
<div class="range-block">
|
||||
<span data-i18n="Select a character to show quick edit options.">Select a character to show quick edit options.</span>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="justifyLeft" data-i18n="Main">Main</div>
|
||||
<div class="wide100p">
|
||||
<textarea id="main_prompt_quick_edit_textarea" class="text_pole textarea_compact" rows="6" placeholder="" data-pm-prompt="main"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="justifyLeft" data-i18n="NSFW">NSFW</div>
|
||||
<div class="wide100p">
|
||||
<textarea id="nsfw_prompt_quick_edit_textarea" class="text_pole textarea_compact" rows="6" placeholder="" data-pm-prompt="nsfw"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1">
|
||||
<div class="justifyLeft" data-i18n="Jailbreak">Jailbreak</div>
|
||||
<div class="wide100p">
|
||||
<textarea id="jailbreak_prompt_quick_edit_textarea" class="text_pole textarea_compact" rows="6" placeholder="" data-pm-prompt="jailbreak"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="claude_assistant_prefill_block" data-source="claude" class="range-block">
|
||||
@ -1612,7 +1591,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block m-t-1" data-source="openai,openrouter">
|
||||
<div class="range-block m-t-1" data-source="openai,openrouter,scale">
|
||||
<div class="range-block-title openai_restorable" data-i18n="Logit Bias">
|
||||
Logit Bias
|
||||
</div>
|
||||
@ -2010,18 +1989,34 @@
|
||||
</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" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_scale"></div>
|
||||
<div id="normal_scale_form">
|
||||
<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" data-i18n="[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">
|
||||
</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 id="alt_scale_form">
|
||||
<h4>Scale Cookie (_jwt)</h4>
|
||||
<div class="flex-container">
|
||||
<input id="scale_cookie" name="scale_cookie" class="text_pole flex1" maxlength="500" value="" autocomplete="off">
|
||||
<div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="scale_cookie"></div>
|
||||
</div>
|
||||
<div data-for="scale_cookie" class="neutral_warning">
|
||||
For privacy reasons, your cookie will be hidden after you reload the page.
|
||||
</div>
|
||||
</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>
|
||||
<label for="scale-alt" class="checkbox_label">
|
||||
<input id="scale-alt" type="checkbox" checked>
|
||||
<span data-i18n="Alt Method">Alt Method</span>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
@ -2999,7 +2994,10 @@
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="Persona Description">Persona Description</h4>
|
||||
<textarea id="persona_description" name="persona_description" placeholder="Example: [{{user}} is a 28-year-old Romanian cat girl.]" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="4"></textarea>
|
||||
<textarea id="persona_description" name="persona_description" placeholder="Example: [{{user}} is a 28-year-old Romanian cat girl.]" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="8"></textarea>
|
||||
<div class="extension_token_counter">
|
||||
Tokens: <span id="persona_description_token_count">0</span>
|
||||
</div>
|
||||
<label for="persona_description_position" data-i18n="Position:">Position:</label>
|
||||
<select id="persona_description_position">
|
||||
<option value="0" data-i18n="In Story String / Chat Completion: Before Character Card">In Story String / Chat Completion: Before Character Card</option>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "🧙 Roleplay",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
|
28
public/jsconfig.json
Normal file
28
public/jsconfig.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "ESNext",
|
||||
"module": "commonjs",
|
||||
"allowUmdGlobalAccess": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"typeAcquisition": {
|
||||
"include": [
|
||||
"jquery",
|
||||
"@popperjs/core",
|
||||
"toastr",
|
||||
"showdown",
|
||||
"dompurify",
|
||||
"moment",
|
||||
"seedrandom",
|
||||
"showdown-katex",
|
||||
"droll",
|
||||
"handlebars",
|
||||
"highlight.js",
|
||||
"localforage"
|
||||
]
|
||||
}
|
||||
}
|
1118
public/script.js
1118
public/script.js
File diff suppressed because it is too large
Load Diff
@ -513,6 +513,38 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
});
|
||||
}
|
||||
|
||||
// Fill quick edit fields for the first time
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const handleQuickEditSave = (event) => {
|
||||
const promptId = event.target.dataset.pmPrompt;
|
||||
const prompt = this.getPromptById(promptId);
|
||||
|
||||
prompt.content = event.target.value;
|
||||
|
||||
// Update edit form if present
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
||||
const popupEditFormPrompt = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt');
|
||||
if (popupEditFormPrompt.offsetParent) {
|
||||
popupEditFormPrompt.value = prompt.content;
|
||||
}
|
||||
|
||||
this.log('Saved prompt: ' + promptId);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
};
|
||||
|
||||
const mainPrompt = this.getPromptById('main');
|
||||
const mainElementId = this.updateQuickEdit('main', mainPrompt);
|
||||
document.getElementById(mainElementId).addEventListener('blur', handleQuickEditSave);
|
||||
|
||||
const nsfwPrompt = this.getPromptById('nsfw');
|
||||
const nsfwElementId = this.updateQuickEdit('nsfw', nsfwPrompt);
|
||||
document.getElementById(nsfwElementId).addEventListener('blur', handleQuickEditSave);
|
||||
|
||||
const jailbreakPrompt = this.getPromptById('jailbreak');
|
||||
const jailbreakElementId = this.updateQuickEdit('jailbreak', jailbreakPrompt);
|
||||
document.getElementById(jailbreakElementId).addEventListener('blur', handleQuickEditSave);
|
||||
}
|
||||
|
||||
// Re-render when chat history changes.
|
||||
eventSource.on(event_types.MESSAGE_DELETED, () => this.renderDebounced());
|
||||
eventSource.on(event_types.MESSAGE_EDITED, () => this.renderDebounced());
|
||||
@ -520,6 +552,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Re-render when chatcompletion settings change
|
||||
eventSource.on(event_types.CHATCOMPLETION_SOURCE_CHANGED, () => this.renderDebounced());
|
||||
|
||||
eventSource.on(event_types.CHATCOMPLETION_MODEL_CHANGED, () => this.renderDebounced());
|
||||
|
||||
// Re-render when the character changes.
|
||||
@ -577,6 +610,15 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.renderDebounced();
|
||||
|
||||
const mainPrompt = this.getPromptById('main');
|
||||
this.updateQuickEdit('main', mainPrompt);
|
||||
|
||||
const nsfwPrompt = this.getPromptById('nsfw');
|
||||
this.updateQuickEdit('nsfw', nsfwPrompt);
|
||||
|
||||
const jailbreakPrompt = this.getPromptById('jailbreak');
|
||||
this.updateQuickEdit('jailbreak', jailbreakPrompt);
|
||||
});
|
||||
});
|
||||
|
||||
@ -609,6 +651,7 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
this.makeDraggable();
|
||||
this.profileEnd('render');
|
||||
}).catch(error => {
|
||||
this.profileEnd('filling context');
|
||||
this.log('Error caught during render: ' + error);
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
@ -1016,8 +1059,11 @@ PromptManagerModule.prototype.createQuickEdit = function (identifier, title) {
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.updateQuickEdit = function (identifier, prompt) {
|
||||
const textarea = document.getElementById(`${identifier}_prompt_quick_edit_textarea`);
|
||||
const elementId = `${identifier}_prompt_quick_edit_textarea`;
|
||||
const textarea = document.getElementById(elementId);
|
||||
textarea.value = prompt.content;
|
||||
|
||||
return elementId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1312,48 +1358,9 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
|
||||
|
||||
const quickEditContainer = document.getElementById('quick-edit-container');
|
||||
const heights = this.saveTextAreaHeights(quickEditContainer);
|
||||
quickEditContainer.innerHTML = '';
|
||||
|
||||
this.createQuickEdit('jailbreak', 'Jailbreak');
|
||||
this.createQuickEdit('nsfw', 'NSFW');
|
||||
this.createQuickEdit('main', 'Main');
|
||||
|
||||
this.restoreTextAreaHeights(quickEditContainer, heights);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @param heights An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.restoreTextAreaHeights = function(container, heights) {
|
||||
if (Object.keys(heights).length === 0) return;
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
const height = heights[this.id];
|
||||
if (height > 0) $(this).height(height);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @returns {{}} An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.saveTextAreaHeights = function(container) {
|
||||
const heights = {};
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
heights[this.id] = $(this).height();
|
||||
});
|
||||
|
||||
return heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties, then re-assembles the prompt list
|
||||
*/
|
||||
|
@ -2,15 +2,12 @@ esversion: 6
|
||||
|
||||
import {
|
||||
Generate,
|
||||
this_chid,
|
||||
characters,
|
||||
online_status,
|
||||
main_api,
|
||||
api_server,
|
||||
api_server_textgenerationwebui,
|
||||
is_send_press,
|
||||
getTokenCount,
|
||||
menu_type,
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
active_group,
|
||||
@ -33,10 +30,9 @@ import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { debounce, delay, getStringHash } from "./utils.js";
|
||||
import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
import { getTokenCount } from "./tokenizers.js";
|
||||
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
var LPanelPin = document.getElementById("lm_button_panel_pin");
|
||||
@ -47,20 +43,8 @@ var LeftNavPanel = document.getElementById("left-nav-panel");
|
||||
var WorldInfo = document.getElementById("WorldInfo");
|
||||
|
||||
var SelectedCharacterTab = document.getElementById("rm_button_selected_ch");
|
||||
var AdvancedCharDefsPopup = document.getElementById("character_popup");
|
||||
var ConfirmationPopup = document.getElementById("dialogue_popup");
|
||||
var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox");
|
||||
var AutoLoadChatCheckbox = document.getElementById("auto-load-chat-checkbox");
|
||||
var SelectedNavTab = ("#" + LoadLocal('SelectedNavTab'));
|
||||
|
||||
var create_save_name;
|
||||
var create_save_description;
|
||||
var create_save_personality;
|
||||
var create_save_first_message;
|
||||
var create_save_scenario;
|
||||
var create_save_mes_example;
|
||||
var count_tokens;
|
||||
var perm_tokens;
|
||||
|
||||
var connection_made = false;
|
||||
var retry_delay = 500;
|
||||
@ -83,32 +67,6 @@ const observer = new MutationObserver(function (mutations) {
|
||||
|
||||
observer.observe(document.documentElement, observerConfig);
|
||||
|
||||
/**
|
||||
* Wait for an element before resolving a promise
|
||||
* @param {String} querySelector - Selector of element to wait for
|
||||
* @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
|
||||
*/
|
||||
function waitForElement(querySelector, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var timer = false;
|
||||
if (document.querySelectorAll(querySelector).length) return resolve();
|
||||
const observer = new MutationObserver(() => {
|
||||
if (document.querySelectorAll(querySelector).length) {
|
||||
observer.disconnect();
|
||||
if (timer !== false) clearTimeout(timer);
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
if (timeout) timer = setTimeout(() => {
|
||||
observer.disconnect();
|
||||
reject();
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts generation time from milliseconds to a human-readable format.
|
||||
@ -225,14 +183,6 @@ export function getMessageTimeStamp() {
|
||||
// triggers:
|
||||
$("#rm_button_create").on("click", function () { //when "+New Character" is clicked
|
||||
$(SelectedCharacterTab).children("h2").html(''); // empty nav's 3rd panel tab
|
||||
|
||||
//empty temp vars to store new char data for counting
|
||||
create_save_name = "";
|
||||
create_save_description = "";
|
||||
create_save_personality = "";
|
||||
create_save_first_message = "";
|
||||
create_save_scenario = "";
|
||||
create_save_mes_example = "";
|
||||
});
|
||||
//when any input is made to the create/edit character form textareas
|
||||
$("#rm_ch_create_block").on("input", function () { countTokensDebounced(); });
|
||||
@ -245,7 +195,7 @@ export function RA_CountCharTokens() {
|
||||
$('[data-token-counter]').each(function () {
|
||||
const counter = $(this);
|
||||
const input = $(document.getElementById(counter.data('token-counter')));
|
||||
const value = input.val();
|
||||
const value = String(input.val());
|
||||
|
||||
if (input.length === 0) {
|
||||
counter.text('Invalid input reference');
|
||||
@ -413,7 +363,7 @@ function RA_autoconnect(PrevApi) {
|
||||
case 'openai':
|
||||
if (((secret_state[SECRET_KEYS.OPENAI] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|
||||
|| ((secret_state[SECRET_KEYS.CLAUDE] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
|
||||
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| ((secret_state[SECRET_KEYS.SCALE] || secret_state[SECRET_KEYS.SCALE_COOKIE]) && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|
||||
|| (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
|
||||
@ -717,7 +667,12 @@ export async function initMovingUI() {
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
$("document").ready(function () {
|
||||
jQuery(async function () {
|
||||
try {
|
||||
await waitUntilCondition(() => online_status !== undefined, 1000, 10);
|
||||
} catch {
|
||||
console.log('Timeout waiting for online_status');
|
||||
}
|
||||
|
||||
// initial status check
|
||||
setTimeout(() => {
|
||||
@ -799,7 +754,7 @@ $("document").ready(function () {
|
||||
//console.log('setting pin class via local var');
|
||||
$(RightNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
if ($(RPanelPin).prop('checked' == true)) {
|
||||
if (!!$(RPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(RightNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
@ -809,7 +764,7 @@ $("document").ready(function () {
|
||||
//console.log('setting pin class via local var');
|
||||
$(LeftNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
if ($(LPanelPin).prop('checked' == true)) {
|
||||
if (!!$(LPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(LeftNavPanel).addClass('pinnedOpen');
|
||||
}
|
||||
@ -821,7 +776,7 @@ $("document").ready(function () {
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
|
||||
if ($(WIPanelPin).prop('checked' == true)) {
|
||||
if (!!$(WIPanelPin).prop('checked')) {
|
||||
console.debug('setting pin class via checkbox state');
|
||||
$(WorldInfo).addClass('pinnedOpen');
|
||||
}
|
||||
@ -884,8 +839,6 @@ $("document").ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
||||
|
||||
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
|
||||
$('#send_textarea').on('input', function () {
|
||||
this.style.height = '40px';
|
||||
@ -896,7 +849,7 @@ $("document").ready(function () {
|
||||
|
||||
document.addEventListener('swiped-left', function (e) {
|
||||
var SwipeButR = $('.swipe_right:last');
|
||||
var SwipeTargetMesClassParent = e.target.closest('.last_mes');
|
||||
var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
|
||||
if (SwipeTargetMesClassParent !== null) {
|
||||
if (SwipeButR.css('display') === 'flex') {
|
||||
SwipeButR.click();
|
||||
@ -905,7 +858,7 @@ $("document").ready(function () {
|
||||
});
|
||||
document.addEventListener('swiped-right', function (e) {
|
||||
var SwipeButL = $('.swipe_left:last');
|
||||
var SwipeTargetMesClassParent = e.target.closest('.last_mes');
|
||||
var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
|
||||
if (SwipeTargetMesClassParent !== null) {
|
||||
if (SwipeButL.css('display') === 'flex') {
|
||||
SwipeButL.click();
|
||||
|
@ -2,7 +2,6 @@ import {
|
||||
chat_metadata,
|
||||
eventSource,
|
||||
event_types,
|
||||
getTokenCount,
|
||||
saveSettingsDebounced,
|
||||
this_chid,
|
||||
} from "../script.js";
|
||||
@ -10,6 +9,7 @@ import { selected_group } from "./group-chats.js";
|
||||
import { extension_settings, getContext, saveMetadataDebounced } from "./extensions.js";
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { getCharaFilename, debounce, waitUntilCondition, delay } from "./utils.js";
|
||||
import { getTokenCount } from "./tokenizers.js";
|
||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders } from "../script.js";
|
||||
import { isSubsetOf, debounce } from "./utils.js";
|
||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, substituteParams } from "../script.js";
|
||||
import { isSubsetOf, debounce, waitUntilCondition } from "./utils.js";
|
||||
export {
|
||||
getContext,
|
||||
getApiUrl,
|
||||
@ -12,10 +12,46 @@ export {
|
||||
};
|
||||
|
||||
let extensionNames = [];
|
||||
let manifests = [];
|
||||
let manifests = {};
|
||||
const defaultUrl = "http://localhost:5100";
|
||||
export const saveMetadataDebounced = debounce(async () => await getContext().saveMetadata(), 1000);
|
||||
|
||||
export const extensionsHandlebars = Handlebars.create();
|
||||
|
||||
/**
|
||||
* Registers a Handlebars helper for use in extensions.
|
||||
* @param {string} name Handlebars helper name
|
||||
* @param {function} helper Handlebars helper function
|
||||
*/
|
||||
export function registerExtensionHelper(name, helper) {
|
||||
extensionsHandlebars.registerHelper(name, helper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies handlebars extension helpers to a message.
|
||||
* @param {number} messageId Message index in the chat.
|
||||
*/
|
||||
export function processExtensionHelpers(messageId) {
|
||||
const context = getContext();
|
||||
const message = context.chat[messageId];
|
||||
|
||||
if (!message?.mes || typeof message.mes !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't waste time if there are no mustaches
|
||||
if (!substituteParams(message.mes).includes('{{')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const template = extensionsHandlebars.compile(substituteParams(message.mes), { noEscape: true });
|
||||
message.mes = template({});
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Disables parallel updates
|
||||
class ModuleWorkerWrapper {
|
||||
constructor(callback) {
|
||||
@ -175,7 +211,10 @@ async function getManifests(names) {
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}).catch(err => reject() && console.log('Could not load manifest.json for ' + name, err));
|
||||
}).catch(err => {
|
||||
reject();
|
||||
console.log('Could not load manifest.json for ' + name, err);
|
||||
});
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
@ -232,9 +271,9 @@ async function activateExtensions() {
|
||||
|
||||
async function connectClickHandler() {
|
||||
const baseUrl = $("#extensions_url").val();
|
||||
extension_settings.apiUrl = baseUrl;
|
||||
extension_settings.apiUrl = String(baseUrl);
|
||||
const testApiKey = $("#extensions_api_key").val();
|
||||
extension_settings.apiKey = testApiKey;
|
||||
extension_settings.apiKey = String(testApiKey);
|
||||
saveSettingsDebounced();
|
||||
await connectToApi(baseUrl);
|
||||
}
|
||||
@ -459,7 +498,7 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
|
||||
* Gets extension data and generates the corresponding HTML for displaying the extension.
|
||||
*
|
||||
* @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest.
|
||||
* @return {object} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string.
|
||||
* @return {Promise<object>} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string.
|
||||
*/
|
||||
async function getExtensionData(extension) {
|
||||
const name = extension[0];
|
||||
@ -576,7 +615,7 @@ async function onDeleteClick() {
|
||||
* Fetches the version details of a specific extension.
|
||||
*
|
||||
* @param {string} extensionName - The name of the extension.
|
||||
* @return {object} - An object containing the extension's version details.
|
||||
* @return {Promise<object>} - An object containing the extension's version details.
|
||||
* This object includes the currentBranchName, currentCommitHash, isUpToDate, and remoteUrl.
|
||||
* @throws {error} - If there is an error during the fetch operation, it logs the error to the console.
|
||||
*/
|
||||
@ -629,8 +668,8 @@ async function runGenerationInterceptors(chat, contextSize) {
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
setTimeout(function () {
|
||||
jQuery(function () {
|
||||
setTimeout(async function () {
|
||||
addExtensionsButtonAndMenu();
|
||||
$("#extensionsMenuButton").css("display", "flex");
|
||||
}, 100)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getBase64Async } from "../../utils.js";
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings } from "../../extensions.js";
|
||||
import { callPopup, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'caption';
|
||||
@ -52,7 +53,7 @@ async function sendCaptionedMessage(caption, image) {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: Date.now(),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
extra: {
|
||||
image: image,
|
||||
|
@ -23,7 +23,8 @@ const defaultSettings = {
|
||||
};
|
||||
const settingType = {
|
||||
guidance_scale: 0,
|
||||
negative_prompt: 1
|
||||
negative_prompt: 1,
|
||||
positive_prompt: 2
|
||||
}
|
||||
|
||||
// Used for character and chat CFG values
|
||||
@ -36,19 +37,19 @@ function setCharCfg(tempValue, setting) {
|
||||
const avatarName = getCharaFilename();
|
||||
|
||||
// Assign temp object
|
||||
let tempCharaCfg;
|
||||
let tempCharaCfg = {
|
||||
name: avatarName
|
||||
};
|
||||
|
||||
switch(setting) {
|
||||
case settingType.guidance_scale:
|
||||
tempCharaCfg = {
|
||||
"name": avatarName,
|
||||
"guidance_scale": Number(tempValue)
|
||||
}
|
||||
tempCharaCfg["guidance_scale"] = Number(tempValue);
|
||||
break;
|
||||
case settingType.negative_prompt:
|
||||
tempCharaCfg = {
|
||||
"name": avatarName,
|
||||
"negative_prompt": tempValue
|
||||
}
|
||||
tempCharaCfg["negative_prompt"] = tempValue;
|
||||
break;
|
||||
case settingType.positive_prompt:
|
||||
tempCharaCfg["positive_prompt"] = tempValue;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@ -66,7 +67,11 @@ function setCharCfg(tempValue, setting) {
|
||||
const tempAssign = Object.assign(existingCharaCfg, tempCharaCfg);
|
||||
|
||||
// If both values are default, remove the entry
|
||||
if (!existingCharaCfg.useChara && (tempAssign.guidance_scale ?? 1.00) === 1.00 && (tempAssign.negative_prompt?.length ?? 0) === 0) {
|
||||
if (!existingCharaCfg.useChara &&
|
||||
(tempAssign.guidance_scale ?? 1.00) === 1.00 &&
|
||||
(tempAssign.negative_prompt?.length ?? 0) === 0 &&
|
||||
(tempAssign.positive_prompt?.length ?? 0) === 0)
|
||||
{
|
||||
extension_settings.cfg.chara.splice(existingCharaCfgIndex, 1);
|
||||
}
|
||||
} else if (avatarName && tempValue.length > 0) {
|
||||
@ -95,6 +100,9 @@ function setChatCfg(tempValue, setting) {
|
||||
case settingType.negative_prompt:
|
||||
chat_metadata[metadataKeys.negative_prompt] = tempValue;
|
||||
break;
|
||||
case settingType.positive_prompt:
|
||||
chat_metadata[metadataKeys.positive_prompt] = tempValue;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -174,20 +182,40 @@ function loadSettings() {
|
||||
$('#chat_cfg_guidance_scale').val(chat_metadata[metadataKeys.guidance_scale] ?? 1.0.toFixed(2));
|
||||
$('#chat_cfg_guidance_scale_counter').text(chat_metadata[metadataKeys.guidance_scale]?.toFixed(2) ?? 1.0.toFixed(2));
|
||||
$('#chat_cfg_negative_prompt').val(chat_metadata[metadataKeys.negative_prompt] ?? '');
|
||||
$('#chat_cfg_positive_prompt').val(chat_metadata[metadataKeys.positive_prompt] ?? '');
|
||||
$('#groupchat_cfg_use_chara').prop('checked', chat_metadata[metadataKeys.groupchat_individual_chars] ?? false);
|
||||
if (chat_metadata[metadataKeys.negative_combine]?.length > 0) {
|
||||
chat_metadata[metadataKeys.negative_combine].forEach((element) => {
|
||||
$(`input[name="cfg_negative_combine"][value="${element}"]`)
|
||||
if (chat_metadata[metadataKeys.prompt_combine]?.length > 0) {
|
||||
chat_metadata[metadataKeys.prompt_combine].forEach((element) => {
|
||||
$(`input[name="cfg_prompt_combine"][value="${element}"]`)
|
||||
.prop("checked", true);
|
||||
});
|
||||
}
|
||||
|
||||
// Display the negative separator in quotes if not quoted already
|
||||
let promptSeparatorDisplay = [];
|
||||
const promptSeparator = chat_metadata[metadataKeys.prompt_separator];
|
||||
if (promptSeparator) {
|
||||
promptSeparatorDisplay.push(promptSeparator);
|
||||
if (!promptSeparator.startsWith(`"`)) {
|
||||
promptSeparatorDisplay.unshift(`"`);
|
||||
}
|
||||
|
||||
if (!promptSeparator.endsWith(`"`)) {
|
||||
promptSeparatorDisplay.push(`"`);
|
||||
}
|
||||
}
|
||||
|
||||
$('#cfg_prompt_separator').val(promptSeparatorDisplay.length === 0 ? '' : promptSeparatorDisplay.join(''));
|
||||
|
||||
$('#cfg_prompt_insertion_depth').val(chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1);
|
||||
|
||||
// Set character CFG if it exists
|
||||
if (!selected_group) {
|
||||
const charaCfg = extension_settings.cfg.chara.find((e) => e.name === getCharaFilename());
|
||||
$('#chara_cfg_guidance_scale').val(charaCfg?.guidance_scale ?? 1.00);
|
||||
$('#chara_cfg_guidance_scale_counter').text(charaCfg?.guidance_scale?.toFixed(2) ?? 1.0.toFixed(2));
|
||||
$('#chara_cfg_negative_prompt').val(charaCfg?.negative_prompt ?? '');
|
||||
$('#chara_cfg_positive_prompt').val(charaCfg?.positive_prompt ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,26 +232,50 @@ async function initialLoadSettings() {
|
||||
$('#global_cfg_guidance_scale').val(extension_settings.cfg.global.guidance_scale);
|
||||
$('#global_cfg_guidance_scale_counter').text(extension_settings.cfg.global.guidance_scale.toFixed(2));
|
||||
$('#global_cfg_negative_prompt').val(extension_settings.cfg.global.negative_prompt);
|
||||
$('#global_cfg_positive_prompt').val(extension_settings.cfg.global.positive_prompt);
|
||||
}
|
||||
|
||||
function migrateSettings() {
|
||||
let performSave = false;
|
||||
let performSettingsSave = false;
|
||||
let performMetaSave = false;
|
||||
|
||||
if (power_user.guidance_scale) {
|
||||
extension_settings.cfg.global.guidance_scale = power_user.guidance_scale;
|
||||
delete power_user['guidance_scale'];
|
||||
performSave = true;
|
||||
performSettingsSave = true;
|
||||
}
|
||||
|
||||
if (power_user.negative_prompt) {
|
||||
extension_settings.cfg.global.negative_prompt = power_user.negative_prompt;
|
||||
delete power_user['negative_prompt'];
|
||||
performSave = true;
|
||||
performSettingsSave = true;
|
||||
}
|
||||
|
||||
if (performSave) {
|
||||
if (chat_metadata["cfg_negative_combine"]) {
|
||||
chat_metadata[metadataKeys.prompt_combine] = chat_metadata["cfg_negative_combine"];
|
||||
chat_metadata["cfg_negative_combine"] = undefined;
|
||||
performMetaSave = true;
|
||||
}
|
||||
|
||||
if (chat_metadata["cfg_negative_insertion_depth"]) {
|
||||
chat_metadata[metadataKeys.prompt_insertion_depth] = chat_metadata["cfg_negative_insertion_depth"];
|
||||
chat_metadata["cfg_negative_insertion_depth"] = undefined;
|
||||
performMetaSave = true;
|
||||
}
|
||||
|
||||
if (chat_metadata["cfg_negative_separator"]) {
|
||||
chat_metadata[metadataKeys.prompt_separator] = chat_metadata["cfg_negative_separator"];
|
||||
chat_metadata["cfg_negative_separator"] = undefined;
|
||||
performMetaSave = true;
|
||||
}
|
||||
|
||||
if (performSettingsSave) {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
if (performMetaSave) {
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
@ -255,6 +307,10 @@ jQuery(async () => {
|
||||
setChatCfg($(this).val(), settingType.negative_prompt);
|
||||
});
|
||||
|
||||
windowHtml.find('#chat_cfg_positive_prompt').on('input', function() {
|
||||
setChatCfg($(this).val(), settingType.positive_prompt);
|
||||
});
|
||||
|
||||
windowHtml.find('#chara_cfg_guidance_scale').on('input', function() {
|
||||
const value = $(this).val();
|
||||
const success = setCharCfg(value, settingType.guidance_scale);
|
||||
@ -267,6 +323,10 @@ jQuery(async () => {
|
||||
setCharCfg($(this).val(), settingType.negative_prompt);
|
||||
});
|
||||
|
||||
windowHtml.find('#chara_cfg_positive_prompt').on('input', function() {
|
||||
setCharCfg($(this).val(), settingType.positive_prompt);
|
||||
});
|
||||
|
||||
windowHtml.find('#global_cfg_guidance_scale').on('input', function() {
|
||||
extension_settings.cfg.global.guidance_scale = Number($(this).val());
|
||||
$('#global_cfg_guidance_scale_counter').text(extension_settings.cfg.global.guidance_scale.toFixed(2));
|
||||
@ -278,14 +338,29 @@ jQuery(async () => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
windowHtml.find(`input[name="cfg_negative_combine"]`).on('input', function() {
|
||||
const values = windowHtml.find(`input[name="cfg_negative_combine"]`)
|
||||
windowHtml.find('#global_cfg_positive_prompt').on('input', function() {
|
||||
extension_settings.cfg.global.positive_prompt = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
windowHtml.find(`input[name="cfg_prompt_combine"]`).on('input', function() {
|
||||
const values = windowHtml.find(`input[name="cfg_prompt_combine"]`)
|
||||
.filter(":checked")
|
||||
.map(function() { return parseInt($(this).val()) })
|
||||
.get()
|
||||
.filter((e) => e !== NaN) || [];
|
||||
|
||||
chat_metadata[metadataKeys.negative_combine] = values;
|
||||
chat_metadata[metadataKeys.prompt_combine] = values;
|
||||
saveMetadataDebounced();
|
||||
});
|
||||
|
||||
windowHtml.find(`#cfg_prompt_insertion_depth`).on('input', function() {
|
||||
chat_metadata[metadataKeys.prompt_insertion_depth] = Number($(this).val());
|
||||
saveMetadataDebounced();
|
||||
});
|
||||
|
||||
windowHtml.find(`#cfg_prompt_separator`).on('input', function() {
|
||||
chat_metadata[metadataKeys.prompt_separator] = $(this).val();
|
||||
saveMetadataDebounced();
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { chat_metadata, this_chid } from "../../../script.js";
|
||||
import { chat_metadata, substituteParams, this_chid } from "../../../script.js";
|
||||
import { extension_settings, getContext } from "../../extensions.js"
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { getCharaFilename } from "../../utils.js";
|
||||
@ -11,46 +11,20 @@ export const cfgType = {
|
||||
export const metadataKeys = {
|
||||
guidance_scale: "cfg_guidance_scale",
|
||||
negative_prompt: "cfg_negative_prompt",
|
||||
negative_combine: "cfg_negative_combine",
|
||||
groupchat_individual_chars: "cfg_groupchat_individual_chars"
|
||||
positive_prompt: "cfg_positive_prompt",
|
||||
prompt_combine: "cfg_prompt_combine",
|
||||
groupchat_individual_chars: "cfg_groupchat_individual_chars",
|
||||
prompt_insertion_depth: "cfg_prompt_insertion_depth",
|
||||
prompt_separator: "cfg_prompt_separator"
|
||||
}
|
||||
|
||||
// Gets the CFG value from hierarchy of chat -> character -> global
|
||||
// Returns undefined values which should be handled in the respective backend APIs
|
||||
export function getCfg() {
|
||||
let splitNegativePrompt = [];
|
||||
// Gets the CFG guidance scale
|
||||
// If the guidance scale is 1, ignore the CFG prompt(s) since it won't be used anyways
|
||||
export function getGuidanceScale() {
|
||||
const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
|
||||
const guidanceScale = getGuidanceScale(charaCfg);
|
||||
const chatNegativeCombine = chat_metadata[metadataKeys.negative_combine] ?? [];
|
||||
|
||||
// If there's a guidance scale, continue. Otherwise assume undefined
|
||||
if (guidanceScale?.value && guidanceScale?.value !== 1) {
|
||||
if (guidanceScale.type === cfgType.chat || chatNegativeCombine.includes(cfgType.chat)) {
|
||||
splitNegativePrompt.push(chat_metadata[metadataKeys.negative_prompt]?.trim());
|
||||
}
|
||||
|
||||
if (guidanceScale.type === cfgType.chara || chatNegativeCombine.includes(cfgType.chara)) {
|
||||
splitNegativePrompt.push(charaCfg.negative_prompt?.trim())
|
||||
}
|
||||
|
||||
if (guidanceScale.type === cfgType.global || chatNegativeCombine.includes(cfgType.global)) {
|
||||
splitNegativePrompt.push(extension_settings.cfg.global.negative_prompt?.trim());
|
||||
}
|
||||
|
||||
const combinedNegatives = splitNegativePrompt.filter((e) => e.length > 0).join(", ");
|
||||
console.debug(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedNegatives}`)
|
||||
|
||||
return {
|
||||
guidanceScale: guidanceScale.value,
|
||||
negativePrompt: combinedNegatives
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the guidance scale is 1, ignore the CFG negative prompt since it won't be used anyways
|
||||
function getGuidanceScale(charaCfg) {
|
||||
const chatGuidanceScale = chat_metadata[metadataKeys.guidance_scale];
|
||||
const groupchatCharOverride = chat_metadata[metadataKeys.groupchat_individual_chars] ?? false;
|
||||
|
||||
if (chatGuidanceScale && chatGuidanceScale !== 1 && !groupchatCharOverride) {
|
||||
return {
|
||||
type: cfgType.chat,
|
||||
@ -70,3 +44,48 @@ function getGuidanceScale(charaCfg) {
|
||||
value: extension_settings.cfg.global.guidance_scale
|
||||
};
|
||||
}
|
||||
|
||||
// Gets the CFG prompt
|
||||
export function getCfgPrompt(guidanceScale, isNegative) {
|
||||
let splitCfgPrompt = [];
|
||||
|
||||
const cfgPromptCombine = chat_metadata[metadataKeys.prompt_combine] ?? [];
|
||||
if (guidanceScale.type === cfgType.chat || cfgPromptCombine.includes(cfgType.chat)) {
|
||||
splitCfgPrompt.unshift(
|
||||
substituteParams(
|
||||
chat_metadata[isNegative ? metadataKeys.negative_prompt : metadataKeys.positive_prompt]
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
|
||||
if (guidanceScale.type === cfgType.chara || cfgPromptCombine.includes(cfgType.chara)) {
|
||||
splitCfgPrompt.unshift(
|
||||
substituteParams(
|
||||
isNegative ? charaCfg.negative_prompt : charaCfg.positive_prompt
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
if (guidanceScale.type === cfgType.global || cfgPromptCombine.includes(cfgType.global)) {
|
||||
splitCfgPrompt.unshift(
|
||||
substituteParams(
|
||||
isNegative ? extension_settings.cfg.global.negative_prompt : extension_settings.cfg.global.positive_prompt
|
||||
)
|
||||
?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
// This line is a bit hacky with a JSON.stringify and JSON.parse. Fix this if possible.
|
||||
const customSeparator = JSON.parse(chat_metadata[metadataKeys.prompt_separator] || JSON.stringify("\n")) ?? "\n";
|
||||
const combinedCfgPrompt = splitCfgPrompt.filter((e) => e.length > 0).join(customSeparator);
|
||||
const insertionDepth = chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1;
|
||||
console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`);
|
||||
|
||||
return {
|
||||
value: combinedCfgPrompt,
|
||||
depth: insertionDepth
|
||||
};
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<small>
|
||||
<b>Unique to this chat.</b><br>
|
||||
</small>
|
||||
<label for="chat_cfg_negative_prompt">
|
||||
<label for="chat_cfg_guidance_scale">
|
||||
<span data-i18n="Scale">Scale</span>
|
||||
<small data-i18n="1 = disabled">1 = disabled</small>
|
||||
</label>
|
||||
@ -33,6 +33,11 @@
|
||||
<span data-i18n="Negative Prompt">Negative Prompt</span>
|
||||
</label>
|
||||
<textarea id="chat_cfg_negative_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
|
||||
<label for="chat_cfg_positive_prompt">
|
||||
<span data-i18n="Positive Prompt">Positive Prompt</span>
|
||||
</label>
|
||||
<textarea id="chat_cfg_positive_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
</div>
|
||||
<div id="groupchat_cfg_use_chara_container">
|
||||
<label class="checkbox_label" for="groupchat_cfg_use_chara">
|
||||
@ -53,7 +58,7 @@
|
||||
<div class="inline-drawer-content">
|
||||
<small><b>Will be automatically added as the CFG for this character.</b></small>
|
||||
<br />
|
||||
<label for="chara_cfg_negative_prompt">
|
||||
<label for="chara_cfg_guidance_scale">
|
||||
<span data-i18n="Scale">Scale</span>
|
||||
<small data-i18n="1 = disabled">1 = disabled</small>
|
||||
</label>
|
||||
@ -72,6 +77,11 @@
|
||||
<span data-i18n="Negative Prompt">Negative Prompt</span>
|
||||
</label>
|
||||
<textarea id="chara_cfg_negative_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
|
||||
<label for="chara_cfg_positive_prompt">
|
||||
<span data-i18n="Positive Prompt">Positive Prompt</span>
|
||||
</label>
|
||||
<textarea id="chara_cfg_positive_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -86,7 +96,7 @@
|
||||
<div class="inline-drawer-content">
|
||||
<small><b>Will be used as the default CFG options for every chat unless overridden.</b></small>
|
||||
<br />
|
||||
<label for="global_cfg_negative_prompt">
|
||||
<label for="global_cfg_guidance_scale">
|
||||
<span data-i18n="Scale">Scale</span>
|
||||
<small data-i18n="1 = disabled">1 = disabled</small>
|
||||
</label>
|
||||
@ -105,39 +115,56 @@
|
||||
<span data-i18n="Negative Prompt">Negative Prompt</span>
|
||||
</label>
|
||||
<textarea id="global_cfg_negative_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
|
||||
<label for="global_cfg_positive_prompt">
|
||||
<span data-i18n="Positive Prompt">Positive Prompt</span>
|
||||
</label>
|
||||
<textarea id="global_cfg_positive_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cfg_negative_combine_container">
|
||||
<div id="cfg_prompt_combine_container">
|
||||
<hr class="sysHR">
|
||||
<div class="inline-drawer">
|
||||
<div id="defaultANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Negative Cascading</b>
|
||||
<b>CFG Prompt Cascading</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>
|
||||
<b>Combine negative prompts from other boxes.</b>
|
||||
<br />
|
||||
For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.
|
||||
</small>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<small>
|
||||
<b>Combine positive/negative prompts from other boxes.</b>
|
||||
<br />
|
||||
For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.
|
||||
</small>
|
||||
</div>
|
||||
<br />
|
||||
<label for="cfg_negative_combine">
|
||||
<span data-i18n="Scale">Always Include</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_negative_combine" value="0" />
|
||||
<span data-i18n="Chat Negatives">Chat Negatives</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_negative_combine" value="1" />
|
||||
<span data-i18n="Character Negatives">Character Negatives</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_negative_combine" value="2" />
|
||||
<span data-i18n="Global Negatives">Global Negatives</span>
|
||||
</label>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="cfg_prompt_combine">
|
||||
<span data-i18n="Scale">Always Include</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_prompt_combine" value="0" />
|
||||
<span data-i18n="Chat Negatives">Chat Negatives</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_prompt_combine" value="1" />
|
||||
<span data-i18n="Character Negatives">Character Negatives</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" name="cfg_prompt_combine" value="2" />
|
||||
<span data-i18n="Global Negatives">Global Negatives</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label>
|
||||
Custom Separator: <input id="cfg_prompt_separator" class="text_pole textarea_compact widthUnset" placeholder=""\n"" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
Insertion Depth: <input id="cfg_prompt_insertion_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { saveSettingsDebounced, getCurrentChatId, system_message_types, extension_prompt_types, eventSource, event_types, getRequestHeaders, CHARACTERS_PER_TOKEN_RATIO, substituteParams, max_context, } from "../../../script.js";
|
||||
import { saveSettingsDebounced, getCurrentChatId, system_message_types, extension_prompt_types, eventSource, event_types, getRequestHeaders, substituteParams, } from "../../../script.js";
|
||||
import { humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
import { getApiUrl, extension_settings, getContext, doExtrasFetch } from "../../extensions.js";
|
||||
import { CHARACTERS_PER_TOKEN_RATIO } from "../../tokenizers.js";
|
||||
import { getFileText, onlyUnique, splitRecursive } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
|
@ -28,7 +28,7 @@ async function updateQuickReplyPresetList() {
|
||||
if (result.ok) {
|
||||
var data = await result.json();
|
||||
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
|
||||
console.log(presets)
|
||||
console.debug('Quick Reply presets', presets);
|
||||
$("#quickReplyPresets").find('option[value!=""]').remove();
|
||||
|
||||
|
||||
@ -284,7 +284,7 @@ async function doQR(_, text) {
|
||||
}
|
||||
|
||||
text = Number(text)
|
||||
//use scale starting with 0
|
||||
//use scale starting with 0
|
||||
//ex: user inputs "/qr 2" >> qr with data-index 1 (but 2nd item displayed) gets triggered
|
||||
let QRnum = Number(text - 1)
|
||||
if (QRnum <= 0) { QRnum = 0 }
|
||||
|
@ -4,11 +4,12 @@ TODO:
|
||||
*/
|
||||
|
||||
import { saveSettingsDebounced } from "../../../script.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
|
||||
import { getContext, extension_settings, ModuleWorkerWrapper } from "../../extensions.js";
|
||||
import { VoskSttProvider } from './vosk.js'
|
||||
import { WhisperSttProvider } from './whisper.js'
|
||||
import { BrowserSttProvider } from './browser.js'
|
||||
import { StreamingSttProvider } from './streaming.js'
|
||||
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'Speech Recognition';
|
||||
@ -61,10 +62,10 @@ async function moduleWorker() {
|
||||
let messageStart = -1;
|
||||
|
||||
if (extension_settings.speech_recognition.Streaming.triggerWordsEnabled) {
|
||||
|
||||
|
||||
for (const triggerWord of extension_settings.speech_recognition.Streaming.triggerWords) {
|
||||
const triggerPos = userMessageRaw.indexOf(triggerWord.toLowerCase());
|
||||
|
||||
|
||||
// Trigger word not found or not starting message and just a substring
|
||||
if (triggerPos == -1){ // | (triggerPos > 0 & userMessageFormatted[triggerPos-1] != " ")) {
|
||||
console.debug(DEBUG_PREFIX+"trigger word not found: ", triggerWord);
|
||||
@ -152,12 +153,12 @@ async function processTranscript(transcript) {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: Date.now(),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
};
|
||||
context.chat.push(message);
|
||||
context.addOneMessage(message);
|
||||
|
||||
|
||||
await context.generate();
|
||||
|
||||
$('#debug_output').text("<SST-module DEBUG>: message sent: \""+ transcriptFormatted +"\"");
|
||||
@ -191,10 +192,10 @@ async function processTranscript(transcript) {
|
||||
function loadNavigatorAudioRecording() {
|
||||
if (navigator.mediaDevices.getUserMedia) {
|
||||
console.debug(DEBUG_PREFIX+' getUserMedia supported by browser.');
|
||||
|
||||
|
||||
let onSuccess = function(stream) {
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
|
||||
$("#microphone_button").off('click').on("click", function() {
|
||||
if (!audioRecording) {
|
||||
mediaRecorder.start();
|
||||
@ -211,30 +212,30 @@ function loadNavigatorAudioRecording() {
|
||||
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mediaRecorder.onstop = async function() {
|
||||
console.debug(DEBUG_PREFIX+"data available after MediaRecorder.stop() called: ", audioChunks.length, " chunks");
|
||||
const audioBlob = new Blob(audioChunks, { type: "audio/wav; codecs=0" });
|
||||
audioChunks = [];
|
||||
|
||||
|
||||
const transcript = await sttProvider.processAudio(audioBlob);
|
||||
|
||||
|
||||
// TODO: lock and release recording while processing?
|
||||
console.debug(DEBUG_PREFIX+"received transcript:", transcript);
|
||||
processTranscript(transcript);
|
||||
}
|
||||
|
||||
|
||||
mediaRecorder.ondataavailable = function(e) {
|
||||
audioChunks.push(e.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let onError = function(err) {
|
||||
console.debug(DEBUG_PREFIX+"The following error occured: " + err);
|
||||
}
|
||||
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
|
||||
|
||||
|
||||
} else {
|
||||
console.debug(DEBUG_PREFIX+"getUserMedia not supported on your browser!");
|
||||
toastr.error("getUserMedia not supported", DEBUG_PREFIX+"not supported for your browser.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
@ -257,7 +258,7 @@ function loadSttProvider(provider) {
|
||||
console.warn(`Provider ${sttProviderName} not in Extension Settings, initiatilizing provider in settings`);
|
||||
extension_settings.speech_recognition[sttProviderName] = {};
|
||||
}
|
||||
|
||||
|
||||
$('#speech_recognition_provider').val(sttProviderName);
|
||||
|
||||
if (sttProviderName == "None") {
|
||||
@ -287,13 +288,13 @@ function loadSttProvider(provider) {
|
||||
loadNavigatorAudioRecording();
|
||||
$("#microphone_button").show();
|
||||
}
|
||||
|
||||
|
||||
if (sttProviderName == "Streaming") {
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
$("#microphone_button").off('click');
|
||||
$("#microphone_button").hide();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function onSttProviderChange() {
|
||||
@ -365,7 +366,7 @@ async function onMessageMappingChange() {
|
||||
console.debug(DEBUG_PREFIX+"Wrong syntax for message mapping, no '=' found in:", text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$("#speech_recognition_message_mapping_status").text("Message mapping updated to: "+JSON.stringify(extension_settings.speech_recognition.messageMapping))
|
||||
console.debug(DEBUG_PREFIX+"Updated message mapping", extension_settings.speech_recognition.messageMapping);
|
||||
extension_settings.speech_recognition.messageMappingText = $('#speech_recognition_message_mapping').val()
|
||||
@ -425,7 +426,7 @@ $(document).ready(function () {
|
||||
$('#speech_recognition_message_mode').on('change', onMessageModeChange);
|
||||
$('#speech_recognition_message_mapping').on('change', onMessageMappingChange);
|
||||
$('#speech_recognition_message_mapping_enabled').on('click', onMessageMappingEnabledClick);
|
||||
|
||||
|
||||
const $button = $('<div id="microphone_button" class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
|
||||
$('#send_but_sheld').prepend($button);
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
import { getMessageTimeStamp, humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@ -755,11 +755,10 @@ async function sendMessage(prompt, image) {
|
||||
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
|
||||
const message = {
|
||||
name: context.groupId ? systemUserName : context.name2,
|
||||
is_system: context.groupId ? true : false,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
is_name: true,
|
||||
send_date: timestampToMoment(Date.now()).format('LL LT'),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: context.groupId ? p(messageText) : messageText,
|
||||
extra: {
|
||||
image: image,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { callPopup, main_api } from "../../../script.js";
|
||||
import { getContext } from "../../extensions.js";
|
||||
import { getTokenizerModel } from "../../openai.js";
|
||||
import { getTokenizerModel } from "../../tokenizers.js";
|
||||
|
||||
async function doTokenCounter() {
|
||||
const selectedTokenizer = main_api == 'openai'
|
||||
|
@ -421,9 +421,9 @@ jQuery(() => {
|
||||
|
||||
loadSettings();
|
||||
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, handleIncomingMessage);
|
||||
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, handleIncomingMessage);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage);
|
||||
eventSource.on(event_types.MESSAGE_SENT, handleOutgoingMessage);
|
||||
eventSource.on(event_types.USER_MESSAGE_RENDERED, handleOutgoingMessage);
|
||||
eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady);
|
||||
eventSource.on(event_types.MESSAGE_EDITED, handleMessageEdit);
|
||||
|
||||
|
66
public/scripts/extensions/variables/index.js
Normal file
66
public/scripts/extensions/variables/index.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { getContext } from "../../extensions.js";
|
||||
|
||||
/**
|
||||
* Gets a chat variable from the current chat metadata.
|
||||
* @param {string} name The name of the variable to get.
|
||||
* @returns {string} The value of the variable.
|
||||
*/
|
||||
function getChatVariable(name) {
|
||||
const metadata = getContext().chatMetadata;
|
||||
|
||||
if (!metadata) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!metadata.variables) {
|
||||
metadata.variables = {};
|
||||
return '';
|
||||
}
|
||||
|
||||
return metadata.variables[name] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a chat variable in the current chat metadata.
|
||||
* @param {string} name The name of the variable to set.
|
||||
* @param {any} value The value of the variable to set.
|
||||
*/
|
||||
function setChatVariable(name, value) {
|
||||
if (name === undefined || value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = getContext().chatMetadata;
|
||||
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metadata.variables) {
|
||||
metadata.variables = {};
|
||||
}
|
||||
|
||||
metadata.variables[name] = value;
|
||||
}
|
||||
|
||||
function listChatVariables() {
|
||||
const metadata = getContext().chatMetadata;
|
||||
|
||||
if (!metadata) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!metadata.variables) {
|
||||
metadata.variables = {};
|
||||
return '';
|
||||
}
|
||||
|
||||
return Object.keys(metadata.variables).map(key => `${key}=${metadata.variables[key]}`).join(';');
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
const context = getContext();
|
||||
context.registerHelper('getvar', getChatVariable);
|
||||
context.registerHelper('setvar', setChatVariable);
|
||||
context.registerHelper('listvar', listChatVariables);
|
||||
});
|
11
public/scripts/extensions/variables/manifest.json
Normal file
11
public/scripts/extensions/variables/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Chat Variables",
|
||||
"loading_order": 100,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "",
|
||||
"author": "Cohee#1207",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
import { fuzzySearchCharacters, fuzzySearchGroups, fuzzySearchWorldInfo, power_user } from "./power-user.js";
|
||||
import { tag_map } from "./tags.js";
|
||||
|
||||
/**
|
||||
* The filter types.
|
||||
* @type {Object.<string, string>}
|
||||
*/
|
||||
export const FILTER_TYPES = {
|
||||
SEARCH: 'search',
|
||||
TAG: 'tag',
|
||||
@ -9,11 +13,26 @@ export const FILTER_TYPES = {
|
||||
WORLD_INFO_SEARCH: 'world_info_search',
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper class for filtering data.
|
||||
* @example
|
||||
* const filterHelper = new FilterHelper(() => console.log('data changed'));
|
||||
* filterHelper.setFilterData(FILTER_TYPES.SEARCH, 'test');
|
||||
* data = filterHelper.applyFilters(data);
|
||||
*/
|
||||
export class FilterHelper {
|
||||
/**
|
||||
* Creates a new FilterHelper
|
||||
* @param {Function} onDataChanged Callback to trigger when the filter data changes
|
||||
*/
|
||||
constructor(onDataChanged) {
|
||||
this.onDataChanged = onDataChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter functions.
|
||||
* @type {Object.<string, Function>}
|
||||
*/
|
||||
filterFunctions = {
|
||||
[FILTER_TYPES.SEARCH]: this.searchFilter.bind(this),
|
||||
[FILTER_TYPES.GROUP]: this.groupFilter.bind(this),
|
||||
@ -22,6 +41,10 @@ export class FilterHelper {
|
||||
[FILTER_TYPES.WORLD_INFO_SEARCH]: this.wiSearchFilter.bind(this),
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter data.
|
||||
* @type {Object.<string, any>}
|
||||
*/
|
||||
filterData = {
|
||||
[FILTER_TYPES.SEARCH]: '',
|
||||
[FILTER_TYPES.GROUP]: false,
|
||||
@ -30,6 +53,11 @@ export class FilterHelper {
|
||||
[FILTER_TYPES.WORLD_INFO_SEARCH]: '',
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a fuzzy search filter to the World Info data.
|
||||
* @param {any[]} data The data to filter. Must have a uid property.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
wiSearchFilter(data) {
|
||||
const term = this.filterData[FILTER_TYPES.WORLD_INFO_SEARCH];
|
||||
|
||||
@ -41,6 +69,11 @@ export class FilterHelper {
|
||||
return data.filter(entity => fuzzySearchResults.includes(entity.uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a tag filter to the data.
|
||||
* @param {any[]} data The data to filter.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
tagFilter(data) {
|
||||
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
|
||||
const { selected, excluded } = this.filterData[FILTER_TYPES.TAG];
|
||||
@ -76,6 +109,11 @@ export class FilterHelper {
|
||||
return data.filter(entity => getIsTagged(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a favorite filter to the data.
|
||||
* @param {any[]} data The data to filter.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
favFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.FAV]) {
|
||||
return data;
|
||||
@ -84,6 +122,11 @@ export class FilterHelper {
|
||||
return data.filter(entity => entity.item.fav || entity.item.fav == "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a group type filter to the data.
|
||||
* @param {any[]} data The data to filter.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
groupFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.GROUP]) {
|
||||
return data;
|
||||
@ -92,6 +135,11 @@ export class FilterHelper {
|
||||
return data.filter(entity => entity.type === 'group');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a search filter to the data. Uses fuzzy search if enabled.
|
||||
* @param {any[]} data The data to filter.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
searchFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.SEARCH]) {
|
||||
return data;
|
||||
@ -122,6 +170,12 @@ export class FilterHelper {
|
||||
return data.filter(entity => getIsValidSearch(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter data for the given filter type.
|
||||
* @param {string} filterType The filter type to set data for.
|
||||
* @param {any} data The data to set.
|
||||
* @param {boolean} suppressDataChanged Whether to suppress the data changed callback.
|
||||
*/
|
||||
setFilterData(filterType, data, suppressDataChanged = false) {
|
||||
const oldData = this.filterData[filterType];
|
||||
this.filterData[filterType] = data;
|
||||
@ -132,10 +186,19 @@ export class FilterHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter data for the given filter type.
|
||||
* @param {string} filterType The filter type to get data for.
|
||||
*/
|
||||
getFilterData(filterType) {
|
||||
return this.filterData[filterType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all filters to the given data.
|
||||
* @param {any[]} data The data to filter.
|
||||
* @returns {any[]} The filtered data.
|
||||
*/
|
||||
applyFilters(data) {
|
||||
return Object.values(this.filterFunctions)
|
||||
.reduce((data, fn) => fn(data), data);
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
saveBase64AsFile,
|
||||
PAGINATION_TEMPLATE,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from "./RossAscends-mods.js";
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
|
||||
import {
|
||||
@ -202,7 +202,7 @@ function getFirstCharacterMessage(character) {
|
||||
mes["is_system"] = false;
|
||||
mes["name"] = character.name;
|
||||
mes["is_name"] = true;
|
||||
mes["send_date"] = humanizedDateTime();
|
||||
mes["send_date"] = getMessageTimeStamp();
|
||||
mes["original_avatar"] = character.avatar;
|
||||
mes["extra"] = { "gen_id": Date.now() * Math.random() * 1000000 };
|
||||
mes["mes"] = messageText
|
||||
@ -463,7 +463,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
is_group_generating = true;
|
||||
setCharacterName('');
|
||||
setCharacterId(undefined);
|
||||
const userInput = $("#send_textarea").val();
|
||||
const userInput = String($("#send_textarea").val());
|
||||
|
||||
if (typingIndicator.length === 0 && !isStreamingEnabled()) {
|
||||
typingIndicator = $(
|
||||
@ -983,11 +983,9 @@ function printGroupCandidates() {
|
||||
const storageKey = 'GroupCandidates_PerPage';
|
||||
$("#rm_group_add_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
|
||||
pageSize: 5,
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
@ -1011,11 +1009,9 @@ function printGroupMembers() {
|
||||
const storageKey = 'GroupMembers_PerPage';
|
||||
$("#rm_group_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
|
||||
pageSize: 5,
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
@ -1320,7 +1316,7 @@ function openCharacterDefinition(characterSelect) {
|
||||
}
|
||||
|
||||
function filterGroupMembers() {
|
||||
const searchValue = $(this).val().toLowerCase();
|
||||
const searchValue = String($(this).val()).toLowerCase();
|
||||
groupCandidatesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||||
}
|
||||
|
||||
@ -1390,7 +1386,7 @@ export async function createNewGroupChat(groupId) {
|
||||
group.chat_metadata = {};
|
||||
updateChatMetadata(group.chat_metadata, true);
|
||||
|
||||
await editGroup(group.id, true);
|
||||
await editGroup(group.id, true, false);
|
||||
await getGroupChat(group.id);
|
||||
}
|
||||
|
||||
|
75
public/scripts/i18n.js
Normal file
75
public/scripts/i18n.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { waitUntilCondition } from "./utils.js";
|
||||
|
||||
const storageKey = "language";
|
||||
export const localeData = await fetch("i18n.json").then(response => response.json());
|
||||
|
||||
export function applyLocale(root = document) {
|
||||
const overrideLanguage = localStorage.getItem("language");
|
||||
var language = overrideLanguage || navigator.language || navigator.userLanguage;
|
||||
language = language.toLowerCase();
|
||||
//load the appropriate language file
|
||||
if (localeData.lang.indexOf(language) < 0) language = "en";
|
||||
|
||||
const $root = root instanceof Document ? $(root) : $(new DOMParser().parseFromString(root, "text/html"));
|
||||
|
||||
//find all the elements with `data-i18n` attribute
|
||||
$root.find("[data-i18n]").each(function () {
|
||||
//read the translation from the language data
|
||||
const keys = $(this).data("i18n").split(';'); // Multi-key entries are ; delimited
|
||||
for (const key of keys) {
|
||||
const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
|
||||
if (attributeMatch) { // attribute-tagged key
|
||||
const localizedValue = localeData?.[language]?.[attributeMatch[2]];
|
||||
if (localizedValue) {
|
||||
$(this).attr(attributeMatch[1], localizedValue);
|
||||
}
|
||||
} else { // No attribute tag, treat as 'text'
|
||||
const localizedValue = localeData?.[language]?.[key];
|
||||
if (localizedValue) {
|
||||
$(this).text(localizedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (root !== document) {
|
||||
return $root.get(0).body.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
function addLanguagesToDropdown() {
|
||||
if (!Array.isArray(localeData?.lang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const lang of localeData.lang) {
|
||||
const option = document.createElement('option');
|
||||
option.value = lang;
|
||||
option.innerText = lang;
|
||||
$('#ui_language_select').append(option);
|
||||
}
|
||||
|
||||
const selectedLanguage = localStorage.getItem(storageKey);
|
||||
if (selectedLanguage) {
|
||||
$('#ui_language_select').val(selectedLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
waitUntilCondition(() => !!localeData);
|
||||
window["applyLocale"] = applyLocale;
|
||||
applyLocale();
|
||||
addLanguagesToDropdown();
|
||||
|
||||
$('#ui_language_select').on('change', async function () {
|
||||
const language = String($(this).val());
|
||||
|
||||
if (language) {
|
||||
localStorage.setItem(storageKey, language);
|
||||
} else {
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
location.reload();
|
||||
});
|
||||
});
|
@ -4,6 +4,9 @@ import { saveSettingsDebounced, substituteParams } from "../script.js";
|
||||
import { selected_group } from "./group-chats.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
|
||||
/**
|
||||
* @type {any[]} Instruct mode presets.
|
||||
*/
|
||||
export let instruct_presets = [];
|
||||
|
||||
const controls = [
|
||||
@ -116,6 +119,11 @@ export function autoSelectInstructPreset(modelId) {
|
||||
* @returns {string[]} Array of instruct mode stopping strings.
|
||||
*/
|
||||
export function getInstructStoppingSequences() {
|
||||
/**
|
||||
* Adds instruct mode sequence to the result array.
|
||||
* @param {string} sequence Sequence string.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addInstructSequence(sequence) {
|
||||
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
|
||||
// But it's a problem for Metharme which doesn't use newlines to separate them.
|
||||
@ -215,6 +223,7 @@ export function formatInstructModeExamples(mesExamples, name1, name2) {
|
||||
* @param {string} promptBias Prompt bias string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted instruct mode last prompt line.
|
||||
*/
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
@ -258,7 +267,7 @@ jQuery(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.instruct.preset = name;
|
||||
power_user.instruct.preset = String(name);
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
power_user.instruct[control.property] = preset[control.property];
|
||||
|
@ -75,18 +75,18 @@ function loadKoboldSettings(preset) {
|
||||
}
|
||||
}
|
||||
|
||||
function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
|
||||
function getKoboldGenerationData(finalPrompt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
|
||||
const sampler_order = kai_settings.sampler_order || this_settings.sampler_order;
|
||||
let generate_data = {
|
||||
prompt: finalPromt,
|
||||
prompt: finalPrompt,
|
||||
gui_settings: false,
|
||||
sampler_order: sampler_order,
|
||||
max_context_length: parseInt(this_max_context),
|
||||
max_context_length: Number(this_max_context),
|
||||
max_length: this_amount_gen,
|
||||
rep_pen: parseFloat(kai_settings.rep_pen),
|
||||
rep_pen_range: parseInt(kai_settings.rep_pen_range),
|
||||
rep_pen: Number(kai_settings.rep_pen),
|
||||
rep_pen_range: Number(kai_settings.rep_pen_range),
|
||||
rep_pen_slope: kai_settings.rep_pen_slope,
|
||||
temperature: parseFloat(kai_settings.temp),
|
||||
temperature: Number(kai_settings.temp),
|
||||
tfs: kai_settings.tfs,
|
||||
top_a: kai_settings.top_a,
|
||||
top_k: kai_settings.top_k,
|
||||
@ -223,16 +223,30 @@ const sliders = [
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Determines if the Kobold stop sequence can be used with the given version.
|
||||
* @param {string} version KoboldAI version to check.
|
||||
* @returns {boolean} True if the Kobold stop sequence can be used, false otherwise.
|
||||
*/
|
||||
function canUseKoboldStopSequence(version) {
|
||||
return (version || '0.0.0').localeCompare(MIN_STOP_SEQUENCE_VERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the Kobold streaming API can be used with the given version.
|
||||
* @param {{ result: string; version: string; }} koboldVersion KoboldAI version object.
|
||||
* @returns {boolean} True if the Kobold streaming API can be used, false otherwise.
|
||||
*/
|
||||
function canUseKoboldStreaming(koboldVersion) {
|
||||
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
|
||||
return (koboldVersion.version || '0.0').localeCompare(MIN_STREAMING_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the sampler items by the given order.
|
||||
* @param {any[]} orderArray Sampler order array.
|
||||
*/
|
||||
function sortItemsByOrder(orderArray) {
|
||||
console.debug('Preset samplers order: ' + orderArray);
|
||||
const $draggableItems = $("#kobold_order");
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {
|
||||
getRequestHeaders,
|
||||
getStoppingStrings,
|
||||
getTextTokens,
|
||||
max_context,
|
||||
novelai_setting_names,
|
||||
saveSettingsDebounced,
|
||||
setGenerationParamsFromPreset
|
||||
} from "../script.js";
|
||||
import { getCfg } from "./extensions/cfg/util.js";
|
||||
import { MAX_CONTEXT_DEFAULT, tokenizers } from "./power-user.js";
|
||||
import { getCfgPrompt } from "./extensions/cfg/util.js";
|
||||
import { MAX_CONTEXT_DEFAULT } from "./power-user.js";
|
||||
import { getTextTokens, tokenizers } from "./tokenizers.js";
|
||||
import {
|
||||
getSortableDelay,
|
||||
getStringHash,
|
||||
@ -395,7 +395,11 @@ function getBadWordPermutations(text) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getNovelGenerationData(finalPrompt, this_settings, this_amount_gen, isImpersonate) {
|
||||
export function getNovelGenerationData(finalPrompt, this_settings, this_amount_gen, isImpersonate, cfgValues) {
|
||||
if (cfgValues.guidanceScale && cfgValues.guidanceScale?.value !== 1) {
|
||||
cfgValues.negativePrompt = (getCfgPrompt(cfgValues.guidanceScale, true))?.value;
|
||||
}
|
||||
|
||||
const clio = nai_settings.model_novel.includes('clio');
|
||||
const kayra = nai_settings.model_novel.includes('kayra');
|
||||
|
||||
@ -410,7 +414,6 @@ export function getNovelGenerationData(finalPrompt, this_settings, this_amount_g
|
||||
: undefined;
|
||||
|
||||
const prefix = selectPrefix(nai_settings.prefix, finalPrompt);
|
||||
const cfgSettings = getCfg();
|
||||
|
||||
let logitBias = [];
|
||||
if (tokenizerType !== tokenizers.NONE && Array.isArray(nai_settings.logit_bias) && nai_settings.logit_bias.length) {
|
||||
@ -437,8 +440,8 @@ export function getNovelGenerationData(finalPrompt, this_settings, this_amount_g
|
||||
"typical_p": parseFloat(nai_settings.typical_p),
|
||||
"mirostat_lr": parseFloat(nai_settings.mirostat_lr),
|
||||
"mirostat_tau": parseFloat(nai_settings.mirostat_tau),
|
||||
"cfg_scale": cfgSettings?.guidanceScale ?? parseFloat(nai_settings.cfg_scale),
|
||||
"cfg_uc": cfgSettings?.negativePrompt ?? nai_settings.cfg_uc ?? "",
|
||||
"cfg_scale": cfgValues?.guidanceScale?.value ?? parseFloat(nai_settings.cfg_scale),
|
||||
"cfg_uc": cfgValues?.negativePrompt ?? nai_settings.cfg_uc ?? "",
|
||||
"phrase_rep_pen": nai_settings.phrase_rep_pen,
|
||||
"stop_sequences": stopSequences,
|
||||
"bad_words_ids": badWordIds,
|
||||
|
@ -48,10 +48,10 @@ import {
|
||||
delay,
|
||||
download,
|
||||
getFileText, getSortableDelay,
|
||||
getStringHash,
|
||||
parseJsonFile,
|
||||
stringFormat,
|
||||
} from "./utils.js";
|
||||
import { countTokensOpenAI } from "./tokenizers.js";
|
||||
|
||||
export {
|
||||
is_get_status_openai,
|
||||
@ -67,7 +67,6 @@ export {
|
||||
sendOpenAIRequest,
|
||||
setOpenAIOnlineStatus,
|
||||
getChatCompletionModel,
|
||||
countTokens,
|
||||
TokenHandler,
|
||||
IdentifierNotFoundError,
|
||||
Message,
|
||||
@ -109,8 +108,8 @@ 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 scale_max = 8191;
|
||||
const claude_max = 9000; // 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;
|
||||
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
||||
@ -124,40 +123,6 @@ const openrouter_website_model = 'OR_Website';
|
||||
|
||||
let biasCache = undefined;
|
||||
let model_list = [];
|
||||
const objectStore = new localforage.createInstance({ name: "SillyTavern_ChatCompletions" });
|
||||
|
||||
let tokenCache = {};
|
||||
|
||||
async function loadTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: loading token cache')
|
||||
tokenCache = await objectStore.getItem('tokenCache') || {};
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to load token cache, using default value', e);
|
||||
tokenCache = {};
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: saving token cache')
|
||||
await objectStore.setItem('tokenCache', tokenCache);
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to save token cache', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: resetting token cache');
|
||||
Object.keys(tokenCache).forEach(key => delete tokenCache[key]);
|
||||
await objectStore.removeItem('tokenCache');
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to reset token cache', e);
|
||||
}
|
||||
}
|
||||
|
||||
window['resetTokenCache'] = resetTokenCache;
|
||||
|
||||
export const chat_completion_sources = {
|
||||
OPENAI: 'openai',
|
||||
@ -219,6 +184,7 @@ const default_settings = {
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
use_alt_scale: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@ -261,15 +227,12 @@ const oai_settings = {
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
use_alt_scale: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
let openai_settings;
|
||||
|
||||
export function getTokenCountOpenAI(text) {
|
||||
const message = { role: 'system', content: text };
|
||||
return countTokens(message, true);
|
||||
}
|
||||
|
||||
let promptManager = null;
|
||||
|
||||
@ -869,8 +832,6 @@ function prepareOpenAIMessages({
|
||||
|
||||
const chat = chatCompletion.getChat();
|
||||
openai_messages_count = chat.filter(x => x?.role === "user" || x?.role === "assistant")?.length || 0;
|
||||
// Save token cache to IndexedDB storage (async, no need to await)
|
||||
saveTokenCache();
|
||||
|
||||
return [chat, promptManager.tokenHandler.counts];
|
||||
}
|
||||
@ -1082,6 +1043,47 @@ function saveModelList(data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal) {
|
||||
const generate_url = '/generate_altscale';
|
||||
|
||||
let firstSysMsgs = []
|
||||
for (let msg of openai_msgs_tosend) {
|
||||
if (msg.role === 'system') {
|
||||
firstSysMsgs.push(substituteParams(msg.name ? msg.name + ": " + msg.content : msg.content));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let subsequentMsgs = openai_msgs_tosend.slice(firstSysMsgs.length);
|
||||
|
||||
const joinedSysMsgs = substituteParams(firstSysMsgs.join("\n"));
|
||||
const joinedSubsequentMsgs = subsequentMsgs.reduce((acc, obj) => {
|
||||
return acc + obj.role + ": " + obj.content + "\n";
|
||||
}, "");
|
||||
|
||||
openai_msgs_tosend = substituteParams(joinedSubsequentMsgs);
|
||||
|
||||
const generate_data = {
|
||||
sysprompt: joinedSysMsgs,
|
||||
prompt: openai_msgs_tosend,
|
||||
temp: parseFloat(oai_settings.temp_openai),
|
||||
top_p: parseFloat(oai_settings.top_p_openai),
|
||||
max_tokens: parseFloat(oai_settings.openai_max_tokens),
|
||||
logit_bias: logit_bias,
|
||||
}
|
||||
|
||||
const response = await fetch(generate_url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(generate_data),
|
||||
headers: getRequestHeaders(),
|
||||
signal: signal
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data.output;
|
||||
}
|
||||
|
||||
async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
// Provide default abort signal
|
||||
if (!signal) {
|
||||
@ -1118,7 +1120,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
return sendWindowAIRequest(openai_msgs_tosend, signal, stream);
|
||||
}
|
||||
|
||||
const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.OPENROUTER];
|
||||
const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.SCALE];
|
||||
if (oai_settings.bias_preset_selected
|
||||
&& logitBiasSources.includes(oai_settings.chat_completion_source)
|
||||
&& Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
|
||||
@ -1127,6 +1129,10 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
biasCache = logit_bias;
|
||||
}
|
||||
|
||||
if (isScale && oai_settings.use_alt_scale) {
|
||||
return sendAltScaleRequest(openai_msgs_tosend, logit_bias, signal)
|
||||
}
|
||||
|
||||
const model = getChatCompletionModel();
|
||||
const generate_data = {
|
||||
"messages": openai_msgs_tosend,
|
||||
@ -1363,63 +1369,8 @@ class TokenHandler {
|
||||
}
|
||||
}
|
||||
|
||||
function countTokens(messages, full = false) {
|
||||
let shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer;
|
||||
let chatId = 'undefined';
|
||||
|
||||
try {
|
||||
if (selected_group) {
|
||||
chatId = groups.find(x => x.id == selected_group)?.chat_id;
|
||||
}
|
||||
else if (this_chid) {
|
||||
chatId = characters[this_chid].chat;
|
||||
}
|
||||
} catch {
|
||||
console.log('No character / group selected. Using default cache item');
|
||||
}
|
||||
|
||||
if (typeof tokenCache[chatId] !== 'object') {
|
||||
tokenCache[chatId] = {};
|
||||
}
|
||||
|
||||
if (!Array.isArray(messages)) {
|
||||
messages = [messages];
|
||||
}
|
||||
|
||||
let token_count = -1;
|
||||
|
||||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
const hash = getStringHash(JSON.stringify(message));
|
||||
const cacheKey = `${model}-${hash}`;
|
||||
const cachedCount = tokenCache[chatId][cacheKey];
|
||||
|
||||
if (typeof cachedCount === 'number') {
|
||||
token_count += cachedCount;
|
||||
}
|
||||
|
||||
else {
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
token_count += Number(data.token_count);
|
||||
tokenCache[chatId][cacheKey] = Number(data.token_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!full) token_count -= 2;
|
||||
|
||||
return token_count;
|
||||
}
|
||||
|
||||
const tokenHandler = new TokenHandler(countTokens);
|
||||
const tokenHandler = new TokenHandler(countTokensOpenAI);
|
||||
|
||||
// Thrown by ChatCompletion when a requested prompt couldn't be found.
|
||||
class IdentifierNotFoundError extends Error {
|
||||
@ -1856,62 +1807,6 @@ class ChatCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenizerModel() {
|
||||
// OpenAI models always provide their own tokenizer
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENAI) {
|
||||
return oai_settings.openai_model;
|
||||
}
|
||||
|
||||
const turboTokenizer = 'gpt-3.5-turbo';
|
||||
const gpt4Tokenizer = 'gpt-4';
|
||||
const gpt2Tokenizer = 'gpt2';
|
||||
const claudeTokenizer = 'claude';
|
||||
|
||||
// Assuming no one would use it for different models.. right?
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
|
||||
// Select correct tokenizer for WindowAI proxies
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && oai_settings.windowai_model) {
|
||||
if (oai_settings.windowai_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('claude')) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('GPT-NeoXT')) {
|
||||
return gpt2Tokenizer;
|
||||
}
|
||||
}
|
||||
|
||||
// And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model) {
|
||||
if (oai_settings.openrouter_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('claude')) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('GPT-NeoXT')) {
|
||||
return gpt2Tokenizer;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
|
||||
// Default to Turbo 3.5
|
||||
return turboTokenizer;
|
||||
}
|
||||
|
||||
function loadOpenAISettings(data, settings) {
|
||||
openai_setting_names = data.openai_setting_names;
|
||||
openai_settings = data.openai_settings;
|
||||
@ -1971,6 +1866,7 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; }
|
||||
if (settings.exclude_assistant !== undefined) oai_settings.exclude_assistant = !!settings.exclude_assistant;
|
||||
if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
@ -2001,6 +1897,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
$('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer);
|
||||
$('#exclude_assistant').prop('checked', oai_settings.exclude_assistant);
|
||||
$('#scale-alt').prop('checked', oai_settings.use_alt_scale);
|
||||
if (settings.impersonation_prompt !== undefined) oai_settings.impersonation_prompt = settings.impersonation_prompt;
|
||||
|
||||
$('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt);
|
||||
@ -2199,6 +2096,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
assistant_prefill: settings.assistant_prefill,
|
||||
use_ai21_tokenizer: settings.use_ai21_tokenizer,
|
||||
exclude_assistant: settings.exclude_assistant,
|
||||
use_alt_scale: settings.use_alt_scale,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@ -2536,6 +2434,7 @@ function onSettingsPresetChange() {
|
||||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', false],
|
||||
exclude_assistant: ['#exclude_assistant', 'exclude_assistant', false],
|
||||
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', false],
|
||||
};
|
||||
|
||||
const presetName = $('#settings_perset_openai').find(":selected").text();
|
||||
@ -2831,20 +2730,31 @@ async function onConnectButtonClick(e) {
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
const api_key_scale = $('#api_key_scale').val().trim();
|
||||
const scale_cookie = $('#scale_cookie').val().trim();
|
||||
|
||||
if (api_key_scale.length) {
|
||||
await writeSecret(SECRET_KEYS.SCALE, api_key_scale);
|
||||
}
|
||||
|
||||
if (!oai_settings.api_url_scale) {
|
||||
if (scale_cookie.length) {
|
||||
await writeSecret(SECRET_KEYS.SCALE_COOKIE, scale_cookie);
|
||||
}
|
||||
|
||||
if (!oai_settings.api_url_scale && !oai_settings.use_alt_scale) {
|
||||
console.log('No API URL saved for Scale');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.SCALE]) {
|
||||
if (!secret_state[SECRET_KEYS.SCALE] && !oai_settings.use_alt_scale) {
|
||||
console.log('No secret key saved for Scale');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.SCALE_COOKIE] && oai_settings.use_alt_scale) {
|
||||
console.log("No cookie set for Scale");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
@ -2958,11 +2868,25 @@ function onProxyPasswordShowClick() {
|
||||
$(this).toggleClass('fa-eye-slash fa-eye');
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
await loadTokenCache();
|
||||
function updateScaleForm() {
|
||||
if (oai_settings.use_alt_scale) {
|
||||
$('#normal_scale_form').css('display', 'none');
|
||||
$('#alt_scale_form').css('display', '');
|
||||
} else {
|
||||
$('#normal_scale_form').css('display', '');
|
||||
$('#alt_scale_form').css('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
$('#test_api_button').on('click', testApiConnection);
|
||||
|
||||
$('#scale-alt').on('change', function () {
|
||||
oai_settings.use_alt_scale = !!$('#scale-alt').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
updateScaleForm();
|
||||
});
|
||||
|
||||
$(document).on('input', '#temp_openai', function () {
|
||||
oai_settings.temp_openai = Number($(this).val());
|
||||
$('#temp_counter_openai').text(Number($(this).val()).toFixed(2));
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
import { loadInstructMode } from "./instruct-mode.js";
|
||||
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { tokenizers } from "./tokenizers.js";
|
||||
|
||||
import { delay } from "./utils.js";
|
||||
|
||||
@ -35,7 +36,6 @@ export {
|
||||
fixMarkdown,
|
||||
power_user,
|
||||
pygmalion_options,
|
||||
tokenizers,
|
||||
send_on_enter_options,
|
||||
};
|
||||
|
||||
@ -63,17 +63,6 @@ const pygmalion_options = {
|
||||
ENABLED: 1,
|
||||
}
|
||||
|
||||
const tokenizers = {
|
||||
NONE: 0,
|
||||
GPT3: 1,
|
||||
CLASSIC: 2,
|
||||
LLAMA: 3,
|
||||
NERD: 4,
|
||||
NERD2: 5,
|
||||
API: 6,
|
||||
BEST_MATCH: 99,
|
||||
}
|
||||
|
||||
const send_on_enter_options = {
|
||||
DISABLED: -1,
|
||||
AUTO: 0,
|
||||
@ -207,7 +196,6 @@ let movingUIPresets = [];
|
||||
let context_presets = [];
|
||||
|
||||
const storage_keys = {
|
||||
ui_language: "language",
|
||||
fast_ui_mode: "TavernAI_fast_ui_mode",
|
||||
avatar_style: "TavernAI_avatar_style",
|
||||
chat_display: "TavernAI_chat_display",
|
||||
@ -247,29 +235,42 @@ function playMessageSound() {
|
||||
}
|
||||
|
||||
const audio = document.getElementById('audio_message_sound');
|
||||
audio.volume = 0.8;
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
if (audio instanceof HTMLAudioElement) {
|
||||
audio.volume = 0.8;
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces consecutive newlines with a single newline.
|
||||
* @param {string} x String to be processed.
|
||||
* @returns {string} Processed string.
|
||||
* @example
|
||||
* collapseNewlines("\n\n\n"); // "\n"
|
||||
*/
|
||||
function collapseNewlines(x) {
|
||||
return x.replaceAll(/\n+/g, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix formatting problems in markdown.
|
||||
* @param {string} text Text to be processed.
|
||||
* @returns {string} Processed text.
|
||||
* @example
|
||||
* "^example * text*\n" // "^example *text*\n"
|
||||
* "^*example * text\n"// "^*example* text\n"
|
||||
* "^example *text *\n" // "^example *text*\n"
|
||||
* "^* example * text\n" // "^*example* text\n"
|
||||
* // take note that the side you move the asterisk depends on where its pairing is
|
||||
* // i.e. both of the following strings have the same broken asterisk ' * ',
|
||||
* // but you move the first to the left and the second to the right, to match the non-broken asterisk
|
||||
* "^example * text*\n" // "^*example * text\n"
|
||||
* // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
|
||||
* "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"
|
||||
*/
|
||||
function fixMarkdown(text) {
|
||||
// fix formatting problems in markdown
|
||||
// e.g.:
|
||||
// "^example * text*\n" -> "^example *text*\n"
|
||||
// "^*example * text\n" -> "^*example* text\n"
|
||||
// "^example *text *\n" -> "^example *text*\n"
|
||||
// "^* example * text\n" -> "^*example* text\n"
|
||||
// take note that the side you move the asterisk depends on where its pairing is
|
||||
// i.e. both of the following strings have the same broken asterisk ' * ',
|
||||
// but you move the first to the left and the second to the right, to match the non-broken asterisk "^example * text*\n" "^*example * text\n"
|
||||
// and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
|
||||
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
|
||||
|
||||
// Find pairs of formatting characters and capture the text in between them
|
||||
const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
|
||||
let matches = [];
|
||||
@ -899,7 +900,7 @@ function loadContextSettings() {
|
||||
});
|
||||
|
||||
$('#context_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const name = String($(this).find(':selected').val());
|
||||
const preset = context_presets.find(x => x.name === name);
|
||||
|
||||
if (!preset) {
|
||||
@ -1020,6 +1021,10 @@ const compareFunc = (first, second) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts an array of entities based on the current sort settings
|
||||
* @param {any[]} entities An array of objects with an `item` property
|
||||
*/
|
||||
function sortEntitiesList(entities) {
|
||||
if (power_user.sort_field == undefined || entities.length === 0) {
|
||||
return;
|
||||
@ -1027,6 +1032,7 @@ function sortEntitiesList(entities) {
|
||||
|
||||
entities.sort((a, b) => sortFunc(a.item, b.item));
|
||||
}
|
||||
|
||||
async function saveTheme() {
|
||||
const name = await callPopup('Enter a theme preset name:', 'input');
|
||||
|
||||
@ -1250,8 +1256,8 @@ async function doDelMode(_, text) {
|
||||
if (text) {
|
||||
await delay(300) //same as above, need event signal for 'entered del mode'
|
||||
console.debug('parsing msgs to del')
|
||||
let numMesToDel = Number(text).toFixed(0)
|
||||
let lastMesID = $('.last_mes').attr('mesid')
|
||||
let numMesToDel = Number(text);
|
||||
let lastMesID = Number($('.last_mes').attr('mesid'));
|
||||
let oldestMesIDToDel = lastMesID - numMesToDel + 1;
|
||||
|
||||
//disallow targeting first message
|
||||
@ -1277,26 +1283,6 @@ function doResetPanels() {
|
||||
$("#movingUIreset").trigger('click');
|
||||
}
|
||||
|
||||
function addLanguagesToDropdown() {
|
||||
$.getJSON('i18n.json', function (data) {
|
||||
if (!Array.isArray(data?.lang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const lang of data.lang) {
|
||||
const option = document.createElement('option');
|
||||
option.value = lang;
|
||||
option.innerText = lang;
|
||||
$('#ui_language_select').append(option);
|
||||
}
|
||||
|
||||
const selectedLanguage = localStorage.getItem(storage_keys.ui_language);
|
||||
if (selectedLanguage) {
|
||||
$('#ui_language_select').val(selectedLanguage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setAvgBG() {
|
||||
const bgimg = new Image();
|
||||
bgimg.src = $('#bg1')
|
||||
@ -1348,10 +1334,6 @@ function setAvgBG() {
|
||||
$("#user-mes-blur-tint-color-picker").attr('color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')');
|
||||
} */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function getAverageRGB(imgEl) {
|
||||
|
||||
var blockSize = 5, // only visit every 5 pixels
|
||||
@ -1396,6 +1378,13 @@ function setAvgBG() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB.
|
||||
* @param {number} h Hue value
|
||||
* @param {number} s Saturation value
|
||||
* @param {number} l Luminance value
|
||||
* @return {Array} The RGB representation
|
||||
*/
|
||||
function hslToRgb(h, s, l) {
|
||||
const hueToRgb = (p, q, t) => {
|
||||
if (t < 0) t += 1;
|
||||
@ -1437,7 +1426,7 @@ function setAvgBG() {
|
||||
|
||||
console.log(`rLum ${rLuminance}, gLum ${gLuminance}, bLum ${bLuminance}`)
|
||||
|
||||
return 0.2126 * rLuminance + 0.7152 * gLuminance + 0.0722 * bLuminance;
|
||||
return 0.2126 * Number(rLuminance) + 0.7152 * Number(gLuminance) + 0.0722 * Number(bLuminance);
|
||||
}
|
||||
|
||||
//this version keeps BG and main text in same hue
|
||||
@ -1620,13 +1609,13 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$("#markdown_escape_strings").on('input', function () {
|
||||
power_user.markdown_escape_strings = $(this).val();
|
||||
power_user.markdown_escape_strings = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
});
|
||||
|
||||
$("#start_reply_with").on('input', function () {
|
||||
power_user.user_prompt_bias = $(this).val();
|
||||
power_user.user_prompt_bias = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
@ -1753,7 +1742,7 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$("#themes").on('change', function () {
|
||||
const themeSelected = $(this).find(':selected').val();
|
||||
const themeSelected = String($(this).find(':selected').val());
|
||||
power_user.theme = themeSelected;
|
||||
applyTheme(themeSelected);
|
||||
saveSettingsDebounced();
|
||||
@ -1761,7 +1750,7 @@ $(document).ready(() => {
|
||||
|
||||
$("#movingUIPresets").on('change', async function () {
|
||||
console.log('saw MUI preset change')
|
||||
const movingUIPresetSelected = $(this).find(':selected').val();
|
||||
const movingUIPresetSelected = String($(this).find(':selected').val());
|
||||
power_user.movingUIPreset = movingUIPresetSelected;
|
||||
applyMovingUIPreset(movingUIPresetSelected);
|
||||
saveSettingsDebounced();
|
||||
@ -1821,7 +1810,7 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$('#auto_swipe_blacklist').on('input', function () {
|
||||
power_user.auto_swipe_blacklist = $(this).val()
|
||||
power_user.auto_swipe_blacklist = String($(this).val())
|
||||
.split(",")
|
||||
.map(str => str.trim())
|
||||
.filter(str => str);
|
||||
@ -1830,7 +1819,7 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$('#auto_swipe_minimum_length').on('input', function () {
|
||||
const number = parseInt($(this).val());
|
||||
const number = Number($(this).val());
|
||||
if (!isNaN(number)) {
|
||||
power_user.auto_swipe_minimum_length = number;
|
||||
saveSettingsDebounced();
|
||||
@ -1838,7 +1827,7 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$('#auto_swipe_blacklist_threshold').on('input', function () {
|
||||
const number = parseInt($(this).val());
|
||||
const number = Number($(this).val());
|
||||
if (!isNaN(number)) {
|
||||
power_user.auto_swipe_blacklist_threshold = number;
|
||||
saveSettingsDebounced();
|
||||
@ -1921,35 +1910,35 @@ $(document).ready(() => {
|
||||
$("#messageTimerEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timer_enabled = value;
|
||||
localStorage.setItem(storage_keys.timer_enabled, power_user.timer_enabled);
|
||||
localStorage.setItem(storage_keys.timer_enabled, String(power_user.timer_enabled));
|
||||
switchTimer();
|
||||
});
|
||||
|
||||
$("#messageTimestampsEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timestamps_enabled = value;
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, String(power_user.timestamps_enabled));
|
||||
switchTimestamps();
|
||||
});
|
||||
|
||||
$("#messageModelIconEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timestamp_model_icon = value;
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, power_user.timestamp_model_icon);
|
||||
localStorage.setItem(storage_keys.timestamp_model_icon, String(power_user.timestamp_model_icon));
|
||||
switchIcons();
|
||||
});
|
||||
|
||||
$("#mesIDDisplayEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.mesIDDisplay_enabled = value;
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, String(power_user.mesIDDisplay_enabled));
|
||||
switchMesIDDisplay();
|
||||
});
|
||||
|
||||
$("#hotswapEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.hotswap_enabled = value;
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, power_user.hotswap_enabled);
|
||||
localStorage.setItem(storage_keys.hotswap_enabled, String(power_user.hotswap_enabled));
|
||||
switchHotswap();
|
||||
});
|
||||
|
||||
@ -1995,7 +1984,7 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$('#custom_stopping_strings').on('input', function () {
|
||||
power_user.custom_stopping_strings = $(this).val();
|
||||
power_user.custom_stopping_strings = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
@ -2025,18 +2014,6 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#ui_language_select').on('change', async function () {
|
||||
const language = $(this).val();
|
||||
|
||||
if (language) {
|
||||
localStorage.setItem(storage_keys.ui_language, language);
|
||||
} else {
|
||||
localStorage.removeItem(storage_keys.ui_language);
|
||||
}
|
||||
|
||||
location.reload();
|
||||
});
|
||||
|
||||
$(window).on('focus', function () {
|
||||
browser_has_focus = true;
|
||||
});
|
||||
@ -2052,5 +2029,4 @@ $(document).ready(() => {
|
||||
registerSlashCommand('cut', doMesCut, [], ' <span class="monospace">(requred number)</span> – cuts the specified message from the chat', true, true);
|
||||
registerSlashCommand('resetpanels', doResetPanels, ['resetui'], ' – resets UI panels to original state.', true, true);
|
||||
registerSlashCommand('bgcol', setAvgBG, [], ' – WIP test of auto-bg avg coloring', true, true);
|
||||
addLanguagesToDropdown();
|
||||
});
|
||||
|
@ -118,7 +118,7 @@ class PresetManager {
|
||||
async savePresetAs() {
|
||||
const popupText = `
|
||||
<h3>Preset name:</h3>
|
||||
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
|
||||
${!this.isNonGenericApi() ? '<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>' : ''}`;
|
||||
const name = await callPopup(popupText, "input");
|
||||
|
||||
if (!name) {
|
||||
@ -131,7 +131,8 @@ class PresetManager {
|
||||
}
|
||||
|
||||
async savePreset(name, settings) {
|
||||
const preset = settings ?? this.getPresetSettings();
|
||||
const preset = settings ?? this.getPresetSettings(name);
|
||||
|
||||
const res = await fetch(`/save_preset`, {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
@ -220,7 +221,7 @@ class PresetManager {
|
||||
}
|
||||
}
|
||||
|
||||
getPresetSettings() {
|
||||
getPresetSettings(name) {
|
||||
function getSettingsByApiId(apiId) {
|
||||
switch (apiId) {
|
||||
case "koboldhorde":
|
||||
@ -232,7 +233,7 @@ class PresetManager {
|
||||
return textgenerationwebui_settings;
|
||||
case "instruct":
|
||||
const preset = deepClone(power_user.instruct);
|
||||
preset['name'] = power_user.instruct.preset;
|
||||
preset['name'] = name || power_user.instruct.preset;
|
||||
return preset;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${apiId}`);
|
||||
@ -346,7 +347,7 @@ jQuery(async () => {
|
||||
|
||||
const selected = $(presetManager.select).find("option:selected");
|
||||
const name = selected.text();
|
||||
const preset = presetManager.getPresetSettings();
|
||||
const preset = presetManager.getPresetSettings(name);
|
||||
const data = JSON.stringify(preset, null, 4);
|
||||
download(data, `${name}.json`, "application/json");
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ export const SECRET_KEYS = {
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21',
|
||||
SCALE_COOKIE: 'scale_cookie',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@ -20,6 +21,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
|
||||
[SECRET_KEYS.SCALE]: '#api_key_scale',
|
||||
[SECRET_KEYS.AI21]: '#api_key_ai21',
|
||||
[SECRET_KEYS.SCALE_COOKIE]: '#scale_cookie',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
reloadCurrentChat,
|
||||
sendMessageAsUser,
|
||||
} from "../script.js";
|
||||
import { humanizedDateTime } from "./RossAscends-mods.js";
|
||||
import { getMessageTimeStamp } 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";
|
||||
@ -327,7 +327,7 @@ async function sendMessageAs(_, text) {
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: isSystem,
|
||||
send_date: humanizedDateTime(),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(mesText),
|
||||
force_avatar: force_avatar,
|
||||
original_avatar: original_avatar,
|
||||
@ -338,8 +338,9 @@ async function sendMessageAs(_, text) {
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
@ -358,7 +359,7 @@ async function sendNarratorMessage(_, text) {
|
||||
is_user: false,
|
||||
is_name: false,
|
||||
is_system: isSystem,
|
||||
send_date: humanizedDateTime(),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(text.trim()),
|
||||
force_avatar: system_avatar,
|
||||
extra: {
|
||||
@ -369,8 +370,9 @@ async function sendNarratorMessage(_, text) {
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
@ -384,7 +386,7 @@ async function sendCommentMessage(_, text) {
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: true,
|
||||
send_date: humanizedDateTime(),
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: substituteParams(text.trim()),
|
||||
force_avatar: comment_avatar,
|
||||
extra: {
|
||||
@ -394,8 +396,9 @@ async function sendCommentMessage(_, text) {
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,12 @@ function createStatBlock(statName, statValue) {
|
||||
* @returns {number} - The stat value if it is a number, otherwise 0.
|
||||
*/
|
||||
function verifyStatValue(stat) {
|
||||
return isNaN(stat) ? 0 : stat;
|
||||
return isNaN(Number(stat)) ? 0 : Number(stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total stats from character statistics.
|
||||
*
|
||||
* @param {Object} charStats - Object containing character statistics.
|
||||
* @returns {Object} - Object containing total statistics.
|
||||
*/
|
||||
function calculateTotalStats() {
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
getCharacters,
|
||||
entitiesFilter,
|
||||
} from "../script.js";
|
||||
import { FILTER_TYPES } from "./filters.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./filters.js";
|
||||
|
||||
import { groupCandidatesFilter, selected_group } from "./group-chats.js";
|
||||
import { uuidv4 } from "./utils.js";
|
||||
@ -24,7 +24,6 @@ export {
|
||||
importTags,
|
||||
};
|
||||
|
||||
const random_id = () => uuidv4();
|
||||
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
|
||||
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
|
||||
|
||||
@ -49,17 +48,21 @@ const InListActionable = {
|
||||
}
|
||||
|
||||
const DEFAULT_TAGS = [
|
||||
{ id: random_id(), name: "Plain Text" },
|
||||
{ id: random_id(), name: "OpenAI" },
|
||||
{ id: random_id(), name: "W++" },
|
||||
{ id: random_id(), name: "Boostyle" },
|
||||
{ id: random_id(), name: "PList" },
|
||||
{ id: random_id(), name: "AliChat" },
|
||||
{ id: uuidv4(), name: "Plain Text" },
|
||||
{ id: uuidv4(), name: "OpenAI" },
|
||||
{ id: uuidv4(), name: "W++" },
|
||||
{ id: uuidv4(), name: "Boostyle" },
|
||||
{ id: uuidv4(), name: "PList" },
|
||||
{ id: uuidv4(), name: "AliChat" },
|
||||
];
|
||||
|
||||
let tags = [];
|
||||
let tag_map = {};
|
||||
|
||||
/**
|
||||
* Applies the favorite filter to the character list.
|
||||
* @param {FilterHelper} filterHelper Instance of FilterHelper class.
|
||||
*/
|
||||
function applyFavFilter(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayFavoritesOnly = !isSelected;
|
||||
@ -68,6 +71,10 @@ function applyFavFilter(filterHelper) {
|
||||
filterHelper.setFilterData(FILTER_TYPES.FAV, displayFavoritesOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the "is group" filter to the character list.
|
||||
* @param {FilterHelper} filterHelper Instance of FilterHelper class.
|
||||
*/
|
||||
function filterByGroups(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayGroupsOnly = !isSelected;
|
||||
@ -253,7 +260,7 @@ async function importTags(imported_char) {
|
||||
|
||||
function createNewTag(tagName) {
|
||||
const tag = {
|
||||
id: random_id(),
|
||||
id: uuidv4(),
|
||||
name: tagName,
|
||||
color: '',
|
||||
};
|
||||
|
21
public/scripts/templates/formatting.html
Normal file
21
public/scripts/templates/formatting.html
Normal file
@ -0,0 +1,21 @@
|
||||
Text formatting commands:
|
||||
<ul>
|
||||
<li><tt>*text*</tt> - displays as <i>italics</i></li>
|
||||
<li><tt>**text**</tt> - displays as <b>bold</b></li>
|
||||
<li><tt>***text***</tt> - displays as <b><i>bold italics</i></b></li>
|
||||
<li><tt>```text```</tt> - displays as a code block (new lines allowed between the backticks)</li>
|
||||
</ul>
|
||||
<pre><code> like this</code></pre>
|
||||
<ul>
|
||||
<li><tt>`text`</tt> - displays as <code>inline code</code></li>
|
||||
<li><tt> text</tt> - displays as a blockquote (note the space after >)</li>
|
||||
<blockquote>like this</blockquote>
|
||||
<li><tt># text</tt> - displays as a large header (note the space)</li>
|
||||
<h1>like this</h1>
|
||||
<li><tt>## text</tt> - displays as a medium header (note the space)</li>
|
||||
<h2>like this</h2>
|
||||
<li><tt>### text</tt> - displays as a small header (note the space)</li>
|
||||
<h3>like this</h3>
|
||||
<li><tt>$$ text $$</tt> - renders a LaTeX formula (if enabled)</li>
|
||||
<li><tt>$ text $</tt> - renders an AsciiMath formula (if enabled)</li>
|
||||
</ul>
|
11
public/scripts/templates/help.html
Normal file
11
public/scripts/templates/help.html
Normal file
@ -0,0 +1,11 @@
|
||||
Hello there! Please select the help topic you would like to learn more about:
|
||||
<ul>
|
||||
<li><a href="#" data-displayHelp="1">Slash Commands</a> (or <tt>/help slash</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="2">Formatting</a> (or <tt>/help format</tt>)</li>
|
||||
<li><a href="#" data-displayHelp="3">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
|
||||
<li><a href="#" data-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>
|
13
public/scripts/templates/hotkeys.html
Normal file
13
public/scripts/templates/hotkeys.html
Normal file
@ -0,0 +1,13 @@
|
||||
Hotkeys/Keybinds:
|
||||
<ul>
|
||||
<li><tt>Up</tt> = Edit last message in chat</li>
|
||||
<li><tt>Ctrl+Up</tt> = Edit last USER message in chat</li>
|
||||
<li><tt>Left</tt> = swipe left</li>
|
||||
<li><tt>Right</tt> = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</li>
|
||||
<li><tt>Ctrl+Left</tt> = view locally stored variables (in the browser console window)</li>
|
||||
<li><tt>Enter</tt> (with chat bar selected) = send your message to AI</li>
|
||||
<li><tt>Ctrl+Enter</tt> = Regenerate the last AI response</li>
|
||||
<li><tt>Escape</tt> = stop AI response generation</li>
|
||||
<li><tt>Ctrl+Shift+Up</tt> = Scroll to context line</li>
|
||||
<li><tt>Ctrl+Shift+Down</tt> = Scroll chat to bottom</li>
|
||||
</ul>
|
128
public/scripts/templates/itemizationChat.html
Normal file
128
public/scripts/templates/itemizationChat.html
Normal file
@ -0,0 +1,128 @@
|
||||
<h3 class="flex-container justifyCenter alignitemscenter">
|
||||
Prompt Itemization
|
||||
<div id="showRawPrompt" class="fa-solid fa-square-poll-horizontal menu_button"></div>
|
||||
</h3>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
API Used: {{this_main_api}}<br>
|
||||
<span class="tokenItemizingSubclass">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
Grey color items may not have been included in the context due to certain prompt format settings.
|
||||
</span>
|
||||
<hr>
|
||||
<div class="justifyLeft">
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flex1 flexFlowColumns flexNoGap wide50p tokenGraph">
|
||||
<div class="wide100p" style="background-color: grey; height: {{oaiSystemTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: salmon; height: {{oaiStartTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: indianred; height: {{storyStringTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: gold; height: {{worldInfoStringTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: palegreen; height: {{ActualChatHistoryTokensPercentage}}%;">
|
||||
</div>
|
||||
<div class="wide100p" style="background-color: cornflowerblue; height: {{allAnchorsTokensPercentage}}%;">
|
||||
</div>
|
||||
<div class="wide100p" style="background-color: mediumpurple; height: {{promptBiasTokensPercentage}}%;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container wide50p">
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: grey;">System Info:</div>
|
||||
<div class=""> {{oaiSystemTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Chat Start: </div>
|
||||
<div class="tokenItemizingSubclass"> {{oaiStartTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Main: </div>
|
||||
<div class="tokenItemizingSubclass">{{oaiMainTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Jailbreak: </div>
|
||||
<div class="tokenItemizingSubclass">{{oaiJailbreakTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- NSFW: </div>
|
||||
<div class="tokenItemizingSubclass">{{oaiNsfwTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Nudge: </div>
|
||||
<div class="tokenItemizingSubclass">{{oaiNudgeTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Impersonate: </div>
|
||||
<div class="tokenItemizingSubclass">{{oaiImpersonateTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: indianred;">Prompt Tokens:</div>
|
||||
<div class=""> {{oaiPromptTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Description: </div>
|
||||
<div class="tokenItemizingSubclass">{{charDescriptionTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Personality:</div>
|
||||
<div class="tokenItemizingSubclass"> {{charPersonalityTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Scenario: </div>
|
||||
<div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- User Persona:</div>
|
||||
<div class="tokenItemizingSubclass"> {{userPersonaStringTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: gold;">World Info:</div>
|
||||
<div class="">{{worldInfoStringTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;">Chat History:</div>
|
||||
<div class=""> {{ActualChatHistoryTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: cornflowerblue;">Extensions:</div>
|
||||
<div class="">{{allAnchorsTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Summarize: </div>
|
||||
<div class="tokenItemizingSubclass">{{summarizeStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Author's Note:</div>
|
||||
<div class="tokenItemizingSubclass"> {{authorsNoteStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Smart Context:</div>
|
||||
<div class="tokenItemizingSubclass"> {{smartContextStringTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: mediumpurple;">{{}} Bias:</div>
|
||||
<div class="">{{oaiBiasTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
<div class="wide100p flex-container flexFlowColumns">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Total Tokens in Prompt:</div>
|
||||
<div class=""> {{finalPromptTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Max Context (Context Size - Response Length):</div>
|
||||
<div class="">{{thisPrompt_max_context}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
108
public/scripts/templates/itemizationText.html
Normal file
108
public/scripts/templates/itemizationText.html
Normal file
@ -0,0 +1,108 @@
|
||||
<h3 class="flex-container justifyCenter alignitemscenter">
|
||||
Prompt Itemization
|
||||
<div id="showRawPrompt" class="fa-solid fa-square-poll-horizontal menu_button"></div>
|
||||
</h3>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
API Used: {{this_main_api}}<br>
|
||||
<span class="tokenItemizingSubclass">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
Grey color items may not have been included in the context due to certain prompt format settings.
|
||||
</span>
|
||||
<hr>
|
||||
<div class="justifyLeft">
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flex1 flexFlowColumns flexNoGap wide50p tokenGraph">
|
||||
<div class="wide100p" style="background-color: indianred; height: {{storyStringTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: gold; height: {{worldInfoStringTokensPercentage}}%;"></div>
|
||||
<div class="wide100p" style="background-color: palegreen; height: {{ActualChatHistoryTokensPercentage}}%;">
|
||||
</div>
|
||||
<div class="wide100p" style="background-color: cornflowerblue; height: {{allAnchorsTokensPercentage}}%;">
|
||||
</div>
|
||||
<div class="wide100p" style="background-color: mediumpurple; height: {{promptBiasTokensPercentage}}%;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container wide50p">
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1" style="color: indianred;"> Character Definitions:</div>
|
||||
<div class=""> {{storyStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Description: </div>
|
||||
<div class="tokenItemizingSubclass">{{charDescriptionTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Personality:</div>
|
||||
<div class="tokenItemizingSubclass"> {{charPersonalityTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Scenario: </div>
|
||||
<div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- User Persona:</div>
|
||||
<div class="tokenItemizingSubclass"> {{userPersonaStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- System Prompt (Instruct):</div>
|
||||
<div class="tokenItemizingSubclass"> {{instructionTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: gold;">World Info:</div>
|
||||
<div class="">{{worldInfoStringTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;">Chat History:</div>
|
||||
<div class=""> {{ActualChatHistoryTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: cornflowerblue;">Extensions:</div>
|
||||
<div class="">{{allAnchorsTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Summarize: </div>
|
||||
<div class="tokenItemizingSubclass">{{summarizeStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Author's Note:</div>
|
||||
<div class="tokenItemizingSubclass"> {{authorsNoteStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Smart Context:</div>
|
||||
<div class="tokenItemizingSubclass"> {{smartContextStringTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: mediumpurple;">{{}} Bias:</div>
|
||||
<div class="">{{promptBiasTokens}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
<div class="wide100p flex-container flexFlowColumns">
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Total Tokens in Prompt:</div>
|
||||
<div class=""> {{totalTokensInPrompt}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Max Context (Context Size - Response Length):</div>
|
||||
<div class="">{{thisPrompt_max_context}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">- Padding:</div>
|
||||
<div class=""> {{thisPrompt_padding}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1">Actual Max Context Allowed:</div>
|
||||
<div class="">{{thisPrompt_actual}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
11
public/scripts/templates/macros.html
Normal file
11
public/scripts/templates/macros.html
Normal file
@ -0,0 +1,11 @@
|
||||
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>{{input}}</tt> - the user input</li>
|
||||
<li><tt>{{time}}</tt> - the current time</li>
|
||||
<li><tt>{{date}}</tt> - the current date</li>
|
||||
<li><tt>{{idle_duration}}</tt> - the time since the last user message was sent</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>
|
||||
<li><tt>{{roll:(formula)}}</tt> - rolls a dice. (ex: {{roll:1d6}} will roll a 6-sided dice and return a number between 1 and 6)</li>
|
||||
</ul>
|
72
public/scripts/templates/welcome.html
Normal file
72
public/scripts/templates/welcome.html
Normal file
@ -0,0 +1,72 @@
|
||||
<h3>
|
||||
<span id="version_display_welcome">SillyTavern</span>
|
||||
<div id="version_display_welcome"></div>
|
||||
</h3>
|
||||
<a href="https://docs.sillytavern.app/usage/update/"" target=" _blank">
|
||||
Want to update?
|
||||
</a>
|
||||
<hr>
|
||||
<h3>How to start chatting?</h3>
|
||||
<ol>
|
||||
<li>Click <code><i class="fa-solid fa-plug"></i></code> and select a <a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank">Chat API</a>.</li>
|
||||
<li>Click <code><i class="fa-solid fa-address-card"></i></code> and pick a character</li>
|
||||
</ol>
|
||||
<hr>
|
||||
<h3>
|
||||
Want more characters?
|
||||
</h3>
|
||||
<i>
|
||||
Not controlled by SillyTavern team.
|
||||
</i>
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" href="https://discord.gg/pygmalionai">
|
||||
Pygmalion AI Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://chub.ai/">
|
||||
Chub (NSFW)
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h3>Confused or lost?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="note-link-span">?</span> - click these icons!
|
||||
</li>
|
||||
<li>
|
||||
Enter <code>/?</code> in the chat bar
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.sillytavern.app/">
|
||||
SillyTavern Documentation Site
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.sillytavern.app/extras/installation/">
|
||||
Extras Installation Guide
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h3>Still have questions?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" href="https://discord.gg/RZdyAEUPvj">
|
||||
Join the SillyTavern Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern/issues">
|
||||
Post a GitHub issue
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern#questions-or-suggestions">
|
||||
Contact the developers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
@ -6,8 +6,6 @@ import {
|
||||
setGenerationParamsFromPreset,
|
||||
} from "../script.js";
|
||||
|
||||
import { getCfg } from "./extensions/cfg/util.js";
|
||||
|
||||
import {
|
||||
power_user,
|
||||
} from "./power-user.js";
|
||||
@ -170,9 +168,9 @@ $(document).ready(function () {
|
||||
textgenerationwebui_settings[id] = value;
|
||||
}
|
||||
else {
|
||||
const value = parseFloat($(this).val());
|
||||
const value = Number($(this).val());
|
||||
$(`#${id}_counter_textgenerationwebui`).text(value.toFixed(2));
|
||||
textgenerationwebui_settings[id] = parseFloat(value);
|
||||
textgenerationwebui_settings[id] = value;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
@ -209,7 +207,7 @@ async function generateTextGenWithStreaming(generate_data, signal) {
|
||||
const response = await fetch('/generate_textgenerationwebui', {
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
'X-Response-Streaming': true,
|
||||
'X-Response-Streaming': String(true),
|
||||
'X-Streaming-URL': textgenerationwebui_settings.streaming_url,
|
||||
},
|
||||
body: JSON.stringify(generate_data),
|
||||
@ -235,9 +233,7 @@ async function generateTextGenWithStreaming(generate_data, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTextGenGenerationData(finalPromt, this_amount_gen, isImpersonate) {
|
||||
const cfgValues = getCfg();
|
||||
|
||||
export function getTextGenGenerationData(finalPromt, this_amount_gen, isImpersonate, cfgValues) {
|
||||
return {
|
||||
'prompt': finalPromt,
|
||||
'max_new_tokens': this_amount_gen,
|
||||
@ -255,7 +251,7 @@ export function getTextGenGenerationData(finalPromt, this_amount_gen, isImperson
|
||||
'penalty_alpha': textgenerationwebui_settings.penalty_alpha,
|
||||
'length_penalty': textgenerationwebui_settings.length_penalty,
|
||||
'early_stopping': textgenerationwebui_settings.early_stopping,
|
||||
'guidance_scale': cfgValues?.guidanceScale ?? textgenerationwebui_settings.guidance_scale ?? 1,
|
||||
'guidance_scale': cfgValues?.guidanceScale?.value ?? textgenerationwebui_settings.guidance_scale ?? 1,
|
||||
'negative_prompt': cfgValues?.negativePrompt ?? textgenerationwebui_settings.negative_prompt ?? '',
|
||||
'seed': textgenerationwebui_settings.seed,
|
||||
'add_bos_token': textgenerationwebui_settings.add_bos_token,
|
||||
|
342
public/scripts/tokenizers.js
Normal file
342
public/scripts/tokenizers.js
Normal file
@ -0,0 +1,342 @@
|
||||
import { characters, main_api, nai_settings, this_chid } from "../script.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { encode } from "../lib/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../lib/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
import { getStringHash } from "./utils.js";
|
||||
|
||||
export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
|
||||
|
||||
export const tokenizers = {
|
||||
NONE: 0,
|
||||
GPT3: 1,
|
||||
CLASSIC: 2,
|
||||
LLAMA: 3,
|
||||
NERD: 4,
|
||||
NERD2: 5,
|
||||
API: 6,
|
||||
BEST_MATCH: 99,
|
||||
};
|
||||
|
||||
const objectStore = new localforage.createInstance({ name: "SillyTavern_ChatCompletions" });
|
||||
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
|
||||
|
||||
let tokenCache = {};
|
||||
|
||||
async function loadTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: loading token cache')
|
||||
tokenCache = await objectStore.getItem('tokenCache') || {};
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to load token cache, using default value', e);
|
||||
tokenCache = {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: saving token cache')
|
||||
await objectStore.setItem('tokenCache', tokenCache);
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to save token cache', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetTokenCache() {
|
||||
try {
|
||||
console.debug('Chat Completions: resetting token cache');
|
||||
Object.keys(tokenCache).forEach(key => delete tokenCache[key]);
|
||||
await objectStore.removeItem('tokenCache');
|
||||
} catch (e) {
|
||||
console.log('Chat Completions: unable to reset token cache', e);
|
||||
}
|
||||
}
|
||||
|
||||
window['resetTokenCache'] = resetTokenCache;
|
||||
|
||||
function getTokenizerBestMatch() {
|
||||
if (main_api === 'novel') {
|
||||
if (nai_settings.model_novel.includes('krake') || nai_settings.model_novel.includes('euterpe')) {
|
||||
return tokenizers.CLASSIC;
|
||||
}
|
||||
if (nai_settings.model_novel.includes('clio')) {
|
||||
return tokenizers.NERD;
|
||||
}
|
||||
if (nai_settings.model_novel.includes('kayra')) {
|
||||
return tokenizers.NERD2;
|
||||
}
|
||||
}
|
||||
if (main_api === 'kobold' || main_api === 'textgenerationwebui' || main_api === 'koboldhorde') {
|
||||
return tokenizers.LLAMA;
|
||||
}
|
||||
|
||||
return tokenizers.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token count for a string using the current model tokenizer.
|
||||
* @param {string} str String to tokenize
|
||||
* @param {number | undefined} padding Optional padding tokens. Defaults to 0.
|
||||
* @returns {number} Token count.
|
||||
*/
|
||||
export function getTokenCount(str, padding = undefined) {
|
||||
/**
|
||||
* Calculates the token count for a string.
|
||||
* @param {number} [type] Tokenizer type.
|
||||
* @returns {number} Token count.
|
||||
*/
|
||||
function calculate(type) {
|
||||
switch (type) {
|
||||
case tokenizers.NONE:
|
||||
return Math.ceil(str.length / CHARACTERS_PER_TOKEN_RATIO) + padding;
|
||||
case tokenizers.GPT3:
|
||||
return gpt3.encode(str).bpe.length + padding;
|
||||
case tokenizers.CLASSIC:
|
||||
return encode(str).length + padding;
|
||||
case tokenizers.LLAMA:
|
||||
return countTokensRemote('/tokenize_llama', str, padding);
|
||||
case tokenizers.NERD:
|
||||
return countTokensRemote('/tokenize_nerdstash', str, padding);
|
||||
case tokenizers.NERD2:
|
||||
return countTokensRemote('/tokenize_nerdstash_v2', str, padding);
|
||||
case tokenizers.API:
|
||||
return countTokensRemote('/tokenize_via_api', str, padding);
|
||||
default:
|
||||
console.warn("Unknown tokenizer type", type);
|
||||
return calculate(tokenizers.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof str !== 'string' || !str?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let tokenizerType = power_user.tokenizer;
|
||||
|
||||
if (main_api === 'openai') {
|
||||
if (padding === power_user.token_padding) {
|
||||
// For main "shadow" prompt building
|
||||
tokenizerType = tokenizers.NONE;
|
||||
} else {
|
||||
// For extensions and WI
|
||||
return counterWrapperOpenAI(str);
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenizerType === tokenizers.BEST_MATCH) {
|
||||
tokenizerType = getTokenizerBestMatch();
|
||||
}
|
||||
|
||||
if (padding === undefined) {
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
const cacheObject = getTokenCacheObject();
|
||||
const hash = getStringHash(str);
|
||||
const cacheKey = `${tokenizerType}-${hash}`;
|
||||
|
||||
if (typeof cacheObject[cacheKey] === 'number') {
|
||||
return cacheObject[cacheKey];
|
||||
}
|
||||
|
||||
const result = calculate(tokenizerType);
|
||||
|
||||
if (isNaN(result)) {
|
||||
console.warn("Token count calculation returned NaN");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cacheObject[cacheKey] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token count for a string using the OpenAI tokenizer.
|
||||
* @param {string} text Text to tokenize.
|
||||
* @returns {number} Token count.
|
||||
*/
|
||||
function counterWrapperOpenAI(text) {
|
||||
const message = { role: 'system', content: text };
|
||||
return countTokensOpenAI(message, true);
|
||||
}
|
||||
|
||||
export function getTokenizerModel() {
|
||||
// OpenAI models always provide their own tokenizer
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENAI) {
|
||||
return oai_settings.openai_model;
|
||||
}
|
||||
|
||||
const turboTokenizer = 'gpt-3.5-turbo';
|
||||
const gpt4Tokenizer = 'gpt-4';
|
||||
const gpt2Tokenizer = 'gpt2';
|
||||
const claudeTokenizer = 'claude';
|
||||
|
||||
// Assuming no one would use it for different models.. right?
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
|
||||
// Select correct tokenizer for WindowAI proxies
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && oai_settings.windowai_model) {
|
||||
if (oai_settings.windowai_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('claude')) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('GPT-NeoXT')) {
|
||||
return gpt2Tokenizer;
|
||||
}
|
||||
}
|
||||
|
||||
// And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model) {
|
||||
if (oai_settings.openrouter_model.includes('gpt-4')) {
|
||||
return gpt4Tokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('gpt-3.5-turbo')) {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('claude')) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
else if (oai_settings.openrouter_model.includes('GPT-NeoXT')) {
|
||||
return gpt2Tokenizer;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
|
||||
// Default to Turbo 3.5
|
||||
return turboTokenizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any[] | Object} messages
|
||||
*/
|
||||
export function countTokensOpenAI(messages, full = false) {
|
||||
const shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer;
|
||||
const cacheObject = getTokenCacheObject();
|
||||
|
||||
if (!Array.isArray(messages)) {
|
||||
messages = [messages];
|
||||
}
|
||||
|
||||
let token_count = -1;
|
||||
|
||||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
|
||||
if (model === 'claude' || shouldTokenizeAI21) {
|
||||
full = true;
|
||||
}
|
||||
|
||||
const hash = getStringHash(JSON.stringify(message));
|
||||
const cacheKey = `${model}-${hash}`;
|
||||
const cachedCount = cacheObject[cacheKey];
|
||||
|
||||
if (typeof cachedCount === 'number') {
|
||||
token_count += cachedCount;
|
||||
}
|
||||
|
||||
else {
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
token_count += Number(data.token_count);
|
||||
cacheObject[cacheKey] = Number(data.token_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!full) token_count -= 2;
|
||||
|
||||
return token_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token cache object for the current chat.
|
||||
* @returns {Object} Token cache object for the current chat.
|
||||
*/
|
||||
function getTokenCacheObject() {
|
||||
let chatId = 'undefined';
|
||||
|
||||
try {
|
||||
if (selected_group) {
|
||||
chatId = groups.find(x => x.id == selected_group)?.chat_id;
|
||||
}
|
||||
else if (this_chid !== undefined) {
|
||||
chatId = characters[this_chid].chat;
|
||||
}
|
||||
} catch {
|
||||
console.log('No character / group selected. Using default cache item');
|
||||
}
|
||||
|
||||
if (typeof tokenCache[chatId] !== 'object') {
|
||||
tokenCache[chatId] = {};
|
||||
}
|
||||
|
||||
return tokenCache[String(chatId)];
|
||||
}
|
||||
|
||||
function countTokensRemote(endpoint, str, padding) {
|
||||
let tokenCount = 0;
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST',
|
||||
url: endpoint,
|
||||
data: JSON.stringify({ text: str }),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
tokenCount = data.count;
|
||||
}
|
||||
});
|
||||
return tokenCount + padding;
|
||||
}
|
||||
|
||||
function getTextTokensRemote(endpoint, str) {
|
||||
let ids = [];
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST',
|
||||
url: endpoint,
|
||||
data: JSON.stringify({ text: str }),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
ids = data.ids;
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function getTextTokens(tokenizerType, str) {
|
||||
switch (tokenizerType) {
|
||||
case tokenizers.LLAMA:
|
||||
return getTextTokensRemote('/tokenize_llama', str);
|
||||
case tokenizers.NERD:
|
||||
return getTextTokensRemote('/tokenize_nerdstash', str);
|
||||
case tokenizers.NERD2:
|
||||
return getTextTokensRemote('/tokenize_nerdstash_v2', str);
|
||||
default:
|
||||
console.warn("Calling getTextTokens with unsupported tokenizer type", tokenizerType);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
await loadTokenCache();
|
||||
});
|
@ -1,21 +1,56 @@
|
||||
import { getContext } from "./extensions.js";
|
||||
import { getRequestHeaders } from "../script.js";
|
||||
|
||||
/**
|
||||
* Pagination status string template.
|
||||
* @type {string}
|
||||
*/
|
||||
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of <%= totalNumber %>';
|
||||
|
||||
/**
|
||||
* Navigation options for pagination.
|
||||
* @enum {number}
|
||||
*/
|
||||
export const navigation_option = { none: 0, previous: 1, last: 2, };
|
||||
|
||||
/**
|
||||
* Determines if a value is unique in an array.
|
||||
* @param {any} value Current value.
|
||||
* @param {number} index Current index.
|
||||
* @param {any} array The array being processed.
|
||||
* @returns {boolean} True if the value is unique, false otherwise.
|
||||
*/
|
||||
export function onlyUnique(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string only contains digits.
|
||||
* @param {string} str The string to check.
|
||||
* @returns {boolean} True if the string only contains digits, false otherwise.
|
||||
* @example
|
||||
* isDigitsOnly('123'); // true
|
||||
* isDigitsOnly('abc'); // false
|
||||
*/
|
||||
export function isDigitsOnly(str) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
// Increase delay on touch screens
|
||||
/**
|
||||
* Gets a drag delay for sortable elements. This is to prevent accidental drags when scrolling.
|
||||
* @returns {number} The delay in milliseconds. 100ms for desktop, 750ms for mobile.
|
||||
*/
|
||||
export function getSortableDelay() {
|
||||
return navigator.maxTouchPoints > 0 ? 750 : 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rearranges an array in a random order.
|
||||
* @param {any[]} array The array to shuffle.
|
||||
* @returns {any[]} The shuffled array.
|
||||
* @example
|
||||
* shuffle([1, 2, 3]); // [2, 3, 1]
|
||||
*/
|
||||
export function shuffle(array) {
|
||||
let currentIndex = array.length,
|
||||
randomIndex;
|
||||
@ -31,6 +66,12 @@ export function shuffle(array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file to the user's devices.
|
||||
* @param {BlobPart} content File content to download.
|
||||
* @param {string} fileName File name.
|
||||
* @param {string} contentType File content type.
|
||||
*/
|
||||
export function download(content, fileName, contentType) {
|
||||
const a = document.createElement("a");
|
||||
const file = new Blob([content], { type: contentType });
|
||||
@ -39,22 +80,38 @@ export function download(content, fileName, contentType) {
|
||||
a.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a file by URL and parses its contents as data URI.
|
||||
* @param {string} url The URL to fetch.
|
||||
* @param {any} params Fetch parameters.
|
||||
* @returns {Promise<string>} A promise that resolves to the data URI.
|
||||
*/
|
||||
export async function urlContentToDataUri(url, params) {
|
||||
const response = await fetch(url, params);
|
||||
const blob = await response.blob();
|
||||
return await new Promise(callback => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () { callback(this.result); };
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
resolve(String(reader.result));
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the file's text.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<string>} A promise that resolves to the file's text.
|
||||
*/
|
||||
export function getFileText(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
resolve(String(reader.result));
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
@ -62,6 +119,10 @@ export function getFileText(file) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the file's array buffer.
|
||||
* @param {Blob} file The file to read.
|
||||
*/
|
||||
export function getFileBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@ -75,12 +136,17 @@ export function getFileBuffer(file) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the base64 encoded string of a file.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded string.
|
||||
*/
|
||||
export function getBase64Async(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
resolve(String(reader.result));
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
@ -88,15 +154,26 @@ export function getBase64Async(file) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a file blob as a JSON object.
|
||||
* @param {Blob} file The file to read.
|
||||
* @returns {Promise<any>} A promise that resolves to the parsed JSON object.
|
||||
*/
|
||||
export async function parseJsonFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = event => resolve(JSON.parse(event.target.result));
|
||||
fileReader.onerror = error => reject(error);
|
||||
fileReader.readAsText(file);
|
||||
fileReader.onload = event => resolve(JSON.parse(String(event.target.result)));
|
||||
fileReader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a hash code for a string.
|
||||
* @param {string} str The string to hash.
|
||||
* @param {number} [seed=0] The seed to use for the hash.
|
||||
* @returns {number} The hash code.
|
||||
*/
|
||||
export function getStringHash(str, seed = 0) {
|
||||
if (typeof str !== 'string') {
|
||||
return 0;
|
||||
@ -116,6 +193,12 @@ export function getStringHash(str, seed = 0) {
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
|
||||
* @param {function} func The function to debounce.
|
||||
* @param {number} [timeout=300] The timeout in milliseconds.
|
||||
* @returns {function} The debounced function.
|
||||
*/
|
||||
export function debounce(func, timeout = 300) {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
@ -124,6 +207,12 @@ export function debounce(func, timeout = 300) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a throttled function that only invokes func at most once per every limit milliseconds.
|
||||
* @param {function} func The function to throttle.
|
||||
* @param {number} [limit=300] The limit in milliseconds.
|
||||
* @returns {function} The throttled function.
|
||||
*/
|
||||
export function throttle(func, limit = 300) {
|
||||
let lastCall;
|
||||
return (...args) => {
|
||||
@ -135,6 +224,11 @@ export function throttle(func, limit = 300) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element is in the viewport.
|
||||
* @param {Element} el The element to check.
|
||||
* @returns {boolean} True if the element is in the viewport, false otherwise.
|
||||
*/
|
||||
export function isElementInViewport(el) {
|
||||
if (typeof jQuery === "function" && el instanceof jQuery) {
|
||||
el = el[0];
|
||||
@ -148,6 +242,12 @@ export function isElementInViewport(el) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name that is unique among the names that exist.
|
||||
* @param {string} name The name to check.
|
||||
* @param {{ (y: any): boolean; }} exists Function to check if name exists.
|
||||
* @returns {string} A unique name.
|
||||
*/
|
||||
export function getUniqueName(name, exists) {
|
||||
let i = 1;
|
||||
let baseName = name;
|
||||
@ -158,18 +258,48 @@ export function getUniqueName(name, exists) {
|
||||
return name;
|
||||
}
|
||||
|
||||
export const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
||||
export const isSubsetOf = (a, b) => (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false;
|
||||
/**
|
||||
* Returns a promise that resolves after the specified number of milliseconds.
|
||||
* @param {number} ms The number of milliseconds to wait.
|
||||
* @returns {Promise<void>} A promise that resolves after the specified number of milliseconds.
|
||||
*/
|
||||
export function delay(ms) {
|
||||
return new Promise((res) => setTimeout(res, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an array is a subset of another array.
|
||||
* @param {any[]} a Array A
|
||||
* @param {any[]} b Array B
|
||||
* @returns {boolean} True if B is a subset of A, false otherwise.
|
||||
*/
|
||||
export function isSubsetOf(a, b) {
|
||||
return (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the trailing number in a string.
|
||||
* @param {string} str The string to process.
|
||||
* @returns {string} The string with the trailing number incremented by 1.
|
||||
* @example
|
||||
* incrementString('Hello, world! 1'); // 'Hello, world! 2'
|
||||
*/
|
||||
export function incrementString(str) {
|
||||
// Find the trailing number or it will match the empty string
|
||||
const count = str.match(/\d*$/);
|
||||
|
||||
// Take the substring up until where the integer was matched
|
||||
// Concatenate it to the matched count incremented by 1
|
||||
return str.substr(0, count.index) + (++count[0]);
|
||||
return str.substring(0, count.index) + (Number(count[0]) + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a string using the specified arguments.
|
||||
* @param {string} format The format string.
|
||||
* @returns {string} The formatted string.
|
||||
* @example
|
||||
* stringFormat('Hello, {0}!', 'world'); // 'Hello, world!'
|
||||
*/
|
||||
export function stringFormat(format) {
|
||||
const args = Array.prototype.slice.call(arguments, 1);
|
||||
return format.replace(/{(\d+)}/g, function (match, number) {
|
||||
@ -180,7 +310,11 @@ export function stringFormat(format) {
|
||||
});
|
||||
};
|
||||
|
||||
// Save the caret position in a contenteditable element
|
||||
/**
|
||||
* Save the caret position in a contenteditable element.
|
||||
* @param {Element} element The element to save the caret position of.
|
||||
* @returns {{ start: number, end: number }} An object with the start and end offsets of the caret.
|
||||
*/
|
||||
export function saveCaretPosition(element) {
|
||||
// Get the current selection
|
||||
const selection = window.getSelection();
|
||||
@ -209,7 +343,11 @@ export function saveCaretPosition(element) {
|
||||
return position;
|
||||
}
|
||||
|
||||
// Restore the caret position in a contenteditable element
|
||||
/**
|
||||
* Restore the caret position in a contenteditable element.
|
||||
* @param {Element} element The element to restore the caret position of.
|
||||
* @param {{ start: any; end: any; }} position An object with the start and end offsets of the caret.
|
||||
*/
|
||||
export function restoreCaretPosition(element, position) {
|
||||
// If the position is null, do nothing
|
||||
if (!position) {
|
||||
@ -236,6 +374,11 @@ export async function resetScrollHeight(element) {
|
||||
$(element).css('height', $(element).prop('scrollHeight') + 3 + 'px');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of an element to its scroll height.
|
||||
* @param {JQuery<HTMLElement>} element The element to initialize the scroll height of.
|
||||
* @returns {Promise<void>} A promise that resolves when the scroll height has been initialized.
|
||||
*/
|
||||
export async function initScrollHeight(element) {
|
||||
await delay(1);
|
||||
|
||||
@ -252,15 +395,27 @@ export async function initScrollHeight(element) {
|
||||
//resetScrollHeight(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares elements by their CSS order property. Used for sorting.
|
||||
* @param {any} a The first element.
|
||||
* @param {any} b The second element.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortByCssOrder(a, b) {
|
||||
const _a = Number($(a).css('order'));
|
||||
const _b = Number($(b).css('order'));
|
||||
return _a - _b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims a string to the end of a nearest sentence.
|
||||
* @param {string} input The string to trim.
|
||||
* @param {boolean} include_newline Whether to include a newline character in the trimmed string.
|
||||
* @returns {string} The trimmed string.
|
||||
* @example
|
||||
* end_trim_to_sentence('Hello, world! I am from'); // 'Hello, world!'
|
||||
*/
|
||||
export function end_trim_to_sentence(input, include_newline = false) {
|
||||
// inspired from https://github.com/kaihordewebui/kaihordewebui.github.io/blob/06b95e6b7720eb85177fbaf1a7f52955d7cdbc02/index.html#L4853-L4867
|
||||
|
||||
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$', '。', '!', '?', '”', ')', '】', '】', '’', '」', '】']); // extend this as you see fit
|
||||
let last = -1;
|
||||
|
||||
@ -285,6 +440,15 @@ export function end_trim_to_sentence(input, include_newline = false) {
|
||||
return input.substring(0, last + 1).trimEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of occurrences of a character in a string.
|
||||
* @param {string} string The string to count occurrences in.
|
||||
* @param {string} character The character to count occurrences of.
|
||||
* @returns {number} The number of occurrences of the character in the string.
|
||||
* @example
|
||||
* countOccurrences('Hello, world!', 'l'); // 3
|
||||
* countOccurrences('Hello, world!', 'x'); // 0
|
||||
*/
|
||||
export function countOccurrences(string, character) {
|
||||
let count = 0;
|
||||
|
||||
@ -297,6 +461,14 @@ export function countOccurrences(string, character) {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a number is odd.
|
||||
* @param {number} number The number to check.
|
||||
* @returns {boolean} True if the number is odd, false otherwise.
|
||||
* @example
|
||||
* isOdd(3); // true
|
||||
* isOdd(4); // false
|
||||
*/
|
||||
export function isOdd(number) {
|
||||
return number % 2 !== 0;
|
||||
}
|
||||
@ -337,6 +509,12 @@ export function timestampToMoment(timestamp) {
|
||||
return moment.invalid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two moment objects for sorting.
|
||||
* @param {*} a The first moment object.
|
||||
* @param {*} b The second moment object.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortMoments(a, b) {
|
||||
if (a.isBefore(b)) {
|
||||
return 1;
|
||||
@ -347,14 +525,21 @@ export function sortMoments(a, b) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Split string to parts no more than length in size */
|
||||
export function splitRecursive(input, length, delimitiers = ['\n\n', '\n', ' ', '']) {
|
||||
const delim = delimitiers[0] ?? '';
|
||||
/** Split string to parts no more than length in size.
|
||||
* @param {string} input The string to split.
|
||||
* @param {number} length The maximum length of each part.
|
||||
* @param {string[]} delimiters The delimiters to use when splitting the string.
|
||||
* @returns {string[]} The split string.
|
||||
* @example
|
||||
* splitRecursive('Hello, world!', 3); // ['Hel', 'lo,', 'wor', 'ld!']
|
||||
*/
|
||||
export function splitRecursive(input, length, delimiters = ['\n\n', '\n', ' ', '']) {
|
||||
const delim = delimiters[0] ?? '';
|
||||
const parts = input.split(delim);
|
||||
|
||||
const flatParts = parts.flatMap(p => {
|
||||
if (p.length < length) return p;
|
||||
return splitRecursive(input, length, delimitiers.slice(1));
|
||||
return splitRecursive(input, length, delimiters.slice(1));
|
||||
});
|
||||
|
||||
// Merge short chunks
|
||||
@ -378,6 +563,13 @@ export function splitRecursive(input, length, delimitiers = ['\n\n', '\n', ' ',
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid data URL.
|
||||
* @param {string} str The string to check.
|
||||
* @returns {boolean} True if the string is a valid data URL, false otherwise.
|
||||
* @example
|
||||
* isDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...'); // true
|
||||
*/
|
||||
export function isDataURL(str) {
|
||||
const regex = /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)*;?)?(base64)?,([a-z0-9!$&',()*+;=\-_%.~:@\/?#]+)?$/i;
|
||||
return regex.test(str);
|
||||
@ -392,6 +584,13 @@ export function getCharaFilename(chid) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts words from a string.
|
||||
* @param {string} value The string to extract words from.
|
||||
* @returns {string[]} The extracted words.
|
||||
* @example
|
||||
* extractAllWords('Hello, world!'); // ['hello', 'world']
|
||||
*/
|
||||
export function extractAllWords(value) {
|
||||
const words = [];
|
||||
|
||||
@ -406,21 +605,45 @@ export function extractAllWords(value) {
|
||||
return words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string for use in a regular expression.
|
||||
* @param {string} string The string to escape.
|
||||
* @returns {string} The escaped string.
|
||||
* @example
|
||||
* escapeRegex('^Hello$'); // '\\^Hello\\$'
|
||||
*/
|
||||
export function escapeRegex(string) {
|
||||
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an interface for rate limiting function calls.
|
||||
*/
|
||||
export class RateLimiter {
|
||||
constructor(intervalMillis) {
|
||||
this._intervalMillis = intervalMillis;
|
||||
this._lastResolveTime = 0;
|
||||
this._pendingResolve = Promise.resolve();
|
||||
/**
|
||||
* Creates a new RateLimiter.
|
||||
* @param {number} interval The interval in milliseconds.
|
||||
* @example
|
||||
* const rateLimiter = new RateLimiter(1000);
|
||||
* rateLimiter.waitForResolve().then(() => {
|
||||
* console.log('Waited 1000ms');
|
||||
* });
|
||||
*/
|
||||
constructor(interval) {
|
||||
this.interval = interval;
|
||||
this.lastResolveTime = 0;
|
||||
this.pendingResolve = Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the remaining time in the interval.
|
||||
* @param {AbortSignal} abortSignal An optional AbortSignal to abort the wait.
|
||||
* @returns {Promise<void>} A promise that resolves when the remaining time has elapsed.
|
||||
*/
|
||||
_waitRemainingTime(abortSignal) {
|
||||
const currentTime = Date.now();
|
||||
const elapsedTime = currentTime - this._lastResolveTime;
|
||||
const remainingTime = Math.max(0, this._intervalMillis - elapsedTime);
|
||||
const elapsedTime = currentTime - this.lastResolveTime;
|
||||
const remainingTime = Math.max(0, this.interval - elapsedTime);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
@ -436,19 +659,29 @@ export class RateLimiter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the next interval to elapse.
|
||||
* @param {AbortSignal} abortSignal An optional AbortSignal to abort the wait.
|
||||
* @returns {Promise<void>} A promise that resolves when the next interval has elapsed.
|
||||
*/
|
||||
async waitForResolve(abortSignal) {
|
||||
await this._pendingResolve;
|
||||
this._pendingResolve = this._waitRemainingTime(abortSignal);
|
||||
await this.pendingResolve;
|
||||
this.pendingResolve = this._waitRemainingTime(abortSignal);
|
||||
|
||||
// Update the last resolve time
|
||||
this._lastResolveTime = Date.now() + this._intervalMillis;
|
||||
console.debug(`RateLimiter.waitForResolve() ${this._lastResolveTime}`);
|
||||
this.lastResolveTime = Date.now() + this.interval;
|
||||
console.debug(`RateLimiter.waitForResolve() ${this.lastResolveTime}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
|
||||
//import tavern png data. adapted from png-chunks-extract under MIT license
|
||||
//accepts png input data, and returns the extracted JSON
|
||||
/**
|
||||
* Extracts a JSON object from a PNG file.
|
||||
* Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
|
||||
* Adapted from png-chunks-extract under MIT license
|
||||
* @param {Uint8Array} data The PNG data to extract the JSON from.
|
||||
* @param {string} identifier The identifier to look for in the PNG tEXT data.
|
||||
* @returns {object} The extracted JSON object.
|
||||
*/
|
||||
export function extractDataFromPng(data, identifier = 'chara') {
|
||||
console.log("Attempting PNG import...");
|
||||
let uint8 = new Uint8Array(4);
|
||||
@ -599,6 +832,13 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "",
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thumbnail from a data URL.
|
||||
* @param {string} dataUrl The data URL encoded data of the image.
|
||||
* @param {number} maxWidth The maximum width of the thumbnail.
|
||||
* @param {number} maxHeight The maximum height of the thumbnail.
|
||||
* @returns {Promise<string>} A promise that resolves to the thumbnail data URL.
|
||||
*/
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
@ -634,6 +874,13 @@ export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a condition to be true. Throws an error if the condition is not true within the timeout.
|
||||
* @param {{ (): boolean; }} condition The condition to wait for.
|
||||
* @param {number} [timeout=1000] The timeout in milliseconds.
|
||||
* @param {number} [interval=100] The interval in milliseconds.
|
||||
* @returns {Promise<void>} A promise that resolves when the condition is true.
|
||||
*/
|
||||
export async function waitUntilCondition(condition, timeout = 1000, interval = 100) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
@ -651,6 +898,12 @@ export async function waitUntilCondition(condition, timeout = 1000, interval = 1
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UUID v4 string.
|
||||
* @returns {string} A UUID v4 string.
|
||||
* @example
|
||||
* uuidv4(); // '3e2fd9e1-0a7a-4f6d-9aaf-8a7a4babe7eb'
|
||||
*/
|
||||
export function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
@ -659,6 +912,11 @@ export function uuidv4() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones an object using JSON serialization.
|
||||
* @param {any} obj The object to clone.
|
||||
* @returns {any} A deep clone of the object.
|
||||
*/
|
||||
export function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types } from "../script.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, deepClone, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE } from "./utils.js";
|
||||
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types } from "../script.js";
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, deepClone, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option } from "./utils.js";
|
||||
import { getContext } from "./extensions.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./authors-note.js";
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
import { deviceInfo } from "./RossAscends-mods.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./filters.js";
|
||||
import { getTokenCount } from "./tokenizers.js";
|
||||
|
||||
export {
|
||||
world_info,
|
||||
@ -46,7 +47,6 @@ const saveSettingsDebounced = debounce(() => {
|
||||
saveSettings()
|
||||
}, 1000);
|
||||
const sortFn = (a, b) => b.order - a.order;
|
||||
const navigation_option = { none: 0, previous: 1, last: 2, };
|
||||
let updateEditor = (navigation) => { navigation; };
|
||||
|
||||
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
|
||||
@ -418,7 +418,7 @@ function getWorldEntry(name, data, entry) {
|
||||
|
||||
keyInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
data.entries[uid].key = value
|
||||
.split(",")
|
||||
@ -454,7 +454,7 @@ function getWorldEntry(name, data, entry) {
|
||||
keySecondaryInput.data("uid", entry.uid);
|
||||
keySecondaryInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
data.entries[uid].keysecondary = value
|
||||
.split(",")
|
||||
@ -1506,19 +1506,6 @@ jQuery(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
let selectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
selectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = selectScrollTop;
|
||||
}
|
||||
*/
|
||||
|
||||
onWorldInfoChange('__notSlashCommand__');
|
||||
});
|
||||
|
||||
|
69
server.js
69
server.js
@ -3301,6 +3301,72 @@ async function sendScaleRequest(request, response) {
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/generate_altscale", jsonParser, function (request, response_generate_scale) {
|
||||
if (!request.body) return response_generate_scale.sendStatus(400);
|
||||
|
||||
fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'cookie': `_jwt=${readSecret(SECRET_KEYS.SCALE_COOKIE)}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
json: {
|
||||
variant: {
|
||||
name: 'New Variant',
|
||||
appId: '',
|
||||
taxonomy: null
|
||||
},
|
||||
prompt: {
|
||||
id: '',
|
||||
template: '{{input}}\n',
|
||||
exampleVariables: {},
|
||||
variablesSourceDataId: null,
|
||||
systemMessage: request.body.sysprompt
|
||||
},
|
||||
modelParameters: {
|
||||
id: '',
|
||||
modelId: 'GPT4',
|
||||
modelType: 'OpenAi',
|
||||
maxTokens: request.body.max_tokens,
|
||||
temperature: request.body.temp,
|
||||
stop: "user:",
|
||||
suffix: null,
|
||||
topP: request.body.top_p,
|
||||
logprobs: null,
|
||||
logitBias: request.body.logit_bias
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
index: '-1',
|
||||
valueByName: {
|
||||
input: request.body.prompt
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
meta: {
|
||||
values: {
|
||||
'variant.taxonomy': ['undefined'],
|
||||
'prompt.variablesSourceDataId': ['undefined'],
|
||||
'modelParameters.suffix': ['undefined'],
|
||||
'modelParameters.logprobs': ['undefined'],
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data.result.data.json.outputs[0])
|
||||
return response_generate_scale.send({ output: data.result.data.json.outputs[0] });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error)
|
||||
return response_generate_scale.send({ error: true })
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
async function sendClaudeRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
@ -3989,7 +4055,8 @@ const SECRET_KEYS = {
|
||||
DEEPL: 'deepl',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21'
|
||||
AI21: 'api_key_ai21',
|
||||
SCALE_COOKIE: 'scale_cookie',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user