Merge branch 'staging' of https://github.com/city-unit/SillyTavern into feature/exorcism

This commit is contained in:
city-unit 2023-09-01 22:24:41 -04:00
commit 838cd81f8e
21 changed files with 1485 additions and 636 deletions

551
Launcher.bat Normal file
View File

@ -0,0 +1,551 @@
@echo off
REM --------------------------------------------
REM This script was created by: Deffcolony
REM --------------------------------------------
title SillyTavern Launcher
setlocal
REM ANSI Escape Code for Colors
set "reset="
REM Strong Foreground Colors
set "white_fg_strong="
set "red_fg_strong="
set "green_fg_strong="
set "yellow_fg_strong="
set "blue_fg_strong="
set "magenta_fg_strong="
set "cyan_fg_strong="
REM Normal Background Colors
set "red_bg="
set "blue_bg="
REM Environment Variables (TOOLBOX 7-Zip)
set "zip7version=7z2301-x64"
set "zip7_install_path=%ProgramFiles%\7-Zip"
set "zip7_download_path=%TEMP%\%zip7version%.exe"
REM Environment Variables (TOOLBOX FFmpeg)
set "ffmpeg_url=https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
set "ffdownload_path=%TEMP%\ffmpeg.7z"
set "ffextract_path=C:\ffmpeg"
set "bin_path=%ffextract_path%\bin"
REM Environment Variables (TOOLBOX Node.js)
set "node_installer_path=%temp%\NodejsInstaller.msi"
REM Environment Variables (winget)
set "winget_path=%userprofile%\AppData\Local\Microsoft\WindowsApps"
REM Check if Winget is installed; if not, then install it
winget --version > nul 2>&1
if %errorlevel% neq 0 (
echo %blue_fg_strong%[INFO]%reset% Winget is not installed on this system.
echo %blue_fg_strong%[INFO]%reset% Installing Winget...
bitsadmin /transfer "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" /download /priority FOREGROUND "https://github.com/microsoft/winget-cli/releases/download/v1.5.2201/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" "%temp%\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
start "" "%temp%\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
echo %green_fg_strong%Winget is now installed.%reset%
) else (
echo %blue_fg_strong%[INFO] Winget is already installed.%reset%
)
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%winget_path%" > nul
set "ff_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %ff_path_exists% neq 0 (
set "new_path=%current_path%;%winget_path%"
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%" > nul
echo %green_fg_strong%winget added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] winget already exists in PATH.%reset%
)
REM Check if Git is installed if not then install git
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %yellow_fg_strong%[WARN] Git is not installed on this system.%reset%
echo %blue_fg_strong%[INFO]%reset% Installing Git using Winget...
winget install -e --id Git.Git
echo %green_fg_strong%Git is installed. Please restart the Launcher.%reset%
pause
exit
) else (
echo %blue_fg_strong%[INFO] Git is already installed.%reset%
)
REM Check for updates
git fetch origin
for /f %%i in ('git rev-list HEAD...origin/%current_branch%') do (
set "update_status=%yellow_fg_strong%Update Available%reset%"
goto :found_update
)
set "update_status=%green_fg_strong%Up to Date%reset%"
:found_update
REM Home - frontend
:home
cls
echo %blue_fg_strong%/ Home%reset%
echo -------------------------------------
echo What would you like to do?
echo 1. Start SillyTavern
echo 2. Update
echo 3. Switch to release branch
echo 4. Switch to staging branch
echo 5. Backup
echo 6. Toolbox
echo 7. Exit
REM Get the current Git branch
for /f %%i in ('git branch --show-current') do set current_branch=%%i
echo ======== VERSION STATUS =========
echo Current branch: %cyan_fg_strong%%current_branch%%reset%
echo Update Status: %update_status%
echo =================================
set /p choice=Choose Your Destiny:
REM Home - backend
if "%choice%"=="1" (
call :start
) else if "%choice%"=="2" (
call :update
) else if "%choice%"=="3" (
call :switch_release
) else if "%choice%"=="4" (
call :switch_staging
) else if "%choice%"=="5" (
call :backup_menu
) else if "%choice%"=="6" (
call :toolbox
) else if "%choice%"=="7" (
exit
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :home
)
:start
REM Check if Node.js is installed
node --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] node command not found in PATH%reset%
echo %red_bg%Please make sure Node.js is installed and added to your PATH.%reset%
echo %blue_bg%To install Node.js go to Toolbox%reset%
pause
goto :home
)
echo Launching SillyTavern...
cls
pushd %~dp0
call npm install --no-audit
node server.js
pause
popd
goto :home
:update
echo Updating...
pushd %~dp0
REM Check if git is installed
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] git command not found in PATH. Skipping update.%reset%
echo %red_bg%Please make sure Git is installed and added to your PATH.%reset%
echo %blue_bg%To install Git go to Toolbox%reset%
) else (
call git pull --rebase --autostash
if %errorlevel% neq 0 (
REM incase there is still something wrong
echo There were errors while updating. Please download the latest version manually.
)
)
pause
goto :home
:switch_release
REM Check if git is installed
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] git command not found in PATH%reset%
echo %red_bg%Please make sure Git is installed and added to your PATH.%reset%
echo %blue_bg%To install Git go to Toolbox%reset%
pause
goto :home
)
echo Switching to release branch...
git switch release
pause
goto :home
:switch_staging
REM Check if git is installed
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] git command not found in PATH%reset%
echo %red_bg%Please make sure git is installed and added to your PATH.%reset%
pause
goto :home
)
echo Switching to staging branch...
git switch staging
pause
goto :home
REM backup - frontend
:backup_menu
REM Check if 7-Zip is installed
7z > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] 7z command not found in PATH%reset%
echo %red_bg%Please make sure 7-Zip is installed and added to your PATH.%reset%
echo %blue_bg%To install 7-Zip go to Toolbox%reset%
pause
goto :home
)
cls
echo %blue_fg_strong%/ Home / Backup%reset%
echo -------------------------------------
echo What would you like to do?
REM color 7
echo 1. Create Backup
echo 2. Restore Backup
echo 3. Back to Home
set /p backup_choice=Choose Your Destiny:
REM backup - backend
if "%backup_choice%"=="1" (
call :create_backup
) else if "%backup_choice%"=="2" (
call :restore_backup
) else if "%backup_choice%"=="3" (
goto :home
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :backup_menu
)
REM toolbox - frontend
:toolbox
cls
echo %blue_fg_strong%/ Home / Toolbox%reset%
echo -------------------------------------
echo What would you like to do?
REM color 7
echo 1. Install 7-Zip
echo 2. Install FFmpeg
echo 3. Install Node.js
echo 4. Edit Environment - Power Users only!
echo 5. Reinstall SillyTavern
echo 6. Back to Home
set /p toolbox_choice=Choose Your Destiny:
REM toolbox - backend
if "%toolbox_choice%"=="1" (
call :install7zip
) else if "%toolbox_choice%"=="2" (
call :installffmpeg
) else if "%toolbox_choice%"=="3" (
call :installnodejs
) else if "%toolbox_choice%"=="4" (
call :editenvironment
) else if "%toolbox_choice%"=="5" (
call :reinstallsillytavern
) else if "%toolbox_choice%"=="6" (
goto :home
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :toolbox
)
:install7zip
echo %blue_fg_strong%[INFO] Installing 7-Zip...%reset%
winget install -e --id 7zip.7zip
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%zip7_install_path%" > nul
set "zip7_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %zip7_path_exists% neq 0 (
set "new_path=%current_path%;%zip7_install_path%"
echo %green_fg_strong%7-Zip added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] 7-Zip already exists in PATH.%reset%
)
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%"
echo %green_fg_strong%7-Zip is installed. Please restart the Launcher.%reset%
pause
exit
:installffmpeg
REM Check if 7-Zip is installed
7z > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] 7z command not found in PATH%reset%
echo %red_bg%Please make sure 7-Zip is installed and added to your PATH.%reset%
echo %blue_bg%To install 7-Zip go to Toolbox%reset%
pause
goto :toolbox
)
echo %blue_fg_strong%[INFO]%reset% Downloading FFmpeg archive...
rem bitsadmin /transfer "ffmpeg" /download /priority FOREGROUND "%ffmpeg_url%" "%ffdownload_path%"
curl -o "%ffdownload_path%" "%ffmpeg_url%"
echo %blue_fg_strong%[INFO]%reset% Creating ffmpeg directory if it doesn't exist...
if not exist "%ffextract_path%" (
mkdir "%ffextract_path%"
)
echo %blue_fg_strong%[INFO]%reset% Extracting FFmpeg archive...
7z x "%ffdownload_path%" -o"%ffextract_path%"
echo %blue_fg_strong%[INFO]%reset% Moving FFmpeg contents to C:\ffmpeg...
for /d %%i in ("%ffextract_path%\ffmpeg-*-full_build") do (
xcopy "%%i\bin" "%ffextract_path%\bin" /E /I /Y
xcopy "%%i\doc" "%ffextract_path%\doc" /E /I /Y
xcopy "%%i\presets" "%ffextract_path%\presets" /E /I /Y
rd "%%i" /S /Q
)
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%bin_path%" > nul
set "ff_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %ff_path_exists% neq 0 (
set "new_path=%current_path%;%bin_path%"
echo %green_fg_strong%ffmpeg added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] ffmpeg already exists in PATH.%reset%
)
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%" > nul
del "%ffdownload_path%"
echo %green_fg_strong%FFmpeg is installed. Please restart the Launcher.%reset%
pause
exit
:installnodejs
echo %blue_fg_strong%[INFO]%reset% Installing Node.js...
winget install -e --id OpenJS.NodeJS
echo %green_fg_strong%Node.js is installed. Please restart the Launcher.%reset%
pause
exit
:editenvironment
rundll32.exe sysdm.cpl,EditEnvironmentVariables
goto :toolbox
:reinstallsillytavern
setlocal enabledelayedexpansion
chcp 65001 > nul
REM Define the names of items to be excluded
set "script_name=%~nx0"
set "excluded_folders=backups"
set "excluded_files=!script_name!"
REM Confirm with the user before proceeding
echo.
echo %red_bg%╔════ DANGER ZONE ══════════════════════════════════════════════════════════════════════════════╗%reset%
echo %red_bg%║ WARNING: This will delete all data in the current branch except the Backups. ║%reset%
echo %red_bg%║ If you want to keep any data, make sure to create a backup before proceeding. ║%reset%
echo %red_bg%╚═══════════════════════════════════════════════════════════════════════════════════════════════╝%reset%
echo.
echo Are you sure you want to proceed? (Y/N)
set /p "confirmation="
if /i "!confirmation!"=="Y" (
REM Remove non-excluded folders
for /d %%D in (*) do (
set "exclude_folder="
for %%E in (!excluded_folders!) do (
if "%%D"=="%%E" set "exclude_folder=true"
)
if not defined exclude_folder (
rmdir /s /q "%%D" 2>nul
)
)
REM Remove non-excluded files
for %%F in (*) do (
set "exclude_file="
for %%E in (!excluded_files!) do (
if "%%F"=="%%E" set "exclude_file=true"
)
if not defined exclude_file (
del /f /q "%%F" 2>nul
)
)
REM Clone repo into %temp% folder
git clone https://github.com/SillyTavern/SillyTavern.git "%temp%\SillyTavernTemp"
REM Move the contents of the temporary folder to the current directory
xcopy /e /y "%temp%\SillyTavernTemp\*" .
REM Clean up the temporary folder
rmdir /s /q "%temp%\SillyTavernTemp"
echo %green_fg_strong%SillyTavern reinstalled successfully!%reset%
) else (
echo Reinstall canceled.
)
endlocal
pause
goto :toolbox
:create_backup
REM Create a backup using 7zip
7z a "backups\backup_.7z" ^
"public\assets\*" ^
"public\Backgrounds\*" ^
"public\Characters\*" ^
"public\Chats\*" ^
"public\context\*" ^
"public\Group chats\*" ^
"public\Groups\*" ^
"public\instruct\*" ^
"public\KoboldAI Settings\*" ^
"public\movingUI\*" ^
"public\NovelAI Settings\*" ^
"public\OpenAI Settings\*" ^
"public\QuickReplies\*" ^
"public\TextGen Settings\*" ^
"public\themes\*" ^
"public\User Avatars\*" ^
"public\user\*" ^
"public\worlds\*" ^
"public\settings.json" ^
"secrets.json"
REM Get current date and time components
for /f "tokens=1-3 delims=/- " %%d in ("%date%") do (
set "day=%%d"
set "month=%%e"
set "year=%%f"
)
for /f "tokens=1-2 delims=:." %%h in ("%time%") do (
set "hour=%%h"
set "minute=%%i"
)
REM Pad single digits with leading zeros
setlocal enabledelayedexpansion
set "day=0!day!"
set "month=0!month!"
set "hour=0!hour!"
set "minute=0!minute!"
set "formatted_date=%month:~-2%-%day:~-2%-%year%_%hour:~-2%%minute:~-2%"
REM Rename the backup file with the formatted date and time
rename "backups\backup_.7z" "backup_%formatted_date%.7z"
endlocal
echo %green_fg_strong%Backup created successfully!%reset%
pause
endlocal
goto :backup_menu
:restore_backup
REM Restore a backup using 7zip
echo List of available backups:
echo =========================
setlocal enabledelayedexpansion
set "backup_count=0"
for %%F in ("backups\backup_*.7z") do (
set /a "backup_count+=1"
set "backup_files[!backup_count!]=%%~nF"
echo !backup_count!. %cyan_fg_strong%%%~nF%reset%
)
echo =========================
set /p "restore_choice=Enter number of backup to restore: "
if "%restore_choice%" geq "1" (
if "%restore_choice%" leq "%backup_count%" (
set "selected_backup=!backup_files[%restore_choice%]!"
echo Restoring backup !selected_backup!...
REM Extract the contents of the "public" folder directly into the existing "public" folder
7z x "backups\!selected_backup!.7z" -o"temp" -aoa
xcopy /y /e "temp\public\*" "public\"
rmdir /s /q "temp"
echo %green_fg_strong%!selected_backup! restored successfully.%reset%
) else (
color 6
echo WARNING: Invalid backup number. Please insert a valid number.
)
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
)
pause
goto :backup_menu

View File

@ -116,6 +116,17 @@
"!npm install -g localtunnel\n",
"!pip install -r requirements-complete.txt\n",
"!pip install tensorflow==2.12\n",
"!pip install Flask-Cors\n",
"!pip install Flask-Compress\n",
"!pip install transformers\n",
"!pip install Flask_Cloudflared\n",
"!pip install webuiapi\n",
"!pip install diffusers\n",
"!pip install accelerate\n",
"!pip install silero_api_server\n",
"!pip install edge_tts\n",
"!pip install chromadb\n",
"!pip install sentence_transformers\n",
"!wget https://github.com/cloudflare/cloudflared/releases/download/2023.5.0/cloudflared-linux-amd64 -O /tmp/cloudflared-linux-amd64\n",
"!chmod +x /tmp/cloudflared-linux-amd64\n",
"\n",

View File

@ -15,17 +15,21 @@ const skipContentCheck = false; // If true, no new default content will be deliv
// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting.
const securityOverride = false;
// Request overrides for additional headers
const requestOverrides = [];
module.exports = {
port,
whitelist,
whitelistMode,
basicAuthMode,
basicAuthUser,
autorun,
enableExtensions,
listen,
disableThumbnails,
allowKeysExposure,
securityOverride,
skipContentCheck,
port,
whitelist,
whitelistMode,
basicAuthMode,
basicAuthUser,
autorun,
enableExtensions,
listen,
disableThumbnails,
allowKeysExposure,
securityOverride,
skipContentCheck,
requestOverrides,
};

121
package-lock.json generated
View File

