mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Copy extensions from extras project to main
This commit is contained in:
1
public/img/dice-solid.svg
Normal file
1
public/img/dice-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M252.3 11.7c-15.6-15.6-40.9-15.6-56.6 0l-184 184c-15.6 15.6-15.6 40.9 0 56.6l184 184c15.6 15.6 40.9 15.6 56.6 0l184-184c15.6-15.6 15.6-40.9 0-56.6l-184-184zM248 224c0 13.3-10.7 24-24 24s-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24zM96 248c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24zm128 80c13.3 0 24 10.7 24 24s-10.7 24-24 24s-24-10.7-24-24s10.7-24 24-24zm128-80c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24zM224 72c13.3 0 24 10.7 24 24s-10.7 24-24 24s-24-10.7-24-24s10.7-24 24-24zm96 392c0 26.5 21.5 48 48 48H592c26.5 0 48-21.5 48-48V240c0-26.5-21.5-48-48-48H472.5c13.4 26.9 8.8 60.5-13.6 82.9L320 413.8V464zm160-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg>
|
After Width: | Height: | Size: 970 B |
1
public/img/image-solid.svg
Normal file
1
public/img/image-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>
|
After Width: | Height: | Size: 634 B |
1
public/img/spinner-solid.svg
Normal file
1
public/img/spinner-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"/></svg>
|
After Width: | Height: | Size: 805 B |
@ -747,6 +747,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="extensions_block">
|
||||||
|
<hr>
|
||||||
|
<h3>Extensions: <a target="_blank" href="https://github.com/SillyLossy/TavernAI-extras">TavernAI-extras</a></h3>
|
||||||
|
<input id="extensions_url" type="text" class="text_pole" />
|
||||||
|
<div class="extensions_url_block">
|
||||||
|
<input id="extensions_connect" class="menu_button" type="submit" value="Connect" />
|
||||||
|
<span class="expander"></span>
|
||||||
|
<label for="extensions_autoconnect"><input id="extensions_autoconnect" type="checkbox"/>Auto-connect</label>
|
||||||
|
</div>
|
||||||
|
<div id="extensions_status">Not connected</div>
|
||||||
|
<div id="extensions_loaded">
|
||||||
|
<h4>Active extensions</h4>
|
||||||
|
<ul id="extensions_list">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="extensions_settings">
|
||||||
|
<h3>Extension settings</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="rm_character_import" class="right_menu" style="display: none;">
|
<div id="rm_character_import" class="right_menu" style="display: none;">
|
||||||
|
@ -2163,7 +2163,7 @@ async function getSettings(type) {
|
|||||||
const src = "scripts/extensions.js";
|
const src = "scripts/extensions.js";
|
||||||
if ($(`script[src="${src}"]`).length === 0) {
|
if ($(`script[src="${src}"]`).length === 0) {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.type = "text/javascript";
|
script.type = "module";
|
||||||
script.src = src;
|
script.src = src;
|
||||||
$("body").append(script);
|
$("body").append(script);
|
||||||
}
|
}
|
||||||
|
@ -1,217 +1,168 @@
|
|||||||
|
import { isSubsetOf } from "./utils.js";
|
||||||
|
export {
|
||||||
|
getContext,
|
||||||
|
getApiUrl,
|
||||||
|
defaultRequestArgs,
|
||||||
|
};
|
||||||
|
|
||||||
|
import * as captionManifest from "./extensions/caption/manifest.json" assert {type: 'json'};
|
||||||
|
import * as diceManifest from "./extensions/dice/manifest.json" assert {type: 'json'};
|
||||||
|
import * as expressionsManifest from "./extensions/expressions/manifest.json" assert {type: 'json'};
|
||||||
|
import * as floatingPromptManifest from "./extensions/floating-prompt/manifest.json" assert {type: 'json'};
|
||||||
|
import * as memoryManifest from "./extensions/memory/manifest.json" assert {type: 'json'};
|
||||||
|
|
||||||
|
const manifests = {
|
||||||
|
'floating-prompt': floatingPromptManifest.default,
|
||||||
|
'dice': diceManifest.default,
|
||||||
|
'caption': captionManifest.default,
|
||||||
|
'expressions': expressionsManifest.default,
|
||||||
|
'memory': memoryManifest.default,
|
||||||
|
};
|
||||||
|
|
||||||
const extensions_urlKey = 'extensions_url';
|
const extensions_urlKey = 'extensions_url';
|
||||||
const extensions_autoConnectKey = 'extensions_autoconnect';
|
const extensions_autoConnectKey = 'extensions_autoconnect';
|
||||||
let extensions = [];
|
let modules = [];
|
||||||
|
let activeExtensions = new Set();
|
||||||
|
|
||||||
(function () {
|
const getContext = () => window['TavernAI'].getContext();
|
||||||
const settings_html = `
|
const getApiUrl = () => localStorage.getItem('extensions_url');
|
||||||
<div class="extensions_block">
|
const defaultUrl = "http://localhost:5100";
|
||||||
<hr>
|
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
|
||||||
<h3>Extensions: <a target="_blank" href="https://github.com/SillyLossy/TavernAI-extras">TavernAI-extras</a></h3>
|
let connectedToApi = false;
|
||||||
<input id="extensions_url" type="text" class="text_pole" />
|
|
||||||
<div class="extensions_url_block">
|
|
||||||
<input id="extensions_connect" class="menu_button" type="submit" value="Connect" />
|
|
||||||
<span class="expander"></span>
|
|
||||||
<label for="extensions_autoconnect"><input id="extensions_autoconnect" type="checkbox"/>Auto-connect</label>
|
|
||||||
</div>
|
|
||||||
<div id="extensions_status">Not connected</div>
|
|
||||||
<div id="extensions_loaded">
|
|
||||||
<h4>Active extensions</h4>
|
|
||||||
<ul id="extensions_list">
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="extensions_settings">
|
|
||||||
<h3>Extension settings</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const settings_style = `
|
async function activateExtensions() {
|
||||||
<style>
|
const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
|
||||||
#extensions_url {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensions_loaded {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensions_status {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions_block input[type="submit"]:hover{
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions_block input[type="checkbox"] {
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label[for="extensions_autoconnect"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions_url_block {
|
for (let entry of extensions) {
|
||||||
display: flex;
|
const name = entry[0];
|
||||||
align-items: center;
|
const manifest = entry[1];
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions_url_block h4 {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions_block {
|
|
||||||
clear: both;
|
|
||||||
padding: 0.05px; /* clear fix */
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.failure {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expander {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const defaultUrl = "http://localhost:5100";
|
if (activeExtensions.has(name)) {
|
||||||
let connectedToApi = false;
|
continue;
|
||||||
|
|
||||||
async function connectClickHandler() {
|
|
||||||
const baseUrl = $("#extensions_url").val();
|
|
||||||
localStorage.setItem(extensions_urlKey, baseUrl);
|
|
||||||
await connectToApi(baseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoConnectInputHandler() {
|
|
||||||
const value = $(this).prop('checked');
|
|
||||||
localStorage.setItem(extensions_autoConnectKey, value.toString());
|
|
||||||
|
|
||||||
if (value && !connectedToApi) {
|
|
||||||
$("#extensions_connect").trigger('click');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function connectToApi(baseUrl) {
|
// all required modules are active (offline extensions require none)
|
||||||
const url = new URL(baseUrl);
|
if (isSubsetOf(modules, manifest.requires)) {
|
||||||
url.pathname = '/api/extensions';
|
try {
|
||||||
|
await addExtensionScript(name, manifest);
|
||||||
try {
|
await addExtensionStyle(name, manifest);
|
||||||
const getExtensionsResult = await fetch(url, { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
activeExtensions.add(name);
|
||||||
|
$('#extensions_list').append(`<li id="${name}">${manifest.display_name}</li>`);
|
||||||
if (getExtensionsResult.ok) {
|
|
||||||
const data = await getExtensionsResult.json();
|
|
||||||
extensions = data.extensions;
|
|
||||||
applyExtensions(baseUrl);
|
|
||||||
}
|
}
|
||||||
|
catch (error) {
|
||||||
updateStatus(getExtensionsResult.ok);
|
console.error(`Could not activate extension: ${name}`);
|
||||||
}
|
console.error(error);
|
||||||
catch {
|
|
||||||
updateStatus(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStatus(success) {
|
|
||||||
connectedToApi = success;
|
|
||||||
const _text = success ? 'Connected to API' : 'Could not connect to API';
|
|
||||||
const _class = success ? 'success' : 'failure';
|
|
||||||
$('#extensions_status').text(_text);
|
|
||||||
$('#extensions_status').attr('class', _class);
|
|
||||||
|
|
||||||
if (success && extensions.length) {
|
|
||||||
$('#extensions_loaded').show(200);
|
|
||||||
$('#extensions_settings').show(200);
|
|
||||||
$('#extensions_list').empty();
|
|
||||||
|
|
||||||
for (let extension of extensions) {
|
|
||||||
$('#extensions_list').append(`<li id="${extension.name}">${extension.metadata.display_name}</li>`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
$('#extensions_loaded').hide(200);
|
}
|
||||||
$('#extensions_settings').hide(200);
|
|
||||||
$('#extensions_list').empty();
|
async function connectClickHandler() {
|
||||||
|
const baseUrl = $("#extensions_url").val();
|
||||||
|
localStorage.setItem(extensions_urlKey, baseUrl);
|
||||||
|
await connectToApi(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoConnectInputHandler() {
|
||||||
|
const value = $(this).prop('checked');
|
||||||
|
localStorage.setItem(extensions_autoConnectKey, value.toString());
|
||||||
|
|
||||||
|
if (value && !connectedToApi) {
|
||||||
|
$("#extensions_connect").trigger('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectToApi(baseUrl) {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
url.pathname = '/api/modules';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const getExtensionsResult = await fetch(url, defaultRequestArgs);
|
||||||
|
|
||||||
|
if (getExtensionsResult.ok) {
|
||||||
|
const data = await getExtensionsResult.json();
|
||||||
|
modules = data.modules;
|
||||||
|
activateExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStatus(getExtensionsResult.ok);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
updateStatus(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(success) {
|
||||||
|
connectedToApi = success;
|
||||||
|
const _text = success ? 'Connected to API' : 'Could not connect to API';
|
||||||
|
const _class = success ? 'success' : 'failure';
|
||||||
|
$('#extensions_status').text(_text);
|
||||||
|
$('#extensions_status').attr('class', _class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addExtensionStyle(name, manifest) {
|
||||||
|
if (manifest.css) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = `/scripts/extensions/${name}/${manifest.css}`;
|
||||||
|
|
||||||
|
if ($(`link[id="${name}"]`).length === 0) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.id = name;
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
link.type = "text/css";
|
||||||
|
link.href = url;
|
||||||
|
link.onload = function () {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
link.onerror = function (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyExtensions(baseUrl) {
|
return Promise.resolve();
|
||||||
const url = new URL(baseUrl);
|
}
|
||||||
|
|
||||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
function addExtensionScript(name, manifest) {
|
||||||
return;
|
if (manifest.js) {
|
||||||
}
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = `/scripts/extensions/${name}/${manifest.js}`;
|
||||||
|
let ready = false;
|
||||||
|
|
||||||
for (let extension of extensions) {
|
if ($(`script[id="${name}"]`).length === 0) {
|
||||||
addExtensionStyle(extension);
|
const script = document.createElement('script');
|
||||||
addExtensionScript(extension);
|
script.id = name;
|
||||||
}
|
script.type = 'module';
|
||||||
|
script.src = url;
|
||||||
async function addExtensionStyle(extension) {
|
script.async = true;
|
||||||
if (extension.metadata.css) {
|
script.onerror = function (err) {
|
||||||
try {
|
reject(err, script);
|
||||||
url.pathname = `/api/style/${extension.name}`;
|
};
|
||||||
const link = url.toString();
|
script.onload = script.onreadystatechange = function () {
|
||||||
|
// console.log(this.readyState); // uncomment this line to see which ready states are called.
|
||||||
const result = await fetch(link, { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
if (!ready && (!this.readyState || this.readyState == 'complete')) {
|
||||||
const text = await result.text();
|
ready = true;
|
||||||
|
resolve();
|
||||||
if ($(`style[id="${link}"]`).length === 0) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = link;
|
|
||||||
style.innerHTML = text;
|
|
||||||
$('head').append(style);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
catch (error) {
|
document.body.appendChild(script);
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
async function addExtensionScript(extension) {
|
|
||||||
if (extension.metadata.js) {
|
|
||||||
try {
|
|
||||||
url.pathname = `/api/script/${extension.name}`;
|
|
||||||
const link = url.toString();
|
|
||||||
|
|
||||||
const result = await fetch(link, { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
|
||||||
const text = await result.text();
|
|
||||||
|
|
||||||
if ($(`script[id="${link}"]`).length === 0) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.id = link;
|
|
||||||
script.type = 'module';
|
|
||||||
script.innerHTML = text;
|
|
||||||
$('body').append(script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(async function () {
|
return Promise.resolve();
|
||||||
const url = localStorage.getItem(extensions_urlKey) ?? defaultUrl;
|
}
|
||||||
const autoConnect = localStorage.getItem(extensions_autoConnectKey) == 'true';
|
|
||||||
$('#rm_api_block').append(settings_html);
|
$(document).ready(async function () {
|
||||||
$('head').append(settings_style);
|
const url = localStorage.getItem(extensions_urlKey) ?? defaultUrl;
|
||||||
$("#extensions_url").val(url);
|
const autoConnect = localStorage.getItem(extensions_autoConnectKey) == 'true';
|
||||||
$("#extensions_connect").on('click', connectClickHandler);
|
$("#extensions_url").val(url);
|
||||||
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
$("#extensions_connect").on('click', connectClickHandler);
|
||||||
$("#extensions_autoconnect").prop('checked', autoConnect).trigger('input');
|
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
||||||
});
|
$("#extensions_autoconnect").prop('checked', autoConnect).trigger('input');
|
||||||
})();
|
|
||||||
|
// Activate offline extensions
|
||||||
|
activateExtensions();
|
||||||
|
});
|
122
public/scripts/extensions/caption/index.js
Normal file
122
public/scripts/extensions/caption/index.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { getBase64Async } from "../../utils.js";
|
||||||
|
import { getContext, getApiUrl } from "../../extensions.js";
|
||||||
|
export { MODULE_NAME };
|
||||||
|
|
||||||
|
const MODULE_NAME = 'caption';
|
||||||
|
const UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
|
const context = getContext();
|
||||||
|
|
||||||
|
context.onlineStatus === 'no_connection'
|
||||||
|
? $('#send_picture').hide(200)
|
||||||
|
: $('#send_picture').show(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setImageIcon() {
|
||||||
|
try {
|
||||||
|
const sendButton = document.getElementById('send_picture');
|
||||||
|
sendButton.style.backgroundImage = `url('/img/image-solid.svg')`;
|
||||||
|
sendButton.classList.remove('spin');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSpinnerIcon() {
|
||||||
|
try {
|
||||||
|
const sendButton = document.getElementById('send_picture');
|
||||||
|
sendButton.style.backgroundImage = `url('/img/spinner-solid.svg')`;
|
||||||
|
sendButton.classList.add('spin');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendCaptionedMessage(caption, image) {
|
||||||
|
const context = getContext();
|
||||||
|
const messageText = `[${context.name1} sends ${context.name2 ?? ''} a picture that contains: ${caption}]`;
|
||||||
|
const message = {
|
||||||
|
name: context.name1,
|
||||||
|
is_user: true,
|
||||||
|
is_name: true,
|
||||||
|
send_date: Date.now(),
|
||||||
|
mes: messageText,
|
||||||
|
extra: { image: image },
|
||||||
|
};
|
||||||
|
context.chat.push(message);
|
||||||
|
context.addOneMessage(message);
|
||||||
|
await context.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSelectImage(e) {
|
||||||
|
setSpinnerIcon();
|
||||||
|
const file = e.target.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const base64Img = await getBase64Async(file);
|
||||||
|
const url = new URL(getApiUrl());
|
||||||
|
url.pathname = '/api/caption';
|
||||||
|
|
||||||
|
const apiResult = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Bypass-Tunnel-Reminder': 'bypass',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ image: base64Img.split(',')[1] })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (apiResult.ok) {
|
||||||
|
const data = await apiResult.json();
|
||||||
|
const caption = data.caption;
|
||||||
|
await sendCaptionedMessage(caption, base64Img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
e.target.form.reset();
|
||||||
|
setImageIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
function patchSendForm() {
|
||||||
|
const columns = $('#send_form').css('grid-template-columns').split(' ');
|
||||||
|
columns[columns.length - 1] = `${parseInt(columns[columns.length - 1]) + 40}px`;
|
||||||
|
columns[1] = 'auto';
|
||||||
|
$('#send_form').css('grid-template-columns', columns.join(' '));
|
||||||
|
}
|
||||||
|
function addSendPictureButton() {
|
||||||
|
const sendButton = document.createElement('input');
|
||||||
|
sendButton.type = 'button';
|
||||||
|
sendButton.id = 'send_picture';
|
||||||
|
$(sendButton).hide();
|
||||||
|
$(sendButton).on('click', () => $('#img_file').click());
|
||||||
|
$('#send_but_sheld').prepend(sendButton);
|
||||||
|
}
|
||||||
|
function addPictureSendForm() {
|
||||||
|
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;
|
||||||
|
const imgForm = document.createElement('form');
|
||||||
|
imgForm.id = 'img_form';
|
||||||
|
$(imgForm).append(inputHtml);
|
||||||
|
$(imgForm).hide();
|
||||||
|
$('#form_sheld').append(imgForm);
|
||||||
|
$('#img_file').on('change', onSelectImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
addPictureSendForm();
|
||||||
|
addSendPictureButton();
|
||||||
|
setImageIcon();
|
||||||
|
patchSendForm();
|
||||||
|
moduleWorker();
|
||||||
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
});
|
9
public/scripts/extensions/caption/manifest.json
Normal file
9
public/scripts/extensions/caption/manifest.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"display_name": "Image Captioning",
|
||||||
|
"loading_order": 4,
|
||||||
|
"requires": [
|
||||||
|
"caption"
|
||||||
|
],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
36
public/scripts/extensions/caption/style.css
Normal file
36
public/scripts/extensions/caption/style.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#send_picture {
|
||||||
|
order: 200;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px;
|
||||||
|
background: no-repeat;
|
||||||
|
background-size: 26px auto;
|
||||||
|
background-position: center center;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.3s;
|
||||||
|
filter: invert(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send_picture:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#img_form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation-name: spin;
|
||||||
|
animation-duration: 2000ms;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {transform:rotate(0deg);}
|
||||||
|
to {transform:rotate(360deg);}
|
||||||
|
}
|
108
public/scripts/extensions/dice/droll.js
Normal file
108
public/scripts/extensions/dice/droll.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Borrowed from the Droll library by thebinarypenguin
|
||||||
|
// https://github.com/thebinarypenguin/droll
|
||||||
|
// Licensed under MIT license
|
||||||
|
var droll = {};
|
||||||
|
|
||||||
|
// Define a "class" to represent a formula
|
||||||
|
function DrollFormula() {
|
||||||
|
this.numDice = 0;
|
||||||
|
this.numSides = 0;
|
||||||
|
this.modifier = 0;
|
||||||
|
|
||||||
|
this.minResult = 0;
|
||||||
|
this.maxResult = 0;
|
||||||
|
this.avgResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a "class" to represent the results of the roll
|
||||||
|
function DrollResult() {
|
||||||
|
this.rolls = [];
|
||||||
|
this.modifier = 0;
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the roll result
|
||||||
|
*/
|
||||||
|
DrollResult.prototype.toString = function () {
|
||||||
|
if (this.rolls.length === 1 && this.modifier === 0) {
|
||||||
|
return this.rolls[0] + '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rolls.length > 1 && this.modifier === 0) {
|
||||||
|
return this.rolls.join(' + ') + ' = ' + this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rolls.length === 1 && this.modifier > 0) {
|
||||||
|
return this.rolls[0] + ' + ' + this.modifier + ' = ' + this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rolls.length > 1 && this.modifier > 0) {
|
||||||
|
return this.rolls.join(' + ') + ' + ' + this.modifier + ' = ' + this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rolls.length === 1 && this.modifier < 0) {
|
||||||
|
return this.rolls[0] + ' - ' + Math.abs(this.modifier) + ' = ' + this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rolls.length > 1 && this.modifier < 0) {
|
||||||
|
return this.rolls.join(' + ') + ' - ' + Math.abs(this.modifier) + ' = ' + this.total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the formula into its component pieces.
|
||||||
|
* Returns a DrollFormula object on success or false on failure.
|
||||||
|
*/
|
||||||
|
droll.parse = function (formula) {
|
||||||
|
var pieces = null;
|
||||||
|
var result = new DrollFormula();
|
||||||
|
|
||||||
|
pieces = formula.match(/^([1-9]\d*)?d([1-9]\d*)([+-]\d+)?$/i);
|
||||||
|
if (!pieces) { return false; }
|
||||||
|
|
||||||
|
result.numDice = (pieces[1] - 0) || 1;
|
||||||
|
result.numSides = (pieces[2] - 0);
|
||||||
|
result.modifier = (pieces[3] - 0) || 0;
|
||||||
|
|
||||||
|
result.minResult = (result.numDice * 1) + result.modifier;
|
||||||
|
result.maxResult = (result.numDice * result.numSides) + result.modifier;
|
||||||
|
result.avgResult = (result.maxResult + result.minResult) / 2;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the validity of the formula.
|
||||||
|
* Returns true on success or false on failure.
|
||||||
|
*/
|
||||||
|
droll.validate = function (formula) {
|
||||||
|
return (droll.parse(formula)) ? true : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll the dice defined by the formula.
|
||||||
|
* Returns a DrollResult object on success or false on failure.
|
||||||
|
*/
|
||||||
|
droll.roll = function (formula) {
|
||||||
|
var pieces = null;
|
||||||
|
var result = new DrollResult();
|
||||||
|
|
||||||
|
pieces = droll.parse(formula);
|
||||||
|
if (!pieces) { return false; }
|
||||||
|
|
||||||
|
for (var a = 0; a < pieces.numDice; a++) {
|
||||||
|
result.rolls[a] = (1 + Math.floor(Math.random() * pieces.numSides));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.modifier = pieces.modifier;
|
||||||
|
|
||||||
|
for (var b = 0; b < result.rolls.length; b++) {
|
||||||
|
result.total += result.rolls[b];
|
||||||
|
}
|
||||||
|
result.total += result.modifier;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// END OF DROLL CODE
|
95
public/scripts/extensions/dice/index.js
Normal file
95
public/scripts/extensions/dice/index.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { getContext } from "../../extensions.js";
|
||||||
|
export { MODULE_NAME };
|
||||||
|
|
||||||
|
const MODULE_NAME = 'dice';
|
||||||
|
const UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
|
function setDiceIcon() {
|
||||||
|
const sendButton = document.getElementById('roll_dice');
|
||||||
|
sendButton.style.backgroundImage = `url(/img/dice-solid.svg)`;
|
||||||
|
sendButton.classList.remove('spin');
|
||||||
|
}
|
||||||
|
|
||||||
|
function doDiceRoll() {
|
||||||
|
const value = $(this).data('value');
|
||||||
|
const isValid = droll.validate(value);
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
const result = droll.roll(value);
|
||||||
|
const context = getContext();
|
||||||
|
context.sendSystemMessage('generic', `${context.name1} rolls the ${value}. The result is: ${result.total}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDiceRollButton() {
|
||||||
|
const buttonHtml = `
|
||||||
|
<input id="roll_dice" type="button" />
|
||||||
|
<div id="dice_dropdown">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item" data-value="d4">d4</li>
|
||||||
|
<li class="list-group-item" data-value="d6">d6</li>
|
||||||
|
<li class="list-group-item" data-value="d8">d8</li>
|
||||||
|
<li class="list-group-item" data-value="d10">d10</li>
|
||||||
|
<li class="list-group-item" data-value="d12">d12</li>
|
||||||
|
<li class="list-group-item" data-value="d20">d20</li>
|
||||||
|
<li class="list-group-item" data-value="d100">d100</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$('#send_but_sheld').prepend(buttonHtml);
|
||||||
|
$('#dice_dropdown li').on('click', doDiceRoll);
|
||||||
|
const button = $('#roll_dice');
|
||||||
|
const dropdown = $('#dice_dropdown');
|
||||||
|
dropdown.hide();
|
||||||
|
button.hide();
|
||||||
|
|
||||||
|
let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
|
||||||
|
placement: 'top-start',
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click touchend', function (e) {
|
||||||
|
const target = $(e.target);
|
||||||
|
if (target.is(dropdown)) return;
|
||||||
|
if (target.is(button) && !dropdown.is(":visible")) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
dropdown.show();
|
||||||
|
popper.update();
|
||||||
|
} else {
|
||||||
|
dropdown.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDiceScript() {
|
||||||
|
if (!window.droll) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = "/scripts/extensions/dice/droll.js";
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchSendForm() {
|
||||||
|
const columns = $('#send_form').css('grid-template-columns').split(' ');
|
||||||
|
columns[columns.length - 1] = `${parseInt(columns[columns.length - 1]) + 40}px`;
|
||||||
|
columns[1] = 'auto';
|
||||||
|
$('#send_form').css('grid-template-columns', columns.join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
|
const context = getContext();
|
||||||
|
|
||||||
|
context.onlineStatus === 'no_connection'
|
||||||
|
? $('#roll_dice').hide(200)
|
||||||
|
: $('#roll_dice').show(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
addDiceScript();
|
||||||
|
addDiceRollButton();
|
||||||
|
patchSendForm();
|
||||||
|
setDiceIcon();
|
||||||
|
moduleWorker();
|
||||||
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
});
|
7
public/scripts/extensions/dice/manifest.json
Normal file
7
public/scripts/extensions/dice/manifest.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"display_name": "D&D Dice",
|
||||||
|
"loading_order": 5,
|
||||||
|
"requires": [],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
40
public/scripts/extensions/dice/style.css
Normal file
40
public/scripts/extensions/dice/style.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#roll_dice {
|
||||||
|
order: 100;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px;
|
||||||
|
background: no-repeat;
|
||||||
|
background-size: 26px auto;
|
||||||
|
background-position: center center;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.3s;
|
||||||
|
filter: invert(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roll_dice:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
background-color: rgba(0,0,0,0.3);
|
||||||
|
border: 1px solid rgba(0,0,0,0.7);
|
||||||
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
218
public/scripts/extensions/expressions/index.js
Normal file
218
public/scripts/extensions/expressions/index.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import { getContext, getApiUrl } from "../../extensions.js";
|
||||||
|
import { urlContentToDataUri } from "../../utils.js";
|
||||||
|
export { MODULE_NAME };
|
||||||
|
|
||||||
|
const MODULE_NAME = 'expressions';
|
||||||
|
const DEFAULT_KEY = 'extensions_expressions_showDefault';
|
||||||
|
const UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
|
let expressionsList = null;
|
||||||
|
let lastCharacter = undefined;
|
||||||
|
let lastMessage = null;
|
||||||
|
let inApiCall = false;
|
||||||
|
let showDefault = false;
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
showDefault = localStorage.getItem(DEFAULT_KEY) == 'true';
|
||||||
|
$('#expressions_show_default').prop('checked', showDefault).trigger('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
localStorage.setItem(DEFAULT_KEY, showDefault.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExpressionsShowDefaultInput() {
|
||||||
|
const value = $(this).prop('checked');
|
||||||
|
showDefault = value;
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
const existingImage = $('div.expression').css('background-image');
|
||||||
|
if (!value && existingImage.includes('data:image/png')) {
|
||||||
|
$('div.expression').css('background-image', 'unset');
|
||||||
|
lastMessage = null;
|
||||||
|
}
|
||||||
|
if (value) {
|
||||||
|
lastMessage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
|
function getLastCharacterMessage() {
|
||||||
|
const reversedChat = context.chat.slice().reverse();
|
||||||
|
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
if (mes.is_user || mes.is_system) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mes.mes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = getContext();
|
||||||
|
|
||||||
|
// group chats and non-characters not supported
|
||||||
|
if (context.groupId || !context.characterId) {
|
||||||
|
removeExpression();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// character changed
|
||||||
|
if (lastCharacter !== context.characterId) {
|
||||||
|
removeExpression();
|
||||||
|
validateImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if last message changed
|
||||||
|
const currentLastMessage = getLastCharacterMessage();
|
||||||
|
if (lastCharacter === context.characterId && lastMessage === currentLastMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API is busy
|
||||||
|
if (inApiCall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
inApiCall = true;
|
||||||
|
const url = new URL(getApiUrl());
|
||||||
|
url.pathname = '/api/classify';
|
||||||
|
|
||||||
|
const apiResult = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Bypass-Tunnel-Reminder': 'bypass',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ text: currentLastMessage })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (apiResult.ok) {
|
||||||
|
const data = await apiResult.json();
|
||||||
|
const expression = data.classification[0].label;
|
||||||
|
setExpression(context.name2, expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
inApiCall = false;
|
||||||
|
lastCharacter = context.characterId;
|
||||||
|
lastMessage = currentLastMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeExpression() {
|
||||||
|
lastMessage = null;
|
||||||
|
$('div.expression').css('background-image', 'unset');
|
||||||
|
$('.expression_settings').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
let imagesValidating = false;
|
||||||
|
|
||||||
|
async function validateImages() {
|
||||||
|
if (imagesValidating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesValidating = true;
|
||||||
|
const context = getContext();
|
||||||
|
$('.expression_settings').show();
|
||||||
|
$('#image_list').empty();
|
||||||
|
|
||||||
|
if (!context.characterId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IMAGE_LIST = (await getExpressionsList()).map(x => `${x}.png`);
|
||||||
|
IMAGE_LIST.forEach((item) => {
|
||||||
|
const image = document.createElement('img');
|
||||||
|
image.src = `/characters/${context.name2}/${item}`;
|
||||||
|
image.classList.add('debug-image');
|
||||||
|
image.width = '0px';
|
||||||
|
image.height = '0px';
|
||||||
|
image.onload = function() {
|
||||||
|
$('#image_list').append(`<li id="${item}" class="success">${item} - OK</li>`);
|
||||||
|
}
|
||||||
|
image.onerror = function() {
|
||||||
|
$('#image_list').append(`<li id="${item}" class="failure">${item} - Missing</li>`);
|
||||||
|
}
|
||||||
|
$('#image_list').prepend(image);
|
||||||
|
});
|
||||||
|
imagesValidating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExpressionsList() {
|
||||||
|
if (Array.isArray(expressionsList)) {
|
||||||
|
return expressionsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(getApiUrl());
|
||||||
|
url.pathname = '/api/classify/labels';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const apiResult = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (apiResult.ok) {
|
||||||
|
const data = await apiResult.json();
|
||||||
|
expressionsList = data.labels;
|
||||||
|
return expressionsList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setExpression(character, expression) {
|
||||||
|
const filename = `${expression}.png`;
|
||||||
|
const imgUrl = `url('/characters/${character}/${filename}')`;
|
||||||
|
$('div.expression').css('background-image', imgUrl);
|
||||||
|
|
||||||
|
const debugImageStatus = document.querySelector(`#image_list li[id="${filename}"]`);
|
||||||
|
if (showDefault && debugImageStatus && debugImageStatus.classList.contains('failure')) {
|
||||||
|
try {
|
||||||
|
const imgUrl = new URL(getApiUrl());
|
||||||
|
imgUrl.pathname = `/api/asset/${MODULE_NAME}/${filename}`;
|
||||||
|
const dataUri = await urlContentToDataUri(imgUrl.toString(), { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
||||||
|
$('div.expression').css('background-image', `url(${dataUri})`);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$('div.expression').css('background-image', 'unset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function addExpressionImage() {
|
||||||
|
const html = `<div class="expression"></div>`
|
||||||
|
$('body').append(html);
|
||||||
|
}
|
||||||
|
function addSettings() {
|
||||||
|
const html = `
|
||||||
|
<div class="expression_settings">
|
||||||
|
<h4>Expression images</h4>
|
||||||
|
<ul id="image_list"></ul>
|
||||||
|
<p><b>Hint:</b> <i>Create new folder in the <tt>public/characters/</tt> folder and name it as the name of the character. Put PNG images with expressions there.</i></p>
|
||||||
|
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$('#extensions_settings').append(html);
|
||||||
|
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||||
|
$('.expression_settings').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
addExpressionImage();
|
||||||
|
addSettings();
|
||||||
|
loadSettings();
|
||||||
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
})();
|
9
public/scripts/extensions/expressions/manifest.json
Normal file
9
public/scripts/extensions/expressions/manifest.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"display_name": "Character Expressions",
|
||||||
|
"loading_order": 6,
|
||||||
|
"requires": [
|
||||||
|
"classify"
|
||||||
|
],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
63
public/scripts/extensions/expressions/style.css
Normal file
63
public/scripts/extensions/expressions/style.css
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
div.expression {
|
||||||
|
background-image: unset;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position-y: bottom;
|
||||||
|
max-height: 90vh;
|
||||||
|
max-width: calc((100vw - 800px)/2);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
filter: drop-shadow(2px 2px 2px #51515199);
|
||||||
|
transition: 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-image {
|
||||||
|
display: none;
|
||||||
|
visibility: collapse;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image_list {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image_list .success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image_list .failure {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression_settings {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression_settings p {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression_settings label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression_settings label input {
|
||||||
|
margin-left: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width:1200px) {
|
||||||
|
div.expression {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
72
public/scripts/extensions/floating-prompt/index.js
Normal file
72
public/scripts/extensions/floating-prompt/index.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { getContext } from "../../extensions.js";
|
||||||
|
export { MODULE_NAME };
|
||||||
|
|
||||||
|
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||||
|
const PROMPT_KEY = 'extensions_floating_prompt';
|
||||||
|
const INTERVAL_KEY = 'extensions_floating_interval';
|
||||||
|
const UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
|
let lastMessageNumber = null;
|
||||||
|
let promptInsertionInterval = 0;
|
||||||
|
|
||||||
|
function onExtensionFloatingPromptInput() {
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExtensionFloatingIntervalInput() {
|
||||||
|
promptInsertionInterval = Number($(this).val());
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
const prompt = localStorage.getItem(PROMPT_KEY);
|
||||||
|
const interval = localStorage.getItem(INTERVAL_KEY);
|
||||||
|
$('#extension_floating_prompt').val(prompt).trigger('input');
|
||||||
|
$('#extension_floating_interval').val(interval).trigger('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
localStorage.setItem(PROMPT_KEY, $('#extension_floating_prompt').val());
|
||||||
|
localStorage.setItem(INTERVAL_KEY, $('#extension_floating_interval').val());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
|
const context = getContext();
|
||||||
|
|
||||||
|
// take the count of messages
|
||||||
|
lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0;
|
||||||
|
|
||||||
|
if (lastMessageNumber <= 0 || promptInsertionInterval <= 0) {
|
||||||
|
$('#extension_floating_counter').text('No');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesTillInsertion = (lastMessageNumber % promptInsertionInterval);
|
||||||
|
const shouldAddPrompt = messagesTillInsertion == 0;
|
||||||
|
const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
|
||||||
|
context.setExtensionPrompt(MODULE_NAME, prompt);
|
||||||
|
$('#extension_floating_counter').text(shouldAddPrompt ? 'This' : messagesTillInsertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
function addExtensionsSettings() {
|
||||||
|
const settingsHtml = `
|
||||||
|
<h4>Floating Prompt</h4>
|
||||||
|
<div class="floating_prompt_settings">
|
||||||
|
<label for="extension_floating_prompt">Append the following text to the scenario:</label>
|
||||||
|
<textarea id="extension_floating_prompt" class="text_pole" rows="2"></textarea>
|
||||||
|
<label for="extension_floating_interval">Every N messages <b>you</b> send (set to 0 to disable):</label>
|
||||||
|
<input id="extension_floating_interval" class="text_pole" type="number" value="0" min="0" max="999" />
|
||||||
|
<span>Appending the prompt in next: <span id="extension_floating_counter">No</span> message(s)</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$('#extensions_settings').append(settingsHtml);
|
||||||
|
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
|
||||||
|
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
addExtensionsSettings();
|
||||||
|
loadSettings();
|
||||||
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
})();
|
7
public/scripts/extensions/floating-prompt/manifest.json
Normal file
7
public/scripts/extensions/floating-prompt/manifest.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"display_name": "Floating Prompt",
|
||||||
|
"loading_order": 1,
|
||||||
|
"requires": [],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
9
public/scripts/extensions/floating-prompt/style.css
Normal file
9
public/scripts/extensions/floating-prompt/style.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.floating_prompt_settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extension_floating_counter {
|
||||||
|
font-weight: 600;
|
||||||
|
color: orange;
|
||||||
|
}
|
357
public/scripts/extensions/memory/index.js
Normal file
357
public/scripts/extensions/memory/index.js
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
import { getStringHash, debounce } from "../../utils.js";
|
||||||
|
import { getContext, getApiUrl } from "../../extensions.js";
|
||||||
|
export { MODULE_NAME };
|
||||||
|
|
||||||
|
const MODULE_NAME = '1_memory';
|
||||||
|
const SETTINGS_KEY = 'extensions_memory_settings';
|
||||||
|
const UPDATE_INTERVAL = 1000;
|
||||||
|
|
||||||
|
let lastCharacterId = null;
|
||||||
|
let lastGroupId = null;
|
||||||
|
let lastChatId = null;
|
||||||
|
let lastMessageHash = null;
|
||||||
|
let lastMessageId = null;
|
||||||
|
let inApiCall = false;
|
||||||
|
|
||||||
|
const formatMemoryValue = (value) => value ? `[Context: "${value.trim()}"]` : '';
|
||||||
|
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
|
||||||
|
|
||||||
|
const defaultSettings = {
|
||||||
|
minLongMemory: 16,
|
||||||
|
maxLongMemory: 512,
|
||||||
|
longMemoryLength: 128,
|
||||||
|
shortMemoryLength: 512,
|
||||||
|
minShortMemory: 128,
|
||||||
|
maxShortMemory: 2048,
|
||||||
|
shortMemoryStep: 16,
|
||||||
|
longMemoryStep: 8,
|
||||||
|
repetitionPenaltyStep: 0.05,
|
||||||
|
repetitionPenalty: 1.0,
|
||||||
|
maxRepetitionPenalty: 2.0,
|
||||||
|
minRepetitionPenalty: 1.0,
|
||||||
|
temperature: 1.0,
|
||||||
|
minTemperature: 0.1,
|
||||||
|
maxTemperature: 2.0,
|
||||||
|
temperatureStep: 0.05,
|
||||||
|
lengthPenalty: 1,
|
||||||
|
minLengthPenalty: 0,
|
||||||
|
maxLengthPenalty: 2,
|
||||||
|
lengthPenaltyStep: 0.05,
|
||||||
|
memoryFrozen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
shortMemoryLength: defaultSettings.shortMemoryLength,
|
||||||
|
longMemoryLength: defaultSettings.longMemoryLength,
|
||||||
|
repetitionPenalty: defaultSettings.repetitionPenalty,
|
||||||
|
temperature: defaultSettings.temperature,
|
||||||
|
lengthPenalty: defaultSettings.lengthPenalty,
|
||||||
|
memoryFrozen: defaultSettings.memoryFrozen,
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
const savedSettings = JSON.parse(localStorage.getItem(SETTINGS_KEY));
|
||||||
|
|
||||||
|
if (savedSettings) {
|
||||||
|
Object.assign(settings, savedSettings);
|
||||||
|
$('#memory_long_length').val(settings.longMemoryLength).trigger('input');
|
||||||
|
$('#memory_short_length').val(settings.shortMemoryLength).trigger('input');
|
||||||
|
$('#memory_repetition_penalty').val(settings.repetitionPenalty).trigger('input');
|
||||||
|
$('#memory_temperature').val(settings.temperature).trigger('input');
|
||||||
|
$('#memory_length_penalty').val(settings.lengthPenalty).trigger('input');
|
||||||
|
$('#memory_frozen').prop('checked', settings.memoryFrozen).trigger('input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryShortInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
settings.shortMemoryLength = Number(value);
|
||||||
|
$('#memory_short_length_tokens').text(value);
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
// Don't let long buffer be bigger than short
|
||||||
|
if (settings.longMemoryLength > settings.shortMemoryLength) {
|
||||||
|
$('#memory_long_length').val(settings.shortMemoryLength).trigger('input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryLongInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
settings.longMemoryLength = Number(value);
|
||||||
|
$('#memory_long_length_tokens').text(value);
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
// Don't let long buffer be bigger than short
|
||||||
|
if (settings.longMemoryLength > settings.shortMemoryLength) {
|
||||||
|
$('#memory_short_length').val(settings.longMemoryLength).trigger('input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryRepetitionPenaltyInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
settings.repetitionPenalty = Number(value);
|
||||||
|
$('#memory_repetition_penalty_value').text(settings.repetitionPenalty.toFixed(2));
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryTemperatureInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
settings.temperature = Number(value);
|
||||||
|
$('#memory_temperature_value').text(settings.temperature.toFixed(2));
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryLengthPenaltyInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
settings.lengthPenalty = Number(value);
|
||||||
|
$('#memory_length_penalty_value').text(settings.lengthPenalty.toFixed(2));
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryFrozenInput() {
|
||||||
|
const value = Boolean($(this).prop('checked'));
|
||||||
|
settings.memoryFrozen = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLastValues() {
|
||||||
|
const context = getContext();
|
||||||
|
lastGroupId = context.groupId;
|
||||||
|
lastCharacterId = context.characterId;
|
||||||
|
lastChatId = context.chatId;
|
||||||
|
lastMessageId = context.chat?.length ?? null;
|
||||||
|
lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLatestMemoryFromChat(chat) {
|
||||||
|
if (!Array.isArray(chat) || !chat.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const reversedChat = chat.slice().reverse();
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
if (mes.extra && mes.extra.memory) {
|
||||||
|
return mes.extra.memory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
|
const context = getContext();
|
||||||
|
const chat = context.chat;
|
||||||
|
|
||||||
|
// no characters or group selected
|
||||||
|
if (!context.groupId && !context.characterId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat/character/group changed
|
||||||
|
if ((context.groupId && lastGroupId !== context.groupId) || (context.characterId !== lastCharacterId) || (context.chatId !== lastChatId)) {
|
||||||
|
const latestMemory = getLatestMemoryFromChat(chat);
|
||||||
|
setMemoryContext(latestMemory, false);
|
||||||
|
saveLastValues();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently summarizing or frozen state - skip
|
||||||
|
if (inApiCall || settings.memoryFrozen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No new messages - do nothing
|
||||||
|
if (lastMessageId === chat.length && getStringHash(chat[chat.length - 1].mes) === lastMessageHash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages has been deleted - rewrite the context with the latest available memory
|
||||||
|
if (chat.length < lastMessageId) {
|
||||||
|
const latestMemory = getLatestMemoryFromChat(chat);
|
||||||
|
setMemoryContext(latestMemory, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message has been edited / regenerated - delete the saved memory
|
||||||
|
if (chat.length
|
||||||
|
&& chat[chat.length - 1].extra
|
||||||
|
&& chat[chat.length - 1].extra.memory
|
||||||
|
&& lastMessageId === chat.length
|
||||||
|
&& getStringHash(chat[chat.length - 1].mes) !== lastMessageHash) {
|
||||||
|
delete chat[chat.length - 1].extra.memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await summarizeChat(context);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
saveLastValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function summarizeChat(context) {
|
||||||
|
function getMemoryString() {
|
||||||
|
return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = context.chat;
|
||||||
|
const longMemory = getLatestMemoryFromChat(chat);
|
||||||
|
const reversedChat = chat.slice().reverse();
|
||||||
|
let memoryBuffer = [];
|
||||||
|
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
// we reached the point of latest memory
|
||||||
|
if (longMemory && mes.extra && mes.extra.memory == longMemory) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't care about system
|
||||||
|
if (mes.is_system) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the sender's name
|
||||||
|
const name = mes.is_user ? (context.name1 ?? 'You') : (mes.force_avatar ? mes.name : context.name2);
|
||||||
|
const entry = `${name}:\n${mes['mes']}`;
|
||||||
|
memoryBuffer.push(entry);
|
||||||
|
|
||||||
|
// check if token limit was reached
|
||||||
|
if (context.encode(getMemoryString()).length >= settings.shortMemoryLength) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultingString = getMemoryString();
|
||||||
|
|
||||||
|
if (context.encode(resultingString).length < settings.shortMemoryLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform the summarization API call
|
||||||
|
try {
|
||||||
|
inApiCall = true;
|
||||||
|
const url = new URL(getApiUrl());
|
||||||
|
url.pathname = '/api/summarize';
|
||||||
|
|
||||||
|
const apiResult = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Bypass-Tunnel-Reminder': 'bypass',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: resultingString,
|
||||||
|
params: {
|
||||||
|
min_length: settings.longMemoryLength * 0.8,
|
||||||
|
max_length: settings.longMemoryLength,
|
||||||
|
repetition_penalty: settings.repetitionPenalty,
|
||||||
|
temperature: settings.temperature,
|
||||||
|
length_penalty: settings.lengthPenalty,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (apiResult.ok) {
|
||||||
|
const data = await apiResult.json();
|
||||||
|
const summary = data.summary;
|
||||||
|
|
||||||
|
const newContext = getContext();
|
||||||
|
|
||||||
|
// something changed during summarization request
|
||||||
|
if (newContext.groupId !== context.groupId || newContext.chatId !== context.chatId || (!newContext.groupId && (newContext.characterId !== context.characterId))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMemoryContext(summary, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
inApiCall = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryRestoreClick() {
|
||||||
|
const context = getContext();
|
||||||
|
const content = $('#memory_contents').val();
|
||||||
|
const reversedChat = context.chat.slice().reverse();
|
||||||
|
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
if (mes.extra && mes.extra.memory == content) {
|
||||||
|
delete mes.extra.memory;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newContent = getLatestMemoryFromChat(context.chat);
|
||||||
|
setMemoryContext(newContent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemoryContentInput() {
|
||||||
|
const value = $(this).val();
|
||||||
|
setMemoryContext(value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMemoryContext(value, saveToMessage) {
|
||||||
|
const context = getContext();
|
||||||
|
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value));
|
||||||
|
$('#memory_contents').val(value);
|
||||||
|
|
||||||
|
if (saveToMessage && context.chat.length) {
|
||||||
|
const mes = context.chat[context.chat.length - 1];
|
||||||
|
|
||||||
|
if (!mes.extra) {
|
||||||
|
mes.extra = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mes.extra.memory = value;
|
||||||
|
saveChatDebounced();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
function addExtensionControls() {
|
||||||
|
const settingsHtml = `
|
||||||
|
<h4>Memory</h4>
|
||||||
|
<div id="memory_settings">
|
||||||
|
<label for="memory_contents">Memory contents</label>
|
||||||
|
<textarea id="memory_contents" class="text_pole" rows="8" placeholder="Context will be generated here..."></textarea>
|
||||||
|
<div class="memory_contents_controls">
|
||||||
|
<input id="memory_restore" class="menu_button" type="submit" value="Restore previous state" />
|
||||||
|
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" /> Freeze context</label>
|
||||||
|
</div>
|
||||||
|
<label for="memory_short_length">Memory summarization [short-term] length (<span id="memory_short_length_tokens"></span> tokens)</label>
|
||||||
|
<input id="memory_short_length" type="range" value="${defaultSettings.shortMemoryLength}" min="${defaultSettings.minShortMemory}" max="${defaultSettings.maxShortMemory}" step="${defaultSettings.shortMemoryStep}" />
|
||||||
|
<label for="memory_long_length">Memory context [long-term] length (<span id="memory_long_length_tokens"></span> tokens)</label>
|
||||||
|
<input id="memory_long_length" type="range" value="${defaultSettings.longMemoryLength}" min="${defaultSettings.minLongMemory}" max="${defaultSettings.maxLongMemory}" step="${defaultSettings.longMemoryStep}" />
|
||||||
|
<label for="memory_temperature">Summarization temperature (<span id="memory_temperature_value"></span>)</label>
|
||||||
|
<input id="memory_temperature" type="range" value="${defaultSettings.temperature}" min="${defaultSettings.minTemperature}" max="${defaultSettings.maxTemperature}" step="${defaultSettings.temperatureStep}" />
|
||||||
|
<label for="memory_repetition_penalty">Summarization repetition penalty (<span id="memory_repetition_penalty_value"></span>)</label>
|
||||||
|
<input id="memory_repetition_penalty" type="range" value="${defaultSettings.repetitionPenalty}" min="${defaultSettings.minRepetitionPenalty}" max="${defaultSettings.maxRepetitionPenalty}" step="${defaultSettings.repetitionPenaltyStep}" />
|
||||||
|
<label for="memory_length_penalty">Summarization length penalty (<span id="memory_length_penalty_value"></span>)</label>
|
||||||
|
<input id="memory_length_penalty" type="range" value="${defaultSettings.lengthPenalty}" min="${defaultSettings.minLengthPenalty}" max="${defaultSettings.maxLengthPenalty}" step="${defaultSettings.lengthPenaltyStep}" />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$('#extensions_settings').append(settingsHtml);
|
||||||
|
$('#memory_restore').on('click', onMemoryRestoreClick);
|
||||||
|
$('#memory_contents').on('input', onMemoryContentInput);
|
||||||
|
$('#memory_long_length').on('input', onMemoryLongInput);
|
||||||
|
$('#memory_short_length').on('input', onMemoryShortInput);
|
||||||
|
$('#memory_repetition_penalty').on('input', onMemoryRepetitionPenaltyInput);
|
||||||
|
$('#memory_temperature').on('input', onMemoryTemperatureInput);
|
||||||
|
$('#memory_length_penalty').on('input', onMemoryLengthPenaltyInput);
|
||||||
|
$('#memory_frozen').on('input', onMemoryFrozenInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
addExtensionControls();
|
||||||
|
loadSettings();
|
||||||
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
});
|
9
public/scripts/extensions/memory/manifest.json
Normal file
9
public/scripts/extensions/memory/manifest.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"display_name": "Memory",
|
||||||
|
"loading_order": 9,
|
||||||
|
"requires": [
|
||||||
|
"summarize"
|
||||||
|
],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css"
|
||||||
|
}
|
34
public/scripts/extensions/memory/style.css
Normal file
34
public/scripts/extensions/memory/style.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#memory_settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#memory_settings textarea {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#memory_settings input[type="range"] {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#memory_settings label {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for="memory_frozen"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for="memory_frozen"] input {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory_contents_controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
@ -7,6 +7,7 @@ export {
|
|||||||
getStringHash,
|
getStringHash,
|
||||||
debounce,
|
debounce,
|
||||||
delay,
|
delay,
|
||||||
|
isSubsetOf,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// UTILS
|
/// UTILS
|
||||||
@ -83,4 +84,5 @@ function debounce(func, timeout = 300) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
||||||
|
const isSubsetOf = (a, b) => (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false;
|
||||||
|
@ -216,6 +216,8 @@ code {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_but {
|
#send_but {
|
||||||
@ -233,6 +235,7 @@ code {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
filter: brightness(0.5);
|
filter: brightness(0.5);
|
||||||
|
order: 99999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_but:hover {
|
#send_but:hover {
|
||||||
@ -2558,4 +2561,56 @@ a {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extensions */
|
||||||
|
#extensions_url {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensions_status {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions_block input[type="submit"]:hover{
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions_block input[type="checkbox"] {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for="extensions_autoconnect"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions_url_block {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions_url_block h4 {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions_block {
|
||||||
|
clear: both;
|
||||||
|
padding: 0.05px; /* clear fix */
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failure {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expander {
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
Reference in New Issue
Block a user