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 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-08-29 23:34:41 +02:00
const fetch = require ( 'node-fetch' ) . default ;
2023-08-29 23:10:40 +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 ;
2024-03-30 21:38:09 +01:00
util . inspect . defaultOptions . depth = 4 ;
2023-09-10 03:12:14 +02:00
2023-08-29 23:26:59 +02:00
// local library imports
2023-12-15 17:43:00 +01:00
const basicAuthMiddleware = require ( './src/middleware/basicAuth' ) ;
2023-12-14 23:36:41 +01:00
const whitelistMiddleware = require ( './src/middleware/whitelist' ) ;
2023-12-03 14:52:43 +01:00
const contentManager = require ( './src/endpoints/content-manager' ) ;
2023-12-08 00:06:17 +01:00
const {
getVersion ,
getConfigValue ,
color ,
forwardFetchResponse ,
} = require ( './src/util' ) ;
2023-12-07 21:21:19 +01:00
const { ensureThumbnailCache } = require ( './src/endpoints/thumbnails' ) ;
2023-12-12 05:50:43 +01:00
const { loadTokenizers } = require ( './src/endpoints/tokenizers' ) ;
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' ) ;
2024-03-29 02:20:16 +01:00
const DEFAULT _PORT = 8000 ;
const DEFAULT _AUTORUN = false ;
const DEFAULT _LISTEN = false ;
const DEFAULT _CORS _PROXY = false ;
2023-07-20 19:32:15 +02:00
const cliArguments = yargs ( hideBin ( process . argv ) )
2024-03-29 02:20:16 +01:00
. usage ( 'Usage: <your-start-script> <command> [options]' )
. option ( 'port' , {
type : 'number' ,
default : null ,
describe : ` Sets the port under which SillyTavern will run. \n If not provided falls back to yaml config 'port'. \n [config default: ${ DEFAULT _PORT } ] ` ,
} ) . option ( 'autorun' , {
2023-11-18 01:09:42 +01:00
type : 'boolean' ,
2024-03-29 02:20:16 +01:00
default : null ,
describe : ` Automatically launch SillyTavern in the browser. \n Autorun is automatically disabled if --ssl is set to true. \n If not provided falls back to yaml config 'autorun'. \n [config default: ${ DEFAULT _AUTORUN } ] ` ,
} ) . option ( 'listen' , {
type : 'boolean' ,
default : null ,
describe : ` SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1). \n If not provided falls back to yaml config 'listen'. \n [config default: ${ DEFAULT _LISTEN } ] ` ,
2023-11-25 20:56:57 +01:00
} ) . option ( 'corsProxy' , {
type : 'boolean' ,
2024-03-29 02:20:16 +01:00
default : null ,
describe : ` Enables CORS proxy \n If not provided falls back to yaml config 'enableCorsProxy'. \n [config default: ${ DEFAULT _CORS _PROXY } ] ` ,
2023-11-25 20:56:57 +01:00
} ) . 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
2024-03-15 14:30:13 +01:00
console . log ( ` Node version: ${ process . version } . Running in ${ process . env . NODE _ENV } environment. ` ) ;
2024-03-06 21:09:31 +01:00
const serverDirectory = _ _dirname ;
2023-12-16 21:21:40 +01:00
process . chdir ( serverDirectory ) ;
2023-07-20 19:32:15 +02:00
const app = express ( ) ;
app . use ( compression ( ) ) ;
app . use ( responseTime ( ) ) ;
2024-03-29 02:20:16 +01:00
const server _port = cliArguments . port ? ? process . env . SILLY _TAVERN _PORT ? ? getConfigValue ( 'port' , DEFAULT _PORT ) ;
const autorun = ( cliArguments . autorun ? ? getConfigValue ( 'autorun' , DEFAULT _AUTORUN ) ) && ! cliArguments . ssl ;
const listen = cliArguments . listen ? ? getConfigValue ( 'listen' , DEFAULT _LISTEN ) ;
2024-03-30 21:46:18 +01:00
const enableCorsProxy = cliArguments . corsProxy ? ? getConfigValue ( 'enableCorsProxy' , DEFAULT _CORS _PROXY ) ;
const basicAuthMode = getConfigValue ( 'basicAuthMode' , false ) ;
2023-07-20 19:32:15 +02:00
2024-03-19 23:39:48 +01:00
const { DIRECTORIES , UPLOADS _PATH } = 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 ) ;
2024-03-30 21:46:18 +01:00
if ( listen && basicAuthMode ) app . use ( basicAuthMiddleware ) ;
2023-07-20 19:32:15 +02:00
2024-03-30 21:42:51 +01:00
app . use ( whitelistMiddleware ( listen ) ) ;
2023-07-20 19:32:15 +02:00
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' ,
} ) ;
} ) ;
}
2024-03-29 02:20:16 +01:00
if ( enableCorsProxy ) {
2023-12-02 14:32:09 +01:00
const bodyParser = require ( 'body-parser' ) ;
2023-12-14 03:39:07 +01:00
app . use ( bodyParser . json ( {
limit : '200mb' ,
} ) ) ;
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 ) => {
2024-03-20 00:07:28 +01:00
const filePath = decodeURIComponent ( path . join ( process . cwd ( ) , DIRECTORIES . backgrounds , 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.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-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-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-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 ( '/delbackground' , '/api/backgrounds/delete' ) ;
redirect ( '/renamebackground' , '/api/backgrounds/rename' ) ;
redirect ( '/downloadbackground' , '/api/backgrounds/upload' ) ; // yes, the downloadbackground endpoint actually uploads one
2024-03-19 23:14:32 +01:00
// Redirect deprecated theme API endpoints
redirect ( '/savetheme' , '/api/themes/save' ) ;
2024-03-19 23:39:48 +01:00
// Redirect deprecated avatar API endpoints
redirect ( '/getuseravatars' , '/api/avatars/get' ) ;
redirect ( '/deleteuseravatar' , '/api/avatars/delete' ) ;
redirect ( '/uploaduseravatar' , '/api/avatars/upload' ) ;
2024-03-19 23:46:46 +01:00
// Redirect deprecated quick reply endpoints
redirect ( '/deletequickreply' , '/api/quick-replies/delete' ) ;
redirect ( '/savequickreply' , '/api/quick-replies/save' ) ;
2024-03-19 23:59:06 +01:00
// Redirect deprecated image endpoints
redirect ( '/uploadimage' , '/api/images/upload' ) ;
redirect ( '/listimgfiles/:folder' , '/api/images/list/:folder' ) ;
2024-03-20 00:07:28 +01:00
// Redirect deprecated moving UI endpoints
redirect ( '/savemovingui' , '/api/moving-ui/save' ) ;
// Moving UI
app . use ( '/api/moving-ui' , require ( './src/endpoints/moving-ui' ) . router ) ;
2024-03-19 23:59:06 +01:00
// Image management
app . use ( '/api/images' , require ( './src/endpoints/images' ) . router ) ;
2024-03-19 23:46:46 +01:00
// Quick reply management
app . use ( '/api/quick-replies' , require ( './src/endpoints/quick-replies' ) . router ) ;
2024-03-19 23:39:48 +01:00
// Avatar management
app . use ( '/api/avatars' , require ( './src/endpoints/avatars' ) . router ) ;
2024-03-19 23:14:32 +01:00
// Theme management
app . use ( '/api/themes' , require ( './src/endpoints/themes' ) . router ) ;
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-12-14 13:37:53 +01:00
//Google API
app . use ( '/api/google' , require ( './src/endpoints/google' ) . router ) ;
2024-03-04 22:07:38 +01:00
//Anthropic API
app . use ( '/api/anthropic' , require ( './src/endpoints/anthropic' ) . router ) ;
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
2023-12-14 22:56:45 +01:00
// Settings load/store
const settingsEndpoint = require ( './src/endpoints/settings' ) ;
app . use ( '/api/settings' , settingsEndpoint . 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-12-12 05:50:43 +01:00
// OpenAI chat completions
app . use ( '/api/backends/chat-completions' , require ( './src/endpoints/backends/chat-completions' ) . router ) ;
2023-12-12 05:56:55 +01:00
// Scale (alt method)
app . use ( '/api/backends/scale-alt' , require ( './src/endpoints/backends/scale-alt' ) . router ) ;
2024-02-01 23:36:40 +01:00
// Speech (text-to-speech and speech-to-text)
app . use ( '/api/speech' , require ( './src/endpoints/speech' ) . router ) ;
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
2024-04-02 22:17:21 +02:00
// Print formatted header
console . log ( ) ;
console . log ( ` SillyTavern ${ version . pkgVersion } ` ) ;
console . log ( version . gitBranch ? ` Running ' ${ version . gitBranch } ' ( ${ version . gitRevision } ) - ${ version . commitDate } ` : '' ) ;
if ( version . gitBranch && ! [ 'staging' , 'release' ] . includes ( version . gitBranch ) ) {
console . log ( 'INFO: Currently running a dev branch.' ) ;
console . log ( ` If this isn't a dev environment, consider switching via 'git switch staging' or 'git switch release'. ` ) ;
} else if ( version . gitBranch && ! version . isLatest ) {
console . log ( 'INFO: Currently not on the latest commit.' ) ;
console . log ( ` Run 'git pull' to upate. If you have any conflicts, run 'git reset --hard' and 'git pull' to reset your branch. ` )
}
console . log ( ) ;
2023-07-20 19:32:15 +02:00
2023-12-14 22:56:45 +01:00
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
await settingsEndpoint . init ( ) ;
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 ( ) ;
2023-12-17 18:26:34 +01:00
const cleanupPlugins = await loadPlugins ( ) ;
2023-12-23 18:05:21 +01:00
const exitProcess = async ( ) => {
2023-12-07 19:01:51 +01:00
statsEndpoint . onExit ( ) ;
2023-12-23 18:03:13 +01:00
if ( typeof cleanupPlugins === 'function' ) {
2023-12-23 18:05:21 +01:00
await cleanupPlugins ( ) ;
2023-12-23 18:03:13 +01:00
}
2023-12-07 19:01:51 +01:00
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
} ) ;
2023-12-16 21:21:40 +01:00
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
}
2024-03-30 19:57:23 +01:00
2024-03-30 21:46:18 +01:00
if ( basicAuthMode ) {
const basicAuthUser = getConfigValue ( 'basicAuthUser' , { } ) ;
if ( ! basicAuthUser ? . username || ! basicAuthUser ? . password ) {
console . warn ( color . yellow ( 'Basic Authentication is enabled, but username or password is not set or empty!' ) ) ;
2024-03-30 19:57:23 +01:00
}
}
2023-12-02 20:11:06 +01:00
} ;
2023-07-20 19:32:15 +02:00
2023-12-23 18:03:13 +01:00
/ * *
* Loads server plugins from a directory .
* @ returns { Promise < Function > } Function to be run on server exit
* /
2023-12-16 21:21:40 +01:00
async function loadPlugins ( ) {
try {
const pluginDirectory = path . join ( serverDirectory , 'plugins' ) ;
const loader = require ( './src/plugin-loader' ) ;
2023-12-23 18:03:13 +01:00
const cleanupPlugins = await loader . loadPlugins ( app , pluginDirectory ) ;
return cleanupPlugins ;
2023-12-16 21:21:40 +01:00
} catch {
console . log ( 'Plugin loading failed.' ) ;
2024-03-30 21:46:18 +01:00
return ( ) => { } ;
2023-12-16 21:21:40 +01:00
}
}
2024-03-30 21:52:57 +01:00
if ( listen && ! getConfigValue ( 'whitelistMode' , true ) && ! basicAuthMode ) {
2023-11-25 22:45:33 +01:00
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
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 } ) ;
}
}
}