@ -12,7 +12,6 @@
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
"@dqbd/tiktoken": "^1.0.2",
"axios": "^1.4.0",
"command-exists": "^1.2.9",
"compression": "^1",
"cookie-parser": "^1.4.6",
@ -32,8 +31,7 @@
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",
"node-fetch": "^2.6.11",
"node-rest-client": "^3.1.1",
"open": "^8.4.0",
"open": "^8.4.2",
"piexifjs": "^1.0.6",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
@ -769,11 +767,6 @@
"node": ">=8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
@ -783,16 +776,6 @@
"node": ">= 4.0.0"
}
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -1011,17 +994,6 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/command-exists": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
@ -1205,14 +1177,6 @@
"node": ">=8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1481,38 +1445,6 @@
"node": ">= 0.8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -2272,40 +2204,6 @@
}
}
},
"node_modules/node-rest-client": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-rest-client/-/node-rest-client-3.1.1.tgz",
"integrity": "sha512-O8RUGGhGLLbzlL7SFOBza1AgUWP3uITv4mas4f5Q7A87HAy6qtYpa8Sj5x4UG9cDf4374v7lWyvgWladI04zzQ==",
"dependencies": {
"debug": "~4.3.3",
"follow-redirects": ">=1.14.7",
"xml2js": ">=0.4.23"
},
"engines": {
"node": "*"
}
},
"node_modules/node-rest-client/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/node-rest-client/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -2752,11 +2650,6 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -3581,18 +3474,6 @@
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="
},
"node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",

View File

@ -3,7 +3,6 @@
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
"@dqbd/tiktoken": "^1.0.2",
"axios": "^1.4.0",
"command-exists": "^1.2.9",
"compression": "^1",
"cookie-parser": "^1.4.6",
@ -23,8 +22,7 @@
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",
"node-fetch": "^2.6.11",
"node-rest-client": "^3.1.1",
"open": "^8.4.0",
"open": "^8.4.2",
"piexifjs": "^1.0.6",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",

View File

@ -60,6 +60,7 @@
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls {
display: flex;
justify-content: space-between;
font-size: calc(var(--mainFontSize)*1.2);
}
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls span {
@ -77,7 +78,7 @@
height: 20px;
width: 20px;
filter: drop-shadow(0px 0px 2px black);
opacity: 0.2;
opacity: 0.4;
}
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt span span:hover {
@ -171,6 +172,10 @@
color: var(--white30a);
}
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt:not(.completion_prompt_manager_prompt_disabled) .prompt-manager-toggle-action {
color: var(--SmartThemeQuoteColor);
}
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt.completion_prompt_manager_prompt_disabled {
border: 1px solid var(--white20a);
}

View File

