Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging
This commit is contained in:
commit
f468a33d60
580
Launcher.bat
580
Launcher.bat
|
@ -38,11 +38,14 @@ set "node_installer_path=%temp%\NodejsInstaller.msi"
|
|||
REM Environment Variables (winget)
|
||||
set "winget_path=%userprofile%\AppData\Local\Microsoft\WindowsApps"
|
||||
|
||||
REM Environment Variables (TOOLBOX Install Extras)
|
||||
set "miniconda_path=%userprofile%\miniconda"
|
||||
|
||||
|
||||
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 %yellow_fg_strong%[WARN] 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"
|
||||
|
@ -106,33 +109,37 @@ 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 2. Start SillyTavern + Extras
|
||||
echo 3. Update
|
||||
echo 4. Backup
|
||||
echo 5. Switch branch
|
||||
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 SillyTavern branch: %cyan_fg_strong%%current_branch%%reset%
|
||||
echo Update Status: %update_status%
|
||||
echo =================================
|
||||
set /p choice=Choose Your Destiny:
|
||||
|
||||
set "choice="
|
||||
set /p "choice=Choose Your Destiny (default is 1): "
|
||||
|
||||
REM Default to choice 1 if no input is provided
|
||||
if not defined choice set "choice=1"
|
||||
|
||||
REM Home - backend
|
||||
if "%choice%"=="1" (
|
||||
call :start
|
||||
) else if "%choice%"=="2" (
|
||||
call :update
|
||||
call :start_extras
|
||||
) else if "%choice%"=="3" (
|
||||
call :switch_release
|
||||
call :update
|
||||
) else if "%choice%"=="4" (
|
||||
call :switch_staging
|
||||
) else if "%choice%"=="5" (
|
||||
call :backup_menu
|
||||
) else if "%choice%"=="5" (
|
||||
call :switchbrance_menu
|
||||
) else if "%choice%"=="6" (
|
||||
call :toolbox
|
||||
) else if "%choice%"=="7" (
|
||||
|
@ -144,6 +151,7 @@ if "%choice%"=="1" (
|
|||
goto :home
|
||||
)
|
||||
|
||||
|
||||
:start
|
||||
REM Check if Node.js is installed
|
||||
node --version > nul 2>&1
|
||||
|
@ -154,20 +162,26 @@ if %errorlevel% neq 0 (
|
|||
pause
|
||||
goto :home
|
||||
)
|
||||
|
||||
echo Launching SillyTavern...
|
||||
cls
|
||||
pushd %~dp0
|
||||
call npm install --no-audit
|
||||
node server.js
|
||||
pause
|
||||
popd
|
||||
echo %blue_fg_strong%[INFO]%reset% A new window has been launched.
|
||||
start /wait cmd /c start.bat
|
||||
goto :home
|
||||
|
||||
|
||||
:start_extras
|
||||
REM Run conda activate from the Miniconda installation
|
||||
call "%miniconda_path%\Scripts\activate.bat"
|
||||
|
||||
REM Activate the sillytavernextras environment
|
||||
call conda activate sillytavernextras
|
||||
|
||||
REM Start SillyTavern Extras with desired configurations
|
||||
python server.py --coqui-gpu --rvc-save-file --cuda-device=0 --max-content-length=1000 --enable-modules=caption,summarize,classify,rvc,coqui-tts --classification-model=joeddav/distilbert-base-uncased-go-emotions-student --share
|
||||
goto :home
|
||||
|
||||
|
||||
:update
|
||||
echo Updating...
|
||||
pushd %~dp0
|
||||
|
||||
REM Check if git is installed
|
||||
git --version > nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
|
@ -185,39 +199,76 @@ 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
|
||||
REM Switch Brance - frontend
|
||||
:switchbrance_menu
|
||||
cls
|
||||
echo %blue_fg_strong%/ Home / Switch Branch%reset%
|
||||
echo -------------------------------------
|
||||
echo What would you like to do?
|
||||
echo 1. Switch to Release - SillyTavern
|
||||
echo 2. Switch to Staging - SillyTavern
|
||||
echo 3. Switch to Main - Extras
|
||||
echo 4. Switch to Neo - Extras
|
||||
echo 5. Back to Home
|
||||
|
||||
REM Get the current Git branch
|
||||
for /f %%i in ('git branch --show-current') do set current_branch=%%i
|
||||
echo ======== VERSION STATUS =========
|
||||
echo SillyTavern branch: %cyan_fg_strong%%current_branch%%reset%
|
||||
echo Extras branch: %cyan_fg_strong%%current_branch%%reset%
|
||||
echo =================================
|
||||
set /p brance_choice=Choose Your Destiny:
|
||||
|
||||
REM Switch Brance - backend
|
||||
if "%brance_choice%"=="1" (
|
||||
call :switch_release_st
|
||||
) else if "%brance_choice%"=="2" (
|
||||
call :switch_staging_st
|
||||
) else if "%brance_choice%"=="3" (
|
||||
call :switch_main_ste
|
||||
) else if "%brance_choice%"=="4" (
|
||||
call :switch_neo_ste
|
||||
) else if "%brance_choice%"=="5" (
|
||||
goto :home
|
||||
) else (
|
||||
color 6
|
||||
echo WARNING: Invalid number. Please insert a valid number.
|
||||
pause
|
||||
goto :switchbrance_menu
|
||||
)
|
||||
echo Switching to release branch...
|
||||
|
||||
|
||||
:switch_release_st
|
||||
echo %blue_fg_strong%[INFO]%reset% Switching to release branch...
|
||||
git switch release
|
||||
pause
|
||||
goto :home
|
||||
goto :switchbrance_menu
|
||||
|
||||
|
||||
: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...
|
||||
:switch_staging_st
|
||||
echo %blue_fg_strong%[INFO]%reset% Switching to staging branch...
|
||||
git switch staging
|
||||
pause
|
||||
goto :home
|
||||
goto :switchbrance_menu
|
||||
|
||||
|
||||
:switch_main_ste
|
||||
echo %blue_fg_strong%[INFO]%reset% Switching to main branch...
|
||||
cd SillyTavern-extras
|
||||
git switch main
|
||||
pause
|
||||
goto :switchbrance_menu
|
||||
|
||||
REM backup - frontend
|
||||
|
||||
:switch_neo_ste
|
||||
echo %blue_fg_strong%[INFO]%reset% Switching to neo branch...
|
||||
cd SillyTavern-extras
|
||||
git switch neo
|
||||
pause
|
||||
goto :switchbrance_menu
|
||||
|
||||
|
||||
REM Backup - Frontend
|
||||
:backup_menu
|
||||
REM Check if 7-Zip is installed
|
||||
7z > nul 2>&1
|
||||
|
@ -228,7 +279,6 @@ if %errorlevel% neq 0 (
|
|||
pause
|
||||
goto :home
|
||||
)
|
||||
|
||||
cls
|
||||
echo %blue_fg_strong%/ Home / Backup%reset%
|
||||
echo -------------------------------------
|
||||
|
@ -240,7 +290,7 @@ echo 3. Back to Home
|
|||
|
||||
set /p backup_choice=Choose Your Destiny:
|
||||
|
||||
REM backup - backend
|
||||
REM Backup - Backend
|
||||
if "%backup_choice%"=="1" (
|
||||
call :create_backup
|
||||
) else if "%backup_choice%"=="2" (
|
||||
|
@ -254,207 +304,6 @@ if "%backup_choice%"=="1" (
|
|||
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" ^
|
||||
|
@ -549,3 +398,244 @@ if "%restore_choice%" geq "1" (
|
|||
)
|
||||
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
|
||||
echo 5. Reinstall SillyTavern
|
||||
echo 6. Reinstall Extras
|
||||
echo 7. 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" (
|
||||
call :reinstallextras
|
||||
) else if "%toolbox_choice%"=="7" (
|
||||
goto :home
|
||||
) else (
|
||||
color 6
|
||||
echo WARNING: Invalid number. Please insert a valid number.
|
||||
pause
|
||||
goto :toolbox
|
||||
)
|
||||
|
||||
|
||||
:install7zip
|
||||
echo %blue_fg_strong%[INFO]%reset% Installing 7-Zip...
|
||||
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
|
||||
|
||||
|
||||
:reinstallextras
|
||||
cls
|
||||
echo %blue_fg_strong%SillyTavern Extras%reset%
|
||||
echo ---------------------------------------------------------------
|
||||
echo %blue_fg_strong%[INFO]%reset% Installing SillyTavern Extras...
|
||||
echo --------------------------------
|
||||
echo %cyan_fg_strong%This may take a while. Please be patient.%reset%
|
||||
|
||||
winget install -e --id Anaconda.Miniconda3
|
||||
|
||||
REM Run conda activate from the Miniconda installation
|
||||
call "%miniconda_path%\Scripts\activate.bat"
|
||||
|
||||
REM Create a Conda environment named sillytavernextras
|
||||
call conda create -n sillytavernextras -y
|
||||
|
||||
REM Activate the sillytavernextras environment
|
||||
call conda activate sillytavernextras
|
||||
|
||||
REM Install Python 3.11 and Git in the sillytavernextras environment
|
||||
call conda install python=3.11 git -y
|
||||
|
||||
REM Clone the SillyTavern Extras repository
|
||||
git clone https://github.com/SillyTavern/SillyTavern-extras
|
||||
|
||||
REM Navigate to the SillyTavern-extras directory
|
||||
cd SillyTavern-extras
|
||||
|
||||
REM Install Python dependencies from requirements files
|
||||
pip install -r requirements-complete.txt
|
||||
pip install -r requirements-rvc.txt
|
||||
|
||||
REM Start SillyTavern Extras with desired configurations
|
||||
python server.py --coqui-gpu --rvc-save-file --cuda-device=0 --max-content-length=1000 --enable-modules=caption,summarize,classify,rvc,coqui-tts --classification-model=joeddav/distilbert-base-uncased-go-emotions-student --share
|
||||
|
||||
echo.
|
||||
echo %green_fg_strong%SillyTavern Extras have been successfully installed.%reset%
|
||||
pause
|
||||
goto :toolbox
|
||||
|
|
|
@ -270,6 +270,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1001px) {
|
||||
#PygOverrides,
|
||||
#ContextFormatting,
|
||||
#UI-Theme-Block,
|
||||
#UI-Customization,
|
||||
#power-user-options-block {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*landscape mode phones and ipads*/
|
||||
@media screen and (max-width: 1000px) and (orientation: landscape) {
|
||||
body.waifuMode img.expression {
|
||||
|
|
|
@ -1019,8 +1019,8 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<label class="checkbox_label" for="use_default_badwordids">
|
||||
<input id="use_default_badwordids" type="checkbox" />
|
||||
<label class="checkbox_label" for="use_default_badwordsids">
|
||||
<input id="use_default_badwordsids" type="checkbox" />
|
||||
<span data-i18n="Ban EOS Token">
|
||||
Ban EOS Token
|
||||
</span>
|
||||
|
@ -1049,7 +1049,7 @@
|
|||
</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" />
|
||||
<input type="range" id="mirostat_tau_kobold" name="volume" min="0" max="20" step="0.01" />
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="mirostat_tau_kobold" id="mirostat_tau_counter_kobold">
|
||||
|
@ -1587,7 +1587,7 @@
|
|||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="mirostat_tau_textgenerationwebui" name="volume" min="0" max="10" step="0.01" />
|
||||
<input type="range" id="mirostat_tau_textgenerationwebui" name="volume" min="0" max="20" step="0.01" />
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="mirostat_tau_textgenerationwebui" id="mirostat_tau_counter_textgenerationwebui">
|
||||
|
@ -2151,13 +2151,13 @@
|
|||
</a>
|
||||
</h3>
|
||||
<div class="flex-container">
|
||||
<div name="PygOverrides" class="flex1">
|
||||
<div id="PygOverrides">
|
||||
<div>
|
||||
<h4 data-i18n="Context Template">
|
||||
Context Template
|
||||
</h4>
|
||||
<div class="preset_buttons flex-container">
|
||||
<select id="context_presets" data-preset-manager-for="context" class="flex1 widthFitContent"></select>
|
||||
<select id="context_presets" data-preset-manager-for="context" class="flex1"></select>
|
||||
<input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
|
||||
<i id="context_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this preset for Instruct Mode."></i>
|
||||
<i data-newbie-hidden data-preset-manager-update="context" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
|
@ -2357,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="200k0" placeholder="—" rows="1"></textarea>
|
||||
<textarea id="instruct_stop_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
|
@ -2374,7 +2374,7 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<div name="ContextFormatting" class="flex1">
|
||||
<div id="ContextFormatting">
|
||||
<div data-newbie-hidden>
|
||||
<h4><span data-i18n="Tokenizer">Tokenizer</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#tokenizer" class="notes-link" target="_blank">
|
||||
|
@ -2701,7 +2701,7 @@
|
|||
<div id="version_display"></div>
|
||||
</div>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div id="UI-Theme-Block" class="flex-container flexFlowColumn drawer33pWidth">
|
||||
<div id="UI-Theme-Block" class="flex-container flexFlowColumn wide100p">
|
||||
<div id="color-picker-block" class="flex-container flexFlowColumn flexNoGap">
|
||||
<div id="UI-Mode-Block">
|
||||
<h4 data-i18n="UI Mode">
|
||||
|
@ -2837,7 +2837,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div name="UI Customization" class="flex-container drawer33pWidth">
|
||||
<div id="UI-Customization" class="flex-container wide100p">
|
||||
<div class="ui-settings">
|
||||
<h4><span data-i18n="UI Customization">UI Customization</span></h4>
|
||||
<div data-newbie-hidden class="range-block">
|
||||
|
@ -2966,14 +2966,13 @@
|
|||
<option value="-1" data-i18n="Always disabled">Always disabled</option>
|
||||
<option value="0" data-i18n="Automatic (desktop)">Automatic (desktop)</option>
|
||||
<option value="1" data-i18n="Always enabled">Always enabled</option>
|
||||
</select>♦
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="power-user-options-block" class="flex-container drawer33pWidth">
|
||||
<div id="power-user-options-block" class="flex-container wide100p">
|
||||
<div id="power-user-option-checkboxes">
|
||||
<h4 data-i18n="Power User Options">Power User Options</h4>
|
||||
<label data-newbie-hidden class="checkbox_label" for="swipes-checkbox">
|
||||
|
@ -3146,12 +3145,12 @@
|
|||
</div>
|
||||
<div class="alignitemsflexstart flex-container wide100p">
|
||||
<input id="extensions_url" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]Extensions URL" placeholder="Extensions URL">
|
||||
<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" data-i18n="Connect">Connect</input>
|
||||
<input id="extensions_api_key" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]API key" placeholder="Extras API key">
|
||||
<div class="extensions_url_block">
|
||||
<div id="extensions_connect" class="menu_button" data-i18n="Connect">Connect</div>
|
||||
<div id="extensions_details" class="menu_button_icon menu_button">
|
||||
Manage extensions
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3533,8 +3532,8 @@
|
|||
<select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order">
|
||||
<option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option>
|
||||
<option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option>
|
||||
<option data-field="date_added" data-order="desc" data-i18n="Newest">Newest</option>
|
||||
<option data-field="date_added" data-order="asc" data-i18n="Oldest">Oldest</option>
|
||||
<option data-field="create_date" data-order="desc" data-i18n="Newest">Newest</option>
|
||||
<option data-field="create_date" data-order="asc" data-i18n="Oldest">Oldest</option>
|
||||
<option data-field="fav" data-order="desc" data-rule="boolean" data-i18n="Favorites">Favorites</option>
|
||||
<option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
|
||||
<option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
|
||||
|
@ -4536,6 +4535,7 @@
|
|||
toastr.options.extendedTimeOut = 10000; // How long the toast will display after a user hovers over it
|
||||
toastr.options.progressBar = true; // Visually indicate how long before a toast expires.
|
||||
toastr.options.closeButton = true; // enable a close button
|
||||
toastr.options.positionClass = "toast-top-center"; // Where to position the toast container
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -557,6 +557,12 @@ export function dragElement(elmnt) {
|
|||
//set a listener for mouseup to save new width/height
|
||||
elmnt.off('mouseup').on('mouseup', () => {
|
||||
console.debug(`Saving ${elmntName} Height/Width`)
|
||||
// check if the height or width actually changed
|
||||
if (power_user.movingUIState[elmntName].width === width && power_user.movingUIState[elmntName].height === height) {
|
||||
console.debug('no change detected, aborting save')
|
||||
return
|
||||
}
|
||||
|
||||
power_user.movingUIState[elmntName].width = width;
|
||||
power_user.movingUIState[elmntName].height = height;
|
||||
eventSource.emit('resizeUI', elmntName);
|
||||
|
|
|
@ -75,20 +75,8 @@ async function initGallery(items, url) {
|
|||
fnThumbnailOpen: viewWithDragbox,
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.nGY2GThumbnailImage').on('click', function () {
|
||||
let imageUrl = $(this).find('.nGY2GThumbnailImg').attr('src');
|
||||
// Do what you want with the imageUrl, for example:
|
||||
// Display it in a full-size view or replace the gallery grid content with this image
|
||||
console.log(imageUrl);
|
||||
});
|
||||
});
|
||||
|
||||
eventSource.on('resizeUI', function (elmntName) {
|
||||
console.log('resizeUI saw', elmntName);
|
||||
// Your logic here
|
||||
|
||||
// If you want to resize the nanogallery2 instance when this event is triggered:
|
||||
jQuery("#dragGallery").nanogallery2('resize');
|
||||
});
|
||||
|
||||
|
@ -98,6 +86,8 @@ async function initGallery(items, url) {
|
|||
dropZone.off('dragleave');
|
||||
dropZone.off('drop');
|
||||
|
||||
// Set dropzone height to be the same as the parent
|
||||
dropZone.css('height', dropZone.parent().css('height'));
|
||||
|
||||
// Initialize dropzone handlers
|
||||
dropZone.on('dragover', function (e) {
|
||||
|
|
|
@ -10,11 +10,13 @@ import {
|
|||
appendImageToMessage,
|
||||
generateQuietPrompt,
|
||||
this_chid,
|
||||
getCurrentChatId,
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { getMessageTimeStamp, humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
import { SECRET_KEYS, secret_state } from "../../secrets.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
|
@ -27,10 +29,12 @@ const p = a => `<p>${a}</p>`
|
|||
const MODULE_NAME = 'sd';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const postHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
};
|
||||
const sources = {
|
||||
extras: 'extras',
|
||||
horde: 'horde',
|
||||
auto: 'auto',
|
||||
novel: 'novel',
|
||||
}
|
||||
|
||||
const generationMode = {
|
||||
CHARACTER: 0,
|
||||
|
@ -116,6 +120,8 @@ const helpString = [
|
|||
].join('<br>');
|
||||
|
||||
const defaultSettings = {
|
||||
source: sources.extras,
|
||||
|
||||
// CFG Scale
|
||||
scale_min: 1,
|
||||
scale_max: 30,
|
||||
|
@ -153,13 +159,48 @@ const defaultSettings = {
|
|||
refine_mode: false,
|
||||
|
||||
prompts: promptTemplates,
|
||||
|
||||
// AUTOMATIC1111 settings
|
||||
auto_url: 'http://localhost:7860',
|
||||
auto_auth: '',
|
||||
|
||||
hr_upscaler: 'Latent',
|
||||
hr_scale: 2.0,
|
||||
hr_scale_min: 1.0,
|
||||
hr_scale_max: 4.0,
|
||||
hr_scale_step: 0.1,
|
||||
denoising_strength: 0.7,
|
||||
denoising_strength_min: 0.0,
|
||||
denoising_strength_max: 1.0,
|
||||
denoising_strength_step: 0.01,
|
||||
hr_second_pass_steps: 0,
|
||||
hr_second_pass_steps_min: 0,
|
||||
hr_second_pass_steps_max: 150,
|
||||
hr_second_pass_steps_step: 1,
|
||||
}
|
||||
|
||||
const getAutoRequestBody = () => ({ url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth });
|
||||
|
||||
function toggleSourceControls() {
|
||||
$('.sd_settings [data-sd-source]').each(function () {
|
||||
const source = $(this).data('sd-source');
|
||||
$(this).toggle(source === extension_settings.sd.source);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
// Initialize settings
|
||||
if (Object.keys(extension_settings.sd).length === 0) {
|
||||
Object.assign(extension_settings.sd, defaultSettings);
|
||||
}
|
||||
|
||||
// Insert missing settings
|
||||
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||
if (extension_settings.sd[key] === undefined) {
|
||||
extension_settings.sd[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (extension_settings.sd.prompts === undefined) {
|
||||
extension_settings.sd.prompts = promptTemplates;
|
||||
}
|
||||
|
@ -175,19 +216,26 @@ async function loadSettings() {
|
|||
extension_settings.sd.character_prompts = {};
|
||||
}
|
||||
|
||||
$('#sd_source').val(extension_settings.sd.source);
|
||||
$('#sd_scale').val(extension_settings.sd.scale).trigger('input');
|
||||
$('#sd_steps').val(extension_settings.sd.steps).trigger('input');
|
||||
$('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
|
||||
$('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input');
|
||||
$('#sd_width').val(extension_settings.sd.width).trigger('input');
|
||||
$('#sd_height').val(extension_settings.sd.height).trigger('input');
|
||||
$('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
|
||||
$('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
|
||||
$('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
|
||||
$('#sd_horde').prop('checked', extension_settings.sd.horde);
|
||||
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
|
||||
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
|
||||
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
|
||||
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
|
||||
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
|
||||
$('#sd_auto_url').val(extension_settings.sd.auto_url);
|
||||
$('#sd_auto_auth').val(extension_settings.sd.auto_auth);
|
||||
|
||||
toggleSourceControls();
|
||||
addPromptTemplates();
|
||||
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
|
@ -332,10 +380,11 @@ function onHeightInput() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onHordeInput() {
|
||||
async function onSourceChange() {
|
||||
extension_settings.sd.source = $('#sd_source').find(':selected').val();
|
||||
extension_settings.sd.model = null;
|
||||
extension_settings.sd.sampler = null;
|
||||
extension_settings.sd.horde = !!$(this).prop('checked');
|
||||
toggleSourceControls();
|
||||
saveSettingsDebounced();
|
||||
await Promise.all([loadModels(), loadSamplers()]);
|
||||
}
|
||||
|
@ -360,13 +409,140 @@ function onHighResFixInput() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onAutoUrlInput() {
|
||||
extension_settings.sd.auto_url = $('#sd_auto_url').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onAutoAuthInput() {
|
||||
extension_settings.sd.auto_auth = $('#sd_auto_auth').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrUpscalerChange() {
|
||||
extension_settings.sd.hr_upscaler = $('#sd_hr_upscaler').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrScaleInput() {
|
||||
extension_settings.sd.hr_scale = Number($('#sd_hr_scale').val());
|
||||
$('#sd_hr_scale_value').text(extension_settings.sd.hr_scale.toFixed(1));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onDenoisingStrengthInput() {
|
||||
extension_settings.sd.denoising_strength = Number($('#sd_denoising_strength').val());
|
||||
$('#sd_denoising_strength_value').text(extension_settings.sd.denoising_strength.toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrSecondPassStepsInput() {
|
||||
extension_settings.sd.hr_second_pass_steps = Number($('#sd_hr_second_pass_steps').val());
|
||||
$('#sd_hr_second_pass_steps_value').text(extension_settings.sd.hr_second_pass_steps);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function validateAutoUrl() {
|
||||
try {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
throw new Error('URL is not set.');
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/ping', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
await loadSamplers();
|
||||
await loadModels();
|
||||
toastr.success('SD WebUI API connected.');
|
||||
} catch (error) {
|
||||
toastr.error(`Could not validate SD WebUI API: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function onModelChange() {
|
||||
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (!extension_settings.sd.horde) {
|
||||
const cloudSources = [sources.horde, sources.novel];
|
||||
|
||||
if (cloudSources.includes(extension_settings.sd.source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
toastr.info('Updating remote model...', 'Please wait');
|
||||
if (extension_settings.sd.source === sources.extras) {
|
||||
await updateExtrasRemoteModel();
|
||||
}
|
||||
if (extension_settings.sd.source === sources.auto) {
|
||||
await updateAutoRemoteModel();
|
||||
}
|
||||
toastr.success('Model successfully loaded!', 'Stable Diffusion');
|
||||
}
|
||||
|
||||
async function getAutoRemoteModel() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/get-model', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.text();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAutoRemoteUpscalers() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/upscalers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [extension_settings.sd.hr_upscaler];
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAutoRemoteModel() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/set-model', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ ...getAutoRequestBody(), model: extension_settings.sd.model }),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
console.log('Model successfully updated on SD WebUI remote.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error(`Could not update SD WebUI model: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateExtrasRemoteModel() {
|
||||
|
@ -374,7 +550,6 @@ async function updateExtrasRemoteModel() {
|
|||
url.pathname = '/api/image/model';
|
||||
const getCurrentModelResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
body: JSON.stringify({ model: extension_settings.sd.model }),
|
||||
});
|
||||
|
||||
|
@ -387,10 +562,19 @@ async function loadSamplers() {
|
|||
$('#sd_sampler').empty();
|
||||
let samplers = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
samplers = await loadHordeSamplers();
|
||||
} else {
|
||||
samplers = await loadExtrasSamplers();
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
samplers = await loadExtrasSamplers();
|
||||
break;
|
||||
case sources.horde:
|
||||
samplers = await loadHordeSamplers();
|
||||
break;
|
||||
case sources.auto:
|
||||
samplers = await loadAutoSamplers();
|
||||
break;
|
||||
case sources.novel:
|
||||
samplers = await loadNovelSamplers();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const sampler of samplers) {
|
||||
|
@ -433,14 +617,63 @@ async function loadExtrasSamplers() {
|
|||
return [];
|
||||
}
|
||||
|
||||
async function loadAutoSamplers() {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fetch('/api/sd/samplers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNovelSamplers() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
toastr.warning('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'k_dpmpp_2m',
|
||||
'k_dpmpp_sde',
|
||||
'k_dpmpp_2s_ancestral',
|
||||
'k_euler',
|
||||
'k_euler_ancestral',
|
||||
'k_dpm_fast',
|
||||
'ddim',
|
||||
];
|
||||
}
|
||||
|
||||
async function loadModels() {
|
||||
$('#sd_model').empty();
|
||||
let models = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
models = await loadHordeModels();
|
||||
} else {
|
||||
models = await loadExtrasModels();
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
models = await loadExtrasModels();
|
||||
break;
|
||||
case sources.horde:
|
||||
models = await loadHordeModels();
|
||||
break;
|
||||
case sources.auto:
|
||||
models = await loadAutoModels();
|
||||
break;
|
||||
case sources.novel:
|
||||
models = await loadNovelModels();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
|
@ -495,6 +728,71 @@ async function loadExtrasModels() {
|
|||
return [];
|
||||
}
|
||||
|
||||
async function loadAutoModels() {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const currentModel = await getAutoRemoteModel();
|
||||
|
||||
if (currentModel) {
|
||||
extension_settings.sd.model = currentModel;
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const upscalers = await getAutoRemoteUpscalers();
|
||||
|
||||
if (Array.isArray(upscalers) && upscalers.length > 0) {
|
||||
$('#sd_hr_upscaler').empty();
|
||||
|
||||
for (const upscaler of upscalers) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = upscaler;
|
||||
option.value = upscaler;
|
||||
option.selected = upscaler === extension_settings.sd.hr_upscaler;
|
||||
$('#sd_hr_upscaler').append(option);
|
||||
}
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNovelModels() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
toastr.warning('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value: 'nai-diffusion',
|
||||
text: 'Full',
|
||||
},
|
||||
{
|
||||
value: 'safe-diffusion',
|
||||
text: 'Safe',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion-furry',
|
||||
text: 'Furry',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getGenerationType(prompt) {
|
||||
for (const [key, values] of Object.entries(triggerWords)) {
|
||||
for (const value of values) {
|
||||
|
@ -537,7 +835,6 @@ function processReply(str) {
|
|||
return str;
|
||||
}
|
||||
|
||||
|
||||
function getRawLastMessage() {
|
||||
const getLastUsableMessage = () => {
|
||||
for (const message of context.chat.slice().reverse()) {
|
||||
|
@ -565,7 +862,7 @@ async function generatePicture(_, trigger, message, callback) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!modules.includes('sd') && !extension_settings.sd.horde) {
|
||||
if (!isValidState()) {
|
||||
toastr.warning("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.");
|
||||
return;
|
||||
}
|
||||
|
@ -600,10 +897,10 @@ async function generatePicture(_, trigger, message, callback) {
|
|||
const callbackOriginal = callback;
|
||||
callback = async function (prompt, base64Image) {
|
||||
const imagePath = base64Image;
|
||||
const imgUrl = `url('${encodeURIComponent(base64Image)}')`;
|
||||
const imgUrl = `url("${encodeURI(base64Image)}")`;
|
||||
|
||||
if ('forceSetBackground' in window) {
|
||||
forceSetBackground(imgUrl);
|
||||
if (typeof window['forceSetBackground'] === 'function') {
|
||||
window['forceSetBackground'](imgUrl);
|
||||
} else {
|
||||
toastr.info('Background image will not be preserved.', '"Chat backgrounds" extension is disabled.');
|
||||
$('#bg_custom').css('background-image', imgUrl);
|
||||
|
@ -669,32 +966,60 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul
|
|||
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
||||
: extension_settings.sd.prompt_prefix;
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
await generateHordeImage(prompt, prefix, characterName, callback);
|
||||
} else {
|
||||
await generateExtrasImage(prompt, prefix, characterName, callback);
|
||||
const prefixedPrompt = combinePrefixes(prefix, prompt);
|
||||
|
||||
let result = { format: '', data: '' };
|
||||
const currentChatId = getCurrentChatId();
|
||||
|
||||
try {
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
result = await generateExtrasImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.horde:
|
||||
result = await generateHordeImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.auto:
|
||||
result = await generateAutoImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.novel:
|
||||
result = await generateNovelImage(prefixedPrompt);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
toastr.error('Image generation failed. Please try again', 'Stable Diffusion');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentChatId !== getCurrentChatId()) {
|
||||
console.warn('Chat changed, aborting SD result saving');
|
||||
toastr.warning('Chat changed, generated image discarded.', 'Stable Diffusion');
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format);
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an "extras" image using a provided prompt and other settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
* Generates an "extras" image using a provided prompt and other settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||
console.debug(extension_settings.sd);
|
||||
async function generateExtrasImage(prompt) {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
|
@ -702,38 +1027,32 @@ async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
|||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
karras: !!extension_settings.sd.horde_karras,
|
||||
hr_upscaler: extension_settings.sd.hr_upscaler,
|
||||
hr_scale: extension_settings.sd.hr_scale,
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
//filename should be character name + human readable timestamp + generation mode
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data.image, characterName, filename, "jpg");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
return { format: 'jpg', data: data.image };
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a "horde" image using the provided prompt and configuration settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
* Generates a "horde" image using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
async function generateHordeImage(prompt) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
|
@ -744,7 +1063,6 @@ async function generateHordeImage(prompt, prefix, characterName, callback) {
|
|||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
model: extension_settings.sd.model,
|
||||
nsfw: extension_settings.sd.horde_nsfw,
|
||||
|
@ -755,11 +1073,80 @@ async function generateHordeImage(prompt, prefix, characterName, callback) {
|
|||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data, characterName, filename, "webp");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
return { format: 'webp', data: data };
|
||||
} else {
|
||||
toastr.error('Image generation has failed. Please try again.');
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in SD WebUI API using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateAutoImage(prompt) {
|
||||
const result = await fetch('/api/sd/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
...getAutoRequestBody(),
|
||||
prompt: prompt,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
sampler_name: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
cfg_scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
hr_upscaler: extension_settings.sd.hr_upscaler,
|
||||
hr_scale: extension_settings.sd.hr_scale,
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
|
||||
// Ensure generated img is saved to disk
|
||||
save_images: true,
|
||||
send_images: true,
|
||||
do_not_save_grid: false,
|
||||
do_not_save_samples: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return { format: 'png', data: data.images[0] };
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in NovelAI API using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateNovelImage(prompt) {
|
||||
const result = await fetch('/api/novelai/generate-image', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
model: extension_settings.sd.model,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
return { format: 'png', data: data };
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -842,12 +1229,21 @@ function addSDGenButtons() {
|
|||
});
|
||||
}
|
||||
|
||||
function isConnectedToExtras() {
|
||||
return modules.includes('sd');
|
||||
function isValidState() {
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
return modules.includes('sd');
|
||||
case sources.horde:
|
||||
return true;
|
||||
case sources.auto:
|
||||
return !!extension_settings.sd.auto_url;
|
||||
case sources.novel:
|
||||
return secret_state[SECRET_KEYS.NOVEL];
|
||||
}
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
if (isConnectedToExtras() || extension_settings.sd.horde) {
|
||||
if (isValidState()) {
|
||||
$('#sd_gen').show();
|
||||
$('.sd_message_gen').show();
|
||||
}
|
||||
|
@ -942,82 +1338,8 @@ $("#sd_dropdown [id]").on("click", function () {
|
|||
jQuery(async () => {
|
||||
getContext().registerSlashCommand('sd', generatePicture, [], helpString, true, true);
|
||||
|
||||
const settingsHtml = `
|
||||
<div class="sd_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Stable Diffusion</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
|
||||
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
|
||||
<input id="sd_refine_mode" type="checkbox" />
|
||||
Edit prompts before generation
|
||||
</label>
|
||||
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde" type="checkbox" />
|
||||
Use Stable Horde
|
||||
</label>
|
||||
<label style="margin-left:1em;" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
Allow NSFW images from Horde
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
|
||||
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
|
||||
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
|
||||
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container flexGap5 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
Karras (only for Horde, not all samplers supported)
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Common prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div id="sd_character_prompt_block">
|
||||
<label for="sd_character_prompt">Character-specific prompt prefix</label>
|
||||
<small>Won't be used in groups.</small>
|
||||
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix. Example: female, green eyes, brown hair, pink shirt"></textarea>
|
||||
</div>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>SD Prompt Templates</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div id="sd_prompt_templates" class="inline-drawer-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
$('#extensions_settings').append(renderExtensionTemplate('stable-diffusion', 'settings', defaultSettings));
|
||||
$('#sd_source').on('change', onSourceChange);
|
||||
$('#sd_scale').on('input', onScaleInput);
|
||||
$('#sd_steps').on('input', onStepsInput);
|
||||
$('#sd_model').on('change', onModelChange);
|
||||
|
@ -1026,13 +1348,19 @@ jQuery(async () => {
|
|||
$('#sd_negative_prompt').on('input', onNegativePromptInput);
|
||||
$('#sd_width').on('input', onWidthInput);
|
||||
$('#sd_height').on('input', onHeightInput);
|
||||
$('#sd_horde').on('input', onHordeInput);
|
||||
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
|
||||
$('#sd_horde_karras').on('input', onHordeKarrasInput);
|
||||
$('#sd_restore_faces').on('input', onRestoreFacesInput);
|
||||
$('#sd_enable_hr').on('input', onHighResFixInput);
|
||||
$('#sd_refine_mode').on('input', onRefineModeInput);
|
||||
$('#sd_character_prompt').on('input', onCharacterPromptInput);
|
||||
$('#sd_auto_validate').on('click', validateAutoUrl);
|
||||
$('#sd_auto_url').on('input', onAutoUrlInput);
|
||||
$('#sd_auto_auth').on('input', onAutoAuthInput);
|
||||
$('#sd_hr_upscaler').on('change', onHrUpscalerChange);
|
||||
$('#sd_hr_scale').on('input', onHrScaleInput);
|
||||
$('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
|
||||
$('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
|
||||
$('#sd_character_prompt_block').hide();
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
|
||||
<div class="sd_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Stable Diffusion</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
|
||||
<input id="sd_refine_mode" type="checkbox" />
|
||||
Edit prompts before generation
|
||||
</label>
|
||||
<label for="sd_source">Source</label>
|
||||
<select id="sd_source">
|
||||
<option value="extras">Extras API (local / remote)</option>
|
||||
<option value="horde">Stable Horde</option>
|
||||
<option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
|
||||
<option value="novel">NovelAI Diffusion</option>
|
||||
</select>
|
||||
<div data-sd-source="auto">
|
||||
<label for="sd_auto_url">SD Web UI URL</label>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="sd_auto_url" type="text" class="text_pole" placeholder="Example: {{auto_url}}" value="{{auto_url}}" />
|
||||
<div id="sd_auto_validate" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
<span data-i18n="Connect">
|
||||
Connect
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<label for="sd_auto_auth">Authentication (optional)</label>
|
||||
<input id="sd_auto_auth" type="text" class="text_pole" placeholder="Example: username:password" value="" />
|
||||
<i><b>Important:</b> run SD Web UI with the <tt>--api</tt> flag! The server must be accessible from the SillyTavern host machine.</i>
|
||||
</div>
|
||||
<div data-sd-source="horde">
|
||||
<i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i>
|
||||
<label for="sd_horde_nsfw" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
<span data-i18n="Allow NSFW images from Horde">
|
||||
Allow NSFW images from Horde
|
||||
</span>
|
||||
</label>
|
||||
<label for="sd_horde_karras" class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
<span data-i18n="Karras (not all samplers supported)">
|
||||
Karras (not all samplers supported)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div data-sd-source="novel">
|
||||
<i>Hint: Save an API key in the NovelAI API settings to use it here.</i>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
<input id="sd_steps" type="range" min="{{steps_min}}" max="{{steps_max}}" step="{{steps_step}}" value="{{steps}}" />
|
||||
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
|
||||
<input id="sd_width" type="range" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{width}}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="{{dimension_max}}" min="{{dimension_min}}" step="{{dimension_step}}" value="{{height}}" />
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
</div>
|
||||
<div data-sd-source="auto">
|
||||
<label for="sd_hr_upscaler">Upscaler</label>
|
||||
<select id="sd_hr_upscaler"></select>
|
||||
<label for="sd_hr_scale">Upscale by (<span id="sd_hr_scale_value"></span>)</label>
|
||||
<input id="sd_hr_scale" type="range" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" />
|
||||
<label for="sd_denoising_strength">Denoising strength (<span id="sd_denoising_strength_value"></span>)</label>
|
||||
<input id="sd_denoising_strength" type="range" min="{{denoising_strength_min}}" max="{{denoising_strength_max}}" step="{{denoising_strength_step}}" value="{{denoising_strength}}" />
|
||||
<label for="sd_hr_second_pass_steps">Hires steps (2nd pass) (<span id="sd_hr_second_pass_steps_value"></span>)</label>
|
||||
<input id="sd_hr_second_pass_steps" type="range" min="{{hr_second_pass_steps_min}}" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_max}}" value="{{hr_second_pass_steps}}" />
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Common prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div id="sd_character_prompt_block">
|
||||
<label for="sd_character_prompt">Character-specific prompt prefix</label>
|
||||
<small>Won't be used in groups.</small>
|
||||
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix. Example: female, green eyes, brown hair, pink shirt"></textarea>
|
||||
</div>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>SD Prompt Templates</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div id="sd_prompt_templates" class="inline-drawer-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -558,7 +558,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||
}
|
||||
|
||||
if (activatedMembers.length === 0) {
|
||||
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
||||
//toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
||||
|
||||
// Send user message as is
|
||||
const bias = getBiasStrings(userInput, type);
|
||||
|
|
|
@ -80,9 +80,9 @@ 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);
|
||||
if (preset.hasOwnProperty('use_default_badwordsids')) {
|
||||
kai_settings.use_default_badwordsids = preset.use_default_badwordsids;
|
||||
$('#use_default_badwordsids').prop('checked', kai_settings.use_default_badwordsids);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,9 +351,9 @@ jQuery(function () {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_default_badwordids').on("input", function () {
|
||||
$('#use_default_badwordsids').on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
kai_settings.use_default_badwordids = value;
|
||||
kai_settings.use_default_badwordsids = value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
|
|
@ -705,7 +705,7 @@ function preparePromptsForChatCompletion({Scenario, charPersonality, name2, worl
|
|||
});
|
||||
|
||||
// Persona Description
|
||||
if (power_user.persona_description) {
|
||||
if (power_user.persona_description && power_user.persona_description_position === persona_description_positions.IN_PROMPT) {
|
||||
systemPrompts.push({ role: 'system', content: power_user.persona_description, identifier: 'personaDescription' });
|
||||
}
|
||||
|
||||
|
@ -1417,7 +1417,7 @@ class Message {
|
|||
this.role = role;
|
||||
this.content = content;
|
||||
|
||||
if (typeof this.content === 'string') {
|
||||
if (typeof this.content === 'string' && this.content.length > 0) {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content });
|
||||
} else {
|
||||
this.tokens = 0;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
|
@ -2066,6 +2066,10 @@ grammarly-extension {
|
|||
}
|
||||
|
||||
/* Override toastr default styles */
|
||||
body #toast-container {
|
||||
top: var(--topBarBlockSize);
|
||||
}
|
||||
|
||||
body #toast-container>div {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
@ -3589,3 +3593,19 @@ a {
|
|||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Jank mobile support for gallery and future draggables */
|
||||
@media screen and (max-width: 1000px) {
|
||||
#gallery {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
}
|
||||
.draggable {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
}
|
||||
}
|
||||
|
|
622
server.js
622
server.js
|
@ -193,6 +193,16 @@ function getOverrideHeaders(urlHost) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the Basic Auth header value for the given user and password.
|
||||
* @param {string} auth username:password
|
||||
* @returns {string} Basic Auth header value
|
||||
*/
|
||||
function getBasicAuthHeader(auth) {
|
||||
const encoded = Buffer.from(`${auth}`).toString('base64');
|
||||
return `Basic ${encoded}`;
|
||||
}
|
||||
|
||||
//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.
|
||||
//During testing, this performs the same as previous date.now() structure.
|
||||
|
@ -299,8 +309,8 @@ function getTiktokenTokenizer(model) {
|
|||
return tokenizer;
|
||||
}
|
||||
|
||||
function humanizedISO8601DateTime() {
|
||||
let baseDate = new Date(Date.now());
|
||||
function humanizedISO8601DateTime(date = Date.now()) {
|
||||
let baseDate = new Date(date);
|
||||
let humanYear = baseDate.getFullYear();
|
||||
let humanMonth = (baseDate.getMonth() + 1);
|
||||
let humanDate = baseDate.getDate();
|
||||
|
@ -555,15 +565,14 @@ app.post("/generate", jsonParser, async function (request, response_generate) {
|
|||
|
||||
const MAX_RETRIES = 50;
|
||||
const delayAmount = 2500;
|
||||
let url, response;
|
||||
for (let i = 0; i < MAX_RETRIES; i++) {
|
||||
try {
|
||||
url = request.body.streaming ? `${api_server}/extra/generate/stream` : `${api_server}/v1/generate`;
|
||||
response = await fetch(url, { method: 'POST', timeout: 0, ...args });
|
||||
const url = request.body.streaming ? `${api_server}/extra/generate/stream` : `${api_server}/v1/generate`;
|
||||
const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
|
||||
|
||||
if (request.body.streaming) {
|
||||
request.socket.on('close', function () {
|
||||
response.body.destroy(); // Close the remote stream
|
||||
if (response.body instanceof Readable) response.body.destroy(); // Close the remote stream
|
||||
response_generate.end(); // End the Express response
|
||||
});
|
||||
|
||||
|
@ -630,7 +639,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
|||
if (request.header('X-Response-Streaming')) {
|
||||
const streamingUrlHeader = request.header('X-Streaming-URL');
|
||||
if (streamingUrlHeader === undefined) return response_generate.sendStatus(400);
|
||||
const streamingUrl = streamingUrlHeader.replace("localhost", "127.0.0.1");
|
||||
const streamingUrlString = streamingUrlHeader.replace("localhost", "127.0.0.1");
|
||||
|
||||
response_generate.writeHead(200, {
|
||||
'Content-Type': 'text/plain;charset=utf-8',
|
||||
|
@ -639,7 +648,6 @@ 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);
|
||||
|
||||
|
@ -915,7 +923,7 @@ function convertToV2(char) {
|
|||
});
|
||||
|
||||
result.chat = char.chat ?? humanizedISO8601DateTime();
|
||||
result.create_date = char.create_date;
|
||||
result.create_date = char.create_date ?? humanizedISO8601DateTime();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -973,7 +981,7 @@ function readFromV2(char) {
|
|||
});
|
||||
|
||||
char['chat'] = char['chat'] ?? humanizedISO8601DateTime();
|
||||
char['create_date'] = char['create_date'] || humanizedISO8601DateTime();
|
||||
char['create_date'] = char['create_date'] ?? humanizedISO8601DateTime();
|
||||
|
||||
return char;
|
||||
}
|
||||
|
@ -1309,6 +1317,9 @@ async function tryReadImage(img_url, crop) {
|
|||
if (crop.want_resize) {
|
||||
final_width = AVATAR_WIDTH
|
||||
final_height = AVATAR_HEIGHT
|
||||
} else {
|
||||
final_width = crop.width;
|
||||
final_height = crop.height;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1372,6 +1383,7 @@ const processCharacter = async (item, i) => {
|
|||
characters[i]['json_data'] = img_data;
|
||||
const charStat = fs.statSync(path.join(charactersPath, item));
|
||||
characters[i]['date_added'] = charStat.birthtimeMs;
|
||||
characters[i]['create_date'] = jsonObject['create_date'] || humanizedISO8601DateTime(charStat.birthtimeMs);
|
||||
const char_dir = path.join(chatsPath, item.replace('.png', ''));
|
||||
|
||||
const { chatSize, dateLastChat } = calculateChatSize(char_dir);
|
||||
|
@ -1807,7 +1819,12 @@ app.post('/savequickreply', jsonParser, (request, response) => {
|
|||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name Name of World Info file
|
||||
* @param {object} entries Entries object
|
||||
*/
|
||||
function convertWorldInfoToCharacterBook(name, entries) {
|
||||
/** @type {{ entries: object[]; name: string }} */
|
||||
const result = { entries: [], name };
|
||||
|
||||
for (const index in entries) {
|
||||
|
@ -1989,7 +2006,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
|||
response.body.pipe(response_generate_novel);
|
||||
|
||||
request.socket.on('close', function () {
|
||||
response.body.destroy(); // Close the remote stream
|
||||
if (response.body instanceof Readable) response.body.destroy(); // Close the remote stream
|
||||
response_generate_novel.end(); // End the Express response
|
||||
});
|
||||
|
||||
|
@ -2220,7 +2237,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
|||
importRisuSprites(jsonData);
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
let char = JSON.stringify(jsonData);
|
||||
jsonData["create_date"] = humanizedISO8601DateTime();
|
||||
const char = JSON.stringify(jsonData);
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else if (jsonData.name !== undefined) {
|
||||
|
@ -2246,8 +2264,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
|||
"tags": jsonData.tags ?? '',
|
||||
};
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
const charJSON = JSON.stringify(char);
|
||||
await charaWrite(uploadPath, charJSON, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else {
|
||||
console.log('Unknown character card format');
|
||||
|
@ -2276,17 +2294,31 @@ app.post("/dupecharacter", jsonParser, async function (request, response) {
|
|||
}
|
||||
let suffix = 1;
|
||||
let newFilename = filename;
|
||||
|
||||
// If filename ends with a _number, increment the number
|
||||
const nameParts = path.basename(filename, path.extname(filename)).split('_');
|
||||
const lastPart = nameParts[nameParts.length - 1];
|
||||
|
||||
let baseName;
|
||||
|
||||
if (!isNaN(Number(lastPart)) && nameParts.length > 1) {
|
||||
suffix = parseInt(lastPart) + 1;
|
||||
baseName = nameParts.slice(0, -1).join("_"); // construct baseName without suffix
|
||||
} else {
|
||||
baseName = nameParts.join("_"); // original filename is completely the baseName
|
||||
}
|
||||
|
||||
newFilename = path.join(directories.characters, `${baseName}_${suffix}${path.extname(filename)}`);
|
||||
|
||||
while (fs.existsSync(newFilename)) {
|
||||
let suffixStr = "_" + suffix;
|
||||
let ext = path.extname(filename);
|
||||
newFilename = filename.slice(0, -ext.length) + suffixStr + ext;
|
||||
newFilename = path.join(directories.characters, `${baseName}${suffixStr}${path.extname(filename)}`);
|
||||
suffix++;
|
||||
}
|
||||
fs.copyFile(filename, newFilename, (err) => {
|
||||
if (err) throw err;
|
||||
console.log(`${filename} was copied to ${newFilename}`);
|
||||
response.sendStatus(200);
|
||||
});
|
||||
|
||||
fs.copyFileSync(filename, newFilename);
|
||||
console.log(`${filename} was copied to ${newFilename}`);
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
|
@ -2379,6 +2411,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
|||
case 'json': {
|
||||
try {
|
||||
let json = await charaRead(filename);
|
||||
if (json === false || json === undefined) return response.sendStatus(400);
|
||||
let jsonObject = getCharaCardV2(json5.parse(json));
|
||||
return response.type('json').send(jsonObject)
|
||||
}
|
||||
|
@ -2389,6 +2422,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
|||
case 'webp': {
|
||||
try {
|
||||
let json = await charaRead(filename);
|
||||
if (json === false || json === undefined) return response.sendStatus(400);
|
||||
let stringByteArray = utf8Encode.encode(json).toString();
|
||||
let inputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_input.webp`);
|
||||
let outputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_output.webp`);
|
||||
|
@ -2426,6 +2460,11 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
|||
app.post("/importgroupchat", urlencodedParser, function (request, response) {
|
||||
try {
|
||||
const filedata = request.file;
|
||||
|
||||
if (!filedata) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const chatname = humanizedISO8601DateTime();
|
||||
const pathToUpload = path.join(UPLOADS_PATH, filedata.filename);
|
||||
const pathToNewFile = path.join(directories.groupChats, `${chatname}.jsonl`);
|
||||
|
@ -2447,128 +2486,118 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
|||
let ch_name = request.body.character_name;
|
||||
let user_name = request.body.user_name || 'You';
|
||||
|
||||
if (filedata) {
|
||||
if (!filedata) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
try {
|
||||
const data = fs.readFileSync(path.join(UPLOADS_PATH, filedata.filename), 'utf8');
|
||||
|
||||
if (format === 'json') {
|
||||
fs.readFile(path.join(UPLOADS_PATH, filedata.filename), 'utf8', (err, data) => {
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
response.send({ error: true });
|
||||
const jsonData = json5.parse(data);
|
||||
if (jsonData.histories !== undefined) {
|
||||
//console.log('/importchat confirms JSON histories are defined');
|
||||
const chat = {
|
||||
from(history) {
|
||||
return [
|
||||
{
|
||||
user_name: user_name,
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
},
|
||||
...history.msgs.map(
|
||||
(message) => ({
|
||||
name: message.src.is_human ? user_name : ch_name,
|
||||
is_user: message.src.is_human,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: message.text,
|
||||
})
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
||||
const jsonData = json5.parse(data);
|
||||
if (jsonData.histories !== undefined) {
|
||||
//console.log('/importchat confirms JSON histories are defined');
|
||||
const chat = {
|
||||
from(history) {
|
||||
return [
|
||||
{
|
||||
user_name: user_name,
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
},
|
||||
...history.msgs.map(
|
||||
(message) => ({
|
||||
name: message.src.is_human ? user_name : ch_name,
|
||||
is_user: message.src.is_human,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: message.text,
|
||||
})
|
||||
)];
|
||||
}
|
||||
const newChats = [];
|
||||
(jsonData.histories.histories ?? []).forEach((history) => {
|
||||
newChats.push(chat.from(history));
|
||||
});
|
||||
|
||||
const errors = [];
|
||||
|
||||
for (const chat of newChats) {
|
||||
const filePath = `${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const fileContent = chat.map(tryParse).filter(x => x).join('\n');
|
||||
|
||||
try {
|
||||
writeFileAtomicSync(filePath, fileContent, 'utf8');
|
||||
} catch (err) {
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
const newChats = [];
|
||||
(jsonData.histories.histories ?? []).forEach((history) => {
|
||||
newChats.push(chat.from(history));
|
||||
});
|
||||
|
||||
const errors = [];
|
||||
|
||||
for (const chat of newChats) {
|
||||
const filePath = `${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const fileContent = chat.map(tryParse).filter(x => x).join('\n');
|
||||
|
||||
try {
|
||||
writeFileAtomicSync(filePath, fileContent, 'utf8');
|
||||
} catch (err) {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 < errors.length) {
|
||||
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
|
||||
}
|
||||
|
||||
response.send({ res: true });
|
||||
} else if (Array.isArray(jsonData.data_visible)) {
|
||||
// oobabooga's format
|
||||
const chat = [{
|
||||
user_name: user_name,
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
}];
|
||||
|
||||
for (const arr of jsonData.data_visible) {
|
||||
if (arr[0]) {
|
||||
const userMessage = {
|
||||
name: user_name,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: arr[0],
|
||||
};
|
||||
chat.push(userMessage);
|
||||
}
|
||||
if (arr[1]) {
|
||||
const charMessage = {
|
||||
name: ch_name,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: arr[1],
|
||||
};
|
||||
chat.push(charMessage);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileAtomicSync(`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`, chat.map(JSON.stringify).join('\n'), 'utf8');
|
||||
|
||||
response.send({ res: true });
|
||||
} else {
|
||||
response.send({ error: true });
|
||||
}
|
||||
});
|
||||
|
||||
if (0 < errors.length) {
|
||||
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
|
||||
}
|
||||
|
||||
response.send({ res: true });
|
||||
} else if (Array.isArray(jsonData.data_visible)) {
|
||||
// oobabooga's format
|
||||
/** @type {object[]} */
|
||||
const chat = [{
|
||||
user_name: user_name,
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
}];
|
||||
|
||||
for (const arr of jsonData.data_visible) {
|
||||
if (arr[0]) {
|
||||
const userMessage = {
|
||||
name: user_name,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: arr[0],
|
||||
};
|
||||
chat.push(userMessage);
|
||||
}
|
||||
if (arr[1]) {
|
||||
const charMessage = {
|
||||
name: ch_name,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
send_date: humanizedISO8601DateTime(),
|
||||
mes: arr[1],
|
||||
};
|
||||
chat.push(charMessage);
|
||||
}
|
||||
}
|
||||
|
||||
const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n');
|
||||
writeFileAtomicSync(`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`, chatContent, 'utf8');
|
||||
|
||||
response.send({ res: true });
|
||||
} else {
|
||||
console.log('Incorrect chat format .json');
|
||||
return response.send({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (format === 'jsonl') {
|
||||
//console.log(humanizedISO8601DateTime()+':imported chat format is JSONL');
|
||||
const fileStream = fs.createReadStream(path.join(UPLOADS_PATH, filedata.filename));
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
const line = data.split('\n')[0];
|
||||
|
||||
rl.once('line', (line) => {
|
||||
let jsonData = json5.parse(line);
|
||||
let jsonData = json5.parse(line);
|
||||
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
fs.copyFile(path.join(UPLOADS_PATH, filedata.filename), (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
if (err) {
|
||||
response.send({ error: true });
|
||||
return console.log(err);
|
||||
} else {
|
||||
response.send({ res: true });
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
response.send({ error: true });
|
||||
return;
|
||||
}
|
||||
rl.close();
|
||||
});
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
fs.copyFileSync(path.join(UPLOADS_PATH, filedata.filename), (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`));
|
||||
response.send({ res: true });
|
||||
} else {
|
||||
console.log('Incorrect chat format .jsonl');
|
||||
return response.send({ error: true });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.send({ error: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2837,7 +2866,7 @@ app.post('/getgroupchat', jsonParser, (request, response) => {
|
|||
const lines = data.split('\n');
|
||||
|
||||
// Iterate through the array of strings and parse each line as JSON
|
||||
const jsonData = lines.map(json5.parse);
|
||||
const jsonData = lines.map(line => tryParse(line)).filter(x => x);
|
||||
return response.send(jsonData);
|
||||
} else {
|
||||
return response.send([]);
|
||||
|
@ -2942,7 +2971,7 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
|
|||
});
|
||||
|
||||
app.get('/get_sprites', jsonParser, function (request, response) {
|
||||
const name = request.query.name;
|
||||
const name = String(request.query.name);
|
||||
const spritesPath = path.join(directories.characters, name);
|
||||
let sprites = [];
|
||||
|
||||
|
@ -3092,7 +3121,7 @@ async function generateThumbnail(type, file) {
|
|||
|
||||
try {
|
||||
const image = await jimp.read(pathToOriginalFile);
|
||||
buffer = await image.cover(mySize[0], mySize[1]).quality(95).getBufferAsync(mime.lookup('jpg'));
|
||||
buffer = await image.cover(mySize[0], mySize[1]).quality(95).getBufferAsync('image/jpeg');
|
||||
}
|
||||
catch (inner) {
|
||||
console.warn(`Thumbnailer can not process the image: ${pathToOriginalFile}. Using original size`);
|
||||
|
@ -4388,7 +4417,7 @@ app.post('/horde_generateimage', jsonParser, async (request, response) => {
|
|||
const ai_horde = getHordeClient();
|
||||
const generation = await ai_horde.postAsyncImageGenerate(
|
||||
{
|
||||
prompt: `${request.body.prompt_prefix} ${request.body.prompt} ### ${request.body.negative_prompt}`,
|
||||
prompt: `${request.body.prompt} ### ${request.body.negative_prompt}`,
|
||||
params:
|
||||
{
|
||||
sampler_name: request.body.sampler,
|
||||
|
@ -4442,6 +4471,325 @@ app.post('/horde_generateimage', jsonParser, async (request, response) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.post('/api/novelai/generate-image', jsonParser, async (request, response) => {
|
||||
if (!request.body) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const key = readSecret(SECRET_KEYS.NOVEL);
|
||||
|
||||
if (!key) {
|
||||
return response.sendStatus(401);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('NAI Diffusion request:', request.body);
|
||||
const url = `${API_NOVELAI}/ai/generate-image`;
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'generate',
|
||||
input: request.body.prompt,
|
||||
model: request.body.model ?? 'nai-diffusion',
|
||||
parameters: {
|
||||
negative_prompt: request.body.negative_prompt ?? '',
|
||||
height: request.body.height ?? 512,
|
||||
width: request.body.width ?? 512,
|
||||
scale: request.body.scale ?? 9,
|
||||
seed: Math.floor(Math.random() * 9999999999),
|
||||
sampler: request.body.sampler ?? 'k_dpmpp_2m',
|
||||
steps: request.body.steps ?? 28,
|
||||
n_samples: 1,
|
||||
// NAI handholding for prompts
|
||||
ucPreset: 0,
|
||||
qualityToggle: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
const archiveBuffer = await result.arrayBuffer();
|
||||
|
||||
const imageBuffer = await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
zipfile.readEntry();
|
||||
zipfile.on('entry', (entry) => {
|
||||
if (entry.fileName.endsWith('.png')) {
|
||||
console.log(`Extracting ${entry.fileName}`);
|
||||
zipfile.openReadStream(entry, (err, readStream) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const chunks = [];
|
||||
readStream.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
readStream.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
resolve(buffer);
|
||||
zipfile.readEntry(); // Continue to the next entry
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
zipfile.readEntry(); // Continue to the next entry
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const base64 = imageBuffer.toString('base64');
|
||||
return response.send(base64);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/ping', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/internal/ping';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
return response.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/upscalers', jsonParser, async (request, response) => {
|
||||
try {
|
||||
async function getUpscalerModels() {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/upscalers';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
const names = data.map(x => x.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
async function getLatentUpscalers() {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/latent-upscale-modes';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
const names = data.map(x => x.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
const [upscalers, latentUpscalers] = await Promise.all([getUpscalerModels(), getLatentUpscalers()]);
|
||||
|
||||
// 0 = None, then Latent Upscalers, then Upscalers
|
||||
upscalers.splice(1, 0, ...latentUpscalers);
|
||||
|
||||
return response.send(upscalers);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/samplers', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/samplers';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
const names = data.map(x => x.name);
|
||||
return response.send(names);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/models', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/sd-models';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
const models = data.map(x => ({ value: x.title, text: x.title }));
|
||||
return response.send(models);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/get-model', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/options';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
const data = await result.json();
|
||||
return response.send(data['sd_model_checkpoint']);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/set-model', jsonParser, async (request, response) => {
|
||||
try {
|
||||
async function getProgress() {
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/progress';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
const data = await result.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/options';
|
||||
|
||||
const options = {
|
||||
sd_model_checkpoint: request.body.model,
|
||||
};
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const MAX_ATTEMPTS = 10;
|
||||
const CHECK_INTERVAL = 2000;
|
||||
|
||||
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||
const progressState = await getProgress();
|
||||
|
||||
const progress = progressState["progress"]
|
||||
const jobCount = progressState["state"]["job_count"];
|
||||
if (progress == 0.0 && jobCount === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`Waiting for SD WebUI to finish model loading... Progress: ${progress}; Job count: ${jobCount}`);
|
||||
await delay(CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
return response.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/sd/generate', jsonParser, async (request, response) => {
|
||||
try {
|
||||
console.log('SD WebUI request:', request.body);
|
||||
|
||||
const url = new URL(request.body.url);
|
||||
url.pathname = '/sdapi/v1/txt2img';
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return response.send(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/libre_translate', jsonParser, async (request, response) => {
|
||||
const key = readSecret(SECRET_KEYS.LIBRE);
|
||||
const url = readSecret(SECRET_KEYS.LIBRE_URL);
|
||||
|
|
|
@ -49,7 +49,7 @@ const krakeBadWordsList = [
|
|||
const badWordsList = [
|
||||
[23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [21], [49209, 21],
|
||||
[21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [3], [49356], [1431], [31715], [34387],
|
||||
[20765], [30702], [10691], [49333], [1266], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115]
|
||||
[20765], [30702], [10691], [49333], [1266], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115], [19438, 2542],
|
||||
]
|
||||
|
||||
const hypeBotBadWordsList = [
|
||||
|
|
Loading…
Reference in New Issue