Merge branch 'main' into feature/ssl-encryption

This commit is contained in:
maver
2023-04-22 21:05:22 +02:00
committed by GitHub
23 changed files with 157 additions and 73 deletions

32
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

4
.npmignore Normal file
View File

@ -0,0 +1,4 @@
node_modules/
/uploads/
.DS_Store
/thumbnails

View File

@ -233,13 +233,10 @@
"# ---\n", "# ---\n",
"# nodejs\n", "# nodejs\n",
"%cd /\n", "%cd /\n",
"def setupNVM():\n",
" !curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash\n",
"ii.addTask(\"Setup NVM\", setupNVM)\n",
"\n",
"def installNode():\n", "def installNode():\n",
" !nvm install 19.1.0\n", " !npm install -g n\n",
" !nvm use 19.1.0\n", " !n 19\n",
" !node --version\n",
"ii.addTask(\"Install node\", installNode)\n", "ii.addTask(\"Install node\", installNode)\n",
"\n", "\n",
"\n", "\n",

10
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.1",
"dependencies": { "dependencies": {
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
"axios": "^1.3.4", "axios": "^1.3.4",
@ -35,7 +35,7 @@
"yargs": "^17.7.1" "yargs": "^17.7.1"
}, },
"bin": { "bin": {
"TavernAI": "server.js" "sillytavern": "server.js"
} }
}, },
"node_modules/@dqbd/tiktoken": { "node_modules/@dqbd/tiktoken": {

View File

@ -31,10 +31,13 @@
"xml2js": "^0.5.0" "xml2js": "^0.5.0"
} }
}, },
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.8",
"scripts": {
"start": "node server.js"
},
"bin": { "bin": {
"TavernAI": "server.js" "sillytavern": "./server.js"
}, },
"rules": { "rules": {
"no-path-concat": "off", "no-path-concat": "off",

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Character Descriptions</title> <title>Character Descriptions</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Import Chat</title> <title>Import Chat</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -12,7 +12,7 @@
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Chat import</h2> <h2>Chat import</h2>
<h3>Import chats into TavernAI</h3> <h3>Import chats into SillyTavern</h3>
<p>To import Character.AI chats, use this tool: <a href="https://github.com/0x000011b/characterai-dumper">https://github.com/0x000011b/characterai-dumper</a>.</p> <p>To import Character.AI chats, use this tool: <a href="https://github.com/0x000011b/characterai-dumper">https://github.com/0x000011b/characterai-dumper</a>.</p>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Example Dialogues</title> <title>Example Dialogues</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Scenario</title> <title>Scenario</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -14,7 +14,7 @@
<h2>World Info</h2> <h2>World Info</h2>
<h4>World Info enhances AI's understanding of the details in your world.</h4> <h4>World Info enhances AI's understanding of the details in your world.</h4>
<p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p> <p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p>
<p>The TavernAI engine activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p> <p>SillyTavern activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p>
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p> <p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p>
<h3>Pro Tips</h3> <h3>Pro Tips</h3>

View File

@ -15,7 +15,7 @@
<div id="content"> <div id="content">
<h2>Scan Depth</h2> <h2>Scan Depth</h2>
<h4>Defines how many messages in the chat history should be scanned for World Info keys.</h4> <h4>Defines how many messages in the chat history should be scanned for World Info keys.</h4>
<p>If set to 1, then TavernAI only scans the message you send and the most recent reply.</p> <p>If set to 1, then SillyTavern only scans the message you send and the most recent reply.</p>
<p>This stacks up to 10 message pairs it total.</p> <p>This stacks up to 10 message pairs it total.</p>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Personality Summary</title> <title>Personality Summary</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - First Message</title> <title>First Message</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - KobolAI Settings</title> <title>KoboldAI Settings</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -17,7 +17,7 @@
<div id="content"> <div id="content">
<h2>KoboldAI Settings</h2> <h2>KoboldAI Settings</h2>
<p>Standard KoboldAI settings files are used here. To add your own settings, simply add the file .settings <p>Standard KoboldAI settings files are used here. To add your own settings, simply add the file .settings
in TavernAI\public\KoboldAI Settings in SillyTavern\public\KoboldAI Settings
</p> </p>
<h3>Temperature</h3> <h3>Temperature</h3>
<p>Value from 0.1 to 2.0. Lower value - the answers are more logical, but less creative. Higher value - the <p>Value from 0.1 to 2.0. Lower value - the answers are more logical, but less creative. Higher value - the
@ -34,7 +34,7 @@
The larger the parameter value, the longer the generation time takes.</p> The larger the parameter value, the longer the generation time takes.</p>
<h3>Context size</h3> <h3>Context size</h3>
<p>How much will the AI remember. Context size also affects the speed of generation.<br><br> <p>How much will the AI remember. Context size also affects the speed of generation.<br><br>
<u>Important</u>: The setting of Context Size in TavernAI GUI overrides the setting for KoboldAI GUI <u>Important</u>: The setting of Context Size in SillyTavern GUI overrides the setting for KoboldAI GUI
</p> </p>
<h2>Advanced Settings</h2> <h2>Advanced Settings</h2>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Novel AI API Key</title> <title>NovelAI API Key</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - NovelAI Settings</title> <title>NovelAI Settings</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -13,7 +13,7 @@
<div id="content"> <div id="content">
<h2>NovelAI settings</h2> <h2>NovelAI settings</h2>
<p> <p>
The files with the settings are here (TavernAI\public\NovelAI Settings).<br> The files with the settings are here (SillyTavern\public\NovelAI Settings).<br>
You can also manually add your own settings files. You can also manually add your own settings files.
</p> </p>
<h3>Temperature</h3> <h3>Temperature</h3>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - NovelAI Models</title> <title>NovelAI Models</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>TavernAI - Note - Anchors</title> <title>Anchors</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -16,7 +16,7 @@
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Message Sound</h2> <h2>Message Sound</h2>
<p>To play your own custom sound on receiving a new message from bot, replace the following MP3 file in your TavernAI folder:</p> <p>To play your own custom sound on receiving a new message from bot, replace the following MP3 file in your SillyTavern folder:</p>
<code> <code>
public/sounds/message.mp3 public/sounds/message.mp3
</code> </code>
@ -24,7 +24,7 @@
Plays at 80% volume. Plays at 80% volume.
</p> </p>
<p> <p>
If "Background Sound Only" option is enabled, the sound plays only if TavernAI window is <b>unfocused</b>. If "Background Sound Only" option is enabled, the sound plays only if SillyTavern window is <b>unfocused</b>.
</p> </p>
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Multigen</h2> <h2>Multigen</h2>
<p>TavernAI tries to create faster and longer responses by chaining the generation using smaller batches.</p> <p>SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches.</p>
<h3>Default settings:</h3> <h3>Default settings:</h3>
<p>First batch = 50 tokens</p> <p>First batch = 50 tokens</p>
<p>Next batches = 30 tokens</p> <p>Next batches = 30 tokens</p>

View File

@ -158,15 +158,15 @@ export {
} }
// API OBJECT FOR EXTERNAL WIRING // API OBJECT FOR EXTERNAL WIRING
window["TavernAI"] = {}; window["SillyTavern"] = {};
let converter = new showdown.Converter({ emoji: "true" }); let converter = new showdown.Converter({ emoji: "true" });
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' }); const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
/* let bg_menu_toggle = false; */ /* let bg_menu_toggle = false; */
const systemUserName = "TavernAI"; const systemUserName = "SillyTavern";
let default_user_name = "You"; let default_user_name = "You";
let name1 = default_user_name; let name1 = default_user_name;
let name2 = "TavernAI"; let name2 = "SillyTavern";
let chat = []; let chat = [];
let safetychat = [ let safetychat = [
{ {
@ -248,7 +248,7 @@ const system_messages = {
is_user: false, is_user: false,
is_name: true, is_name: true,
mes: [ mes: [
'Welcome to TavernAI! In order to begin chatting:', 'Welcome to SillyTavern! In order to begin chatting:',
'<ul>', '<ul>',
'<li>Connect to one of the supported generation APIs</li>', '<li>Connect to one of the supported generation APIs</li>',
'<li>Create or pick a character from the list</li>', '<li>Create or pick a character from the list</li>',
@ -1231,6 +1231,7 @@ class StreamingProcessor {
let formattedText = messageFormating(processedText, chat[messageId].name, chat[messageId].is_system, chat[messageId].force_avatar); let formattedText = messageFormating(processedText, chat[messageId].name, chat[messageId].is_system, chat[messageId].force_avatar);
const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`); const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
mesText.html(formattedText); mesText.html(formattedText);
this.setFirstSwipe(messageId);
} }
scrollChatToBottom(); scrollChatToBottom();
@ -1257,6 +1258,14 @@ class StreamingProcessor {
showSwipeButtons(); showSwipeButtons();
} }
setFirstSwipe(messageId) {
if (this.type !== 'swipe' && this.type !== 'impersonate') {
if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {
chat[messageId]['swipes'][0] = chat[messageId]['mes'];
}
}
}
onStopStreaming() { onStopStreaming() {
this.onErrorStreaming(); this.onErrorStreaming();
} }
@ -1274,11 +1283,12 @@ class StreamingProcessor {
this.isFinished = false; this.isFinished = false;
this.generator = this.nullStreamingGeneration; this.generator = this.nullStreamingGeneration;
this.abortController = new AbortController(); this.abortController = new AbortController();
this.firstMessageText = '...';
} }
async generate() { async generate() {
if (this.messageId == -1) { if (this.messageId == -1) {
this.messageId = this.onStartStreaming('...'); this.messageId = this.onStartStreaming(this.firstMessageText);
await delay(1); // delay for message to be rendered await delay(1); // delay for message to be rendered
} }
@ -1327,6 +1337,7 @@ async function Generate(type, automatic_trigger, force_name2) {
if (isStreamingEnabled()) { if (isStreamingEnabled()) {
streamingProcessor = new StreamingProcessor(type, force_name2); streamingProcessor = new StreamingProcessor(type, force_name2);
hideSwipeButtons();
} }
else { else {
streamingProcessor = false; streamingProcessor = false;
@ -1566,8 +1577,6 @@ async function Generate(type, automatic_trigger, force_name2) {
hordeAmountGen = adjustedParams.maxLength; hordeAmountGen = adjustedParams.maxLength;
} }
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
// Extension added strings // Extension added strings
const allAnchors = getAllExtensionPrompts(); const allAnchors = getAllExtensionPrompts();
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO); const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
@ -1575,12 +1584,13 @@ async function Generate(type, automatic_trigger, force_name2) {
/////////////////////// swipecode /////////////////////// swipecode
if (type == 'swipe') { if (type == 'swipe') {
console.log('pre swipe shift: ' + chat2.length); console.log('pre swipe shift: ' + chat2.length);
console.log('shifting swipe chat2'); console.log('shifting swipe chat2');
chat2.shift(); chat2.shift();
} }
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
console.log('post swipe shift:' + chat2.length); console.log('post swipe shift:' + chat2.length);
var i = 0; var i = 0;
@ -3444,7 +3454,7 @@ function isHordeGenerationNotAllowed() {
return false; return false;
} }
window["TavernAI"].getContext = function () { window["SillyTavern"].getContext = function () {
return { return {
chat: chat, chat: chat,
characters: characters, characters: characters,

View File

@ -29,7 +29,7 @@ const extension_settings = {
let modules = []; let modules = [];
let activeExtensions = new Set(); let activeExtensions = new Set();
const getContext = () => window['TavernAI'].getContext(); const getContext = () => window['SillyTavern'].getContext();
const getApiUrl = () => extension_settings.apiUrl; const getApiUrl = () => extension_settings.apiUrl;
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } }; const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
let connectedToApi = false; let connectedToApi = false;

View File

@ -1,3 +1,5 @@
#!/usr/bin/env node
const yargs = require('yargs/yargs'); const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers'); const { hideBin } = require('yargs/helpers');
@ -16,6 +18,10 @@ const cliArguments = yargs(hideBin(process.argv))
describe: 'Path to your private key file.' describe: 'Path to your private key file.'
}).argv; }).argv;
// change all relative paths
const process = require('process')
process.chdir(__dirname)
const express = require('express'); const express = require('express');
const compression = require('compression'); const compression = require('compression');
const app = express(); const app = express();
@ -48,10 +54,10 @@ const ExifReader = require('exifreader');
const exif = require('piexifjs'); const exif = require('piexifjs');
const webp = require('webp-converter'); const webp = require('webp-converter');
const config = require(path.join(process.cwd(), './config.conf')); const config = require(path.join(__dirname, './config.conf'));
const server_port = process.env.SILLY_TAVERN_PORT || config.port; const server_port = process.env.SILLY_TAVERN_PORT || config.port;
const whitelistPath = path.join(process.cwd(), "./whitelist.txt"); const whitelistPath = path.join(__dirname, "./whitelist.txt");
let whitelist = config.whitelist; let whitelist = config.whitelist;
if (fs.existsSync(whitelistPath)) { if (fs.existsSync(whitelistPath)) {
@ -202,8 +208,8 @@ app.use(function (req, res, next) { //Security
//clientIp = req.connection.remoteAddress.split(':').pop(); //clientIp = req.connection.remoteAddress.split(':').pop();
if (whitelistMode === true && !whitelist.includes(clientIp)) { if (whitelistMode === true && !whitelist.includes(clientIp)) {
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.\n'); console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.'); return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
} }
next(); next();
}); });
@ -230,7 +236,7 @@ app.use((req, res, next) => {
app.use(express.static(__dirname + "/public", { refresh: true })); app.use(express.static(__dirname + "/public", { refresh: true }));
app.use('/backgrounds', (req, res) => { app.use('/backgrounds', (req, res) => {
const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' '))); const filePath = decodeURIComponent(path.join(__dirname, 'public/backgrounds', req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => { fs.readFile(filePath, (err, data) => {
if (err) { if (err) {
res.status(404).send('File not found'); res.status(404).send('File not found');
@ -242,7 +248,7 @@ app.use('/backgrounds', (req, res) => {
}); });
app.use('/characters', (req, res) => { app.use('/characters', (req, res) => {
const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' '))); const filePath = decodeURIComponent(path.join(__dirname, charactersPath, req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => { fs.readFile(filePath, (err, data) => {
if (err) { if (err) {
res.status(404).send('File not found'); res.status(404).send('File not found');
@ -387,7 +393,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
if (!!request.header('X-Response-Streaming')) { if (!!request.header('X-Response-Streaming')) {
const fn_index = Number(request.header('X-Gradio-Streaming-Function')); const fn_index = Number(request.header('X-Gradio-Streaming-Function'));
let isStreamingStopped = false; let isStreamingStopped = false;
request.socket.on('close', function() { request.socket.on('close', function () {
isStreamingStopped = true; isStreamingStopped = true;
}); });
@ -1628,7 +1634,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors)); response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
} }
response.send({res: true}); response.send({ res: true });
} else { } else {
response.send({ error: true }); response.send({ error: true });
} }
@ -1859,7 +1865,7 @@ app.post('/deletegroup', jsonParser, async (request, response) => {
const POE_DEFAULT_BOT = 'a2'; const POE_DEFAULT_BOT = 'a2';
async function getPoeClient(token, useCache=false) { async function getPoeClient(token, useCache = false) {
let client = new poe.Client(false, useCache); let client = new poe.Client(false, useCache);
await client.init(token); await client.init(token);
return client; return client;
@ -1925,7 +1931,7 @@ app.post('/generate_poe', jsonParser, async (request, response) => {
if (streaming) { if (streaming) {
let isStreamingStopped = false; let isStreamingStopped = false;
request.socket.on('close', function() { request.socket.on('close', function () {
isStreamingStopped = true; isStreamingStopped = true;
client.abortController.abort(); client.abortController.abort();
}); });
@ -2171,12 +2177,42 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
}); });
}); });
// Shamelessly stolen from Agnai
app.post("/openai_usage", jsonParser, async function (_, response) {
if (!request.body) return response.sendStatus(400);
const key = request.body.key;
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${key}`,
};
const date = new Date();
date.setDate(1);
const start_date = date.toISOString().slice(0, 10);
date.setMonth(date.getMonth() + 1);
const end_date = date.toISOString().slice(0, 10);
try {
const res = await getAsync(
`${api_url}/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`,
{ headers },
);
return response.send(res);
}
catch {
return response.sendStatus(400);
}
});
app.post("/generate_openai", jsonParser, function (request, response_generate_openai) { app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
if (!request.body) return response_generate_openai.sendStatus(400); if (!request.body) return response_generate_openai.sendStatus(400);
const api_url = new URL(request.body.reverse_proxy || api_openai).toString(); const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
const controller = new AbortController(); const controller = new AbortController();
request.socket.on('close', function() { request.socket.on('close', function () {
controller.abort(); controller.abort();
}); });
@ -2352,8 +2388,10 @@ const setupTasks = async function () {
if (!is_colab) await convertWebp(); if (!is_colab) await convertWebp();
console.log('Launching...'); console.log('Launching...');
if (autorun) open(tavernUrl); if (autorun) open(tavernUrl);
console.log('TavernAI started: ' + tavernUrl); console.log('SillyTavern started: ' + tavernUrl);
if (fs.existsSync('public/characters/update.txt') && !is_colab) { if (fs.existsSync('public/characters/update.txt') && !is_colab) {
convertStage1(); convertStage1();
} }