@ -8,15 +8,10 @@ body.tts .mes_narrate {
display: inline-block;
}
body.no-hotswap .hotswap {
display: none !important;
}
body.no-timer .mes_timer {
display: none !important;
}
body.no-hotswap .hotswap,
body.no-timer .mes_timer,
body.no-timestamps .timestamp,
body.no-tokenCount .tokenCounterDisplay,
body.no-mesIDDisplay .mesIDDisplay,
body.no-modelIcons .icon-svg {
display: none !important;

View File

@ -1713,19 +1713,19 @@
"Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
"Context Size (tokens)": "Размер контекста (в токенах)",
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
"Temperature": "Temperature",
"Frequency Penalty": "Frequency Penalty",
"Presence Penalty": "Presence Penalty",
"Temperature": "Температура",
"Frequency Penalty": "Штраф за частоту",
"Presence Penalty": "Штраф за присутствие",
"Top-p": "Top-p",
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
"Top A": "Top-a",
"Typical Sampling": "Typical Sampling",
"Tail Free Sampling": "Tail Free Sampling",
"Rep. Pen. Slope": "Rep. Pen. Slope",
"Typical Sampling": "Типичная выборка",
"Tail Free Sampling": "Бесхвостовая выборка",
"Rep. Pen. Slope": "Rep. Pen. Склон",
"Single-line mode": "Режим одной строки",
"Top K": "Top-k",
"Top P": "Top-p",
"Do Sample": "Do Sample",
"Do Sample": "Сделать образец",
"Add BOS Token": "Добавить BOS-токен",
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять BOS-токен в начале инструкции. Выключение этого может сделать ответы более креативными. ",
"Ban EOS Token": "Заблокировать EOS-токен",
@ -1733,11 +1733,24 @@
"Skip Special Tokens": "Пропускать специальные токены",
"Beam search": "Поиск Beam",
"Number of Beams": "Количество Beam",
"Length Penalty": "Length Penalty",
"Length Penalty": "Штраф за длину",
"Early Stopping": "Преждевременная остановка",
"Contrastive search": "Contrastive search",
"Penalty Alpha": "Penalty Alpha",
"Contrastive search": "Контрастный поиск",
"Penalty Alpha": "Штраф Альфа",
"Seed": "Зерно",
"Epsilon Cutoff": "Отсечение эпсилона",
"Eta Cutoff": "Отсечка Eta",
"Negative Prompt": "Отрицательная подсказка",
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (режим = 1 только для llama.cpp)",
"Add text here that would make the AI generate things you don't want in your outputs.": "Добавьте сюда текст, который заставит ИИ генерировать то, что вы не хотите видеть в своих выводах",
"Phrase Repetition Penalty": "Штраф за повторение фразы",
"Preamble": "Преамбула",
"Use style tags to modify the writing style of the output.": "Используйте теги стиля, чтобы изменить стиль написания вывода.",
"Banned Tokens": "Запрещенные токены",
"Sequences you don't want to appear in the output. One per line.": "Последовательности, которые вы не хотите отображать в выводе. По одному на строку.",
"AI Module": "Модуль ИИ",
"Changes the style of the generated text.": "Изменяет стиль создаваемого текста.",
"Used if CFG Scale is unset globally, per chat or character": "Используется, если масштаб CFG не установлен глобально, для каждого чата или персонажа.",
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
"NSFW Encouraged": "Поощрять NSFW",
@ -1760,7 +1773,7 @@
"Prompt that is used when the Jailbreak toggle is on": "Инструкция, отправляемая ИИ при включенном JailBreak.",
"Impersonation prompt": "Инструкция для перевоплощения",
"Prompt that is used for Impersonation function": "Инструкция, отправляемая ИИ для генерации действий за пользователя",
"Logit Bias": "Logit Bias",
"Logit Bias": "Ошибка логита",
"Helps to ban or reenforce the usage of certain words": "Позволяет запретить или поощрять использование определенных слов",
"View / Edit bias preset": "Посмотреть/Настроить предустановку для bias",
"Add bias entry": "Добавить инструкцию в Bias",
@ -1770,21 +1783,27 @@
"Bot must send this back to confirm jailbreak": "Это сообщение будет отправлено ИИ при успешном включении JailBreak.",
"Character Note": "Заметки о персонаже",
"Influences bot behavior in its responses": "Влияет на поведение ИИ и его ответы.",
"Connect": "Подключить",
"Test Message": "Тестовое сообщение",
"API": "API",
"KoboldAI": "KoboldAI",
"Use Horde": "Использовать Horde",
"API url": "API URL",
"Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
"Adjust context size to worker capabilities": "Уточнить размер контекста для возможностей рабочих",
"Adjust response length to worker capabilities": "Уточнить длинну ответа для возможностей рабочий",
"Adjust context size to worker capabilities": "Уточнить размер контекста в соответствии с возможностями рабочих машин",
"Adjust response length to worker capabilities": "Уточнить длинну ответа в соответствии с возможностями рабочих машин",
"API key": "API-ключ",
"Register": "Регист",
"Get it here:": "Получить здесь:",
"Register": "Регистрация",
"View my Kudos": "Посмотреть мой рейтинг(Kudos)",
"Enter": "Вставьте",
"to use anonymous mode.": "чтобы использовать анонимный режим.",
"For privacy reasons": "В целях конфиденциальности API-ключ будет скрыт после перезагрузки страницы",
"Model": "Модель",
"Models": "Модели",
"Hold Control / Command key to select multiple models.": "Удерживайте Control / Command для выбора нескольких моделей.",
"Horde models not loaded": "Модели Horde не загружены",
"Not connected": "Нет подключения",
"Not connected...": "Не подключено...",
"Novel API key": "API-ключ для NovelAI",
"Follow": "Следуйте",
"these directions": "данным инструкциям",
@ -1793,19 +1812,43 @@
"Novel AI Model": "Модель NovelAI",
"Euterpe": "Euterpe",
"Krake": "Krake",
"No connection": "Нет подключения",
"If you are using:": "Если вы используете:",
"oobabooga/text-generation-webui": "",
"Make sure you run it with": "Убедитесь, что при запуске указали аргумент --api",
"Blocking API url": "Блокирующий API URL",
"Mancer AI": "",
"Use API key (Only required for Mancer)": "Нажмите на ячейку (и добавьте свой API ключ!):",
"Blocking API url": "Блокирующий API url",
"Example: http://127.0.0.1:5000/api": "Пример: http://127.0.0.1:5000/api",
"Streaming API url": "Потоковый API URL",
"Example: ws://127.0.0.1:5005/api/v1/stream": "Пример: ws://127.0.0.1:5005/api/v1/stream",
"Mancer API key": "Mancer API ключ",
"Example: https://neuro.mancer.tech/webui/MODEL/api": "Пример: https://neuro.mancer.tech/webui/MODEL/api",
"to get your OpenAI API key.": "для получения API-ключа OpenAI",
"Window AI Model": "Модель Window AI",
"OpenAI Model": "Модель OpenAI",
"Claude API Key": "Claude API ключ",
"Get your key from": "Получить ключ из",
"Anthropic's developer console": "Консоли разработчика Anthropic",
"Slack and Poe cookies will not work here, do not bother trying.": "Файлы cookie Slack и Poe здесь не подойдут, не пытайтесь.",
"Claude Model": "Модель Claude",
"Scale API Key": "Scale API ключ",
"Alt Method": "Альтернативный метод",
"AI21 API Key": "AI21 API ключ",
"AI21 Model": "Модель AI21",
"View API Usage Metrics": "Посмотреть статистику использования API",
"Show External models (provided by API)": "Показать \"сторонние\" модели (предоставленные API)",
"Bot": "Бот:",
"Allow fallback routes": "Разрешить резервные маршруты",
"Allow fallback routes Description": "Автоматически выбирает альтернативную модель, если выбранная модель не может удовлетворить ваш запрос.",
"OpenRouter API Key": "OpenRouter API ключ",
"Connect to the API": "Соединение с API",
"OpenRouter Model": "Модель OpenRouter",
"View Remaining Credits": "Посмотреть оставшиеся кредиты",
"Click Authorize below or get the key from": "Нажмите «Авторизовать» ниже или получите ключ от",
"Auto-connect to Last Server": "Автоматическое подключение к последнему серверу",
"View hidden API keys": "Посмотреть скрытые API-ключи",
"Advanced Formatting": "Расширенное форматирование",
"Context Template": "Шаблон контекста",
"AutoFormat Overrides": "Замена АвтоФормата",
"Disable description formatting": "Отключить форматирование описания",
"Disable personality formatting": "Отключить форматирование личности",
@ -1813,18 +1856,26 @@
"Disable example chats formatting": "Отключить форматирование примеров чата",
"Disable chat start formatting": "Отключить форматирование начала чата",
"Custom Chat Separator": "Пользовательское разделение чата",
"Instruct Mode": "Режим Instruct",
"Replace Macro in Custom Stopping Strings": "Заменить макрос в пользовательских стоп-строках",
"Strip Example Messages from Prompt": "Удалить примеры сообщений из подсказки",
"Story String": "Строка истории",
"Example Separator": "Пример разделителя",
"Chat Start": "Начало чата",
"Activation Regex": "Активация Regex",
"Instruct Mode": "Режим \"Инструктаж\"",
"Enabled": "Включен",
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
"Include Names": "Показывать имена",
"Force for Groups and Personas": "Усилия для Групп и Персон",
"System Prompt": "Системная инструкция",
"Instruct Mode Sequences": "Последовательности режима обучения",
"Input Sequence": "Input Sequence",
"Input Sequence": "Входная последовательность",
"Output Sequence": "Выходная последовательность",
"First Output Sequence": "Первая выходная последовательность",
"Last Output Sequence": "Последняя выходная последовательность",
"System Sequence Prefix": "Префикс системной последовательности",
"System Sequence Suffix": "Суффикс системной последовательности",
"Stop Sequence": "Stop Sequence",
"Stop Sequence": "Последовательность остановки",
"Context Formatting": "Форматирование контекста",
"Tokenizer": "Токенайзер",
"None / Estimated": "Отсутствует/Приблизительно",
@ -1847,6 +1898,9 @@
"Style Anchor": "Стиль Anchors",
"World Info": "Информация о мире",
"Scan Depth": "Глубина сканирования",
"Context %": "Процент контекста",
"Budget Cap": "Бюджетный лимит",
"(0 = disabled)": "(0 = отключено)",
"depth": "глубина",
"Token Budget": "Объем токенов",
"budget": "объем",
@ -1855,7 +1909,10 @@
"About soft prompts": "О мягких инструкциях",
"None": "Отсутствует",
"User Settings": "Настройки пользователя",
"UI Customization": "Настройки UI",
"UI Mode": "Режим интерфейса",
"UI Language": "Язык интерфейса",
"MovingUI Preset": "Предустановка MovingUI",
"UI Customization": "Настройки интерфейса",
"Avatar Style": "Стиль аватаров",
"Circle": "Круглые",
"Rectangle": "Прямоугольные",
@ -1867,6 +1924,14 @@
"No Text Shadows": "Отключить тень текста",
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
"Message Timer": "Таймер сообщений",
"Model Icon": "Показать значки модели",
"Lazy Chat Loading": "Ленивая загрузка чата",
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
"Advanced Character Search": "Расширенный поиск персонажей",
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
"Allow {{user}}: in bot messages": "Показать {{user}}: в ответах",
"Show tags in responses": "Показывать <теги> в ответах",
"Relaxed API URLS": "Смягченные URL-адреса API",
"Characters Hotswap": "Смена персонажей на лету",
"Movable UI Panels": "Перемещение панелей интерфейса",
"Reset Panels": "Сбросить панели",
@ -1894,6 +1959,7 @@
"Always disabled": "Всегда выключена",
"Automatic (desktop)": "Автоматически (системные настройки)",
"Always enabled": "Всегда включена",
"Debug Menu": "Меню отладки",
"Name": "Имя",
"Your Avatar": "Ваш Аватар",
"Extensions API:": "API для расширений",
@ -1977,9 +2043,9 @@
"Unrestricted maximum value for the context slider": "Неограниченное максимальное значение для ползунка с размером контекста",
"Chat Completion Source": "Источник для Chat Completion",
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
"Review the Privacy statement": "Посмотреть Privacy statement",
"Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
"Learn how to contribute your idel GPU cycles to the Horde": "Изучите, как использовать GPU в состоянии простоя на благо Horde",
"Trusted workers only": "Только доверенные рабочие",
"Trusted workers only": "Только доверенные рабочие машины",
"For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
"-- Horde models not loaded --": "--Модель Horde не загружена--",
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
@ -1993,7 +2059,7 @@
"Trim spaces": "Обрезать пробелы",
"Trim Incomplete Sentences": "Обрезать неоконченные предложения",
"Include Newline": "Использовать красную строку",
"Non-markdown strings": "Неподчеркиваемые Strings",
"Non-markdown strings": "Строки без разметки",
"Replace Macro in Sequences": "Заменить макросы в последовательности",
"Presets": "Предустановки",
"Separator": "Разделитель",
@ -2003,12 +2069,16 @@
"Active World(s)": "Активные миры",
"Character Lore Insertion Strategy": "Порядок включения сведений",
"Sorted Evenly": "Равномерная сортировка",
"Active World(s) for all chats": "Активные миры для всех чатов",
"-- World Info not found --": "-- Информация о мире не найдена --",
"--- Pick to Edit ---": "Редактировать",
"or": "или",
"Character Lore First": "Сначала сведения о персонаже",
"Global Lore First": "Сначала общие сведения",
"-- World Info not found --": "Информация о Мире не найдена",
"Recursive Scan": "Рекурсивное сканирование",
"Case Sensitive": "Учитывать регистр",
"Match whole words": "Только полное совпадение",
"Alert On Overflow": "Оповещение о переполнении",
"World/Lore Editor": "Редактировать Мир/Сведения",
"--- None ---": "---Отсутствует---",
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
@ -2045,8 +2115,12 @@
"Not Connected": "Не подключено",
"Persona Management": "Управление Персоной",
"Persona Description": "Описание Персоны",
"Your Persona": "Ваша Персона",
"Show notifications on switching personas": "Показывать уведомления о смене персоны",
"Blank": "Пустой",
"In Story String / Chat Completion: Before Character Card": "В строке истории / Дополнение диалога: Перед Карточкой Персонажа",
"In Story String / Chat Completion: After Character Card": "В строке истории / Дополнение диалога: После Карточки Персонажа",
"In Story String / Prompt Manager": "В строке истории/Менеджер подсказок",
"Top of Author's Note": "Перед Авторскими Заметками",
"Bottom of Author's Note": "После Авторских Заметок",
"How do I use this?": "Как мне это использовать?",
@ -2081,8 +2155,6 @@
"Samplers Order": "Порядок семплирования",
"Samplers will be applied in a top-down order. Use with caution.": "Семплирование будет применено в порядке сверху-вниз. Используйте с осторожностью.",
"Repetition Penalty": "Наказание за повторы",
"Epsilon Cutoff": "Epsilon Cutoff",
"Eta Cutoff": "Eta Cutoff",
"Rep. Pen. Range.": "Размер наказания за повторы",
"Rep. Pen. Freq.": "Частота наказания за повторы",
"Rep. Pen. Presence": "Наличие наказания за повторы",
@ -2093,7 +2165,8 @@
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Безлимитный контекст' для активации кусочной генерации",
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
"Continue": "Пролдолжить",
"Continue": "Продолжить",
"CFG Scale": "Масштаб CFG",
"Editing:": "Изменения",
"AI reply prefix": "Префикс Ответ ИИ",
"Custom Stopping Strings": "Настройка ограничивающий нитей",
@ -2244,7 +2317,7 @@
"Change persona image": "Сменить изображение личности",
"Delete persona": "Удалить личность"
},
"it-it": {
"it-it": {
"clickslidertips": "consigli per gli slider",
"kobldpresets": "Preset Kobold",
"guikoboldaisettings": "settaggi KoboldAI",
@ -2800,133 +2873,131 @@
"Select this as default persona for the new chats.": "Seleziona questo alterego come predefinito per tutte le nuove chat",
"Change persona image": "Cambia l'immagine del tuo alterego",
"Delete persona": "Elimina il tuo alterego",
"--- Pick to Edit ---": "--- Scegli per modificare ---",
"Add text here that would make the AI generate things you don't want in your outputs.": "Scrivi qui ciò che non vuoi l'IA generi nel suo output.",
"write short replies, write replies using past tense": "Scrivi risposte brevi, scrivi risposte usando il passato",
"Alert if your world info is greater than the allocated budget.": "Questo avvisa nel momento in cui le 'Info Mondo' consumano più di quanto allocato nel budget.",
"Clear your cookie": "Cancella i cookie",
"Restore new group chat prompt": "Ripristina il prompt della nuova chat di gruppo",
"Save movingUI changes to a new file": "Salva i cambiamenti apportati alla posizione dei pannelli dell'UI (MovingUI) in un nuovo file",
"Export all": "Esporta tutto",
"Import": "Importa",
"Insert": "Inserisci",
"New": "Nuovo",
"Prompts": "Prompt",
"Tokens": "Token",
"Reset current character": "Ripristina il personaggio attuale",
"(0 = disabled)": "(0 = disabilitato)",
"1 = disabled": "1 = disabilitato",
"Activation Regex": "Attivazione Regex",
"Active World(s) for all chats": "Attiva i Mondi per tutte le chat",
"Add character names": "Aggiungi i nomi dei personaggi",
"Add Memo": "Aggiungi note",
"Advanced Character Search": "Ricerca dei personaggi avanzata",
"Aggressive": "Aggressivo",
"AI21 Model": "Modello AI21",
"Alert On Overflow": "Avviso in caso di Overflow",
"Allow fallback routes": "Permetti fallback routes",
"Allow fallback routes Description": "Permetti la descrizione di fallback routes",
"Alt Method": "Metodo Alt",
"Alternate Greetings": "Alterna i saluti",
"Alternate Greetings Hint": "Suggerimenti per i saluti alternati",
"Alternate Greetings Subtitle": "Sottotitoli per i saluti alternati",
"Assistant Prefill": "Assistant Prefill",
"Banned Tokens": "Token banditi",
"Blank": "In bianco",
"Browser default": "Predefinito del browser",
"Budget Cap": "Limite budget",
"CFG": "CFG",
"CFG Scale": "CFG Scale",
"Changes the style of the generated text.": "Cambia lo stile del testo generato.",
"Character Negatives": "Character Negatives",
"Chat Negatives": "Chat Negatives",
"Chat Scenario Override": "Sovrascrittura dello scenario della chat",
"Chat Start": "Avvio della chat",
"Claude Model": "Modello Claude",
"Close chat": "Chiudi chat",
"Context %": "Context %",
"Context Template": "Context Template",
"Count Penalty": "Count Penalty",
"Example Separator": "Separatore d'esempio",
"Exclude Assistant suffix": "Escludi il suffisso assistente",
"Exclude the assistant suffix from being added to the end of prompt.": "Esclude il suffisso assistente dall'essere aggiunto alla fine del prompt.",
"Force for Groups and Personas": "Forzalo per gruppi e alterego",
"Global Negatives": "Global Negatives",
"In Story String / Chat Completion: After Character Card": "Nella stringa narrativa / Chat Completion: Dopo la 'Carta Personaggio'",
"In Story String / Chat Completion: Before Character Card": "Nella stringa narrativa / Chat Completion: Prima della 'Carta Personaggio",
"Instruct": "Instruct",
"Instruct Mode": "Modalità Instruct",
"Last Sequence": "Ultima sequenza",
"Lazy Chat Loading": "Caricamento svogliato della chat",
"Least tokens": "Token minimi",
"Light": "Leggero",
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
"Main": "Principale",
"Mancer API key": "Chiave API di Mancer",
"Mancer API url": "Url API di Mancer",
"May help the model to understand context. Names must only contain letters or numbers.": "Può aiutare il modello a comprendere meglio il contesto. I nomi devono contenere solo numeri e lettere.",
"Medium": "Medium",
"Mirostat": "Mirostat",
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (mode=1 è valido solo per llama.cpp)",
"Mirostat Eta": "Mirostat Eta",
"Mirostat LR": "Mirostat LR",
"Mirostat Mode": "Mirostat Mode",
"Mirostat Tau": "Mirostat Tau",
"Model Icon": "Icona del modello",
"Most tokens": "Token massimi",
"MovingUI Preset": "Preset MovingUI",
"Negative Prompt": "Prompt negativo",
"No Module": "Nessun modulo",
"NSFW": "NSFW",
"Nucleus Sampling": "Nucleus Sampling",
"Off": "Spento",
"OpenRouter API Key": "Chiave API di OpenRouter",
"OpenRouter Model": "Modello OpenRouter",
"or": "o",
"Phrase Repetition Penalty": "Phrase Repetition Penalty",
"Positive Prompt": "Prompt positivo",
"Preamble": "Premessa",
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "Sovrascrittura del prompt (Per le API di OpenAI/Claude/Scale, Window/OpenRouter, e la Modalità Instruct)",
"Prompt that is used when the NSFW toggle is O": "Prompt utilizzato quando l'interruttore NSFW è disattivato.",
"Prose Augmenter": "Prose Augmenter",
"Proxy Password": "Password proxy",
"Quick Edit": "Editing rapido",
"Random": "Casuale",
"Relaxed API URLS": "URL API sciatto",
"Replace Macro in Custom Stopping Strings": "Rimpiazza le macro nelle stringe d'arresto personalizzate",
"Scale": "Scale",
"Scale": "Scale",
"Sequences you don't want to appear in the output. One per line.": "Sequenze che non vuoi appaiano nell'output. Una per linea.",
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Impostato all'inizio degli Esempi di dialogo per indicare che un nuovo esempio di chat sta per iniziare.",
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio degli esempi di dialogo per indicare che una nuova chat sta per iniziare.",
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che una nuova chat sta per iniziare.",
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che un nuova chat di gruppo sta per iniziare.",
"Show External models (provided by API)": "Mostra modelli esterni (Forniti dall'API)",
"Show Notifications Show notifications on switching personas": "Mostra una notifica quando l'alterego viene cambiato",
"Show tags in responses": "Mostra i tag nelle risposte",
"Story String": "Stringa narrativa",
"Text Adventure": "Avventura testuale",
"Text Gen WebUI (ooba/Mancer) presets": "Preset Text Gen WebUI (ooba/Mancer)",
"Toggle Panels": "Interruttore pannelli",
"Top A Sampling": "Top A Sampling",
"Top K Sampling": "Top K Sampling",
"UI Language": "Linguaggio interfaccia grafica",
"Unlocked Context Size": "Sblocca dimensione contesto",
"Usage Stats": "Statistiche di utilizzo",
"Use AI21 Tokenizer": "Utilizza il Tokenizer di AI21",
"Use API key (Only required for Mancer)": "Utilizza la chiave API (Necessario soltanto per Mancer)",
"Use character author's note": "Utilizza le note d'autore del personaggio",
"Use character CFG scales": "Utilizza CFG scales del personaggio",
"Use Proxy password field instead. This input will be ignored.": "Utilizza il campo del password proxy al suo posto. Questo input verrà ignorato.",
"Use style tags to modify the writing style of the output": "Utilizza lo stile delle tag per modificare lo stile di scrittura in output",
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Utilizza il tokenizer appropiato per i modelli giurassici, visto che è più efficente di quello di GPT.",
"Used if CFG Scale is unset globally, per chat or character": "Usato se CFG Scale non è settato globalmente, per le chat o per i personaggi",
"Very aggressive": "Esageratamente aggressivo",
"Very light": "Esageratamente leggero",
"Welcome to SillyTavern!": "Benvenuto in SillyTavern!",
"Will be used as a password for the proxy instead of API key.": "Verrà usato come password per il proxy invece che la chiave API.",
"Window AI Model": "Modello Window AI",
"Your Persona": "Il tuo alterego"
"--- Pick to Edit ---": "--- Scegli per modificare ---",
"Add text here that would make the AI generate things you don't want in your outputs.": "Scrivi qui ciò che non vuoi l'IA generi nel suo output.",
"write short replies, write replies using past tense": "Scrivi risposte brevi, scrivi risposte usando il passato",
"Alert if your world info is greater than the allocated budget.": "Questo avvisa nel momento in cui le 'Info Mondo' consumano più di quanto allocato nel budget.",
"Clear your cookie": "Cancella i cookie",
"Restore new group chat prompt": "Ripristina il prompt della nuova chat di gruppo",
"Save movingUI changes to a new file": "Salva i cambiamenti apportati alla posizione dei pannelli dell'UI (MovingUI) in un nuovo file",
"Export all": "Esporta tutto",
"Import": "Importa",
"Insert": "Inserisci",
"New": "Nuovo",
"Prompts": "Prompt",
"Tokens": "Token",
"Reset current character": "Ripristina il personaggio attuale",
"(0 = disabled)": "(0 = disabilitato)",
"1 = disabled": "1 = disabilitato",
"Activation Regex": "Attivazione Regex",
"Active World(s) for all chats": "Attiva i Mondi per tutte le chat",
"Add character names": "Aggiungi i nomi dei personaggi",
"Add Memo": "Aggiungi note",
"Advanced Character Search": "Ricerca dei personaggi avanzata",
"Aggressive": "Aggressivo",
"AI21 Model": "Modello AI21",
"Alert On Overflow": "Avviso in caso di Overflow",
"Allow fallback routes": "Permetti fallback routes",
"Allow fallback routes Description": "Permetti la descrizione di fallback routes",
"Alt Method": "Metodo Alt",
"Alternate Greetings": "Alterna i saluti",
"Alternate Greetings Hint": "Suggerimenti per i saluti alternati",
"Alternate Greetings Subtitle": "Sottotitoli per i saluti alternati",
"Assistant Prefill": "Assistant Prefill",
"Banned Tokens": "Token banditi",
"Blank": "In bianco",
"Browser default": "Predefinito del browser",
"Budget Cap": "Limite budget",
"CFG": "CFG",
"CFG Scale": "CFG Scale",
"Changes the style of the generated text.": "Cambia lo stile del testo generato.",
"Character Negatives": "Character Negatives",
"Chat Negatives": "Chat Negatives",
"Chat Scenario Override": "Sovrascrittura dello scenario della chat",
"Chat Start": "Avvio della chat",
"Claude Model": "Modello Claude",
"Close chat": "Chiudi chat",
"Context %": "Context %",
"Context Template": "Context Template",
"Count Penalty": "Count Penalty",
"Example Separator": "Separatore d'esempio",
"Exclude Assistant suffix": "Escludi il suffisso assistente",
"Exclude the assistant suffix from being added to the end of prompt.": "Esclude il suffisso assistente dall'essere aggiunto alla fine del prompt.",
"Force for Groups and Personas": "Forzalo per gruppi e alterego",
"Global Negatives": "Global Negatives",
"In Story String / Chat Completion: After Character Card": "Nella stringa narrativa / Chat Completion: Dopo la 'Carta Personaggio'",
"In Story String / Chat Completion: Before Character Card": "Nella stringa narrativa / Chat Completion: Prima della 'Carta Personaggio",
"Instruct": "Instruct",
"Instruct Mode": "Modalità Instruct",
"Last Sequence": "Ultima sequenza",
"Lazy Chat Loading": "Caricamento svogliato della chat",
"Least tokens": "Token minimi",
"Light": "Leggero",
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
"Main": "Principale",
"Mancer API key": "Chiave API di Mancer",
"Mancer API url": "Url API di Mancer",
"May help the model to understand context. Names must only contain letters or numbers.": "Può aiutare il modello a comprendere meglio il contesto. I nomi devono contenere solo numeri e lettere.",
"Medium": "Medium",
"Mirostat": "Mirostat",
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (mode=1 è valido solo per llama.cpp)",
"Mirostat Eta": "Mirostat Eta",
"Mirostat LR": "Mirostat LR",
"Mirostat Mode": "Mirostat Mode",
"Mirostat Tau": "Mirostat Tau",
"Model Icon": "Icona del modello",
"Most tokens": "Token massimi",
"MovingUI Preset": "Preset MovingUI",
"Negative Prompt": "Prompt negativo",
"No Module": "Nessun modulo",
"NSFW": "NSFW",
"Nucleus Sampling": "Nucleus Sampling",
"Off": "Spento",
"OpenRouter API Key": "Chiave API di OpenRouter",
"OpenRouter Model": "Modello OpenRouter",
"or": "o",
"Phrase Repetition Penalty": "Phrase Repetition Penalty",
"Positive Prompt": "Prompt positivo",
"Preamble": "Premessa",
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "Sovrascrittura del prompt (Per le API di OpenAI/Claude/Scale, Window/OpenRouter, e la Modalità Instruct)",
"Prompt that is used when the NSFW toggle is O": "Prompt utilizzato quando l'interruttore NSFW è disattivato.",
"Prose Augmenter": "Prose Augmenter",
"Proxy Password": "Password proxy",
"Quick Edit": "Editing rapido",
"Random": "Casuale",
"Relaxed API URLS": "URL API sciatto",
"Replace Macro in Custom Stopping Strings": "Rimpiazza le macro nelle stringe d'arresto personalizzate",
"Scale": "Scale",
"Sequences you don't want to appear in the output. One per line.": "Sequenze che non vuoi appaiano nell'output. Una per linea.",
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Impostato all'inizio degli Esempi di dialogo per indicare che un nuovo esempio di chat sta per iniziare.",
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che una nuova chat sta per iniziare.",
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che un nuova chat di gruppo sta per iniziare.",
"Show External models (provided by API)": "Mostra modelli esterni (Forniti dall'API)",
"Show Notifications Show notifications on switching personas": "Mostra una notifica quando l'alterego viene cambiato",
"Show tags in responses": "Mostra i tag nelle risposte",
"Story String": "Stringa narrativa",
"Text Adventure": "Avventura testuale",
"Text Gen WebUI (ooba/Mancer) presets": "Preset Text Gen WebUI (ooba/Mancer)",
"Toggle Panels": "Interruttore pannelli",
"Top A Sampling": "Top A Sampling",
"Top K Sampling": "Top K Sampling",
"UI Language": "Linguaggio interfaccia grafica",
"Unlocked Context Size": "Sblocca dimensione contesto",
"Usage Stats": "Statistiche di utilizzo",
"Use AI21 Tokenizer": "Utilizza il Tokenizer di AI21",
"Use API key (Only required for Mancer)": "Utilizza la chiave API (Necessario soltanto per Mancer)",
"Use character author's note": "Utilizza le note d'autore del personaggio",
"Use character CFG scales": "Utilizza CFG scales del personaggio",
"Use Proxy password field instead. This input will be ignored.": "Utilizza il campo del password proxy al suo posto. Questo input verrà ignorato.",
"Use style tags to modify the writing style of the output": "Utilizza lo stile delle tag per modificare lo stile di scrittura in output",
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Utilizza il tokenizer appropiato per i modelli giurassici, visto che è più efficente di quello di GPT.",
"Used if CFG Scale is unset globally, per chat or character": "Usato se CFG Scale non è settato globalmente, per le chat o per i personaggi",
"Very aggressive": "Esageratamente aggressivo",
"Very light": "Esageratamente leggero",
"Welcome to SillyTavern!": "Benvenuto in SillyTavern!",
"Will be used as a password for the proxy instead of API key.": "Verrà usato come password per il proxy invece che la chiave API.",
"Window AI Model": "Modello Window AI",
"Your Persona": "Il tuo alterego"
},
"nl-nl": {
"clickslidertips": "klikregel tips",
@ -3615,4 +3686,4 @@
"Rep. Pen. Freq.": "Rep. Pen. Freq.",
"Rep. Pen. Presence": "Rep. Pen. Presence."
}
}
}

