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,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_autoConnectKey = 'extensions_autoconnect';
|
||||
let extensions = [];
|
||||
let modules = [];
|
||||
let activeExtensions = new Set();
|
||||
|
||||
(function () {
|
||||
const settings_html = `
|
||||
<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>
|
||||
`;
|
||||
const getContext = () => window['TavernAI'].getContext();
|
||||
const getApiUrl = () => localStorage.getItem('extensions_url');
|
||||
const defaultUrl = "http://localhost:5100";
|
||||
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
|
||||
let connectedToApi = false;
|
||||
|
||||
const settings_style = `
|
||||
<style>
|
||||
#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;
|
||||
}
|
||||
async function activateExtensions() {
|
||||
const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
for (let entry of extensions) {
|
||||
const name = entry[0];
|
||||
const manifest = entry[1];
|
||||
|
||||
const defaultUrl = "http://localhost:5100";
|
||||
let connectedToApi = false;
|
||||
|
||||
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');
|
||||
if (activeExtensions.has(name)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
async function connectToApi(baseUrl) {
|
||||
const url = new URL(baseUrl);
|
||||
url.pathname = '/api/extensions';
|
||||
|
||||
try {
|
||||
const getExtensionsResult = await fetch(url, { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
||||
|
||||
if (getExtensionsResult.ok) {
|
||||
const data = await getExtensionsResult.json();
|
||||
extensions = data.extensions;
|
||||
applyExtensions(baseUrl);
|
||||
// all required modules are active (offline extensions require none)
|
||||
if (isSubsetOf(modules, manifest.requires)) {
|
||||
try {
|
||||
await addExtensionScript(name, manifest);
|
||||
await addExtensionStyle(name, manifest);
|
||||
activeExtensions.add(name);
|
||||
$('#extensions_list').append(`<li id="${name}">${manifest.display_name}</li>`);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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>`);
|
||||
catch (error) {
|
||||
console.error(`Could not activate extension: ${name}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
const url = new URL(baseUrl);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
||||
return;
|
||||
}
|
||||
function addExtensionScript(name, manifest) {
|
||||
if (manifest.js) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `/scripts/extensions/${name}/${manifest.js}`;
|
||||
let ready = false;
|
||||
|
||||
for (let extension of extensions) {
|
||||
addExtensionStyle(extension);
|
||||
addExtensionScript(extension);
|
||||
}
|
||||
|
||||
async function addExtensionStyle(extension) {
|
||||
if (extension.metadata.css) {
|
||||
try {
|
||||
url.pathname = `/api/style/${extension.name}`;
|
||||
const link = url.toString();
|
||||
|
||||
const result = await fetch(link, { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } });
|
||||
const text = await result.text();
|
||||
|
||||
if ($(`style[id="${link}"]`).length === 0) {
|
||||
const style = document.createElement('style');
|
||||
style.id = link;
|
||||
style.innerHTML = text;
|
||||
$('head').append(style);
|
||||
if ($(`script[id="${name}"]`).length === 0) {
|
||||
const script = document.createElement('script');
|
||||
script.id = name;
|
||||
script.type = 'module';
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
script.onerror = function (err) {
|
||||
reject(err, script);
|
||||
};
|
||||
script.onload = script.onreadystatechange = function () {
|
||||
// console.log(this.readyState); // uncomment this line to see which ready states are called.
|
||||
if (!ready && (!this.readyState || this.readyState == 'complete')) {
|
||||
ready = true;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
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 () {
|
||||
const url = localStorage.getItem(extensions_urlKey) ?? defaultUrl;
|
||||
const autoConnect = localStorage.getItem(extensions_autoConnectKey) == 'true';
|
||||
$('#rm_api_block').append(settings_html);
|
||||
$('head').append(settings_style);
|
||||
$("#extensions_url").val(url);
|
||||
$("#extensions_connect").on('click', connectClickHandler);
|
||||
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
||||
$("#extensions_autoconnect").prop('checked', autoConnect).trigger('input');
|
||||
});
|
||||
})();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
const url = localStorage.getItem(extensions_urlKey) ?? defaultUrl;
|
||||
const autoConnect = localStorage.getItem(extensions_autoConnectKey) == 'true';
|
||||
$("#extensions_url").val(url);
|
||||
$("#extensions_connect").on('click', connectClickHandler);
|
||||
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
||||
$("#extensions_autoconnect").prop('checked', autoConnect).trigger('input');
|
||||
|
||||
// Activate offline extensions
|
||||
activateExtensions();
|
||||
});
|
Reference in New Issue
Block a user