2023-07-20 19:32:15 +02:00
#!/usr/bin/env node
2023-08-29 23:05:18 +02:00
// native node modules
2023-08-29 23:20:37 +02:00
const crypto = require ( 'crypto' ) ;
2023-08-29 23:06:37 +02:00
const fs = require ( 'fs' ) ;
2023-12-02 19:04:51 +01:00
const http = require ( 'http' ) ;
2023-08-29 23:20:37 +02:00
const https = require ( 'https' ) ;
2023-08-29 23:06:37 +02:00
const path = require ( 'path' ) ;
2023-08-29 23:34:41 +02:00
const util = require ( 'util' ) ;
2023-08-29 23:05:18 +02:00
2023-08-29 23:16:39 +02:00
// cli/fs related library imports
2023-08-29 23:26:59 +02:00
const open = require ( 'open' ) ;
2023-08-29 23:23:53 +02:00
const sanitize = require ( 'sanitize-filename' ) ;
2023-08-29 23:16:39 +02:00
const writeFileAtomicSync = require ( 'write-file-atomic' ) . sync ;
2023-08-29 23:23:53 +02:00
const yargs = require ( 'yargs/yargs' ) ;
const { hideBin } = require ( 'yargs/helpers' ) ;
2023-08-29 23:16:39 +02:00
2023-08-29 23:34:41 +02:00
// express/server related library imports
const cors = require ( 'cors' ) ;
const doubleCsrf = require ( 'csrf-csrf' ) . doubleCsrf ;
2023-08-29 23:10:40 +02:00
const express = require ( 'express' ) ;
const compression = require ( 'compression' ) ;
2023-08-29 23:23:53 +02:00
const cookieParser = require ( 'cookie-parser' ) ;
2023-12-02 19:04:51 +01:00
const multer = require ( 'multer' ) ;
2023-08-29 23:23:53 +02:00
const responseTime = require ( 'response-time' ) ;
2023-08-29 23:26:59 +02:00
// net related library imports
2023-12-02 19:04:51 +01:00
const net = require ( 'net' ) ;
2023-09-10 18:02:58 +02:00
const dns = require ( 'dns' ) ;
2023-12-02 19:04:51 +01:00
const DeviceDetector = require ( 'device-detector-js' ) ;
2023-08-29 23:34:41 +02:00
const fetch = require ( 'node-fetch' ) . default ;
2023-08-29 23:26:59 +02:00
const ipaddr = require ( 'ipaddr.js' ) ;
const ipMatching = require ( 'ip-matching' ) ;
2023-08-29 23:10:40 +02:00
2023-08-29 23:12:47 +02:00
// image processing related library imports
const jimp = require ( 'jimp' ) ;
2023-08-29 23:26:59 +02:00
2023-09-10 03:12:14 +02:00
// Unrestrict console logs display limit
util . inspect . defaultOptions . maxArrayLength = null ;
util . inspect . defaultOptions . maxStringLength = null ;
2023-08-29 23:26:59 +02:00
// local library imports
const basicAuthMiddleware = require ( './src/middleware/basicAuthMiddleware' ) ;
2023-12-03 15:50:20 +01:00
const { jsonParser , urlencodedParser } = require ( './src/express-common.js' ) ;
2023-12-03 14:52:43 +01:00
const contentManager = require ( './src/endpoints/content-manager' ) ;
const { readSecret , migrateSecrets , SECRET _KEYS } = require ( './src/endpoints/secrets' ) ;
2023-12-08 00:06:17 +01:00
const {
getVersion ,
getConfigValue ,
color ,
uuidv4 ,
tryParse ,
clientRelativePath ,
removeFileExtension ,
generateTimestamp ,
removeOldBackups ,
getImages ,
forwardFetchResponse ,
} = require ( './src/util' ) ;
2023-12-07 21:21:19 +01:00
const { ensureThumbnailCache } = require ( './src/endpoints/thumbnails' ) ;
2023-12-03 14:52:43 +01:00
const { getTokenizerModel , getTiktokenTokenizer , loadTokenizers , TEXT _COMPLETION _MODELS , getSentencepiceTokenizer , sentencepieceTokenizers } = require ( './src/endpoints/tokenizers' ) ;
2023-09-16 17:48:06 +02:00
const { convertClaudePrompt } = require ( './src/chat-completion' ) ;
2023-08-29 23:12:47 +02:00
2023-09-10 17:22:39 +02:00
// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
// Safe to remove once support for Node v20 is dropped.
if ( process . versions && process . versions . node && process . versions . node . match ( /20\.[0-2]\.0/ ) ) {
// @ts-ignore
if ( net . setDefaultAutoSelectFamily ) net . setDefaultAutoSelectFamily ( false ) ;
}
2023-07-20 19:32:15 +02:00
2023-09-10 18:02:58 +02:00
// Set default DNS resolution order to IPv4 first
dns . setDefaultResultOrder ( 'ipv4first' ) ;
2023-07-20 19:32:15 +02:00
const cliArguments = yargs ( hideBin ( process . argv ) )
2023-11-18 01:09:42 +01:00
. option ( 'autorun' , {
type : 'boolean' ,
2023-12-06 23:55:36 +01:00
default : false ,
2023-12-02 21:06:57 +01:00
describe : 'Automatically launch SillyTavern in the browser.' ,
2023-11-25 20:56:57 +01:00
} ) . option ( 'corsProxy' , {
type : 'boolean' ,
default : false ,
describe : 'Enables CORS proxy' ,
} ) . option ( 'disableCsrf' , {
2023-08-06 15:42:15 +02:00
type : 'boolean' ,
default : false ,
2023-12-02 21:06:57 +01:00
describe : 'Disables CSRF protection' ,
2023-08-06 15:42:15 +02:00
} ) . option ( 'ssl' , {
2023-07-20 19:32:15 +02:00
type : 'boolean' ,
default : false ,
2023-12-02 21:06:57 +01:00
describe : 'Enables SSL' ,
2023-07-20 19:32:15 +02:00
} ) . option ( 'certPath' , {
type : 'string' ,
default : 'certs/cert.pem' ,
2023-12-02 21:06:57 +01:00
describe : 'Path to your certificate file.' ,
2023-07-20 19:32:15 +02:00
} ) . option ( 'keyPath' , {
type : 'string' ,
default : 'certs/privkey.pem' ,
2023-12-02 21:06:57 +01:00
describe : 'Path to your private key file.' ,
2023-08-30 20:34:45 +02:00
} ) . parseSync ( ) ;
2023-07-20 19:32:15 +02:00
// change all relative paths
2023-08-30 17:41:38 +02:00
const directory = process [ 'pkg' ] ? path . dirname ( process . execPath ) : _ _dirname ;
console . log ( process [ 'pkg' ] ? 'Running from binary' : 'Running from source' ) ;
2023-07-20 19:32:15 +02:00
process . chdir ( directory ) ;
const app = express ( ) ;
app . use ( compression ( ) ) ;
app . use ( responseTime ( ) ) ;
2023-11-26 12:13:44 +01:00
const server _port = process . env . SILLY _TAVERN _PORT || getConfigValue ( 'port' , 8000 ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
const whitelistPath = path . join ( process . cwd ( ) , './whitelist.txt' ) ;
2023-11-25 22:45:33 +01:00
let whitelist = getConfigValue ( 'whitelist' , [ ] ) ;
2023-07-20 19:32:15 +02:00
if ( fs . existsSync ( whitelistPath ) ) {
try {
let whitelistTxt = fs . readFileSync ( whitelistPath , 'utf-8' ) ;
2023-12-02 19:04:51 +01:00
whitelist = whitelistTxt . split ( '\n' ) . filter ( ip => ip ) . map ( ip => ip . trim ( ) ) ;
2023-12-02 15:14:48 +01:00
} catch ( e ) {
// Ignore errors that may occur when reading the whitelist (e.g. permissions)
}
2023-07-20 19:32:15 +02:00
}
2023-11-25 22:45:33 +01:00
const whitelistMode = getConfigValue ( 'whitelistMode' , true ) ;
2023-12-06 23:55:36 +01:00
const autorun = ( getConfigValue ( 'autorun' , false ) || cliArguments . autorun ) && ! cliArguments . ssl ;
2023-11-25 22:45:33 +01:00
const enableExtensions = getConfigValue ( 'enableExtensions' , true ) ;
const listen = getConfigValue ( 'listen' , false ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
const API _OPENAI = 'https://api.openai.com/v1' ;
const API _CLAUDE = 'https://api.anthropic.com/v1' ;
2023-08-31 18:44:58 +02:00
2023-09-08 12:57:27 +02:00
const SETTINGS _FILE = './public/settings.json' ;
2023-12-12 05:19:53 +01:00
const { DIRECTORIES , UPLOADS _PATH , PALM _SAFETY , CHAT _COMPLETION _SOURCES , AVATAR _WIDTH , AVATAR _HEIGHT } = require ( './src/constants' ) ;
2023-07-20 19:32:15 +02:00
// CORS Settings //
const CORS = cors ( {
origin : 'null' ,
2023-12-02 21:06:57 +01:00
methods : [ 'OPTIONS' ] ,
2023-07-20 19:32:15 +02:00
} ) ;
app . use ( CORS ) ;
2023-11-25 22:45:33 +01:00
if ( listen && getConfigValue ( 'basicAuthMode' , false ) ) app . use ( basicAuthMiddleware ) ;
2023-07-20 19:32:15 +02:00
2023-08-26 13:17:57 +02:00
// IP Whitelist //
let knownIPs = new Set ( ) ;
function getIpFromRequest ( req ) {
2023-07-20 19:32:15 +02:00
let clientIp = req . connection . remoteAddress ;
let ip = ipaddr . parse ( clientIp ) ;
// Check if the IP address is IPv4-mapped IPv6 address
2023-08-30 18:14:46 +02:00
if ( ip . kind ( ) === 'ipv6' && ip instanceof ipaddr . IPv6 && ip . isIPv4MappedAddress ( ) ) {
2023-07-20 19:32:15 +02:00
const ipv4 = ip . toIPv4Address ( ) . toString ( ) ;
clientIp = ipv4 ;
} else {
clientIp = ip ;
clientIp = clientIp . toString ( ) ;
}
2023-08-26 13:17:57 +02:00
return clientIp ;
}
app . use ( function ( req , res , next ) {
const clientIp = getIpFromRequest ( req ) ;
if ( listen && ! knownIPs . has ( clientIp ) ) {
const userAgent = req . headers [ 'user-agent' ] ;
2023-08-26 15:05:42 +02:00
console . log ( color . yellow ( ` New connection from ${ clientIp } ; User Agent: ${ userAgent } \n ` ) ) ;
2023-08-26 13:17:57 +02:00
knownIPs . add ( clientIp ) ;
// Write access log
const timestamp = new Date ( ) . toISOString ( ) ;
const log = ` ${ timestamp } ${ clientIp } ${ userAgent } \n ` ;
fs . appendFile ( 'access.log' , log , ( err ) => {
if ( err ) {
console . error ( 'Failed to write access log:' , err ) ;
}
} ) ;
}
2023-07-20 19:32:15 +02:00
//clientIp = req.connection.remoteAddress.split(':').pop();
if ( whitelistMode === true && ! whitelist . some ( x => ipMatching . matches ( clientIp , ipMatching . getMatch ( x ) ) ) ) {
2023-11-25 22:45:33 +01:00
console . log ( color . red ( 'Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml 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.yaml in root of SillyTavern folder.' ) ;
2023-07-20 19:32:15 +02:00
}
next ( ) ;
} ) ;
2023-12-09 18:56:26 +01:00
// CSRF Protection //
if ( ! cliArguments . disableCsrf ) {
const CSRF _SECRET = crypto . randomBytes ( 8 ) . toString ( 'hex' ) ;
const COOKIES _SECRET = crypto . randomBytes ( 8 ) . toString ( 'hex' ) ;
const { generateToken , doubleCsrfProtection } = doubleCsrf ( {
getSecret : ( ) => CSRF _SECRET ,
cookieName : 'X-CSRF-Token' ,
cookieOptions : {
httpOnly : true ,
sameSite : 'strict' ,
secure : false ,
} ,
size : 64 ,
getTokenFromRequest : ( req ) => req . headers [ 'x-csrf-token' ] ,
} ) ;
app . get ( '/csrf-token' , ( req , res ) => {
res . json ( {
'token' : generateToken ( res , req ) ,
} ) ;
} ) ;
app . use ( cookieParser ( COOKIES _SECRET ) ) ;
app . use ( doubleCsrfProtection ) ;
} else {
console . warn ( '\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n' ) ;
app . get ( '/csrf-token' , ( req , res ) => {
res . json ( {
'token' : 'disabled' ,
} ) ;
} ) ;
}
2023-12-06 23:57:23 +01:00
if ( getConfigValue ( 'enableCorsProxy' , false ) || cliArguments . corsProxy ) {
2023-12-02 14:32:09 +01:00
const bodyParser = require ( 'body-parser' ) ;
app . use ( bodyParser . json ( ) ) ;
2023-11-25 20:56:57 +01:00
console . log ( 'Enabling CORS proxy' ) ;
2023-11-27 06:17:07 +01:00
app . use ( '/proxy/:url(*)' , async ( req , res ) => {
2023-11-25 20:56:57 +01:00
const url = req . params . url ; // get the url from the request path
// Disallow circular requests
const serverUrl = req . protocol + '://' + req . get ( 'host' ) ;
if ( url . startsWith ( serverUrl ) ) {
return res . status ( 400 ) . send ( 'Circular requests are not allowed' ) ;
}
try {
const headers = JSON . parse ( JSON . stringify ( req . headers ) ) ;
delete headers [ 'x-csrf-token' ] ;
delete headers [ 'host' ] ;
delete headers [ 'referer' ] ;
delete headers [ 'origin' ] ;
delete headers [ 'cookie' ] ;
delete headers [ 'sec-fetch-mode' ] ;
delete headers [ 'sec-fetch-site' ] ;
delete headers [ 'sec-fetch-dest' ] ;
const bodyMethods = [ 'POST' , 'PUT' , 'PATCH' ] ;
const response = await fetch ( url , {
method : req . method ,
headers : headers ,
body : bodyMethods . includes ( req . method ) ? JSON . stringify ( req . body ) : undefined ,
} ) ;
2023-12-02 07:41:00 +01:00
// Copy over relevant response params to the proxy response
2023-12-08 00:06:17 +01:00
forwardFetchResponse ( response , res ) ;
2023-11-25 20:56:57 +01:00
} catch ( error ) {
res . status ( 500 ) . send ( 'Error occurred while trying to proxy to: ' + url + ' ' + error ) ;
}
} ) ;
2023-12-02 15:04:30 +01:00
} else {
app . use ( '/proxy/:url(*)' , async ( _ , res ) => {
const message = 'CORS proxy is disabled. Enable it in config.yaml or use the --corsProxy flag.' ;
console . log ( message ) ;
res . status ( 404 ) . send ( message ) ;
} ) ;
2023-11-25 20:56:57 +01:00
}
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
app . use ( express . static ( process . cwd ( ) + '/public' , { } ) ) ;
2023-07-20 19:32:15 +02:00
app . use ( '/backgrounds' , ( req , res ) => {
const filePath = decodeURIComponent ( path . join ( process . cwd ( ) , 'public/backgrounds' , req . url . replace ( /%20/g , ' ' ) ) ) ;
fs . readFile ( filePath , ( err , data ) => {
if ( err ) {
res . status ( 404 ) . send ( 'File not found' ) ;
return ;
}
//res.contentType('image/jpeg');
res . send ( data ) ;
} ) ;
} ) ;
app . use ( '/characters' , ( req , res ) => {
2023-12-04 13:39:38 +01:00
const filePath = decodeURIComponent ( path . join ( process . cwd ( ) , DIRECTORIES . characters , req . url . replace ( /%20/g , ' ' ) ) ) ;
2023-07-20 19:32:15 +02:00
fs . readFile ( filePath , ( err , data ) => {
if ( err ) {
res . status ( 404 ) . send ( 'File not found' ) ;
return ;
}
res . send ( data ) ;
} ) ;
} ) ;
2023-12-02 19:04:51 +01:00
app . use ( multer ( { dest : UPLOADS _PATH , limits : { fieldSize : 10 * 1024 * 1024 } } ) . single ( 'avatar' ) ) ;
app . get ( '/' , function ( request , response ) {
response . sendFile ( process . cwd ( ) + '/public/index.html' ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-12-02 19:04:51 +01:00
app . get ( '/notes/*' , function ( request , response ) {
response . sendFile ( process . cwd ( ) + '/public' + request . url + '.html' ) ;
2023-07-20 19:32:15 +02:00
} ) ;
app . get ( '/deviceinfo' , function ( request , response ) {
const userAgent = request . header ( 'user-agent' ) ;
const deviceDetector = new DeviceDetector ( ) ;
2023-12-02 19:04:51 +01:00
const deviceInfo = deviceDetector . parse ( userAgent || '' ) ;
2023-07-20 19:32:15 +02:00
return response . send ( deviceInfo ) ;
} ) ;
2023-09-17 13:27:41 +02:00
app . get ( '/version' , async function ( _ , response ) {
const data = await getVersion ( ) ;
2023-07-20 19:32:15 +02:00
response . send ( data ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
app . post ( '/getuseravatars' , jsonParser , function ( request , response ) {
var images = getImages ( 'public/User Avatars' ) ;
2023-07-20 19:32:15 +02:00
response . send ( JSON . stringify ( images ) ) ;
} ) ;
app . post ( '/deleteuseravatar' , jsonParser , function ( request , response ) {
if ( ! request . body ) return response . sendStatus ( 400 ) ;
if ( request . body . avatar !== sanitize ( request . body . avatar ) ) {
console . error ( 'Malicious avatar name prevented' ) ;
return response . sendStatus ( 403 ) ;
}
2023-09-16 16:28:28 +02:00
const fileName = path . join ( DIRECTORIES . avatars , sanitize ( request . body . avatar ) ) ;
2023-07-20 19:32:15 +02:00
if ( fs . existsSync ( fileName ) ) {
fs . rmSync ( fileName ) ;
return response . send ( { result : 'ok' } ) ;
}
return response . sendStatus ( 404 ) ;
} ) ;
2023-12-02 19:04:51 +01:00
app . post ( '/savesettings' , jsonParser , function ( request , response ) {
2023-08-18 11:11:18 +02:00
try {
writeFileAtomicSync ( 'public/settings.json' , JSON . stringify ( request . body , null , 4 ) , 'utf8' ) ;
2023-12-02 19:04:51 +01:00
response . send ( { result : 'ok' } ) ;
2023-08-18 11:11:18 +02:00
} catch ( err ) {
console . log ( err ) ;
response . send ( err ) ;
}
2023-07-20 19:32:15 +02:00
} ) ;
function readAndParseFromDirectory ( directoryPath , fileExtension = '.json' ) {
const files = fs
. readdirSync ( directoryPath )
. filter ( x => path . parse ( x ) . ext == fileExtension )
. sort ( ) ;
const parsedFiles = [ ] ;
files . forEach ( item => {
try {
const file = fs . readFileSync ( path . join ( directoryPath , item ) , 'utf-8' ) ;
2023-12-03 13:04:43 +01:00
parsedFiles . push ( fileExtension == '.json' ? JSON . parse ( file ) : file ) ;
2023-07-20 19:32:15 +02:00
}
catch {
// skip
}
} ) ;
return parsedFiles ;
}
function sortByName ( _ ) {
return ( a , b ) => a . localeCompare ( b ) ;
}
function readPresetsFromDirectory ( directoryPath , options = { } ) {
const {
sortFunction ,
2023-12-02 21:06:57 +01:00
removeFileExtension = false ,
2023-12-03 14:07:21 +01:00
fileExtension = '.json' ,
2023-07-20 19:32:15 +02:00
} = options ;
2023-12-03 14:07:21 +01:00
const files = fs . readdirSync ( directoryPath ) . sort ( sortFunction ) . filter ( x => path . parse ( x ) . ext == fileExtension ) ;
2023-07-20 19:32:15 +02:00
const fileContents = [ ] ;
const fileNames = [ ] ;
files . forEach ( item => {
try {
const file = fs . readFileSync ( path . join ( directoryPath , item ) , 'utf8' ) ;
2023-12-03 13:04:43 +01:00
JSON . parse ( file ) ;
2023-07-20 19:32:15 +02:00
fileContents . push ( file ) ;
fileNames . push ( removeFileExtension ? item . replace ( /\.[^/.]+$/ , '' ) : item ) ;
} catch {
// skip
console . log ( ` ${ item } is not a valid JSON ` ) ;
}
} ) ;
return { fileContents , fileNames } ;
}
// Wintermute's code
app . post ( '/getsettings' , jsonParser , ( request , response ) => {
2023-12-02 20:11:06 +01:00
let settings ;
2023-08-31 18:44:58 +02:00
try {
settings = fs . readFileSync ( 'public/settings.json' , 'utf8' ) ;
} catch ( e ) {
return response . sendStatus ( 500 ) ;
}
2023-07-20 19:32:15 +02:00
// NovelAI Settings
const { fileContents : novelai _settings , fileNames : novelai _setting _names }
2023-09-16 16:28:28 +02:00
= readPresetsFromDirectory ( DIRECTORIES . novelAI _Settings , {
sortFunction : sortByName ( DIRECTORIES . novelAI _Settings ) ,
2023-12-02 21:06:57 +01:00
removeFileExtension : true ,
2023-07-20 19:32:15 +02:00
} ) ;
// OpenAI Settings
const { fileContents : openai _settings , fileNames : openai _setting _names }
2023-09-16 16:28:28 +02:00
= readPresetsFromDirectory ( DIRECTORIES . openAI _Settings , {
2023-12-02 21:06:57 +01:00
sortFunction : sortByName ( DIRECTORIES . openAI _Settings ) , removeFileExtension : true ,
2023-07-20 19:32:15 +02:00
} ) ;
// TextGenerationWebUI Settings
const { fileContents : textgenerationwebui _presets , fileNames : textgenerationwebui _preset _names }
2023-09-16 16:28:28 +02:00
= readPresetsFromDirectory ( DIRECTORIES . textGen _Settings , {
2023-12-02 21:06:57 +01:00
sortFunction : sortByName ( DIRECTORIES . textGen _Settings ) , removeFileExtension : true ,
2023-07-20 19:32:15 +02:00
} ) ;
//Kobold
const { fileContents : koboldai _settings , fileNames : koboldai _setting _names }
2023-09-16 16:28:28 +02:00
= readPresetsFromDirectory ( DIRECTORIES . koboldAI _Settings , {
2023-12-02 21:06:57 +01:00
sortFunction : sortByName ( DIRECTORIES . koboldAI _Settings ) , removeFileExtension : true ,
2023-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
const worldFiles = fs
2023-09-16 16:28:28 +02:00
. readdirSync ( DIRECTORIES . worlds )
2023-07-20 19:32:15 +02:00
. filter ( file => path . extname ( file ) . toLowerCase ( ) === '.json' )
2023-08-31 18:44:58 +02:00
. sort ( ( a , b ) => a . localeCompare ( b ) ) ;
2023-07-20 19:32:15 +02:00
const world _names = worldFiles . map ( item => path . parse ( item ) . name ) ;
2023-09-16 16:28:28 +02:00
const themes = readAndParseFromDirectory ( DIRECTORIES . themes ) ;
const movingUIPresets = readAndParseFromDirectory ( DIRECTORIES . movingUI ) ;
const quickReplyPresets = readAndParseFromDirectory ( DIRECTORIES . quickreplies ) ;
2023-07-29 23:22:03 +02:00
2023-09-16 16:28:28 +02:00
const instruct = readAndParseFromDirectory ( DIRECTORIES . instruct ) ;
const context = readAndParseFromDirectory ( DIRECTORIES . context ) ;
2023-07-20 19:32:15 +02:00
response . send ( {
settings ,
koboldai _settings ,
koboldai _setting _names ,
world _names ,
novelai _settings ,
novelai _setting _names ,
openai _settings ,
openai _setting _names ,
textgenerationwebui _presets ,
textgenerationwebui _preset _names ,
themes ,
movingUIPresets ,
2023-07-29 23:22:03 +02:00
quickReplyPresets ,
2023-07-20 19:32:15 +02:00
instruct ,
context ,
enable _extensions : enableExtensions ,
} ) ;
} ) ;
app . post ( '/savetheme' , jsonParser , ( request , response ) => {
if ( ! request . body || ! request . body . name ) {
return response . sendStatus ( 400 ) ;
}
2023-09-16 16:28:28 +02:00
const filename = path . join ( DIRECTORIES . themes , sanitize ( request . body . name ) + '.json' ) ;
2023-08-17 14:20:02 +02:00
writeFileAtomicSync ( filename , JSON . stringify ( request . body , null , 4 ) , 'utf8' ) ;
2023-07-20 19:32:15 +02:00
return response . sendStatus ( 200 ) ;
} ) ;
app . post ( '/savemovingui' , jsonParser , ( request , response ) => {
if ( ! request . body || ! request . body . name ) {
return response . sendStatus ( 400 ) ;
}
2023-09-16 16:28:28 +02:00
const filename = path . join ( DIRECTORIES . movingUI , sanitize ( request . body . name ) + '.json' ) ;
2023-08-17 14:20:02 +02:00
writeFileAtomicSync ( filename , JSON . stringify ( request . body , null , 4 ) , 'utf8' ) ;
2023-07-20 19:32:15 +02:00
return response . sendStatus ( 200 ) ;
} ) ;
2023-07-29 23:22:03 +02:00
app . post ( '/savequickreply' , jsonParser , ( request , response ) => {
if ( ! request . body || ! request . body . name ) {
return response . sendStatus ( 400 ) ;
}
2023-09-16 16:28:28 +02:00
const filename = path . join ( DIRECTORIES . quickreplies , sanitize ( request . body . name ) + '.json' ) ;
2023-08-17 14:20:02 +02:00
writeFileAtomicSync ( filename , JSON . stringify ( request . body , null , 4 ) , 'utf8' ) ;
2023-07-29 23:22:03 +02:00
return response . sendStatus ( 200 ) ;
} ) ;
2023-07-20 19:32:15 +02:00
app . post ( '/uploaduseravatar' , urlencodedParser , async ( request , response ) => {
if ( ! request . file ) return response . sendStatus ( 400 ) ;
try {
2023-08-19 16:43:56 +02:00
const pathToUpload = path . join ( UPLOADS _PATH , request . file . filename ) ;
2023-07-20 19:32:15 +02:00
const crop = tryParse ( request . query . crop ) ;
let rawImg = await jimp . read ( pathToUpload ) ;
if ( typeof crop == 'object' && [ crop . x , crop . y , crop . width , crop . height ] . every ( x => typeof x === 'number' ) ) {
rawImg = rawImg . crop ( crop . x , crop . y , crop . width , crop . height ) ;
}
const image = await rawImg . cover ( AVATAR _WIDTH , AVATAR _HEIGHT ) . getBufferAsync ( jimp . MIME _PNG ) ;
const filename = request . body . overwrite _name || ` ${ Date . now ( ) } .png ` ;
2023-09-16 16:28:28 +02:00
const pathToNewFile = path . join ( DIRECTORIES . avatars , filename ) ;
2023-08-17 14:20:02 +02:00
writeFileAtomicSync ( pathToNewFile , image ) ;
2023-07-20 19:32:15 +02:00
fs . rmSync ( pathToUpload ) ;
return response . send ( { path : filename } ) ;
} catch ( err ) {
return response . status ( 400 ) . send ( 'Is not a valid image' ) ;
}
} ) ;
2023-08-20 05:01:09 +02:00
2023-08-20 06:15:57 +02:00
/ * *
* Ensure the directory for the provided file path exists .
* If not , it will recursively create the directory .
2023-08-20 11:37:38 +02:00
*
2023-08-20 06:15:57 +02:00
* @ param { string } filePath - The full path of the file for which the directory should be ensured .
* /
2023-08-20 05:01:09 +02:00
function ensureDirectoryExistence ( filePath ) {
const dirname = path . dirname ( filePath ) ;
if ( fs . existsSync ( dirname ) ) {
return true ;
}
ensureDirectoryExistence ( dirname ) ;
fs . mkdirSync ( dirname ) ;
}
2023-08-20 06:15:57 +02:00
/ * *
* Endpoint to handle image uploads .
* The image should be provided in the request body in base64 format .
* Optionally , a character name can be provided to save the image in a sub - folder .
2023-08-20 11:37:38 +02:00
*
2023-08-20 06:15:57 +02:00
* @ route POST / uploadimage
* @ param { Object } request . body - The request payload .
* @ param { string } request . body . image - The base64 encoded image data .
* @ param { string } [ request . body . ch _name ] - Optional character name to determine the sub - directory .
* @ returns { Object } response - The response object containing the path where the image was saved .
* /
2023-08-20 05:01:09 +02:00
app . post ( '/uploadimage' , jsonParser , async ( request , response ) => {
// Check for image data
if ( ! request . body || ! request . body . image ) {
2023-12-02 19:04:51 +01:00
return response . status ( 400 ) . send ( { error : 'No image data provided' } ) ;
2023-08-20 05:01:09 +02:00
}
2023-11-18 23:40:21 +01:00
try {
// Extracting the base64 data and the image format
const splitParts = request . body . image . split ( ',' ) ;
const format = splitParts [ 0 ] . split ( ';' ) [ 0 ] . split ( '/' ) [ 1 ] ;
const base64Data = splitParts [ 1 ] ;
const validFormat = [ 'png' , 'jpg' , 'webp' , 'jpeg' , 'gif' ] . includes ( format ) ;
if ( ! validFormat ) {
2023-12-02 19:04:51 +01:00
return response . status ( 400 ) . send ( { error : 'Invalid image format' } ) ;
2023-11-18 23:40:21 +01:00
}
2023-08-20 05:01:09 +02:00
2023-11-18 23:40:21 +01:00
// Constructing filename and path
2023-12-06 23:29:10 +01:00
let filename ;
2023-11-18 23:40:21 +01:00
if ( request . body . filename ) {
2023-12-06 23:29:10 +01:00
filename = ` ${ removeFileExtension ( request . body . filename ) } . ${ format } ` ;
} else {
filename = ` ${ Date . now ( ) } . ${ format } ` ;
2023-11-18 23:40:21 +01:00
}
2023-08-20 07:41:58 +02:00
2023-11-18 23:40:21 +01:00
// if character is defined, save to a sub folder for that character
let pathToNewFile = path . join ( DIRECTORIES . userImages , filename ) ;
if ( request . body . ch _name ) {
pathToNewFile = path . join ( DIRECTORIES . userImages , request . body . ch _name , filename ) ;
}
2023-08-20 05:01:09 +02:00
ensureDirectoryExistence ( pathToNewFile ) ;
const imageBuffer = Buffer . from ( base64Data , 'base64' ) ;
await fs . promises . writeFile ( pathToNewFile , imageBuffer ) ;
2023-12-06 00:04:32 +01:00
response . send ( { path : clientRelativePath ( pathToNewFile ) } ) ;
2023-08-20 05:01:09 +02:00
} catch ( error ) {
console . log ( error ) ;
2023-12-02 19:04:51 +01:00
response . status ( 500 ) . send ( { error : 'Failed to save the image' } ) ;
2023-08-20 05:01:09 +02:00
}
} ) ;
2023-08-30 23:55:17 +02:00
app . post ( '/listimgfiles/:folder' , ( req , res ) => {
const directoryPath = path . join ( process . cwd ( ) , 'public/user/images/' , sanitize ( req . params . folder ) ) ;
2023-08-21 22:06:27 +02:00
if ( ! fs . existsSync ( directoryPath ) ) {
fs . mkdirSync ( directoryPath , { recursive : true } ) ;
}
2023-08-30 23:55:17 +02:00
try {
const images = getImages ( directoryPath ) ;
return res . send ( images ) ;
} catch ( error ) {
console . error ( error ) ;
2023-12-02 19:04:51 +01:00
return res . status ( 500 ) . send ( { error : 'Unable to retrieve files' } ) ;
2023-08-30 23:55:17 +02:00
}
2023-08-21 06:43:04 +02:00
} ) ;
2023-08-20 05:01:09 +02:00
2023-08-19 16:43:56 +02:00
function cleanUploads ( ) {
try {
if ( fs . existsSync ( UPLOADS _PATH ) ) {
const uploads = fs . readdirSync ( UPLOADS _PATH ) ;
if ( ! uploads . length ) {
return ;
}
console . debug ( ` Cleaning uploads folder ( ${ uploads . length } files) ` ) ;
uploads . forEach ( file => {
const pathToFile = path . join ( UPLOADS _PATH , file ) ;
fs . unlinkSync ( pathToFile ) ;
} ) ;
}
} catch ( err ) {
console . error ( err ) ;
}
}
2023-07-20 19:32:15 +02:00
/* OpenAI */
2023-12-02 19:04:51 +01:00
app . post ( '/getstatus_openai' , jsonParser , async function ( request , response _getstatus _openai ) {
2023-07-20 19:32:15 +02:00
if ( ! request . body ) return response _getstatus _openai . sendStatus ( 400 ) ;
let api _url ;
let api _key _openai ;
let headers ;
2023-12-03 21:03:32 +01:00
if ( request . body . chat _completion _source !== CHAT _COMPLETION _SOURCES . OPENROUTER ) {
2023-08-31 18:44:58 +02:00
api _url = new URL ( request . body . reverse _proxy || API _OPENAI ) . toString ( ) ;
2023-07-28 20:33:29 +02:00
api _key _openai = request . body . reverse _proxy ? request . body . proxy _password : readSecret ( SECRET _KEYS . OPENAI ) ;
2023-07-20 19:32:15 +02:00
headers = { } ;
} else {
api _url = 'https://openrouter.ai/api/v1' ;
api _key _openai = readSecret ( SECRET _KEYS . OPENROUTER ) ;
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer' : request . headers . referer } ;
}
2023-07-28 20:33:29 +02:00
if ( ! api _key _openai && ! request . body . reverse _proxy ) {
2023-12-09 14:11:35 +01:00
console . log ( 'OpenAI API key is missing.' ) ;
return response _getstatus _openai . status ( 400 ) . send ( { error : true } ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-31 21:46:13 +02:00
try {
2023-12-02 19:04:51 +01:00
const response = await fetch ( api _url + '/models' , {
2023-08-31 21:46:13 +02:00
method : 'GET' ,
headers : {
2023-12-02 19:04:51 +01:00
'Authorization' : 'Bearer ' + api _key _openai ,
2023-08-31 21:46:13 +02:00
... headers ,
} ,
} ) ;
if ( response . ok ) {
const data = await response . json ( ) ;
2023-07-20 19:32:15 +02:00
response _getstatus _openai . send ( data ) ;
2023-08-31 21:46:13 +02:00
2023-12-03 21:03:32 +01:00
if ( request . body . chat _completion _source === CHAT _COMPLETION _SOURCES . OPENROUTER && Array . isArray ( data ? . data ) ) {
2023-08-10 06:08:26 +02:00
let models = [ ] ;
2023-08-31 21:46:13 +02:00
2023-08-10 19:06:18 +02:00
data . data . forEach ( model => {
const context _length = model . context _length ;
2023-11-12 12:07:57 +01:00
const tokens _dollar = Number ( 1 / ( 1000 * model . pricing ? . prompt ) ) ;
2023-08-10 21:13:24 +02:00
const tokens _rounded = ( Math . round ( tokens _dollar * 1000 ) / 1000 ) . toFixed ( 0 ) ;
2023-08-10 19:06:18 +02:00
models [ model . id ] = {
2023-08-10 21:13:24 +02:00
tokens _per _dollar : tokens _rounded + 'k' ,
2023-08-10 19:06:18 +02:00
context _length : context _length ,
} ;
} ) ;
2023-08-31 21:46:13 +02:00
2023-08-10 06:08:26 +02:00
console . log ( 'Available OpenRouter models:' , models ) ;
} else {
2023-10-16 19:25:51 +02:00
const models = data ? . data ;
if ( Array . isArray ( models ) ) {
const modelIds = models . filter ( x => x && typeof x === 'object' ) . map ( x => x . id ) . sort ( ) ;
console . log ( 'Available OpenAI models:' , modelIds ) ;
} else {
2023-12-02 20:11:06 +01:00
console . log ( 'OpenAI endpoint did not return a list of models.' ) ;
2023-10-16 19:25:51 +02:00
}
2023-08-10 06:08:26 +02:00
}
2023-07-20 19:32:15 +02:00
}
2023-08-31 21:46:13 +02:00
else {
2023-11-12 12:23:46 +01:00
console . log ( 'OpenAI status check failed. Either Access Token is incorrect or API endpoint is down.' ) ;
response _getstatus _openai . send ( { error : true , can _bypass : true , data : { data : [ ] } } ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-31 21:46:13 +02:00
} catch ( e ) {
console . error ( e ) ;
2023-10-16 19:25:51 +02:00
if ( ! response _getstatus _openai . headersSent ) {
response _getstatus _openai . send ( { error : true } ) ;
} else {
response _getstatus _openai . end ( ) ;
}
2023-08-31 21:46:13 +02:00
}
2023-07-20 19:32:15 +02:00
} ) ;
2023-12-02 19:04:51 +01:00
app . post ( '/openai_bias' , jsonParser , async function ( request , response ) {
2023-07-20 19:32:15 +02:00
if ( ! request . body || ! Array . isArray ( request . body ) )
return response . sendStatus ( 400 ) ;
2023-11-09 00:03:54 +01:00
try {
const result = { } ;
const model = getTokenizerModel ( String ( request . query . model || '' ) ) ;
2023-07-20 19:32:15 +02:00
2023-11-09 00:03:54 +01:00
// no bias for claude
if ( model == 'claude' ) {
return response . send ( result ) ;
}
2023-07-20 19:32:15 +02:00
2023-11-09 00:03:54 +01:00
let encodeFunction ;
2023-07-20 19:32:15 +02:00
2023-11-09 00:03:54 +01:00
if ( sentencepieceTokenizers . includes ( model ) ) {
const tokenizer = getSentencepiceTokenizer ( model ) ;
2023-11-15 18:39:55 +01:00
const instance = await tokenizer ? . get ( ) ;
encodeFunction = ( text ) => new Uint32Array ( instance ? . encodeIds ( text ) ) ;
2023-11-09 00:03:54 +01:00
} else {
const tokenizer = getTiktokenTokenizer ( model ) ;
encodeFunction = ( tokenizer . encode . bind ( tokenizer ) ) ;
2023-07-20 19:32:15 +02:00
}
2023-11-09 00:03:54 +01:00
for ( const entry of request . body ) {
if ( ! entry || ! entry . text ) {
continue ;
2023-07-20 19:32:15 +02:00
}
2023-10-19 12:37:08 +02:00
try {
2023-11-09 00:03:54 +01:00
const tokens = getEntryTokens ( entry . text , encodeFunction ) ;
for ( const token of tokens ) {
result [ token ] = entry . value ;
2023-10-19 12:37:08 +02:00
}
} catch {
2023-11-09 00:03:54 +01:00
console . warn ( 'Tokenizer failed to encode:' , entry . text ) ;
2023-10-19 12:37:08 +02:00
}
}
2023-11-09 00:03:54 +01:00
// not needed for cached tokenizers
//tokenizer.free();
return response . send ( result ) ;
/ * *
* Gets tokenids for a given entry
* @ param { string } text Entry text
* @ param { ( string ) => Uint32Array } encode Function to encode text to token ids
* @ returns { Uint32Array } Array of token ids
* /
function getEntryTokens ( text , encode ) {
// Get raw token ids from JSON array
if ( text . trim ( ) . startsWith ( '[' ) && text . trim ( ) . endsWith ( ']' ) ) {
try {
const json = JSON . parse ( text ) ;
if ( Array . isArray ( json ) && json . every ( x => typeof x === 'number' ) ) {
return new Uint32Array ( json ) ;
}
} catch {
// ignore
}
}
// Otherwise, get token ids from tokenizer
return encode ( text ) ;
}
} catch ( error ) {
console . error ( error ) ;
return response . send ( { } ) ;
2023-10-19 12:37:08 +02:00
}
2023-07-20 19:32:15 +02:00
} ) ;
function convertChatMLPrompt ( messages ) {
2023-11-02 23:34:22 +01:00
if ( typeof messages === 'string' ) {
return messages ;
}
2023-07-20 19:32:15 +02:00
const messageStrings = [ ] ;
messages . forEach ( m => {
if ( m . role === 'system' && m . name === undefined ) {
2023-12-02 19:04:51 +01:00
messageStrings . push ( 'System: ' + m . content ) ;
2023-07-20 19:32:15 +02:00
}
else if ( m . role === 'system' && m . name !== undefined ) {
2023-12-02 19:04:51 +01:00
messageStrings . push ( m . name + ': ' + m . content ) ;
2023-07-20 19:32:15 +02:00
}
else {
2023-12-02 19:04:51 +01:00
messageStrings . push ( m . role + ': ' + m . content ) ;
2023-07-20 19:32:15 +02:00
}
} ) ;
2023-12-02 19:04:51 +01:00
return messageStrings . join ( '\n' ) + '\nassistant:' ;
2023-07-20 19:32:15 +02:00
}
async function sendScaleRequest ( request , response ) {
const api _url = new URL ( request . body . api _url _scale ) . toString ( ) ;
const api _key _scale = readSecret ( SECRET _KEYS . SCALE ) ;
if ( ! api _key _scale ) {
2023-12-09 14:11:35 +01:00
console . log ( 'Scale API key is missing.' ) ;
return response . status ( 400 ) . send ( { error : true } ) ;
2023-07-20 19:32:15 +02:00
}
const requestPrompt = convertChatMLPrompt ( request . body . messages ) ;
console . log ( 'Scale request:' , requestPrompt ) ;
try {
const controller = new AbortController ( ) ;
request . socket . removeAllListeners ( 'close' ) ;
request . socket . on ( 'close' , function ( ) {
controller . abort ( ) ;
} ) ;
const generateResponse = await fetch ( api _url , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-07-20 19:32:15 +02:00
body : JSON . stringify ( { input : { input : requestPrompt } } ) ,
headers : {
'Content-Type' : 'application/json' ,
'Authorization' : ` Basic ${ api _key _scale } ` ,
} ,
timeout : 0 ,
} ) ;
if ( ! generateResponse . ok ) {
console . log ( ` Scale API returned error: ${ generateResponse . status } ${ generateResponse . statusText } ${ await generateResponse . text ( ) } ` ) ;
return response . status ( generateResponse . status ) . send ( { error : true } ) ;
}
const generateResponseJson = await generateResponse . json ( ) ;
console . log ( 'Scale response:' , generateResponseJson ) ;
2023-12-02 21:06:57 +01:00
const reply = { choices : [ { 'message' : { 'content' : generateResponseJson . output } } ] } ;
2023-07-20 19:32:15 +02:00
return response . send ( reply ) ;
} catch ( error ) {
console . log ( error ) ;
if ( ! response . headersSent ) {
return response . status ( 500 ) . send ( { error : true } ) ;
}
}
}
2023-12-02 19:04:51 +01:00
app . post ( '/generate_altscale' , jsonParser , function ( request , response _generate _scale ) {
2023-08-22 16:46:37 +02:00
if ( ! request . body ) return response _generate _scale . sendStatus ( 400 ) ;
2023-08-20 12:55:37 +02:00
fetch ( 'https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
'cookie' : ` _jwt= ${ readSecret ( SECRET _KEYS . SCALE _COOKIE ) } ` ,
} ,
body : JSON . stringify ( {
json : {
variant : {
name : 'New Variant' ,
appId : '' ,
2023-12-02 21:06:57 +01:00
taxonomy : null ,
2023-08-20 12:55:37 +02:00
} ,
prompt : {
id : '' ,
template : '{{input}}\n' ,
exampleVariables : { } ,
variablesSourceDataId : null ,
2023-12-02 21:06:57 +01:00
systemMessage : request . body . sysprompt ,
2023-08-20 12:55:37 +02:00
} ,
modelParameters : {
id : '' ,
modelId : 'GPT4' ,
modelType : 'OpenAi' ,
maxTokens : request . body . max _tokens ,
temperature : request . body . temp ,
2023-12-02 19:04:51 +01:00
stop : 'user:' ,
2023-08-20 12:55:37 +02:00
suffix : null ,
2023-08-22 13:29:18 +02:00
topP : request . body . top _p ,
2023-08-20 12:55:37 +02:00
logprobs : null ,
2023-12-02 21:06:57 +01:00
logitBias : request . body . logit _bias ,
2023-08-20 12:55:37 +02:00
} ,
inputs : [
{
index : '-1' ,
valueByName : {
2023-12-02 21:06:57 +01:00
input : request . body . prompt ,
} ,
} ,
] ,
2023-08-20 12:55:37 +02:00
} ,
meta : {
values : {
'variant.taxonomy' : [ 'undefined' ] ,
'prompt.variablesSourceDataId' : [ 'undefined' ] ,
'modelParameters.suffix' : [ 'undefined' ] ,
'modelParameters.logprobs' : [ 'undefined' ] ,
2023-12-02 21:06:57 +01:00
} ,
} ,
} ) ,
2023-08-20 12:55:37 +02:00
} )
. then ( response => response . json ( ) )
. then ( data => {
2023-12-02 20:11:06 +01:00
console . log ( data . result . data . json . outputs [ 0 ] ) ;
2023-08-22 16:46:37 +02:00
return response _generate _scale . send ( { output : data . result . data . json . outputs [ 0 ] } ) ;
2023-08-20 12:55:37 +02:00
} )
. catch ( ( error ) => {
2023-12-02 20:11:06 +01:00
console . error ( 'Error:' , error ) ;
return response _generate _scale . send ( { error : true } ) ;
2023-08-20 12:55:37 +02:00
} ) ;
} ) ;
2023-08-31 18:44:58 +02:00
/ * *
* @ param { express . Request } request
* @ param { express . Response } response
* /
2023-07-20 19:32:15 +02:00
async function sendClaudeRequest ( request , response ) {
2023-08-31 18:44:58 +02:00
const api _url = new URL ( request . body . reverse _proxy || API _CLAUDE ) . toString ( ) ;
2023-07-28 20:33:29 +02:00
const api _key _claude = request . body . reverse _proxy ? request . body . proxy _password : readSecret ( SECRET _KEYS . CLAUDE ) ;
2023-07-20 19:32:15 +02:00
if ( ! api _key _claude ) {
2023-12-09 14:11:35 +01:00
console . log ( 'Claude API key is missing.' ) ;
return response . status ( 400 ) . send ( { error : true } ) ;
2023-07-20 19:32:15 +02:00
}
try {
const controller = new AbortController ( ) ;
request . socket . removeAllListeners ( 'close' ) ;
request . socket . on ( 'close' , function ( ) {
controller . abort ( ) ;
} ) ;
2023-11-21 21:11:26 +01:00
let doSystemPrompt = request . body . model === 'claude-2' || request . body . model === 'claude-2.1' ;
let requestPrompt = convertClaudePrompt ( request . body . messages , true , ! request . body . exclude _assistant , doSystemPrompt ) ;
2023-07-30 00:51:59 +02:00
2023-08-19 19:09:50 +02:00
if ( request . body . assistant _prefill && ! request . body . exclude _assistant ) {
2023-07-30 00:51:59 +02:00
requestPrompt += request . body . assistant _prefill ;
}
2023-07-20 19:32:15 +02:00
console . log ( 'Claude request:' , requestPrompt ) ;
2023-12-02 19:04:51 +01:00
const stop _sequences = [ '\n\nHuman:' , '\n\nSystem:' , '\n\nAssistant:' ] ;
2023-08-25 23:12:11 +02:00
// Add custom stop sequences
if ( Array . isArray ( request . body . stop ) ) {
stop _sequences . push ( ... request . body . stop ) ;
}
2023-07-20 19:32:15 +02:00
const generateResponse = await fetch ( api _url + '/complete' , {
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-07-20 19:32:15 +02:00
signal : controller . signal ,
body : JSON . stringify ( {
prompt : requestPrompt ,
model : request . body . model ,
max _tokens _to _sample : request . body . max _tokens ,
2023-08-25 23:12:11 +02:00
stop _sequences : stop _sequences ,
2023-07-20 19:32:15 +02:00
temperature : request . body . temperature ,
top _p : request . body . top _p ,
top _k : request . body . top _k ,
stream : request . body . stream ,
} ) ,
headers : {
2023-12-02 19:04:51 +01:00
'Content-Type' : 'application/json' ,
'anthropic-version' : '2023-06-01' ,
'x-api-key' : api _key _claude ,
2023-07-20 19:32:15 +02:00
} ,
timeout : 0 ,
} ) ;
if ( request . body . stream ) {
// Pipe remote SSE stream to Express response
2023-12-08 00:06:17 +01:00
forwardFetchResponse ( generateResponse , response ) ;
2023-07-20 19:32:15 +02:00
} else {
if ( ! generateResponse . ok ) {
console . log ( ` Claude API returned error: ${ generateResponse . status } ${ generateResponse . statusText } ${ await generateResponse . text ( ) } ` ) ;
return response . status ( generateResponse . status ) . send ( { error : true } ) ;
}
const generateResponseJson = await generateResponse . json ( ) ;
const responseText = generateResponseJson . completion ;
console . log ( 'Claude response:' , responseText ) ;
// Wrap it back to OAI format
2023-12-02 21:06:57 +01:00
const reply = { choices : [ { 'message' : { 'content' : responseText } } ] } ;
2023-07-20 19:32:15 +02:00
return response . send ( reply ) ;
}
} catch ( error ) {
console . log ( 'Error communicating with Claude: ' , error ) ;
if ( ! response . headersSent ) {
return response . status ( 500 ) . send ( { error : true } ) ;
}
}
}
2023-09-23 19:48:56 +02:00
/ * *
* @ param { express . Request } request
* @ param { express . Response } response
* /
async function sendPalmRequest ( request , response ) {
const api _key _palm = readSecret ( SECRET _KEYS . PALM ) ;
if ( ! api _key _palm ) {
2023-12-09 14:11:35 +01:00
console . log ( 'Palm API key is missing.' ) ;
return response . status ( 400 ) . send ( { error : true } ) ;
2023-09-23 19:48:56 +02:00
}
const body = {
prompt : {
text : request . body . messages ,
} ,
stopSequences : request . body . stop ,
safetySettings : PALM _SAFETY ,
temperature : request . body . temperature ,
topP : request . body . top _p ,
topK : request . body . top _k || undefined ,
maxOutputTokens : request . body . max _tokens ,
candidate _count : 1 ,
} ;
console . log ( 'Palm request:' , body ) ;
try {
const controller = new AbortController ( ) ;
request . socket . removeAllListeners ( 'close' ) ;
request . socket . on ( 'close' , function ( ) {
controller . abort ( ) ;
} ) ;
const generateResponse = await fetch ( ` https://generativelanguage.googleapis.com/v1beta2/models/text-bison-001:generateText?key= ${ api _key _palm } ` , {
body : JSON . stringify ( body ) ,
2023-12-02 19:04:51 +01:00
method : 'POST' ,
2023-09-23 19:48:56 +02:00
headers : {
2023-12-02 21:06:57 +01:00
'Content-Type' : 'application/json' ,
2023-09-23 19:48:56 +02:00
} ,
signal : controller . signal ,
timeout : 0 ,
} ) ;
if ( ! generateResponse . ok ) {
console . log ( ` Palm API returned error: ${ generateResponse . status } ${ generateResponse . statusText } ${ await generateResponse . text ( ) } ` ) ;
return response . status ( generateResponse . status ) . send ( { error : true } ) ;
}
const generateResponseJson = await generateResponse . json ( ) ;
2023-11-10 14:55:49 +01:00
const responseText = generateResponseJson ? . candidates [ 0 ] ? . output ;
if ( ! responseText ) {
console . log ( 'Palm API returned no response' , generateResponseJson ) ;
let message = ` Palm API returned no response: ${ JSON . stringify ( generateResponseJson ) } ` ;
// Check for filters
if ( generateResponseJson ? . filters [ 0 ] ? . message ) {
message = ` Palm filter triggered: ${ generateResponseJson . filters [ 0 ] . message } ` ;
}
return response . send ( { error : { message } } ) ;
}
2023-09-23 19:48:56 +02:00
console . log ( 'Palm response:' , responseText ) ;
// Wrap it back to OAI format
2023-12-02 21:06:57 +01:00
const reply = { choices : [ { 'message' : { 'content' : responseText } } ] } ;
2023-09-23 19:48:56 +02:00
return response . send ( reply ) ;
} catch ( error ) {
console . log ( 'Error communicating with Palm API: ' , error ) ;
if ( ! response . headersSent ) {
return response . status ( 500 ) . send ( { error : true } ) ;
}
}
}
2023-12-02 19:04:51 +01:00
app . post ( '/generate_openai' , jsonParser , function ( request , response _generate _openai ) {
2023-07-20 19:32:15 +02:00
if ( ! request . body ) return response _generate _openai . status ( 400 ) . send ( { error : true } ) ;
2023-12-03 14:31:58 +01:00
switch ( request . body . chat _completion _source ) {
2023-12-03 21:03:32 +01:00
case CHAT _COMPLETION _SOURCES . CLAUDE : return sendClaudeRequest ( request , response _generate _openai ) ;
case CHAT _COMPLETION _SOURCES . SCALE : return sendScaleRequest ( request , response _generate _openai ) ;
case CHAT _COMPLETION _SOURCES . AI21 : return sendAI21Request ( request , response _generate _openai ) ;
case CHAT _COMPLETION _SOURCES . PALM : return sendPalmRequest ( request , response _generate _openai ) ;
2023-09-23 19:48:56 +02:00
}
2023-07-20 19:32:15 +02:00
let api _url ;
let api _key _openai ;
let headers ;
2023-08-01 17:49:03 +02:00
let bodyParams ;
2023-07-20 19:32:15 +02:00
2023-12-03 21:03:32 +01:00
if ( request . body . chat _completion _source !== CHAT _COMPLETION _SOURCES . OPENROUTER ) {
2023-08-31 18:44:58 +02:00
api _url = new URL ( request . body . reverse _proxy || API _OPENAI ) . toString ( ) ;
2023-07-28 20:33:29 +02:00
api _key _openai = request . body . reverse _proxy ? request . body . proxy _password : readSecret ( SECRET _KEYS . OPENAI ) ;
2023-07-20 19:32:15 +02:00
headers = { } ;
2023-08-01 17:49:03 +02:00
bodyParams = { } ;
2023-11-28 23:11:10 +01:00
if ( getConfigValue ( 'openai.randomizeUserId' , false ) ) {
bodyParams [ 'user' ] = uuidv4 ( ) ;
}
2023-07-20 19:32:15 +02:00
} else {
api _url = 'https://openrouter.ai/api/v1' ;
api _key _openai = readSecret ( SECRET _KEYS . OPENROUTER ) ;
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer' : request . headers . referer } ;
2023-12-02 19:04:51 +01:00
bodyParams = { 'transforms' : [ 'middle-out' ] } ;
2023-08-24 02:21:17 +02:00
if ( request . body . use _fallback ) {
bodyParams [ 'route' ] = 'fallback' ;
}
2023-07-20 19:32:15 +02:00
}
2023-07-28 20:33:29 +02:00
if ( ! api _key _openai && ! request . body . reverse _proxy ) {
2023-12-09 14:11:35 +01:00
console . log ( 'OpenAI API key is missing.' ) ;
return response _generate _openai . status ( 400 ) . send ( { error : true } ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-25 23:12:11 +02:00
// Add custom stop sequences
2023-08-27 12:17:20 +02:00
if ( Array . isArray ( request . body . stop ) && request . body . stop . length > 0 ) {
2023-08-25 23:12:11 +02:00
bodyParams [ 'stop' ] = request . body . stop ;
}
2023-11-02 23:34:22 +01:00
const isTextCompletion = Boolean ( request . body . model && TEXT _COMPLETION _MODELS . includes ( request . body . model ) ) || typeof request . body . messages === 'string' ;
2023-07-20 19:32:15 +02:00
const textPrompt = isTextCompletion ? convertChatMLPrompt ( request . body . messages ) : '' ;
2023-12-03 21:03:32 +01:00
const endpointUrl = isTextCompletion && request . body . chat _completion _source !== CHAT _COMPLETION _SOURCES . OPENROUTER ?
2023-12-03 14:31:58 +01:00
` ${ api _url } /completions ` :
` ${ api _url } /chat/completions ` ;
2023-07-20 19:32:15 +02:00
const controller = new AbortController ( ) ;
request . socket . removeAllListeners ( 'close' ) ;
request . socket . on ( 'close' , function ( ) {
controller . abort ( ) ;
} ) ;
2023-09-01 18:00:32 +02:00
/** @type {import('node-fetch').RequestInit} */
2023-07-20 19:32:15 +02:00
const config = {
method : 'post' ,
headers : {
'Content-Type' : 'application/json' ,
'Authorization' : 'Bearer ' + api _key _openai ,
... headers ,
} ,
2023-09-01 18:00:32 +02:00
body : JSON . stringify ( {
2023-12-02 19:04:51 +01:00
'messages' : isTextCompletion === false ? request . body . messages : undefined ,
'prompt' : isTextCompletion === true ? textPrompt : undefined ,
'model' : request . body . model ,
'temperature' : request . body . temperature ,
'max_tokens' : request . body . max _tokens ,
'stream' : request . body . stream ,
'presence_penalty' : request . body . presence _penalty ,
'frequency_penalty' : request . body . frequency _penalty ,
'top_p' : request . body . top _p ,
'top_k' : request . body . top _k ,
'stop' : isTextCompletion === false ? request . body . stop : undefined ,
'logit_bias' : request . body . logit _bias ,
'seed' : request . body . seed ,
2023-08-01 17:49:03 +02:00
... bodyParams ,
2023-09-01 18:00:32 +02:00
} ) ,
2023-07-20 19:32:15 +02:00
signal : controller . signal ,
2023-09-01 18:00:32 +02:00
timeout : 0 ,
2023-07-20 19:32:15 +02:00
} ;
2023-09-01 18:00:32 +02:00
console . log ( JSON . parse ( String ( config . body ) ) ) ;
2023-07-20 19:32:15 +02:00
2023-09-01 18:00:32 +02:00
makeRequest ( config , response _generate _openai , request ) ;
2023-07-20 19:32:15 +02:00
2023-09-01 18:00:32 +02:00
/ * *
*
* @ param { * } config
* @ param { express . Response } response _generate _openai
* @ param { express . Request } request
* @ param { Number } retries
* @ param { Number } timeout
* /
2023-08-20 15:25:16 +02:00
async function makeRequest ( config , response _generate _openai , request , retries = 5 , timeout = 5000 ) {
2023-07-20 19:32:15 +02:00
try {
2023-12-02 20:11:06 +01:00
const fetchResponse = await fetch ( endpointUrl , config ) ;
2023-07-20 19:32:15 +02:00
2023-12-08 01:01:08 +01:00
if ( request . body . stream ) {
console . log ( 'Streaming request in progress' ) ;
forwardFetchResponse ( fetchResponse , response _generate _openai ) ;
return ;
}
2023-09-01 18:00:32 +02:00
if ( fetchResponse . ok ) {
2023-12-08 01:01:08 +01:00
let json = await fetchResponse . json ( ) ;
response _generate _openai . send ( json ) ;
console . log ( json ) ;
console . log ( json ? . choices [ 0 ] ? . message ) ;
2023-09-01 18:00:32 +02:00
} else if ( fetchResponse . status === 429 && retries > 0 ) {
2023-08-20 15:25:16 +02:00
console . log ( ` Out of quota, retrying in ${ Math . round ( timeout / 1000 ) } s ` ) ;
2023-07-20 19:32:15 +02:00
setTimeout ( ( ) => {
2023-11-08 11:07:14 +01:00
timeout *= 2 ;
makeRequest ( config , response _generate _openai , request , retries - 1 , timeout ) ;
2023-07-20 19:32:15 +02:00
} , timeout ) ;
} else {
2023-09-01 18:00:32 +02:00
await handleErrorResponse ( fetchResponse ) ;
}
} catch ( error ) {
console . log ( 'Generation failed' , error ) ;
if ( ! response _generate _openai . headersSent ) {
response _generate _openai . send ( { error : true } ) ;
} else {
response _generate _openai . end ( ) ;
2023-07-20 19:32:15 +02:00
}
}
}
2023-09-01 18:00:32 +02:00
async function handleErrorResponse ( response ) {
const responseText = await response . text ( ) ;
const errorData = tryParse ( responseText ) ;
2023-07-20 19:32:15 +02:00
2023-08-08 19:07:41 +02:00
const statusMessages = {
400 : 'Bad request' ,
401 : 'Unauthorized' ,
402 : 'Credit limit reached' ,
403 : 'Forbidden' ,
404 : 'Not found' ,
429 : 'Too many requests' ,
451 : 'Unavailable for legal reasons' ,
2023-09-01 18:00:32 +02:00
502 : 'Bad gateway' ,
2023-08-08 19:07:41 +02:00
} ;
2023-09-01 18:00:32 +02:00
const message = errorData ? . error ? . message || statusMessages [ response . status ] || 'Unknown error occurred' ;
const quota _error = response . status === 429 && errorData ? . error ? . type === 'insufficient_quota' ;
console . log ( message ) ;
2023-08-08 19:07:41 +02:00
2023-07-20 19:32:15 +02:00
if ( ! response _generate _openai . headersSent ) {
2023-09-01 18:00:32 +02:00
response _generate _openai . send ( { error : { message } , quota _error : quota _error } ) ;
2023-07-20 19:32:15 +02:00
} else if ( ! response _generate _openai . writableEnded ) {
response _generate _openai . write ( response ) ;
2023-09-01 18:00:32 +02:00
} else {
response _generate _openai . end ( ) ;
2023-07-20 19:32:15 +02:00
}
}
} ) ;
2023-08-19 17:20:42 +02:00
async function sendAI21Request ( request , response ) {
if ( ! request . body ) return response . sendStatus ( 400 ) ;
const controller = new AbortController ( ) ;
2023-12-02 20:11:06 +01:00
console . log ( request . body . messages ) ;
2023-08-19 17:20:42 +02:00
request . socket . removeAllListeners ( 'close' ) ;
request . socket . on ( 'close' , function ( ) {
controller . abort ( ) ;
} ) ;
const options = {
method : 'POST' ,
headers : {
accept : 'application/json' ,
'content-type' : 'application/json' ,
2023-12-02 21:06:57 +01:00
Authorization : ` Bearer ${ readSecret ( SECRET _KEYS . AI21 ) } ` ,
2023-08-19 17:20:42 +02:00
} ,
body : JSON . stringify ( {
numResults : 1 ,
maxTokens : request . body . max _tokens ,
minTokens : 0 ,
temperature : request . body . temperature ,
topP : request . body . top _p ,
stopSequences : request . body . stop _tokens ,
topKReturn : request . body . top _k ,
frequencyPenalty : {
scale : request . body . frequency _penalty * 100 ,
applyToWhitespaces : false ,
applyToPunctuations : false ,
applyToNumbers : false ,
applyToStopwords : false ,
2023-12-02 21:06:57 +01:00
applyToEmojis : false ,
2023-08-19 17:20:42 +02:00
} ,
presencePenalty : {
scale : request . body . presence _penalty ,
applyToWhitespaces : false ,
applyToPunctuations : false ,
applyToNumbers : false ,
applyToStopwords : false ,
2023-12-02 21:06:57 +01:00
applyToEmojis : false ,
2023-08-19 17:20:42 +02:00
} ,
countPenalty : {
scale : request . body . count _pen ,
applyToWhitespaces : false ,
applyToPunctuations : false ,
applyToNumbers : false ,
applyToStopwords : false ,
2023-12-02 21:06:57 +01:00
applyToEmojis : false ,
2023-08-19 17:20:42 +02:00
} ,
2023-12-02 21:06:57 +01:00
prompt : request . body . messages ,
2023-08-19 17:20:42 +02:00
} ) ,
signal : controller . signal ,
} ;
fetch ( ` https://api.ai21.com/studio/v1/ ${ request . body . model } /complete ` , options )
. then ( r => r . json ( ) )
. then ( r => {
if ( r . completions === undefined ) {
2023-12-02 20:11:06 +01:00
console . log ( r ) ;
2023-08-19 17:20:42 +02:00
} else {
2023-12-02 20:11:06 +01:00
console . log ( r . completions [ 0 ] . data . text ) ;
2023-08-19 17:20:42 +02:00
}
2023-12-02 21:06:57 +01:00
const reply = { choices : [ { 'message' : { 'content' : r . completions [ 0 ] . data . text } } ] } ;
2023-12-02 20:11:06 +01:00
return response . send ( reply ) ;
2023-08-19 17:20:42 +02:00
} )
. catch ( err => {
2023-12-02 20:11:06 +01:00
console . error ( err ) ;
return response . send ( { error : true } ) ;
2023-08-19 17:20:42 +02:00
} ) ;
}
2023-12-06 18:04:44 +01:00
/ * *
* Redirect a deprecated API endpoint URL to its replacement . Because fetch , form submissions , and $ . ajax follow
* redirects , this is transparent to client - side code .
* @ param { string } src The URL to redirect from .
* @ param { string } destination The URL to redirect to .
* /
function redirect ( src , destination ) {
app . use ( src , ( req , res ) => {
console . warn ( ` API endpoint ${ src } is deprecated; use ${ destination } instead ` ) ;
// HTTP 301 causes the request to become a GET. 308 preserves the request method.
res . redirect ( 308 , destination ) ;
} ) ;
}
// Redirect deprecated character API endpoints
redirect ( '/createcharacter' , '/api/characters/create' ) ;
redirect ( '/renamecharacter' , '/api/characters/rename' ) ;
redirect ( '/editcharacter' , '/api/characters/edit' ) ;
redirect ( '/editcharacterattribute' , '/api/characters/edit-attribute' ) ;
redirect ( '/v2/editcharacterattribute' , '/api/characters/merge-attributes' ) ;
redirect ( '/deletecharacter' , '/api/characters/delete' ) ;
redirect ( '/getcharacters' , '/api/characters/all' ) ;
redirect ( '/getonecharacter' , '/api/characters/get' ) ;
redirect ( '/getallchatsofcharacter' , '/api/characters/chats' ) ;
redirect ( '/importcharacter' , '/api/characters/import' ) ;
redirect ( '/dupecharacter' , '/api/characters/duplicate' ) ;
redirect ( '/exportcharacter' , '/api/characters/export' ) ;
2023-12-06 18:11:57 +01:00
// Redirect deprecated chat API endpoints
redirect ( '/savechat' , '/api/chats/save' ) ;
redirect ( '/getchat' , '/api/chats/get' ) ;
redirect ( '/renamechat' , '/api/chats/rename' ) ;
redirect ( '/delchat' , '/api/chats/delete' ) ;
redirect ( '/exportchat' , '/api/chats/export' ) ;
redirect ( '/importgroupchat' , '/api/chats/group/import' ) ;
redirect ( '/importchat' , '/api/chats/import' ) ;
redirect ( '/getgroupchat' , '/api/chats/group/get' ) ;
redirect ( '/deletegroupchat' , '/api/chats/group/delete' ) ;
redirect ( '/savegroupchat' , '/api/chats/group/save' ) ;
2023-12-06 18:40:58 +01:00
// Redirect deprecated group API endpoints
redirect ( '/getgroups' , '/api/groups/all' ) ;
redirect ( '/creategroup' , '/api/groups/create' ) ;
redirect ( '/editgroup' , '/api/groups/edit' ) ;
redirect ( '/deletegroup' , '/api/groups/delete' ) ;
2023-12-06 23:09:48 +01:00
// Redirect deprecated worldinfo API endpoints
redirect ( '/getworldinfo' , '/api/worldinfo/get' ) ;
redirect ( '/deleteworldinfo' , '/api/worldinfo/delete' ) ;
redirect ( '/importworldinfo' , '/api/worldinfo/import' ) ;
redirect ( '/editworldinfo' , '/api/worldinfo/edit' ) ;
2023-12-07 18:31:34 +01:00
// Redirect deprecated stats API endpoints
redirect ( '/getstats' , '/api/stats/get' ) ;
redirect ( '/recreatestats' , '/api/stats/recreate' ) ;
redirect ( '/updatestats' , '/api/stats/update' ) ;
2023-12-07 21:17:19 +01:00
// Redirect deprecated backgrounds API endpoints
redirect ( '/getbackgrounds' , '/api/backgrounds/all' ) ;
redirect ( '/setbackground' , '/api/backgrounds/set' ) ;
redirect ( '/delbackground' , '/api/backgrounds/delete' ) ;
redirect ( '/renamebackground' , '/api/backgrounds/rename' ) ;
redirect ( '/downloadbackground' , '/api/backgrounds/upload' ) ; // yes, the downloadbackground endpoint actually uploads one
2023-11-06 20:47:00 +01:00
// OpenAI API
2023-12-04 18:53:17 +01:00
app . use ( '/api/openai' , require ( './src/endpoints/openai' ) . router ) ;
2023-11-06 20:47:00 +01:00
2023-09-16 17:48:06 +02:00
// Tokenizers
2023-12-04 19:00:13 +01:00
app . use ( '/api/tokenizers' , require ( './src/endpoints/tokenizers' ) . router ) ;
2023-09-16 17:48:06 +02:00
2023-09-16 16:36:54 +02:00
// Preset management
2023-12-04 18:54:18 +01:00
app . use ( '/api/presets' , require ( './src/endpoints/presets' ) . router ) ;
2023-09-16 16:36:54 +02:00
2023-09-16 16:28:28 +02:00
// Secrets managemenet
2023-12-04 18:55:13 +01:00
app . use ( '/api/secrets' , require ( './src/endpoints/secrets' ) . router ) ;
2023-09-16 16:28:28 +02:00
2023-12-04 18:59:24 +01:00
// Thumbnail generation. These URLs are saved in chat, so this route cannot be renamed!
app . use ( '/thumbnail' , require ( './src/endpoints/thumbnails' ) . router ) ;
2023-09-16 16:28:28 +02:00
// NovelAI generation
2023-12-04 18:52:27 +01:00
app . use ( '/api/novelai' , require ( './src/endpoints/novelai' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Third-party extensions
2023-12-04 18:48:29 +01:00
app . use ( '/api/extensions' , require ( './src/endpoints/extensions' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Asset management
2023-12-04 18:40:53 +01:00
app . use ( '/api/assets' , require ( './src/endpoints/assets' ) . router ) ;
// File management
app . use ( '/api/files' , require ( './src/endpoints/files' ) . router ) ;
2023-09-16 16:28:28 +02:00
2023-12-04 13:56:42 +01:00
// Character management
app . use ( '/api/characters' , require ( './src/endpoints/characters' ) . router ) ;
2023-12-06 05:01:23 +01:00
// Chat management
app . use ( '/api/chats' , require ( './src/endpoints/chats' ) . router ) ;
2023-12-06 18:42:57 +01:00
// Group management
app . use ( '/api/groups' , require ( './src/endpoints/groups' ) . router ) ;
2023-12-06 23:14:10 +01:00
// World info management
app . use ( '/api/worldinfo' , require ( './src/endpoints/worldinfo' ) . router ) ;
2023-12-07 18:33:46 +01:00
// Stats calculation
2023-12-07 19:01:51 +01:00
const statsEndpoint = require ( './src/endpoints/stats' ) ;
app . use ( '/api/stats' , statsEndpoint . router ) ;
2023-12-07 18:33:46 +01:00
2023-12-07 21:21:19 +01:00
// Background management
app . use ( '/api/backgrounds' , require ( './src/endpoints/backgrounds' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Character sprite management
2023-12-04 18:57:13 +01:00
app . use ( '/api/sprites' , require ( './src/endpoints/sprites' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Custom content management
2023-12-04 18:47:38 +01:00
app . use ( '/api/content' , require ( './src/endpoints/content-manager' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Stable Diffusion generation
2023-12-04 18:58:02 +01:00
app . use ( '/api/sd' , require ( './src/endpoints/stable-diffusion' ) . router ) ;
2023-09-16 16:28:28 +02:00
// LLM and SD Horde generation
2023-12-04 18:51:02 +01:00
app . use ( '/api/horde' , require ( './src/endpoints/horde' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Vector storage DB
2023-12-04 19:01:52 +01:00
app . use ( '/api/vector' , require ( './src/endpoints/vectors' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Chat translation
2023-12-04 19:00:59 +01:00
app . use ( '/api/translate' , require ( './src/endpoints/translate' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Emotion classification
2023-12-04 18:45:17 +01:00
app . use ( '/api/extra/classify' , require ( './src/endpoints/classify' ) . router ) ;
2023-09-16 16:28:28 +02:00
// Image captioning
2023-12-04 18:43:37 +01:00
app . use ( '/api/extra/caption' , require ( './src/endpoints/caption' ) . router ) ;
2023-09-16 16:28:28 +02:00
2023-11-13 23:16:41 +01:00
// Web search extension
2023-12-04 18:56:22 +01:00
app . use ( '/api/serpapi' , require ( './src/endpoints/serpapi' ) . router ) ;
2023-11-13 23:16:41 +01:00
2023-12-12 05:19:53 +01:00
// The different text generation APIs
// Ooba/OpenAI text completions
2023-12-13 01:22:35 +01:00
app . use ( '/api/backends/text-completions' , require ( './src/endpoints/backends/text-completions' ) . router ) ;
2023-12-12 05:19:53 +01:00
2023-12-12 05:27:16 +01:00
// KoboldAI
2023-12-14 00:37:51 +01:00
app . use ( '/api/backends/kobold' , require ( './src/endpoints/backends/kobold' ) . router ) ;
2023-12-12 05:27:16 +01:00
2023-07-20 19:32:15 +02:00
const tavernUrl = new URL (
( cliArguments . ssl ? 'https://' : 'http://' ) +
( listen ? '0.0.0.0' : '127.0.0.1' ) +
2023-12-02 21:06:57 +01:00
( ':' + server _port ) ,
2023-07-20 19:32:15 +02:00
) ;
const autorunUrl = new URL (
( cliArguments . ssl ? 'https://' : 'http://' ) +
( '127.0.0.1' ) +
2023-12-02 21:06:57 +01:00
( ':' + server _port ) ,
2023-07-20 19:32:15 +02:00
) ;
const setupTasks = async function ( ) {
2023-09-17 13:27:41 +02:00
const version = await getVersion ( ) ;
2023-07-20 19:32:15 +02:00
console . log ( ` SillyTavern ${ version . pkgVersion } ` + ( version . gitBranch ? ` ' ${ version . gitBranch } ' ( ${ version . gitRevision } ) ` : '' ) ) ;
backupSettings ( ) ;
2023-09-08 12:57:27 +02:00
migrateSecrets ( SETTINGS _FILE ) ;
2023-07-20 19:32:15 +02:00
ensurePublicDirectoriesExist ( ) ;
await ensureThumbnailCache ( ) ;
2023-07-21 14:28:32 +02:00
contentManager . checkForNewContent ( ) ;
2023-08-19 16:43:56 +02:00
cleanUploads ( ) ;
2023-07-20 19:32:15 +02:00
2023-09-16 17:48:06 +02:00
await loadTokenizers ( ) ;
2023-12-07 19:01:51 +01:00
await statsEndpoint . init ( ) ;
const exitProcess = ( ) => {
statsEndpoint . onExit ( ) ;
process . exit ( ) ;
} ;
2023-07-20 19:32:15 +02:00
// Set up event listeners for a graceful shutdown
2023-12-07 19:01:51 +01:00
process . on ( 'SIGINT' , exitProcess ) ;
process . on ( 'SIGTERM' , exitProcess ) ;
2023-07-20 19:32:15 +02:00
process . on ( 'uncaughtException' , ( err ) => {
console . error ( 'Uncaught exception:' , err ) ;
2023-12-07 19:01:51 +01:00
exitProcess ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
console . log ( 'Launching...' ) ;
if ( autorun ) open ( autorunUrl . toString ( ) ) ;
2023-08-19 14:58:17 +02:00
2023-08-26 13:17:57 +02:00
console . log ( color . green ( 'SillyTavern is listening on: ' + tavernUrl ) ) ;
2023-07-20 19:32:15 +02:00
if ( listen ) {
2023-11-25 22:45:33 +01:00
console . log ( '\n0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost (127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n' ) ;
2023-07-20 19:32:15 +02:00
}
2023-12-02 20:11:06 +01:00
} ;
2023-07-20 19:32:15 +02:00
2023-11-25 22:45:33 +01:00
if ( listen && ! getConfigValue ( 'whitelistMode' , true ) && ! getConfigValue ( 'basicAuthMode' , false ) ) {
if ( getConfigValue ( 'securityOverride' , false ) ) {
2023-12-02 19:04:51 +01:00
console . warn ( color . red ( 'Security has been overridden. If it\'s not a trusted network, change the settings.' ) ) ;
2023-08-26 13:17:57 +02:00
}
2023-07-20 19:32:15 +02:00
else {
2023-08-26 13:17:57 +02:00
console . error ( color . red ( 'Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.' ) ) ;
2023-07-20 19:32:15 +02:00
process . exit ( 1 ) ;
}
}
2023-08-26 13:17:57 +02:00
2023-12-06 19:29:58 +01:00
if ( cliArguments . ssl ) {
2023-07-20 19:32:15 +02:00
https . createServer (
{
cert : fs . readFileSync ( cliArguments . certPath ) ,
2023-12-02 21:06:57 +01:00
key : fs . readFileSync ( cliArguments . keyPath ) ,
2023-07-20 19:32:15 +02:00
} , app )
. listen (
2023-08-30 20:32:13 +02:00
Number ( tavernUrl . port ) || 443 ,
2023-07-20 19:32:15 +02:00
tavernUrl . hostname ,
2023-12-02 21:06:57 +01:00
setupTasks ,
2023-07-20 19:32:15 +02:00
) ;
2023-08-30 20:32:13 +02:00
} else {
2023-07-20 19:32:15 +02:00
http . createServer ( app ) . listen (
2023-08-30 20:32:13 +02:00
Number ( tavernUrl . port ) || 80 ,
2023-07-20 19:32:15 +02:00
tavernUrl . hostname ,
2023-12-02 21:06:57 +01:00
setupTasks ,
2023-07-20 19:32:15 +02:00
) ;
2023-08-30 20:32:13 +02:00
}
2023-07-20 19:32:15 +02:00
2023-10-24 21:09:55 +02:00
function backupSettings ( ) {
2023-07-20 19:32:15 +02:00
try {
2023-09-16 16:28:28 +02:00
if ( ! fs . existsSync ( DIRECTORIES . backups ) ) {
fs . mkdirSync ( DIRECTORIES . backups ) ;
2023-07-20 19:32:15 +02:00
}
2023-09-16 16:28:28 +02:00
const backupFile = path . join ( DIRECTORIES . backups , ` settings_ ${ generateTimestamp ( ) } .json ` ) ;
2023-07-20 19:32:15 +02:00
fs . copyFileSync ( SETTINGS _FILE , backupFile ) ;
2023-10-24 21:09:55 +02:00
removeOldBackups ( 'settings_' ) ;
2023-07-20 19:32:15 +02:00
} catch ( err ) {
console . log ( 'Could not backup settings file' , err ) ;
}
}
function ensurePublicDirectoriesExist ( ) {
2023-09-16 16:28:28 +02:00
for ( const dir of Object . values ( DIRECTORIES ) ) {
2023-07-20 19:32:15 +02:00
if ( ! fs . existsSync ( dir ) ) {
fs . mkdirSync ( dir , { recursive : true } ) ;
}
}
}