View File

@ -191,7 +191,7 @@
<div data-newbie-hidden id="ai_module_block_novel" class="width100p">
<div class="range-block">
<div class="range-block-title justifyLeft">
<div class="range-block-title justifyLeft" data-i18n="AI Module">
AI Module
</div>
<div class="toggle-description justifyLeft" data-i18n="Changes the style of the generated text.">
@ -1018,11 +1018,67 @@
Single-line mode</span>
</label>
</div>
<div class="range-block">
<label class="checkbox_label" for="use_default_badwordids">
<input id="use_default_badwordids" type="checkbox" />
<span data-i18n="Ban EOS Token">
Ban EOS Token
</span>
</label>
</div>
<hr>
<h4 data-i18n="Mirostat">Mirostat</h4>
<div class="range-block">
<div class="range-block-title" data-i18n="Mirostat Mode">
Mirostat Mode
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="mirostat_mode_kobold" name="volume" min="0" max="2" step="1" />
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="mirostat_mode_kobold" id="mirostat_mode_counter_kobold">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Mirostat Tau">
Mirostat Tau
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="mirostat_tau_kobold" name="volume" min="0" max="10" step="0.01" />
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="mirostat_tau_kobold" id="mirostat_tau_counter_kobold">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Mirostat Eta">
Mirostat Eta
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="mirostat_eta_kobold" name="volume" min="0" max="1" step="0.01" />
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="mirostat_eta_kobold" id="mirostat_eta_counter_kobold">
select
</div>
</div>
</div>
</div>
<hr>
<div class="range-block flexFlowColumn">
<div class="range-block-title">
<span data-i18n="Samplers Order">Samplers Order</span>
</div>
<div class="toggle-description" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
<div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
Samplers will be applied in a top-down order.
Use with caution.
</div>
@ -1070,7 +1126,7 @@
<div class="fa-solid fa-clock-rotate-left "></div>
</div>
</div>
<div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output">
<div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output.">
Use style tags to modify the writing style of the output.
</div>
<div class="wide100p">
@ -1240,7 +1296,7 @@
<div class="range-block-title">
<span data-i18n="Samplers Order">Samplers Order</span>
</div>
<div class="toggle-description" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
<div class="toggle-description widthUnset" data-i18n="Samplers will be applied in a top-down order. Use with caution.">
Samplers will be applied in a top-down order. Use with caution.
</div>
<div id="novel_order" class="prompt_order">
@ -1350,7 +1406,7 @@
</div>
</div>
<div class="range-block">
<div class="range-block-title">
<div class="range-block-title" data-i18n="Tail Free Sampling">
Tail Free Sampling
</div>
<div class="range-block-range-and-counter">
@ -1691,8 +1747,9 @@
</label>
<h4 data-i18n="API key">API key</h4>
<small>Get it here: <a target="_blank" href="https://horde.koboldai.net/register">Register</a> (<a id="horde_kudos" href="javascript:void(0);">View my Kudos</a>)<br>
Enter <span class="monospace">0000000000</span> to use anonymous mode.
<small>
<span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://horde.koboldai.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
<span data-i18n="Enter">Enter </span> <span class="monospace">0000000000</span> <span data-i18n="to use anonymous mode.">to use anonymous mode. </span>
</small>
<!-- <div>
<a id="horde_kudos" href="javascript:void(0);">View my Kudos</a>
@ -1705,7 +1762,7 @@
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<h4 class="horde_model_title">
Models
<span data-i18n="Models">Models </span>
<div id="horde_refresh" title="Refresh models" data-i18n="[title]Refresh models" class="right_menu_button">
<div class="fa-solid fa-repeat "></div>
</div>
@ -1716,7 +1773,7 @@
</div>
<div id="online_status_horde">
<div id="online_status_indicator_horde"></div>
<div id="online_status_text_horde" data-i18n="Not connected">Not connected</div>
<div id="online_status_text_horde" data-i18n="Not connected...">Not connected...</div>
</div>
</form>
</div>
@ -1726,12 +1783,12 @@
<h4 data-i18n="API url">API url</h4>
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
<input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off">
<input id="api_button" class="menu_button" type="submit" value="Connect">
<div id="api_button" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
<div id="api_loading" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
</div>
<div id="online_status2">
<div id="online_status_indicator2"></div>
<div id="online_status_text2" data-i18n="Not connected">Not connected</div>
<div id="online_status_text2" data-i18n="Not connected...">Not connected...</div>
</div>
</form>
</div>
@ -1756,7 +1813,7 @@
<div data-for="api_key_novel" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<input id="api_button_novel" class="menu_button" type="submit" value="Connect">
<div id="api_button_novel" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
<div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
<h4><span data-i18n="Novel AI Model">Novel AI Model</span>
<a href="https://docs.sillytavern.app/usage/api-connections/novelai/#models" class="notes-link" target="_blank">
@ -1772,12 +1829,12 @@
</form>
<div id="online_status3">
<div id="online_status_indicator3"></div>
<div id="online_status_text3" data-i18n="No connection...">No connection...</div>
<div id="online_status_text3" data-i18n="No connection...">No connection... </div>
</div>
</div>
<div id="textgenerationwebui_api" style="display: none;position: relative;">
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">
If you are using:
<span data-i18n="If you are using:"> If you are using:</span>
<div class="flex-container indent20p">
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
oobabooga/text-generation-webui
@ -1810,29 +1867,29 @@
</div>
<div class="flex1">
<h4 data-i18n="Mancer API url">Mancer API url</h4>
<small>Example: https://neuro.mancer.tech/webui/MODEL/api</small>
<small data-i18n="Example: https://neuro.mancer.tech/webui/MODEL/api">Example: https://neuro.mancer.tech/webui/MODEL/api </small>
<input id="mancer_api_url_text" name="mancer_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
</div>
<div id="tgwebui_api_subpanel" class="flex-container flexFlowColumn">
<div class="flex1">
<h4 data-i18n="Blocking API url">Blocking API url</h4>
<small>Example: http://127.0.0.1:5000/api</small>
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
<div class="flex1">
<h4 data-i18n="Streaming API url">Streaming API url</h4>
<small>Example: ws://127.0.0.1:5005/api/v1/stream</small>
<small data-i18n="Example: ws://127.0.0.1:5005/api/v1/stream">Example: ws://127.0.0.1:5005/api/v1/stream </small>
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
</div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
<div id="api_button_textgenerationwebui" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
</div>
</form>
<div class="online_status4">
<div class="online_status_indicator4"></div>
<div class="online_status_text4" data-i18n="Not connected">Not connected</div>
<div class="online_status_text4" data-i18n="Not connected...">Not connected...</div>
</div>
</div>
<div id="openai_api" style="display: none;position: relative;">
@ -1916,10 +1973,10 @@
<form id="claude_form" data-source="claude" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<h4>Claude API Key</h4>
<div>
Get your key from <a target="_blank" href="https://console.anthropic.com/account/keys">Anthropic's developer console</a>.
<span data-i18n="Get your key from">Get your key from </span> <a target="_blank" href="https://console.anthropic.com/account/keys" data-i18n="Anthropic's developer console">Anthropic's developer console</a>.
</div>
<div>
<b>
<b data-i18n="Slack and Poe cookies will not work here, do not bother trying.">
Slack and Poe cookies will not work here, do not bother trying.
</b>
</div>
@ -1996,10 +2053,10 @@
</div>
<h4 data-i18n="OpenRouter API Key">OpenRouter API Key</h4>
<div>
<small>
Click "Authorize" below or get the key from <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
</small><br>
<a href="https://openrouter.ai/account" target="_blank">View Remaining Credits</a>
<small data-i18n="Click Authorize below or get the key from">
Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
<br>
<a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a>
</div>
<div class="flex-container">
<input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
@ -2012,7 +2069,7 @@
<form id="scale_form" data-source="scale" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<div id="normal_scale_form">
<h4>Scale API Key</h4>
<h4 data-i18n="Scale API Key">Scale API Key</h4>
<div class="flex-container">
<input id="api_key_scale" name="api_key_scale" class="text_pole flex1" maxlength="500" value="" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_scale"></div>
@ -2042,7 +2099,7 @@
</form>
<form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<h4>AI21 API Key</h4>
<h4 data-i18n="AI21 API Key">AI21 API Key</h4>
<div class="flex-container">
<input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_ai21"></div>
@ -2063,14 +2120,14 @@
</form>
<div class="flex-container flex">
<input id="api_button_openai" class="menu_button" type="submit" value="Connect">
<input data-source="openrouter" id="openrouter_authorize" class="menu_button" type="button" value="Authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">
<input id="test_api_button" class="menu_button" type="button" value="Test Message" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!">
<div id="api_button_openai" class="menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
<div data-source="openrouter" id="openrouter_authorize" class="menu_button menu_button_icon" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div id="test_api_button" class="menu_button menu_button_icon" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!">Test Message</div>
</div>
<div id="api_loading_openai" class=" api-load-icon fa-solid fa-hourglass fa-spin"></div>
<div class="online_status4">
<div class="online_status_indicator4"></div>
<div class="online_status_text4">No connection...</div>
<div class="online_status_text4" data-i18n="No connection...">No connection...</div>
</div>
</div>
</div>
@ -2133,7 +2190,45 @@
</div>
</div>
</div>
<div data-newbie-hidden class="inline-drawer wide100p flexFlowColumn margin-bot-10px" style="display:none;">
<div class="inline-drawer-toggle inline-drawer-header">
<b><span data-i18n="Context Order">Context Order</span></b>
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<div id="context_order" class="prompt_order">
<div data-id="0">
<span data-i18n="Story String">Story String</span>
<small>0</small>
</div>
<div data-id="1">
<span data-i18n="Summary">Summary</span>
<small>1</small>
</div>
<div data-id="2">
<span data-i18n="Author's Note">Author's Note</span>
<small>2</small>
</div>
<div data-id="3">
<span data-i18n="Example Dialogues">Example Dialogues</span>
<small>3</small>
</div>
<div data-id="4">
<span data-i18n="Chat Start">Chat History</span>
<small>4</small>
</div>
</div>
<small>
<b data-i18n="Hint">Hint:</b>
<span data-i18n="In-Chat Position not affected">
Summary and Author's Note orders are only affected when they don't have an In-Chat position set.
</span>
</small>
</div>
</div>
</div>
<div>
<h4 data-i18n="Instruct Mode">Instruct Mode
<a href="https://docs.sillytavern.app/usage/core-concepts/instructmode/" class="notes-link" target="_blank">
@ -2208,7 +2303,7 @@
<small data-i18n="Input Sequence">Input Sequence</small>
</label>
<div>
<textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@ -2216,7 +2311,7 @@
<small data-i18n="Output Sequence">Output Sequence</small>
</label>
<div>
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
@ -2226,7 +2321,7 @@
<small data-i18n="First Output Sequence">First Output Sequence</small>
</label>
<div>
<textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_first_output_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@ -2234,7 +2329,7 @@
<small data-i18n="Last Output Sequence">Last Output Sequence</small>
</label>
<div>
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
@ -2244,7 +2339,7 @@
<small data-i18n="System Sequence Prefix">System Sequence Prefix</small>
</label>
<div>
<textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_system_sequence_prefix" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@ -2252,7 +2347,7 @@
<small data-i18n="System Sequence Suffix">System Sequence Suffix</small>
</label>
<div>
<textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_system_sequence_suffix" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
@ -2262,7 +2357,7 @@
<small data-i18n="Stop Sequence">Stop Sequence</small>
</label>
<div>
<textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="200k0" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@ -2270,7 +2365,7 @@
<small data-i18n="Separator">Separator</small>
</label>
<div>
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="500" placeholder="&mdash;" rows="1"></textarea>
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
@ -2320,7 +2415,9 @@
</label>
<label data-newbie-hidden class="checkbox_label" for="remove-examples-checkbox">
<input id="remove-examples-checkbox" type="checkbox" />
Strip Example Messages from Prompt
<span data-i18n="Strip Example Messages from Prompt">
Strip Example Messages from Prompt
</span>
</label>
<label class="checkbox_label" for="collapse-newlines-checkbox"><input id="collapse-newlines-checkbox" type="checkbox" />
<span data-i18n="Remove Empty New Lines from Output">
@ -2761,7 +2858,7 @@
<div data-newbie-hidden class="range-block">
<div class="range-block-title">
<span data-i18n="Lazy Chat Loading">Lazy Chat Loading</span><br>
<small># of messages (0 = disabled)</small>
<small data-i18n="# of messages (0 = disabled)"># of messages (0 = disabled)</small>
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
@ -2841,6 +2938,11 @@
<span data-i18n="Message IDs">Show Message IDs</span>
</label>
<label data-newbie-hidden for="messageTokensEnabled" class="checkbox_label">
<input id="messageTokensEnabled" type="checkbox" />
<span data-i18n="Show Message Token Count">Show Message Token Count</span>
</label>
<label data-newbie-hidden for="auto_scroll_chat_to_bottom" class="checkbox_label">
<input id="auto_scroll_chat_to_bottom" type="checkbox" />
<span data-i18n="Auto-scroll Chat">Auto-scroll Chat</span>
@ -3035,7 +3137,7 @@
</a>
</h3>
<div class="flex-container">
<div id="extensions_status" data-i18n="Not Connected">Not Connected</div>
<div id="extensions_status" data-i18n="Not connected...">Not connected...</div>
<label for="extensions_autoconnect">
<input id="extensions_autoconnect" type="checkbox">
<span data-i18n="Auto-connect">Auto-connect</span>
@ -3047,7 +3149,7 @@
<input id="extensions_api_key" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]API key" placeholder="API key">
<div class="alignitemsflexstart extensions_url_block">
<div class="" style="text-align: center">
<input id="extensions_connect" class="menu_button" type="submit" value="Connect">
<input id="extensions_connect" class="menu_button" type="submit" data-i18n="Connect">Connect</input>
</div>
<input id="extensions_details" class="alignitemsflexstart menu_button" type="button" value="Manage extensions">
<div id="third_party_extension_button" title="Import Extension From Git Repo" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
@ -3100,7 +3202,7 @@
<div class="range-block">
<label for="persona_show_notifications" class="checkbox_label">
<input id="persona_show_notifications" type="checkbox" />
<span data-i18n="Show Notifications Show notifications on switching personas">
<span data-i18n="Show notifications on switching personas">
Show notifications on switching personas
</span>
</label>
@ -4010,6 +4112,7 @@
</div>
<div class="mesIDDisplay"></div>
<div class="mes_timer"></div>
<div class="tokenCounterDisplay"></div>
</div>
<div class="swipe_left fa-solid fa-chevron-left"></div>
<div class="mes_block">
@ -4392,7 +4495,7 @@
<div class="panelControlBar flex-container">
<div class="fa-solid fa-grip drag-grabber"></div>
<div class="fa-solid fa-circle-xmark dragClose"></div>
</div>
</div>
</div>
</template>
@ -4445,4 +4548,4 @@
</script>
</body>
</html>
</html>

View File

@ -6,9 +6,8 @@ import {
loadKoboldSettings,
formatKoboldUrl,
getKoboldGenerationData,
canUseKoboldStopSequence,
canUseKoboldStreaming,
canUseKoboldTokenization,
kai_flags,
setKoboldFlags,
} from "./scripts/kai-settings.js";
import {
@ -280,7 +279,8 @@ export const event_types = {
CHATCOMPLETION_SOURCE_CHANGED: 'chatcompletion_source_changed',
CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed',
OAI_BEFORE_CHATCOMPLETION: 'oai_before_chatcompletion',
OAI_PRESET_CHANGED: 'oai_preset_changed',
OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before',
OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after',
WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated',
CHARACTER_EDITED: 'character_edited',
USER_MESSAGE_RENDERED: 'user_message_rendered',
@ -702,9 +702,16 @@ $.ajaxPrefilter((options, originalOptions, xhr) => {
xhr.setRequestHeader("X-CSRF-Token", token);
});
///// initialization protocol ////////
$.get("/csrf-token").then(async (data) => {
token = data.token;
async function firstLoadInit() {
try {
const tokenResponse = await fetch('/csrf-token');
const tokenData = await tokenResponse.json();
token = tokenData.token;
} catch {
toastr.error("Couldn't get CSRF token. Please refresh the page.", "Error", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
throw new Error("Initialization failed");
}
getSystemMessages();
sendSystemMessage(system_message_types.WELCOME);
await readSecretState();
@ -713,7 +720,10 @@ $.get("/csrf-token").then(async (data) => {
await getUserAvatars();
await getCharacters();
await getBackgrounds();
});
initAuthorsNote();
initPersonas();
initRossMods();
}
function checkOnlineStatus() {
///////// REMOVED LINES THAT DUPLICATE RA_CHeckOnlineStatus FEATURES
@ -798,9 +808,7 @@ async function getStatus() {
// determine if we can use stop sequence and streaming
if (main_api === "kobold" || main_api === "koboldhorde") {
kai_settings.use_stop_sequence = canUseKoboldStopSequence(data.version);
kai_settings.can_use_streaming = canUseKoboldStreaming(data.koboldVersion);
kai_settings.can_use_tokenization = canUseKoboldTokenization(data.koboldVersion);
setKoboldFlags(data.version, data.koboldVersion);
}
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
@ -1236,7 +1244,7 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
}
if (power_user.auto_fix_generated_markdown) {
mes = fixMarkdown(mes);
mes = fixMarkdown(mes, true);
}
if (!isSystem && power_user.encode_tags) {
@ -1299,7 +1307,7 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
* the function fetches the "claude.svg". Otherwise, it fetches the SVG named after
* the value in `extra.api`.
*
* @param {jQuery} mes - The message element containing the timestamp where the icon should be inserted or replaced.
* @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.
* @param {Object} extra - Contains the API and model details.
* @param {string} extra.api - The name of the API, used to determine which SVG to fetch.
* @param {string} extra.model - The model name, used to check for the substring "claude".
@ -1361,6 +1369,7 @@ function getMessageFromTemplate({
bookmarkLink,
forceAvatar,
timestamp,
tokenCount,
extra,
} = {}) {
const mes = $('#message_template .mes').clone();
@ -1378,6 +1387,7 @@ function getMessageFromTemplate({
mes.find('.mes_bias').html(bias);
mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
mes.find('.mesIDDisplay').text(`#${mesId}`);
tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
title && mes.attr('title', title);
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
@ -1508,7 +1518,8 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
forceAvatar: mes.force_avatar,
timestamp: timestamp,
extra: mes.extra,
...formatGenerationTimer(mes.gen_started, mes.gen_finished),
tokenCount: mes.extra?.token_count,
...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count),
};
const HTMLForEachMes = getMessageFromTemplate(params);
@ -1581,20 +1592,23 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
});
if (type === 'swipe') {
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.mes_text').html('');
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.mes_text').append(messageText);
appendImageToMessage(mes, $("#chat").find(`[mesid="${count_view_mes - 1}"]`));
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).attr('title', title);
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
const swipeMessage = $("#chat").find(`[mesid="${count_view_mes - 1}"]`);
swipeMessage.find('.mes_text').html('');
swipeMessage.find('.mes_text').append(messageText);
appendImageToMessage(mes, swipeMessage);
swipeMessage.attr('title', title);
swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
if (power_user.timestamp_model_icon && params.extra?.api) {
insertSVGIcon($("#chat").find(`[mesid="${count_view_mes - 1}"]`), params.extra);
insertSVGIcon(swipeMessage, params.extra);
}
if (mes.swipe_id == mes.swipes.length - 1) {
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.mes_timer').text(params.timerValue);
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.mes_timer').attr('title', params.timerTitle);
swipeMessage.find('.mes_timer').text(params.timerValue);
swipeMessage.find('.mes_timer').attr('title', params.timerTitle);
swipeMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`);
} else {
$("#chat").find(`[mesid="${count_view_mes - 1}"]`).find('.mes_timer').html('');
swipeMessage.find('.mes_timer').html('');
swipeMessage.find('.tokenCounterDisplay').html('');
}
} else {
$("#chat").find(`[mesid="${count_view_mes}"]`).find('.mes_text').append(messageText);
@ -1620,7 +1634,18 @@ function getUserAvatar(avatarImg) {
return `User Avatars/${avatarImg}`;
}
function formatGenerationTimer(gen_started, gen_finished) {
/**
* Formats the title for the generation timer.
* @param {Date} gen_started Date when generation was started
* @param {Date} gen_finished Date when generation was finished
* @param {number} tokenCount Number of tokens generated (0 if not available)
* @returns {Object} Object containing the formatted timer value and title
* @example
* const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount);
* console.log(timerValue); // 1.2s
* console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s
*/
function formatGenerationTimer(gen_started, gen_finished, tokenCount) {
if (!gen_started || !gen_finished) {
return {};
}
@ -1634,6 +1659,7 @@ function formatGenerationTimer(gen_started, gen_finished) {
`Generation queued: ${start.format(dateFormat)}`,
`Reply received: ${finish.format(dateFormat)}`,
`Time to generate: ${seconds} seconds`,
tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '',
].join('\n');
return { timerValue, timerTitle };
@ -1998,7 +2024,7 @@ function baseChatReplace(value, name1, name2) {
function isStreamingEnabled() {
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE && oai_settings.chat_completion_source !== chat_completion_sources.AI21)
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_settings.can_use_streaming)
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
|| (main_api == 'novel' && nai_settings.streaming_novel)
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
&& !isMultigenEnabled(); // Multigen has a quasi-streaming mode which breaks the real streaming
@ -2086,12 +2112,24 @@ class StreamingProcessor {
}
else {
let currentTime = new Date();
const timePassed = formatGenerationTimer(this.timeStarted, currentTime);
// Don't waste time calculating token count for streaming
let currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0;
const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount);
chat[messageId]['is_name'] = isName;
chat[messageId]['mes'] = processedText;
chat[messageId]['gen_started'] = this.timeStarted;
chat[messageId]['gen_finished'] = currentTime;
if (currentTokenCount) {
if (!chat[messageId]['extra']) {
chat[messageId]['extra'] = {};
}
chat[messageId]['extra']['token_count'] = currentTokenCount;
const tokenCounter = $(`#chat .mes[mesid="${messageId}"] .tokenCounterDisplay`);
tokenCounter.text(`${currentTokenCount}t`);
}
if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {
chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
@ -2278,7 +2316,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
return;
}
if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_settings.can_use_streaming) {
if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true, });
is_send_press = false;
return;
@ -3327,6 +3365,10 @@ export async function sendMessageAsUser(textareaText, messageBias) {
chat[chat.length - 1]['mes'] = substituteParams(textareaText);
chat[chat.length - 1]['extra'] = {};
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
// Lock user avatar to a persona.
if (user_avatar in power_user.personas) {
chat[chat.length - 1]['force_avatar'] = getUserAvatar(user_avatar);
@ -3862,7 +3904,7 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete
}
}
if (power_user.auto_fix_generated_markdown) {
getMessage = fixMarkdown(getMessage);
getMessage = fixMarkdown(getMessage, false);
}
return getMessage;
}
@ -3892,6 +3934,9 @@ async function saveReply(type, getMessage, this_mes_is_name, title) {
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
@ -3908,6 +3953,9 @@ async function saveReply(type, getMessage, this_mes_is_name, title) {
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
@ -3921,6 +3969,9 @@ async function saveReply(type, getMessage, this_mes_is_name, title) {
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
@ -3943,6 +3994,10 @@ async function saveReply(type, getMessage, this_mes_is_name, title) {
chat[chat.length - 1]['gen_started'] = generation_started;
chat[chat.length - 1]['gen_finished'] = generationFinished;
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
if (selected_group) {
console.debug('entering chat update for groups');
let avatarImg = 'img/ai4.png';
@ -4614,9 +4669,6 @@ export async function getUserAvatars() {
}
}
function highlightSelectedAvatar() {
$("#user_avatar_block").find(".avatar").removeClass("selected");
$("#user_avatar_block")
@ -4664,7 +4716,6 @@ export function setUserName(value) {
saveSettings("change_name");
}
function setUserAvatar() {
user_avatar = $(this).attr("imgfile");
reloadUserAvatar();
@ -4728,8 +4779,6 @@ async function uploadUserAvatar(e) {
$("#form_upload_avatar").trigger("reset");
}
async function doOnboarding(avatarId) {
let simpleUiMode = false;
const template = $('#onboarding_template .onboarding');
@ -5022,18 +5071,6 @@ export function setGenerationParamsFromPreset(preset) {
}
}
function setCharacterBlockHeight() {
const $children = $("#rm_print_characters_block").children();
const originalHeight = $children.length * $children.find(':visible').first().outerHeight();
$("#rm_print_characters_block").css('height', originalHeight);
//show and hide charlist divs on pageload (causes load lag)
//$children.each(function () { setCharListVisible($(this)) });
//delay timer to allow for charlist to populate,
//should be set to an onload for rm_print_characters or windows?
}
// Common code for message editor done and auto-save
function updateMessage(div) {
const mesBlock = div.closest(".mes_block");
@ -6390,6 +6427,18 @@ function swipe_left() { // when we swipe left..but no generation.
const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
//console.log('on left swipe click calling addOneMessage');
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
if (power_user.message_token_count_enabled) {
if (!chat[chat.length - 1].extra) {
chat[chat.length - 1].extra = {};
}
const swipeMessage = $("#chat").find(`[mesid="${count_view_mes - 1}"]`);
const tokenCount = getTokenCount(chat[chat.length - 1].mes, 0);
chat[chat.length -1]['extra']['token_count'] = tokenCount;
swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
}
let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
if (new_height < 103) new_height = 103;
this_mes_div.animate({ height: new_height + 'px' }, {
@ -6545,23 +6594,27 @@ const swipe_right = () => {
const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
//console.log(parseInt(chat[chat.length-1]['swipe_id']));
//console.log(chat[chat.length-1]['swipes'].length);
const swipeMessage = $("#chat").find('[mesid="' + (count_view_mes - 1) + '"]');
if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
//console.log('showing ""..."');
/* if (!selected_group) {
} else { */
$("#chat")
.find('[mesid="' + (count_view_mes - 1) + '"]')
.find('.mes_text')
.html('...'); //shows "..." while generating
$("#chat")
.find('[mesid="' + (count_view_mes - 1) + '"]')
.find('.mes_timer')
.html(''); // resets the timer
/* } */
//shows "..." while generating
swipeMessage.find('.mes_text').html('...');
// resets the timer
swipeMessage.find('.mes_timer').html('');
swipeMessage.find('.tokenCounterDisplay').text('');
} else {
//console.log('showing previously generated swipe candidate, or "..."');
//console.log('onclick right swipe calling addOneMessage');
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
if (power_user.message_token_count_enabled) {
if (!chat[chat.length - 1].extra) {
chat[chat.length - 1].extra = {};
}
const tokenCount = getTokenCount(chat[chat.length - 1].mes, 0);
chat[chat.length -1]['extra']['token_count'] = tokenCount;
swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
}
}
let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
if (new_height < 103) new_height = 103;
@ -6855,7 +6908,6 @@ export async function handleDeleteCharacter(popup_type, this_chid, delete_chats)
}
}
/**
* Function to delete a character from UI after character deletion API success.
* It manages necessary UI changes such as closing advanced editing popup, unsetting
@ -6889,8 +6941,7 @@ function doTogglePanels() {
$("#option_settings").trigger('click')
}
$(document).ready(function () {
jQuery(async function () {
if (isMobile() === true) {
console.debug('hiding movingUI and sheldWidth toggles for mobile')
@ -7178,6 +7229,7 @@ $(document).ready(function () {
$("#character_popup").css("display", "none");
}
});
$("#character_cross").click(function () {
is_advanced_char_open = false;
$("#character_popup").transition({
@ -7187,10 +7239,12 @@ $(document).ready(function () {
});
setTimeout(function () { $("#character_popup").css("display", "none"); }, 200);
});
$("#character_popup_ok").click(function () {
is_advanced_char_open = false;
$("#character_popup").css("display", "none");
});
$("#dialogue_popup_ok").click(async function (e) {
$("#shadow_popup").transition({
opacity: 0,
@ -7285,6 +7339,7 @@ $(document).ready(function () {
dialogueResolve = null;
}
});
$("#dialogue_popup_cancel").click(function (e) {
$("#shadow_popup").transition({
opacity: 0,
@ -7817,8 +7872,6 @@ $(document).ready(function () {
}
});
const sliders = [
{
sliderId: "#amount_gen",
@ -7908,7 +7961,6 @@ $(document).ready(function () {
$('#rawPromptPopup').toggle();
})
//********************
//***Message Editor***
$(document).on("click", ".mes_edit", async function () {
@ -8155,7 +8207,6 @@ $(document).ready(function () {
setUserName($('#your_name').val());
});
$('#sync_name_button').on('click', async function () {
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
@ -8200,6 +8251,7 @@ $(document).ready(function () {
$("#character_import_button").click(function () {
$("#character_import_file").click();
});
$("#character_import_file").on("change", function (e) {
$("#rm_info_avatar").html("");
if (!e.target.files.length) {
@ -8210,10 +8262,12 @@ $(document).ready(function () {
importCharacter(file);
}
});
$("#export_button").on('click', function (e) {
$('#export_format_popup').toggle();
exportPopper.update();
});
$(document).on('click', '.export_format', async function () {
const format = $(this).data('format');
@ -8579,7 +8633,6 @@ $(document).ready(function () {
$("#char-management-dropdown").prop('selectedIndex', 0);
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
@ -8782,7 +8835,26 @@ $(document).ready(function () {
});
// Added here to prevent execution before script.js is loaded and get rid of quirky timeouts
initAuthorsNote();
initRossMods();
initPersonas();
await firstLoadInit();
registerDebugFunction('backfillTokenCounts', 'Backfill token counters',
`Recalculates token counts of all messages in the current chat to refresh the counters.
Useful when you switch between models that have different tokenizers.
This is a visual change only. Your chat will be reloaded.`, async () => {
for (const message of chat) {
// System messages are not counted
if (message.is_system) {
continue;
}
if (!message.extra) {
message.extra = {};
}
message.extra.token_count = getTokenCount(message.mes, 0);
}
await saveChatConditional();
await reloadCurrentChat();
});
});

View File

@ -53,7 +53,7 @@ const registerPromptManagerMigration = () => {
};
eventSource.on(event_types.SETTINGS_LOADED_BEFORE, settings => migrate(settings));
eventSource.on(event_types.OAI_PRESET_CHANGED, event => migrate(event.preset, event.savePreset, event.presetName));
eventSource.on(event_types.OAI_PRESET_CHANGED_BEFORE, event => migrate(event.preset, event.savePreset, event.presetName));
}
/**
@ -604,22 +604,20 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_close_button').addEventListener('click', closeAndClearPopup);
// Re-render prompt manager on openai preset change
eventSource.on(event_types.OAI_PRESET_CHANGED, settings => {
// Save configuration and wrap everything up.
this.saveServiceSettings().then(() => {
const mainPrompt = this.getPromptById('main');
this.updateQuickEdit('main', mainPrompt);
eventSource.on(event_types.OAI_PRESET_CHANGED_AFTER, () => {
this.sanitizeServiceSettings();
const mainPrompt = this.getPromptById('main');
this.updateQuickEdit('main', mainPrompt);
const nsfwPrompt = this.getPromptById('nsfw');
this.updateQuickEdit('nsfw', nsfwPrompt);
const nsfwPrompt = this.getPromptById('nsfw');
this.updateQuickEdit('nsfw', nsfwPrompt);
const jailbreakPrompt = this.getPromptById('jailbreak');
this.updateQuickEdit('jailbreak', jailbreakPrompt);
const jailbreakPrompt = this.getPromptById('jailbreak');
this.updateQuickEdit('jailbreak', jailbreakPrompt);
this.hidePopup();
this.clearEditForm();
this.renderDebounced();
});
this.hidePopup();
this.clearEditForm();
this.renderDebounced();
});
// Re-render prompt manager on world settings update
@ -643,19 +641,13 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
if (true === afterTryGenerate) {
// Executed during dry-run for determining context composition
this.profileStart('filling context');
this.tryGenerate().then(() => {
this.tryGenerate().finally(() => {
this.profileEnd('filling context');
this.profileStart('render');
this.renderPromptManager();
this.renderPromptManagerListItems()
this.makeDraggable();
this.profileEnd('render');
}).catch(error => {
this.profileEnd('filling context');
this.log('Error caught during render: ' + error);
this.renderPromptManager();
this.renderPromptManagerListItems()
this.makeDraggable();
});
} else {
// Executed during live communication
@ -1383,6 +1375,9 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
</li>
`;
console.log(this.activeCharacter)
console.log(this.serviceSettings)
console.log(this.getPromptsForCharacter(this.activeCharacter))
this.getPromptsForCharacter(this.activeCharacter).forEach(prompt => {
if (!prompt) return;

View File

@ -580,6 +580,7 @@ export function dragElement(elmnt) {
}
//prevent underlap with topbar div
/*
if (top < topBarLastY
&& (maxX >= topBarFirstX && left <= topBarFirstX //elmnt is hitting topbar from left side
|| left <= topBarLastX && maxX >= topBarLastX //elmnt is hitting topbar from right side
@ -588,6 +589,7 @@ export function dragElement(elmnt) {
console.debug('topbar hit')
elmnt.css('top', top + 1 + "px");
}
*/
}
// Check if the element header exists and set the listener on the grabber
@ -906,7 +908,7 @@ export function initRossMods() {
//Enter to send when send_textarea in focus
if ($(':focus').attr('id') === 'send_textarea') {
const sendOnEnter = shouldSendOnEnter();
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter" && is_send_press == false && sendOnEnter) {
if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == "Enter" && is_send_press == false && sendOnEnter) {
event.preventDefault();
Generate();
}
@ -949,9 +951,13 @@ export function initRossMods() {
console.debug("Ctrl+Enter ignored");
}
}
//ctrl+left to show all local stored vars (debug)
if (event.ctrlKey && event.key == "ArrowLeft") {
CheckLocal();
// Alt+Enter to Continue
if (event.altKey && event.key == "Enter") {
if (is_send_press == false) {
console.debug("Continuing with Alt+Enter");
$('#option_continue').trigger('click');
}
}
// Helper function to check if nanogallery2's lightbox is active

View File

@ -22,6 +22,8 @@ let lastGroupId = null
let lastChatId = null
let lastMessageHash = null
const DEFAULT_VOICE_MARKER = '[Default Voice]';
export function getPreviewString(lang) {
const previewStrings = {
'en-US': 'The quick brown fox jumps over the lazy dog',
@ -460,10 +462,12 @@ async function processTtsQueue() {
return;
}
if (!voiceMap[char]) {
const voiceMapEntry = voiceMap[char] || voiceMap[DEFAULT_VOICE_MARKER]
if (!voiceMapEntry) {
throw `${char} not in voicemap. Configure character in extension settings voice map`
}
const voice = await ttsProvider.getVoice((voiceMap[char]))
const voice = await ttsProvider.getVoice(voiceMapEntry)
const voiceId = voice.voice_id
if (voiceId == null) {
toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`)
@ -636,10 +640,12 @@ function getCharacters(){
let characters = []
if (context.groupId === null){
// Single char chat
characters.push(DEFAULT_VOICE_MARKER)
characters.push(context.name1)
characters.push(context.name2)
} else {
// Group chat
characters.push(DEFAULT_VOICE_MARKER)
characters.push(context.name1)
const group = context.groups.find(group => context.groupId == group.id)
for (let member of group.members) {

View File

@ -20,15 +20,29 @@ export const kai_settings = {
tfs: 1,
rep_pen_slope: 0.9,
single_line: false,
use_stop_sequence: false,
can_use_tokenization: false,
streaming_kobold: false,
sampler_order: [0, 1, 2, 3, 4, 5, 6],
mirostat: 0,
mirostat_tau: 5.0,
mirostat_eta: 0.1,
use_default_badwordsids: true,
};
export const kai_flags = {
can_use_tokenization: false,
can_use_stop_sequence: false,
can_use_streaming: false,
can_use_default_badwordsids: false,
can_use_mirostat: false,
};
const defaultValues = Object.freeze(structuredClone(kai_settings));
const MIN_STOP_SEQUENCE_VERSION = '1.2.2';
const MIN_UNBAN_VERSION = '1.2.4';
const MIN_STREAMING_KCPPVERSION = '1.30';
const MIN_TOKENIZATION_KCPPVERSION = '1.41';
const MIN_MIROSTAT_KCPPVERSION = '1.35';
const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
export function formatKoboldUrl(value) {
@ -44,16 +58,16 @@ export function formatKoboldUrl(value) {
export function loadKoboldSettings(preset) {
for (const name of Object.keys(kai_settings)) {
const value = preset[name];
const value = preset[name] ?? defaultValues[name];
const slider = sliders.find(x => x.name === name);
if (value === undefined || !slider) {
if (!slider) {
continue;
}
const formattedValue = slider.format(value);
slider.setValue(preset[name]);
$(slider.sliderId).val(preset[name]);
slider.setValue(value);
$(slider.sliderId).val(value);
$(slider.counterId).text(formattedValue);
}
@ -66,6 +80,10 @@ export function loadKoboldSettings(preset) {
kai_settings.streaming_kobold = preset.streaming_kobold;
$('#streaming_kobold').prop('checked', kai_settings.streaming_kobold);
}
if (preset.hasOwnProperty('use_default_badwordids')) {
kai_settings.use_default_badwordids = preset.use_default_badwordids;
$('#use_default_badwordids').prop('checked', kai_settings.use_default_badwordids);
}
}
export function getKoboldGenerationData(finalPrompt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
@ -94,9 +112,13 @@ export function getKoboldGenerationData(finalPrompt, this_settings, this_amount_
s7: sampler_order[6],
use_world_info: false,
singleline: kai_settings.single_line,
stop_sequence: kai_settings.use_stop_sequence ? getStoppingStrings(isImpersonate, false) : undefined,
streaming: kai_settings.streaming_kobold && kai_settings.can_use_streaming && type !== 'quiet',
can_abort: kai_settings.can_use_streaming,
stop_sequence: kai_flags.can_use_stop_sequence ? getStoppingStrings(isImpersonate, false) : undefined,
streaming: kai_settings.streaming_kobold && kai_flags.can_use_streaming && type !== 'quiet',
can_abort: kai_flags.can_use_streaming,
mirostat: kai_flags.can_use_mirostat ? kai_settings.mirostat : undefined,
mirostat_tau: kai_flags.can_use_mirostat ? kai_settings.mirostat_tau : undefined,
mirostat_eta: kai_flags.can_use_mirostat ? kai_settings.mirostat_eta : undefined,
use_default_badwordsids: kai_flags.can_use_default_badwordsids ? kai_settings.use_default_badwordsids : undefined,
};
return generate_data;
}
@ -213,24 +235,62 @@ const sliders = [
counterId: "#no_op_selector",
format: (val) => val,
setValue: (val) => { sortItemsByOrder(val); kai_settings.sampler_order = val; },
}
},
{
name: "mirostat",
sliderId: "#mirostat_mode_kobold",
counterId: "#mirostat_mode_counter_kobold",
format: (val) => val,
setValue: (val) => { kai_settings.mirostat = Number(val); },
},
{
name: "mirostat_tau",
sliderId: "#mirostat_tau_kobold",
counterId: "#mirostat_tau_counter_kobold",
format: (val) => val,
setValue: (val) => { kai_settings.mirostat_tau = Number(val); },
},
{
name: "mirostat_eta",
sliderId: "#mirostat_eta_kobold",
counterId: "#mirostat_eta_counter_kobold",
format: (val) => val,
setValue: (val) => { kai_settings.mirostat_eta = Number(val); },
},
];
export function setKoboldFlags(version, koboldVersion) {
kai_flags.can_use_stop_sequence = canUseKoboldStopSequence(version);
kai_flags.can_use_streaming = canUseKoboldStreaming(koboldVersion);
kai_flags.can_use_tokenization = canUseKoboldTokenization(koboldVersion);
kai_flags.can_use_default_badwordsids = canUseDefaultBadwordIds(version);
kai_flags.can_use_mirostat = canUseMirostat(koboldVersion);
}
/**
* Determines if the Kobold stop sequence can be used with the given version.
* @param {string} version KoboldAI version to check.
* @returns {boolean} True if the Kobold stop sequence can be used, false otherwise.
*/
export function canUseKoboldStopSequence(version) {
function canUseKoboldStopSequence(version) {
return (version || '0.0.0').localeCompare(MIN_STOP_SEQUENCE_VERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
}
/**
* Determines if the Kobold default badword ids can be used with the given version.
* @param {string} version KoboldAI version to check.
* @returns {boolean} True if the Kobold default badword ids can be used, false otherwise.
*/
function canUseDefaultBadwordIds(version) {
return (version || '0.0.0').localeCompare(MIN_UNBAN_VERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
}
/**
* Determines if the Kobold streaming API can be used with the given version.
* @param {{ result: string; version: string; }} koboldVersion KoboldAI version object.
* @returns {boolean} True if the Kobold streaming API can be used, false otherwise.
*/
export function canUseKoboldStreaming(koboldVersion) {
function canUseKoboldStreaming(koboldVersion) {
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
return (koboldVersion.version || '0.0').localeCompare(MIN_STREAMING_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
} else return false;
@ -241,12 +301,18 @@ export function canUseKoboldStreaming(koboldVersion) {
* @param {{ result: string; version: string; }} koboldVersion KoboldAI version object.
* @returns {boolean} True if the Kobold tokenization API can be used, false otherwise.
*/
export function canUseKoboldTokenization(koboldVersion) {
function canUseKoboldTokenization(koboldVersion) {
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
return (koboldVersion.version || '0.0').localeCompare(MIN_TOKENIZATION_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
} else return false;
}
function canUseMirostat(koboldVersion) {
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
return (koboldVersion.version || '0.0').localeCompare(MIN_MIROSTAT_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
} else return false;
}
/**
* Sorts the sampler items by the given order.
* @param {any[]} orderArray Sampler order array.
@ -274,17 +340,23 @@ jQuery(function () {
});
$('#single_line').on("input", function () {
const value = $(this).prop('checked');
const value = !!$(this).prop('checked');
kai_settings.single_line = value;
saveSettingsDebounced();
});
$('#streaming_kobold').on("input", function () {
const value = $(this).prop('checked');
const value = !!$(this).prop('checked');
kai_settings.streaming_kobold = value;
saveSettingsDebounced();
});
$('#use_default_badwordids').on("input", function () {
const value = !!$(this).prop('checked');
kai_settings.use_default_badwordids = value;
saveSettingsDebounced();
});
$('#kobold_order').sortable({
delay: getSortableDelay(),
stop: function () {

View File

@ -354,7 +354,11 @@ function setupChatCompletionPromptManager(openAiSettings) {
}
promptManager.tryGenerate = () => {
return Generate('normal', {}, true);
if (characters[this_chid]) {
return Generate('normal', {}, true);
} else{
return Promise.resolve();
}
}
promptManager.tokenHandler = tokenHandler;
@ -613,7 +617,7 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
// Insert nsfw avoidance prompt into main, if no nsfw prompt is present
if (false === chatCompletion.has('nsfw') && oai_settings.nsfw_avoidance_prompt)
if (prompts.has('nsfwAvoidance')) chatCompletion.insert(Message.fromPrompt(prompts.get('nsfwAvoidance')), 'main');
if (prompts.has('nsfwAvoidance') && prompts.has('main')) chatCompletion.insert(Message.fromPrompt(prompts.get('nsfwAvoidance')), 'main');
// Bias
if (bias && bias.trim().length) addToChatCompletion('bias');
@ -2463,7 +2467,7 @@ function onSettingsPresetChange() {
const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input');
// Allow subscribers to alter the preset before applying deltas
eventSource.emit(event_types.OAI_PRESET_CHANGED, {
eventSource.emit(event_types.OAI_PRESET_CHANGED_BEFORE, {
preset: preset,
presetName: presetName,
settingsToUpdate: settingsToUpdate,
@ -2485,6 +2489,7 @@ function onSettingsPresetChange() {
$(`#openai_logit_bias_preset`).trigger('change');
saveSettingsDebounced();
eventSource.emit(event_types.OAI_PRESET_CHANGED_AFTER);
});
}

View File

@ -30,7 +30,7 @@ import {
import { registerSlashCommand } from "./slash-commands.js";
import { tokenizers } from "./tokenizers.js";
import { delay, resetScrollHeight } from "./utils.js";
import { countOccurrences, delay, isOdd, resetScrollHeight, sortMoments, timestampToMoment } from "./utils.js";
export {
loadPowerUserSettings,
@ -159,6 +159,7 @@ let power_user = {
timestamp_model_icon: false,
mesIDDisplay_enabled: false,
max_context_unlocked: false,
message_token_count_enabled: false,
prefer_character_prompt: true,
prefer_character_jailbreak: true,
quick_continue: false,
@ -240,6 +241,7 @@ const storage_keys = {
timestamps_enabled: 'TimestampsEnabled',
timestamp_model_icon: 'TimestampModelIcon',
mesIDDisplay_enabled: 'mesIDDisplayEnabled',
message_token_count_enabled: 'MessageTokenCountEnabled',
};
let browser_has_focus = true;
@ -283,6 +285,7 @@ function collapseNewlines(x) {
/**
* Fix formatting problems in markdown.
* @param {string} text Text to be processed.
* @param {boolean} forDisplay Whether the text is being processed for display.
* @returns {string} Processed text.
* @example
* "^example * text*\n" // "^example *text*\n"
@ -296,7 +299,7 @@ function collapseNewlines(x) {
* // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
* "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"
*/
function fixMarkdown(text) {
function fixMarkdown(text, forDisplay) {
// Find pairs of formatting characters and capture the text in between them
const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
let matches = [];
@ -313,6 +316,27 @@ function fixMarkdown(text) {
newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length);
}
// Don't auto-fix asterisks if this is a message clean-up procedure.
// It botches the continue function. Apply this to display only.
if (!forDisplay) {
return newText;
}
const splitText = newText.split('\n');
// Fix asterisks, and quotes that are not paired
for (let index = 0; index < splitText.length; index++) {
const line = splitText[index];
const charsToCheck = ['*', '"'];
for (const char of charsToCheck) {
if (line.includes(char) && isOdd(countOccurrences(line, char))) {
splitText[index] = line.trimEnd() + char;
}
}
}
newText = splitText.join('\n');
return newText;
}
@ -344,6 +368,13 @@ function switchIcons() {
$("#messageModelIconEnabled").prop("checked", power_user.timestamp_model_icon);
}
function switchTokenCount() {
const value = localStorage.getItem(storage_keys.message_token_count_enabled);
power_user.message_token_count_enabled = value === null ? false : value == "true";
$("body").toggleClass("no-tokenCount", !power_user.message_token_count_enabled);
$("#messageTokensEnabled").prop("checked", power_user.message_token_count_enabled);
}
function switchMesIDDisplay() {
const value = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
power_user.mesIDDisplay_enabled = value === null ? true : value == "true";
@ -599,42 +630,49 @@ async function applyTheme(name) {
power_user.chat_width = 50;
}
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
localStorage.setItem(storage_keys.chat_width, String(power_user.chat_width));
applyChatWidth();
}
},
{
key: 'timer_enabled',
action: async () => {
localStorage.setItem(storage_keys.timer_enabled, power_user.timer_enabled);
localStorage.setItem(storage_keys.timer_enabled, String(power_user.timer_enabled));
switchTimer();
}
},
{
key: 'timestamps_enabled',
action: async () => {
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
localStorage.setItem(storage_keys.timestamps_enabled, String(power_user.timestamps_enabled));
switchTimestamps();
}
},
{
key: 'timestamp_model_icon',
action: async () => {
localStorage.setItem(storage_keys.timestamp_model_icon, power_user.timestamp_model_icon);
localStorage.setItem(storage_keys.timestamp_model_icon, String(power_user.timestamp_model_icon));
switchIcons();
}
},
{
key: 'message_token_count',
action: async () => {
localStorage.setItem(storage_keys.message_token_count_enabled, String(power_user.message_token_count_enabled));
switchTokenCount();
}
},
{
key: 'mesIDDisplay_enabled',
action: async () => {
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
localStorage.setItem(storage_keys.mesIDDisplay_enabled, String(power_user.mesIDDisplay_enabled));
switchMesIDDisplay();
}
},
{
key: 'hotswap_enabled',
action: async () => {
localStorage.setItem(storage_keys.hotswap_enabled, power_user.hotswap_enabled);
localStorage.setItem(storage_keys.hotswap_enabled, String(power_user.hotswap_enabled));
switchHotswap();
}
}
@ -701,6 +739,7 @@ switchTimer();
switchTimestamps();
switchIcons();
switchMesIDDisplay();
switchTokenCount();
function loadPowerUserSettings(settings, data) {
// Load from settings.json
@ -1126,6 +1165,11 @@ const compareFunc = (first, second) => {
case 'boolean':
const a = first[power_user.sort_field];
const b = second[power_user.sort_field];
if (power_user.sort_field === 'create_date') {
return sortMoments(timestampToMoment(a), timestampToMoment(b));
}
if (a === true || a === 'true') return 1; // Prioritize 'true' or true
if (b === true || b === 'true') return -1; // Prioritize 'true' or true
if (a && !b) return -1; // Move truthy values to the end
@ -2069,6 +2113,13 @@ $(document).ready(() => {
switchIcons();
});
$("#messageTokensEnabled").on("input", function () {
const value = !!$(this).prop('checked');
power_user.message_token_count_enabled = value;
localStorage.setItem(storage_keys.message_token_count_enabled, String(power_user.message_token_count_enabled));
switchTokenCount();
});
$("#mesIDDisplayEnabled").on("input", function () {
const value = !!$(this).prop('checked');
power_user.mesIDDisplay_enabled = value;

View File

@ -4,10 +4,10 @@ Hotkeys/Keybinds:
<li><tt>Ctrl+Up</tt> = Edit last USER message in chat</li>
<li><tt>Left</tt> = swipe left</li>
<li><tt>Right</tt> = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</li>
<li><tt>Ctrl+Left</tt> = view locally stored variables (in the browser console window)</li>
<li><tt>Enter</tt> (with chat bar selected) = send your message to AI</li>
<li><tt>Ctrl+Enter</tt> = Regenerate the last AI response</li>
<li><tt>Escape</tt> = stop AI response generation</li>
<li><tt>Alt+Enter</tt> = Continue the last AI response</li>
<li><tt>Escape</tt> = stop AI response generation, close UI panels, cancel message edit</li>
<li><tt>Ctrl+Shift+Up</tt> = Scroll to context line</li>
<li><tt>Ctrl+Shift+Down</tt> = Scroll chat to bottom</li>
</ul>

View File

@ -3,7 +3,7 @@ import { power_user, registerDebugFunction } from "./power-user.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
import { groups, selected_group } from "./group-chats.js";
import { getStringHash } from "./utils.js";
import { kai_settings } from "./kai-settings.js";
import { kai_flags } from "./kai-settings.js";
export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
@ -82,7 +82,7 @@ function getTokenizerBestMatch() {
// - API must be connected
// - Kobold must pass a version check
// - Tokenizer haven't reported an error previously
if (kai_settings.can_use_tokenization && !sessionStorage.getItem(TOKENIZER_WARNING_KEY) && online_status !== 'no_connection') {
if (kai_flags.can_use_tokenization && !sessionStorage.getItem(TOKENIZER_WARNING_KEY) && online_status !== 'no_connection') {
return tokenizers.API;
}

View File

@ -1103,7 +1103,7 @@ async function checkWorldInfo(chat, maxContext) {
console.debug(`WI budget reached, stopping`);
if (world_info_overflow_alert) {
console.log("Alerting");
toastr.warning(`World info budget reached after ${count} entries.`, 'World Info');
toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
}
needsToScan = false;
break;

View File

@ -287,7 +287,8 @@ table.responsiveTable {
}
.mes .mes_timer,
.mes .mesIDDisplay {
.mes .mesIDDisplay,
.mes .tokenCounterDisplay {
cursor: default;
opacity: 0.7;
font-size: calc(var(--mainFontSize) * 0.9);
@ -399,9 +400,10 @@ hr {
#sheld {
display: grid;
grid-template-rows: auto min-content;
height: calc(100vh - var(--topBarBlockSize));
height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
/* -1px to give sheld some wiggle room to bounce off tobar when moving*/
height: calc(100vh - var(--topBarBlockSize) - 1px);
height: calc(100svh - var(--topBarBlockSize) - 1px);
max-height: calc(100svh - var(--topBarBlockSize) - 1px);
overflow-x: hidden;
/* max-width: 50vw; */
position: absolute;
@ -3436,8 +3438,10 @@ a {
/* Hide scrollbar for IE, Edge, and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
#groupMemberListPopoutClose {
@ -3560,7 +3564,7 @@ a {
text-align: left;
}
.onboarding > h3 {
.onboarding>h3 {
align-self: center;
}
@ -3600,4 +3604,4 @@ a {
height: 100vh;
z-index: 9999;
}
}
}

325
server.js
View File

@ -32,13 +32,11 @@ const multer = require("multer");
const responseTime = require('response-time');
// net related library imports
const axios = require('axios');
const DeviceDetector = require("device-detector-js");
const fetch = require('node-fetch').default;
const ipaddr = require('ipaddr.js');
const ipMatching = require('ip-matching');
const json5 = require('json5');
const RESTClient = require('node-rest-client').Client;
const WebSocket = require('ws');
// image processing related library imports
@ -154,16 +152,12 @@ function getHordeClient() {
return ai_horde;
}
const restClient = new RESTClient();
restClient.on('error', (err) => {
console.error('An error occurred:', err);
});
const API_NOVELAI = "https://api.novelai.net";
const API_OPENAI = "https://api.openai.com/v1";
const API_CLAUDE = "https://api.anthropic.com/v1";
// These should be gone and come from the frontend. But for now, they're here.
let api_server = "http://0.0.0.0:5000";
let api_novelai = "https://api.novelai.net";
let api_openai = "https://api.openai.com/v1";
let api_claude = "https://api.anthropic.com/v1";
let main_api = "kobold";
let characters = {};
@ -190,7 +184,14 @@ function get_mancer_headers() {
return api_key_mancer ? { "X-API-KEY": api_key_mancer } : {};
}
function getOverrideHeaders(urlHost) {
const overrideHeaders = config.requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers;
if (overrideHeaders && urlHost) {
return overrideHeaders;
} else {
return {};
}
}
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
@ -311,7 +312,6 @@ function humanizedISO8601DateTime() {
return HumanizedDateTime;
};
var is_colab = process.env.colaburl !== undefined;
var charactersPath = 'public/characters/';
var chatsPath = 'public/chats/';
const UPLOADS_PATH = './uploads';
@ -319,7 +319,6 @@ const AVATAR_WIDTH = 400;
const AVATAR_HEIGHT = 600;
const jsonParser = express.json({ limit: '100mb' });
const urlencodedParser = express.urlencoded({ extended: true, limit: '100mb' });
const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
const directories = {
worlds: 'public/worlds/',
avatars: 'public/User Avatars',
@ -534,6 +533,10 @@ app.post("/generate", jsonParser, async function (request, response_generate) {
typical: request.body.typical,
sampler_order: sampler_order,
singleline: !!request.body.singleline,
use_default_badwordsids: request.body.use_default_badwordsids,
mirostat: request.body.mirostat,
mirostat_eta: request.body.mirostat_eta,
mirostat_tau: request.body.mirostat_tau,
};
if (!!request.body.stop_sequence) {
this_settings['stop_sequence'] = request.body.stop_sequence;
@ -543,7 +546,10 @@ app.post("/generate", jsonParser, async function (request, response_generate) {
console.log(this_settings);
const args = {
body: JSON.stringify(this_settings),
headers: { "Content-Type": "application/json" },
headers: Object.assign(
{ "Content-Type": "application/json" },
getOverrideHeaders((new URL(api_server))?.host)
),
signal: controller.signal,
};
@ -633,11 +639,19 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
});
async function* readWebsocket() {
const streamingUrlString = request.header('X-Streaming-URL').replace("localhost", "127.0.0.1");
const streamingUrl = new URL(streamingUrlString);
const websocket = new WebSocket(streamingUrl);
websocket.on('open', async function () {
console.log('WebSocket opened');
const combined_args = Object.assign(request.body.use_mancer ? get_mancer_headers() : {}, request.body);
const combined_args = Object.assign(
{},
request.body.use_mancer ? get_mancer_headers() : getOverrideHeaders(streamingUrl?.host),
request.body
);
console.log(combined_args);
websocket.send(JSON.stringify(combined_args));
});
@ -719,6 +733,8 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
if (request.body.use_mancer) {
args.headers = Object.assign(args.headers, get_mancer_headers());
} else {
args.headers = Object.assign(args.headers, getOverrideHeaders((new URL(api_server))?.host));
}
try {
@ -786,6 +802,7 @@ app.post("/getchat", jsonParser, function (request, response) {
}
});
// Only called for kobold and ooba/mancer
app.post("/getstatus", jsonParser, async function (request, response) {
if (!request.body) return response.sendStatus(400);
api_server = request.body.api_server;
@ -800,6 +817,8 @@ app.post("/getstatus", jsonParser, async function (request, response) {
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
args.headers = Object.assign(args.headers, get_mancer_headers());
} else {
args.headers = Object.assign(args.headers, getOverrideHeaders((new URL(api_server))?.host));
}
const url = api_server + "/v1/model";
@ -808,13 +827,13 @@ app.post("/getstatus", jsonParser, async function (request, response) {
if (main_api == "kobold") {
try {
version = (await getAsync(api_server + "/v1/info/version")).result;
version = (await fetchJSON(api_server + "/v1/info/version")).result
}
catch {
version = '0.0.0';
}
try {
koboldVersion = (await getAsync(api_server + "/extra/version"));
koboldVersion = (await fetchJSON(api_server + "/extra/version"));
}
catch {
koboldVersion = {
@ -825,7 +844,7 @@ app.post("/getstatus", jsonParser, async function (request, response) {
}
try {
let data = await getAsync(url, args);
let data = await fetchJSON(url, args);
if (!data || typeof data !== 'object') {
data = {};
@ -954,6 +973,7 @@ function readFromV2(char) {
});
char['chat'] = char['chat'] ?? humanizedISO8601DateTime();
char['create_date'] = char['create_date'] || humanizedISO8601DateTime();
return char;
}
@ -1256,7 +1276,7 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
// Get the chunks
const chunks = extract(image);
const tEXtChunks = chunks.filter(chunk => chunk.create_date === 'tEXt' || chunk.name === 'tEXt');
const tEXtChunks = chunks.filter(chunk => chunk.name === 'tEXt');
// Remove all existing tEXt chunks
for (let tEXtChunk of tEXtChunks) {
@ -1267,7 +1287,7 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
chunks.splice(-1, 0, PNGtext.encode('chara', base64EncodedData));
//chunks.splice(-1, 0, text.encode('lorem', 'ipsum'));
writeFileAtomicSync(charactersPath + target_img + '.png', new Buffer.from(encode(chunks)));
writeFileAtomicSync(charactersPath + target_img + '.png', Buffer.from(encode(chunks)));
if (response !== undefined) response.send(mes);
return true;
} catch (err) {
@ -1309,7 +1329,7 @@ async function charaRead(img_url, input_format) {
* calculateChatSize - Calculates the total chat size for a given character.
*
* @param {string} charDir The directory where the chats are stored.
* @return {number} The total chat size.
* @return { {chatSize: number, dateLastChat: number} } The total chat size.
*/
const calculateChatSize = (charDir) => {
let chatSize = 0;
@ -1344,6 +1364,8 @@ const calculateDataSize = (data) => {
const processCharacter = async (item, i) => {
try {
const img_data = await charaRead(charactersPath + item);
if (img_data === false || img_data === undefined) throw new Error("Failed to read character file");
let jsonObject = getCharaCardV2(json5.parse(img_data));
jsonObject.avatar = item;
characters[i] = jsonObject;
@ -1458,14 +1480,7 @@ app.post("/getbackgrounds", jsonParser, function (request, response) {
response.send(JSON.stringify(images));
});
app.post("/iscolab", jsonParser, function (request, response) {
let send_data = false;
if (is_colab) {
send_data = String(process.env.colaburl).trim();
}
response.send({ colaburl: send_data });
});
app.post("/getuseravatars", jsonParser, function (request, response) {
var images = getImages("public/User Avatars");
response.send(JSON.stringify(images));
@ -1632,7 +1647,7 @@ function readAndParseFromDirectory(directoryPath, fileExtension = '.json') {
}
function sortByModifiedDate(directory) {
return (a, b) => new Date(fs.statSync(`${directory}/${b}`).mtime) - new Date(fs.statSync(`${directory}/${a}`).mtime);
return (a, b) => +(new Date(fs.statSync(`${directory}/${b}`).mtime)) - +(new Date(fs.statSync(`${directory}/${a}`).mtime));
}
function sortByName(_) {
@ -1666,11 +1681,12 @@ function readPresetsFromDirectory(directoryPath, options = {}) {
// Wintermute's code
app.post('/getsettings', jsonParser, (request, response) => {
const settings = fs.readFileSync('public/settings.json', 'utf8', (err, data) => {
if (err) return response.sendStatus(500);
return data;
});
let settings
try {
settings = fs.readFileSync('public/settings.json', 'utf8');
} catch (e) {
return response.sendStatus(500);
}
// NovelAI Settings
const { fileContents: novelai_settings, fileNames: novelai_setting_names }
@ -1700,7 +1716,7 @@ app.post('/getsettings', jsonParser, (request, response) => {
const worldFiles = fs
.readdirSync(directories.worlds)
.filter(file => path.extname(file).toLowerCase() === '.json')
.sort((a, b) => a < b);
.sort((a, b) => a.localeCompare(b));
const world_names = worldFiles.map(item => path.parse(item).name);
const themes = readAndParseFromDirectory(directories.themes);
@ -1862,7 +1878,7 @@ app.post("/getstatus_novelai", jsonParser, async function (request, response_get
}
try {
const response = await fetch(api_novelai + "/user/subscription", {
const response = await fetch(API_NOVELAI + "/user/subscription", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@ -1887,7 +1903,7 @@ app.post("/getstatus_novelai", jsonParser, async function (request, response_get
}
});
app.post("/generate_novelai", jsonParser, async function (request, response_generate_novel = response) {
app.post("/generate_novelai", jsonParser, async function (request, response_generate_novel) {
if (!request.body) return response_generate_novel.sendStatus(400);
const api_key_novel = readSecret(SECRET_KEYS.NOVEL);
@ -1925,7 +1941,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"input": request.body.input,
"model": request.body.model,
"parameters": {
"use_string": request.body.use_string,
"use_string": request.body.use_string ?? true,
"temperature": request.body.temperature,
"max_length": request.body.max_length,
"min_length": request.body.min_length,
@ -1950,7 +1966,6 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"logit_bias_exp": logit_bias_exp,
"generate_until_sentence": request.body.generate_until_sentence,
"use_cache": request.body.use_cache,
"use_string": request.body.use_string ?? true,
"return_full_text": request.body.return_full_text,
"prefix": request.body.prefix,
"order": request.body.order
@ -1966,7 +1981,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
};
try {
const url = request.body.streaming ? `${api_novelai}/ai/generate-stream` : `${api_novelai}/ai/generate`;
const url = request.body.streaming ? `${API_NOVELAI}/ai/generate-stream` : `${API_NOVELAI}/ai/generate`;
const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
if (request.body.streaming) {
@ -2094,7 +2109,7 @@ function getPngName(file) {
app.post("/importcharacter", urlencodedParser, async function (request, response) {
if (!request.body) return response.sendStatus(400);
if (!request.body || request.file === undefined) return response.sendStatus(400);
let png_name = '';
let filedata = request.file;
@ -2145,8 +2160,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
"tags": jsonData.tags ?? '',
};
char = convertToV2(char);
char = JSON.stringify(char);
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
let charJSON = JSON.stringify(char);
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
console.log('importing from gradio json');
jsonData.char_name = sanitize(jsonData.char_name);
@ -2170,8 +2185,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
"tags": jsonData.tags ?? '',
};
char = convertToV2(char);
char = JSON.stringify(char);
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
let charJSON = JSON.stringify(char);
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
} else {
console.log('Incorrect character format .json');
response.send({ error: true });
@ -2180,6 +2195,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
} else {
try {
var img_data = await charaRead(uploadPath, format);
if (img_data === false || img_data === undefined) throw new Error('Failed to read character data');
let jsonData = json5.parse(img_data);
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
@ -2871,7 +2888,7 @@ app.post('/deletegroup', jsonParser, async (request, response) => {
try {
// Delete group chats
const group = json5.parse(fs.readFileSync(pathToGroup));
const group = json5.parse(fs.readFileSync(pathToGroup, 'utf8'));
if (group && Array.isArray(group.chats)) {
for (const chat of group.chats) {
@ -2985,6 +3002,8 @@ function getOriginalFolder(type) {
function invalidateThumbnail(type, file) {
const folder = getThumbnailFolder(type);
if (folder === undefined) throw new Error("Invalid thumbnail type")
const pathToThumbnail = path.join(folder, file);
if (fs.existsSync(pathToThumbnail)) {
@ -3034,8 +3053,12 @@ async function ensureThumbnailCache() {
}
async function generateThumbnail(type, file) {
const pathToCachedFile = path.join(getThumbnailFolder(type), file);
const pathToOriginalFile = path.join(getOriginalFolder(type), file);
let thumbnailFolder = getThumbnailFolder(type)
let originalFolder = getOriginalFolder(type)
if (thumbnailFolder === undefined || originalFolder === undefined) throw new Error("Invalid thumbnail type")
const pathToCachedFile = path.join(thumbnailFolder, file);
const pathToOriginalFile = path.join(originalFolder, file);
const cachedFileExists = fs.existsSync(pathToCachedFile);
const originalFileExists = fs.existsSync(pathToOriginalFile);
@ -3086,6 +3109,8 @@ async function generateThumbnail(type, file) {
}
app.get('/thumbnail', jsonParser, async function (request, response) {
if (typeof request.query.file !== 'string' || typeof request.query.type !== 'string') return response.sendStatus(400);
const type = request.query.type;
const file = sanitize(request.query.file);
@ -3103,7 +3128,9 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
}
if (config.disableThumbnails == true) {
const pathToOriginalFile = path.join(getOriginalFolder(type), file);
let folder = getOriginalFolder(type);
if (folder === undefined) return response.sendStatus(400);
const pathToOriginalFile = path.join(folder, file);
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
}
@ -3117,7 +3144,7 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
});
/* OpenAI */
app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_openai) {
app.post("/getstatus_openai", jsonParser, async function (request, response_getstatus_openai) {
if (!request.body) return response_getstatus_openai.sendStatus(400);
let api_url;
@ -3125,7 +3152,7 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
let headers;
if (request.body.use_openrouter == false) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_url = new URL(request.body.reverse_proxy || API_OPENAI).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
headers = {};
} else {
@ -3139,17 +3166,22 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
return response_getstatus_openai.status(401).send({ error: true });
}
const args = {
headers: {
"Authorization": "Bearer " + api_key_openai,
...headers,
},
};
restClient.get(api_url + "/models", args, function (data, response) {
if (response.statusCode == 200) {
try {
const response = await fetch(api_url + "/models", {
method: 'GET',
headers: {
"Authorization": "Bearer " + api_key_openai,
...headers,
},
});
if (response.ok) {
const data = await response.json();
response_getstatus_openai.send(data);
if (request.body.use_openrouter) {
let models = [];
data.data.forEach(model => {
const context_length = model.context_length;
const tokens_dollar = Number(1 / (1000 * model.pricing.prompt));
@ -3159,27 +3191,21 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
context_length: context_length,
};
});
console.log('Available OpenRouter models:', models);
} else {
const modelIds = data?.data?.map(x => x.id)?.sort();
console.log('Available OpenAI models:', modelIds);
}
}
if (response.statusCode == 401) {
else {
console.log('Access Token is incorrect.');
response_getstatus_openai.send({ error: true });
}
if (response.statusCode == 404) {
console.log('Endpoint not found.');
response_getstatus_openai.send({ error: true });
}
if (response.statusCode == 500 || response.statusCode == 501 || response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
console.log(data);
response_getstatus_openai.send({ error: true });
}
}).on('error', function () {
} catch (e) {
console.error(e);
response_getstatus_openai.send({ error: true });
});
}
});
app.post("/openai_bias", jsonParser, async function (request, response) {
@ -3407,9 +3433,13 @@ app.post("/generate_altscale", jsonParser, function (request, response_generate_
});
/**
* @param {express.Request} request
* @param {express.Response} response
*/
async function sendClaudeRequest(request, response) {
const api_url = new URL(request.body.reverse_proxy || api_claude).toString();
const api_url = new URL(request.body.reverse_proxy || API_CLAUDE).toString();
const api_key_claude = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.CLAUDE);
if (!api_key_claude) {
@ -3463,7 +3493,7 @@ async function sendClaudeRequest(request, response) {
generateResponse.body.pipe(response);
request.socket.on('close', function () {
generateResponse.body.destroy(); // Close the remote stream
if (generateResponse.body instanceof Readable) generateResponse.body.destroy(); // Close the remote stream
response.end(); // End the Express response
});
@ -3514,7 +3544,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
let bodyParams;
if (!request.body.use_openrouter) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_url = new URL(request.body.reverse_proxy || API_OPENAI).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
headers = {};
bodyParams = {};
@ -3549,15 +3579,15 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
controller.abort();
});
/** @type {import('node-fetch').RequestInit} */
const config = {
method: 'post',
url: endpointUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + api_key_openai,
...headers,
},
data: {
body: JSON.stringify({
"messages": isTextCompletion === false ? request.body.messages : undefined,
"prompt": isTextCompletion === true ? textPrompt : undefined,
"model": request.body.model,
@ -3571,74 +3601,62 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
"stop": request.body.stop,
"logit_bias": request.body.logit_bias,
...bodyParams,
},
}),
signal: controller.signal,
timeout: 0,
};
console.log(config.data);
console.log(JSON.parse(String(config.body)));
if (request.body.stream) {
config.responseType = 'stream';
}
makeRequest(config, response_generate_openai, request);
/**
*
* @param {*} config
* @param {express.Response} response_generate_openai
* @param {express.Request} request
* @param {Number} retries
* @param {Number} timeout
*/
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 5000) {
try {
const response = await axios(config);
const fetchResponse = await fetch(endpointUrl, config)
if (response.status <= 299) {
if (fetchResponse.ok) {
if (request.body.stream) {
console.log('Streaming request in progress');
response.data.pipe(response_generate_openai);
response.data.on('end', () => {
fetchResponse.body.pipe(response_generate_openai);
fetchResponse.body.on('end', () => {
console.log('Streaming request finished');
response_generate_openai.end();
});
} else {
response_generate_openai.send(response.data);
console.log(response.data);
console.log(response.data?.choices[0]?.message);
let json = await fetchResponse.json()
response_generate_openai.send(json);
console.log(json);
console.log(json?.choices[0]?.message);
}
} else {
handleErrorResponse(response, response_generate_openai, request);
}
} catch (error) {
if (error.response && error.response.status === 429 && retries > 0) {
} else if (fetchResponse.status === 429 && retries > 0) {
console.log(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`);
setTimeout(() => {
makeRequest(config, response_generate_openai, request, retries - 1);
}, timeout);
} else {
let errorData = error?.response?.data;
if (request.body.stream) {
try {
const chunks = await readAllChunks(errorData);
const blob = new Blob(chunks, { type: 'application/json' });
const text = await blob.text();
errorData = JSON.parse(text);
} catch {
console.warn('Error parsing streaming response');
}
} else {
errorData = typeof errorData === 'string' ? tryParse(errorData) : errorData;
}
handleError(error, response_generate_openai, errorData);
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();
}
}
}
function handleErrorResponse(response, response_generate_openai, request) {
if (response.status >= 400 && response.status <= 504) {
console.log('Error occurred:', response.status, response.data);
response_generate_openai.send({ error: true });
}
}
function handleError(error, response_generate_openai, errorData) {
console.error('Error:', error?.message);
let message = error?.response?.statusText;
async function handleErrorResponse(response) {
const responseText = await response.text();
const errorData = tryParse(responseText);
const statusMessages = {
400: 'Bad request',
@ -3648,24 +3666,21 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
404: 'Not found',
429: 'Too many requests',
451: 'Unavailable for legal reasons',
502: 'Bad gateway',
};
const status = error?.response?.status;
if (statusMessages.hasOwnProperty(status)) {
message = errorData?.error?.message || statusMessages[status];
console.log(message);
}
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);
const quota_error = error?.response?.status === 429 && errorData?.error?.type === 'insufficient_quota';
const response = { error: { message }, quota_error: quota_error }
if (!response_generate_openai.headersSent) {
response_generate_openai.send(response);
response_generate_openai.send({ error: { message }, quota_error: quota_error });
} else if (!response_generate_openai.writableEnded) {
response_generate_openai.write(response);
} else {
response_generate_openai.end();
}
}
makeRequest(config, response_generate_openai, request);
});
app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai) {
@ -3776,7 +3791,7 @@ async function sendAI21Request(request, response) {
}
app.post("/tokenize_ai21", jsonParser, function (request, response_tokenize_ai21) {
app.post("/tokenize_ai21", jsonParser, async function (request, response_tokenize_ai21) {
if (!request.body) return response_tokenize_ai21.sendStatus(400);
const options = {
method: 'POST',
@ -3788,10 +3803,14 @@ app.post("/tokenize_ai21", jsonParser, function (request, response_tokenize_ai21
body: JSON.stringify({ text: request.body[0].content })
};
fetch('https://api.ai21.com/studio/v1/tokenize', options)
.then(response => response.json())
.then(response => response_tokenize_ai21.send({ "token_count": response.tokens.length }))
.catch(err => console.error(err));
try {
const response = await fetch('https://api.ai21.com/studio/v1/tokenize', options);
const data = await response.json();
return response_tokenize_ai21.send({ "token_count": data?.tokens?.length || 0 });
} catch (err) {
console.error(err);
return response_tokenize_ai21.send({ "token_count": 0 });
}
});
app.post("/save_preset", jsonParser, function (request, response) {
@ -3836,10 +3855,9 @@ app.post("/delete_preset", jsonParser, function (request, response) {
});
app.post("/savepreset_openai", jsonParser, function (request, response) {
if (!request.body || typeof request.query.name !== 'string') return response.sendStatus(400);
const name = sanitize(request.query.name);
if (!request.body || !name) {
return response.sendStatus(400);
}
if (!name) return response.sendStatus(400);
const filename = `${name}.settings`;
const fullpath = path.join(directories.openAI_Settings, filename);
@ -3982,8 +4000,14 @@ app.post("/tokenize_via_api", jsonParser, async function (request, response) {
// ** REST CLIENT ASYNC WRAPPERS **
async function postAsync(url, args) {
const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
/**
* Convenience function for fetch requests (default GET) returning as JSON.
* @param {string} url
* @param {import('node-fetch').RequestInit} args
*/
async function fetchJSON(url, args = {}) {
if (args.method === undefined) args.method = 'GET';
const response = await fetch(url, args);
if (response.ok) {
const data = await response.json();
@ -3992,17 +4016,13 @@ async function postAsync(url, args) {
throw response;
}
/**
* Convenience function for fetch requests (default POST with no timeout) returning as JSON.
* @param {string} url
* @param {import('node-fetch').RequestInit} args
*/
async function postAsync(url, args) { return fetchJSON(url, { method: 'POST', timeout: 0, ...args }) }
function getAsync(url, args) {
return new Promise((resolve, reject) => {
restClient.get(url, args, (data, response) => {
if (response.statusCode >= 400) {
reject(data);
}
resolve(data);
}).on('error', e => reject(e));
})
}
// ** END **
const tavernUrl = new URL(
@ -4029,8 +4049,7 @@ const setupTasks = async function () {
contentManager.checkForNewContent();
cleanUploads();
// Colab users could run the embedded tool
if (!is_colab) await convertWebp();
await convertWebp();
[spp_llama, spp_nerd, spp_nerd_v2, claude_tokenizer] = await Promise.all([
loadSentencepieceTokenizer('src/sentencepiece/tokenizer.model'),
@ -4277,10 +4296,10 @@ app.post('/generate_horde', jsonParser, async (request, response) => {
"body": JSON.stringify(request.body),
"headers": {
"Content-Type": "application/json",
"Client-Agent": request.header('Client-Agent'),
"apikey": api_key_horde,
}
};
if (request.header('Client-Agent') !== undefined) args.headers['Client-Agent'] = request.header('Client-Agent');
console.log(args.body);
try {
@ -4554,7 +4573,7 @@ app.post('/novel_tts', jsonParser, async (request, response) => {
}
try {
const url = `${api_novelai}/ai/generate-voice?text=${encodeURIComponent(text)}&voice=-1&seed=${encodeURIComponent(voice)}&opus=false&version=v2`;
const url = `${API_NOVELAI}/ai/generate-voice?text=${encodeURIComponent(text)}&voice=-1&seed=${encodeURIComponent(voice)}&opus=false&version=v2`;
const result = await fetch(url, {
method: 'GET',
headers: {