This commit is contained in:
RossAscends
2023-05-03 05:22:28 +09:00
14 changed files with 597 additions and 623 deletions

80
.replit Normal file
View File

@@ -0,0 +1,80 @@
hidden = [".config", "package-lock.json"]
run = "chmod 755 ./start.sh && ./start.sh"
[[hints]]
regex = "Error \\[ERR_REQUIRE_ESM\\]"
message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)"
[nix]
channel = "stable-22_11"
[env]
XDG_CONFIG_HOME = "/home/runner/$REPL_SLUG/.config"
PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin"
npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global"
[gitHubImport]
requiredFiles = [".replit", "replit.nix", ".config", "package.json", "package-lock.json"]
[packager]
language = "nodejs"
[packager.features]
packageSearch = true
guessImports = true
enabledForHosting = false
[unitTest]
language = "nodejs"
[debugger]
support = true
[debugger.interactive]
transport = "localhost:0"
startCommand = [ "dap-node" ]
[debugger.interactive.initializeMessage]
command = "initialize"
type = "request"
[debugger.interactive.initializeMessage.arguments]
clientID = "replit"
clientName = "replit.com"
columnsStartAt1 = true
linesStartAt1 = true
locale = "en-us"
pathFormat = "path"
supportsInvalidatedEvent = true
supportsProgressReporting = true
supportsRunInTerminalRequest = true
supportsVariablePaging = true
supportsVariableType = true
[debugger.interactive.launchMessage]
command = "launch"
type = "request"
[debugger.interactive.launchMessage.arguments]
args = []
console = "externalTerminal"
cwd = "."
environment = []
pauseForSourceMap = false
program = "./index.js"
request = "launch"
sourceMaps = true
stopOnEntry = false
type = "pwa-node"
[languages]
[languages.javascript]
pattern = "**/{*.js,*.jsx,*.ts,*.tsx,*.json}"
[languages.javascript.languageServer]
start = "typescript-language-server --stdio"
[deployment]
run = ["sh", "-c", "./start.sh"]

707
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
"gpt3-tokenizer": "^1.1.5",
"ipaddr.js": "^2.0.1",
"jimp": "^0.22.7",
"jquery": "^3.6.4",
"json5": "^2.2.3",
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",

View File

@@ -116,8 +116,9 @@
<select id="settings_perset_openai">
<option value="gui">Default</option>
</select>
<i id="update_preset" class="menu_button fa-solid fa-save" title="Update current preset"></i>
<i id="new_preset" class="menu_button fa-solid fa-plus" title="Create new preset"></i>
<i id="update_oai_preset" class="menu_button fa-solid fa-save" title="Update current preset"></i>
<i id="new_oai_preset" class="menu_button fa-solid fa-plus" title="Create new preset"></i>
<i id="delete_oai_preset" class="menu_button fa-solid fa-trash-can" title="Delete the preset"></i>
</div>
</div>
<div>
@@ -1506,7 +1507,7 @@
<div id="tags_div">
<div class="tag_controls">
<input id="tagInput" class="text_pole tag_input" placeholder="Search / Create tags" maxlength="25" />
<div class="tags_view menu_button fa-solid fa-pen-to-square" title="View all tags"></div>
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
</div>
<div id="tagList" class="tags"></div>
</div>
@@ -1588,7 +1589,7 @@
<div id="group_tags_div" class="wide100p">
<div class="tag_controls">
<input id="groupTagInput" class="text_pole tag_input flex1" placeholder="Search / Create tags" maxlength="25" />
<div class="tags_view menu_button fa-solid fa-pen-to-square" title="View all tags"></div>
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags"></div>
</div>
<div id="groupTagList" class="tags"></div>
</div>
@@ -1614,6 +1615,7 @@
<div class="form_create_bottom_buttons_block">
<div id="rm_button_create" title="Create New Character" class="menu_button fa-solid fa-user-plus "></div>
<div id="character_import_button" title="Import Character from File" class="menu_button fa-solid fa-file-arrow-up "></div>
<div class="tags_view menu_button fa-solid fa-tags" title="Manage Tags"></div>
<div id="rm_button_group_chats" title="Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
</div>
<form id="form_character_search_form" action="javascript:void(null);">

View File

@@ -3614,7 +3614,7 @@ async function saveMetadata() {
}
}
async function saveChatConditional() {
export async function saveChatConditional() {
if (selected_group) {
await saveGroupChat(selected_group, true);
}

View File

@@ -47,7 +47,7 @@ var count_tokens;
var perm_tokens;
var connection_made = false;
var retry_delay = 100;
var retry_delay = 500;
var RA_AC_retries = 1;
const observerConfig = { childList: true, subtree: true };

View File

@@ -12,9 +12,17 @@ import {
getThumbnailUrl,
getCharacters,
chat,
saveChatConditional,
} from "../script.js";
import { humanizedDateTime } from "./RossAscends-mods.js";
import { group_activation_strategy, groups, selected_group } from "./group-chats.js";
import {
getGroupPastChats,
group_activation_strategy,
groups,
openGroupChat,
saveGroupBookmarkChat,
selected_group,
} from "./group-chats.js";
import { createTagMapFromList } from "./tags.js";
import {
@@ -30,6 +38,10 @@ export {
const bookmarkNameToken = 'Bookmark #';
async function getExistingChatNames() {
if (selected_group) {
const data = await getGroupPastChats(selected_group);
return data.map(x => x.file_name);
} else {
const response = await fetch("/getallchatsofcharacter", {
method: 'POST',
headers: getRequestHeaders(),
@@ -40,20 +52,20 @@ async function getExistingChatNames() {
const data = await response.json();
return Object.values(data).map(x => x.file_name.replace('.jsonl', ''));
}
}
}
async function getBookmarkName() {
const chatNames = await getExistingChatNames();
const popupText = `<h3>Enter the bookmark name:<h3>
<small>Using existing name will overwrite your bookmark chat.
<br>Leave empty to auto-generate.</small>`;
<small>Leave empty to auto-generate.</small>`;
let name = await callPopup(popupText, 'input');
if (name === false) {
return null;
}
else if (name === '') {
for (let i = 0; i < 1000; i++) {
for (let i = chatNames.length; i < 1000; i++) {
name = bookmarkNameToken + i;
if (!chatNames.includes(name)) {
break;
@@ -61,7 +73,7 @@ async function getBookmarkName() {
}
}
return name;
return `${name} - ${humanizedDateTime()}`;
}
function getMainChatName() {
@@ -69,6 +81,10 @@ function getMainChatName() {
if (chat_metadata['main_chat']) {
return chat_metadata['main_chat'];
}
// groups didn't support bookmarks before chat metadata was introduced
else if (selected_group) {
return null;
}
else if (characters[this_chid].chat && characters[this_chid].chat.includes(bookmarkNameToken)) {
const tokenIndex = characters[this_chid].chat.lastIndexOf(bookmarkNameToken);
chat_metadata['main_chat'] = characters[this_chid].chat.substring(0, tokenIndex).trim();
@@ -80,24 +96,24 @@ function getMainChatName() {
function showBookmarksButtons() {
try {
// In groups or without an active chat
if (selected_group || !characters[this_chid].chat) {
$("#option_back_to_main").hide();
$("#option_new_bookmark").hide();
if (selected_group) {
$("#option_convert_to_group").hide();
}
// In main chat
else if (!chat_metadata['main_chat']) {
$("#option_back_to_main").hide();
$("#option_new_bookmark").show();
} else {
$("#option_convert_to_group").show();
}
if (chat_metadata['main_chat']) {
// In bookmark chat
else {
$("#option_back_to_main").show();
$("#option_new_bookmark").show();
$("#option_convert_to_group").show();
} else if (!selected_group && !characters[this_chid].chat) {
// No chat recorded on character
$("#option_back_to_main").hide();
$("#option_new_bookmark").hide();
} else {
// In main chat
$("#option_back_to_main").hide();
$("#option_new_bookmark").show();
}
}
catch {
@@ -108,30 +124,36 @@ function showBookmarksButtons() {
}
async function createNewBookmark() {
if (selected_group) {
alert('Chat bookmarks unsupported for groups');
throw new Error();
}
let name = await getBookmarkName(characters[this_chid].chat);
let name = await getBookmarkName();
if (!name) {
return;
}
const newMetadata = { main_chat: characters[this_chid].chat };
saveChat(name, newMetadata);
const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
const newMetadata = { main_chat: mainChat };
if (selected_group) {
await saveGroupBookmarkChat(selected_group, name, newMetadata);
} else {
await saveChat(name, newMetadata);
}
let mainMessage = stringFormat(system_messages[system_message_types.BOOKMARK_CREATED].mes, name, name);
sendSystemMessage(system_message_types.BOOKMARK_CREATED, mainMessage);
saveChat();
await saveChatConditional();
}
async function backToMainChat() {
const mainChatName = getMainChatName(characters[this_chid].chat);
const mainChatName = getMainChatName();
const allChats = await getExistingChatNames();
if (allChats.includes(mainChatName)) {
openCharacterChat(mainChatName);
if (selected_group) {
await openGroupChat(selected_group, mainChatName);
} else {
await openCharacterChat(mainChatName);
}
}
}
@@ -158,6 +180,7 @@ async function convertSoloToGroupChat() {
const allowSelfResponses = false;
const favChecked = character.fav == 'true';
const metadata = Object.assign({}, chat_metadata);
delete metadata.main_chat;
const createGroupResponse = await fetch("/creategroup", {
method: "POST",

View File

@@ -1187,6 +1187,25 @@ export async function deleteGroupChat(groupId, chatId) {
}
}
export async function saveGroupBookmarkChat(groupId, name, metadata) {
const group = groups.find(x => x.id === groupId);
if (!group) {
return;
}
group.past_metadata[name] = { ...chat_metadata, ...(metadata || {}) };
group.chats.push(name);
await editGroup(groupId, true);
await fetch("/savegroupchat", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({ id: name, chat: [...chat] }),
});
}
$(document).ready(() => {
$(document).on("click", ".group_select", selectGroup);
$("#rm_group_filter").on("input", filterGroupMembers);

View File

@@ -728,7 +728,7 @@ async function getStatusOpen() {
reverse_proxy: oai_settings.reverse_proxy,
};
jQuery.ajax({
return jQuery.ajax({
type: 'POST', //
url: '/getstatus_openai', //
data: JSON.stringify(data),
@@ -986,6 +986,39 @@ function onLogitBiasPresetExportClick() {
download(presetJsonString, oai_settings.bias_preset_selected, 'application/json');
}
async function onDeletePresetClick() {
const confirm = await callPopup('Delete the preset? This action is irreversible and your current settings will be overwritten.', 'confirm');
if (!confirm) {
return;
}
const nameToDelete = oai_settings.preset_settings_openai;
const value = openai_setting_names[oai_settings.preset_settings_openai];
$(`#settings_perset_openai option[value="${value}"]`).remove();
delete openai_setting_names[oai_settings.preset_settings_openai];
oai_settings.preset_settings_openai = null;
if (Object.keys(openai_setting_names).length) {
oai_settings.preset_settings_openai = Object.keys(openai_setting_names)[0];
const newValue = openai_setting_names[oai_settings.preset_settings_openai];
$(`#settings_perset_openai option[value="${newValue}"]`).attr('selected', true);
$('#settings_perset_openai').trigger('change');
}
const response = await fetch('/deletepreset_openai', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({name: nameToDelete}),
});
if (!response.ok) {
console.warn('Preset was not deleted from server');
}
saveSettingsDebounced();
}
async function onLogitBiasPresetDeleteClick() {
const value = await callPopup('Delete the preset?', 'confirm');
@@ -1007,82 +1040,7 @@ async function onLogitBiasPresetDeleteClick() {
saveSettingsDebounced();
}
$(document).ready(function () {
$(document).on('input', '#temp_openai', function () {
oai_settings.temp_openai = $(this).val();
$('#temp_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#freq_pen_openai', function () {
oai_settings.freq_pen_openai = $(this).val();
$('#freq_pen_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#pres_pen_openai', function () {
oai_settings.pres_pen_openai = $(this).val();
$('#pres_pen_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#openai_max_context', function () {
oai_settings.openai_max_context = parseInt($(this).val());
$('#openai_max_context_counter').text(`${$(this).val()}`);
saveSettingsDebounced();
});
$(document).on('input', '#openai_max_tokens', function () {
oai_settings.openai_max_tokens = parseInt($(this).val());
saveSettingsDebounced();
});
$("#model_openai_select").change(function () {
const value = $(this).val();
oai_settings.openai_model = value;
if (value == 'gpt-4' || value == 'gpt-4-0314') {
$('#openai_max_context').attr('max', gpt4_max);
}
else if (value == 'gpt-4-32k') {
$('#openai_max_context').attr('max', gpt4_32k_max);
}
else {
$('#openai_max_context').attr('max', gpt3_max);
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
}
saveSettingsDebounced();
});
$('#stream_toggle').change(function () {
oai_settings.stream_openai = !!$('#stream_toggle').prop('checked');
saveSettingsDebounced();
});
$('#nsfw_toggle').change(function () {
oai_settings.nsfw_toggle = !!$('#nsfw_toggle').prop('checked');
saveSettingsDebounced();
});
$('#enhance_definitions').change(function () {
oai_settings.enhance_definitions = !!$('#enhance_definitions').prop('checked');
saveSettingsDebounced();
});
$('#wrap_in_quotes').change(function () {
oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');
saveSettingsDebounced();
});
$('#nsfw_first').change(function () {
oai_settings.nsfw_first = !!$('#nsfw_first').prop('checked');
saveSettingsDebounced();
});
$("#settings_perset_openai").change(function () {
function onSettingsPresetChange() {
oai_settings.preset_settings_openai = $('#settings_perset_openai').find(":selected").text();
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
@@ -1122,19 +1080,115 @@ $(document).ready(function () {
$(`#model_openai_select`).trigger('change');
$(`#openai_logit_bias_preset`).trigger('change');
saveSettingsDebounced();
});
}
$("#api_button_openai").click(function (e) {
function onModelChange() {
const value = $(this).val();
oai_settings.openai_model = value;
if (value == 'gpt-4' || value == 'gpt-4-0314') {
$('#openai_max_context').attr('max', gpt4_max);
}
else if (value == 'gpt-4-32k') {
$('#openai_max_context').attr('max', gpt4_32k_max);
}
else {
$('#openai_max_context').attr('max', gpt3_max);
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
}
saveSettingsDebounced();
}
async function onNewPresetClick() {
const popupText = `
<h3>Preset name:</h3>
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
const name = await callPopup(popupText, 'input');
if (!name) {
return;
}
await saveOpenAIPreset(name, oai_settings);
}
function onReverseProxyInput() {
oai_settings.reverse_proxy = $(this).val();
if (oai_settings.reverse_proxy == '') {
$("#ReverseProxyWarningMessage").css('display', 'none');
} else { $("#ReverseProxyWarningMessage").css('display', 'block'); }
saveSettingsDebounced();
}
async function onConnectButtonClick(e) {
e.stopPropagation();
if ($('#api_key_openai').val() != '') {
$("#api_loading_openai").css("display", 'inline-block');
$("#api_button_openai").css("display", 'none');
oai_settings.api_key_openai = $.trim($('#api_key_openai').val());
oai_settings.api_key_openai = $('#api_key_openai').val().trim();
saveSettingsDebounced();
is_get_status_openai = true;
is_api_button_press_openai = true;
getStatusOpen();
await getStatusOpen();
}
}
$(document).ready(function () {
$(document).on('input', '#temp_openai', function () {
oai_settings.temp_openai = $(this).val();
$('#temp_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#freq_pen_openai', function () {
oai_settings.freq_pen_openai = $(this).val();
$('#freq_pen_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#pres_pen_openai', function () {
oai_settings.pres_pen_openai = $(this).val();
$('#pres_pen_counter_openai').text(Number($(this).val()).toFixed(2));
saveSettingsDebounced();
});
$(document).on('input', '#openai_max_context', function () {
oai_settings.openai_max_context = parseInt($(this).val());
$('#openai_max_context_counter').text(`${$(this).val()}`);
saveSettingsDebounced();
});
$(document).on('input', '#openai_max_tokens', function () {
oai_settings.openai_max_tokens = parseInt($(this).val());
saveSettingsDebounced();
});
$('#stream_toggle').on('change', function () {
oai_settings.stream_openai = !!$('#stream_toggle').prop('checked');
saveSettingsDebounced();
});
$('#nsfw_toggle').on('change', function () {
oai_settings.nsfw_toggle = !!$('#nsfw_toggle').prop('checked');
saveSettingsDebounced();
});
$('#enhance_definitions').on('change', function () {
oai_settings.enhance_definitions = !!$('#enhance_definitions').prop('checked');
saveSettingsDebounced();
});
$('#wrap_in_quotes').on('change', function () {
oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');
saveSettingsDebounced();
});
$('#nsfw_first').on('change', function () {
oai_settings.nsfw_first = !!$('#nsfw_first').prop('checked');
saveSettingsDebounced();
});
$("#jailbreak_prompt_textarea").on('input', function () {
@@ -1157,7 +1211,7 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$("#jailbreak_system").change(function () {
$("#jailbreak_system").on('change', function () {
oai_settings.jailbreak_system = !!$(this).prop("checked");
saveSettingsDebounced();
});
@@ -1185,58 +1239,42 @@ $(document).ready(function () {
trySelectPresetByName(name);
});
$("#update_preset").click(async function () {
$("#update_oai_preset").on('click', async function () {
const name = oai_settings.preset_settings_openai;
await saveOpenAIPreset(name, oai_settings);
callPopup('Preset updated', 'text');
});
$("#new_preset").click(async function () {
const popupText = `
<h3>Preset name:</h3>
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
$("#save_prompts").click();
const name = await callPopup(popupText, 'input');
if (!name) {
return;
}
await saveOpenAIPreset(name, oai_settings);
});
$("#main_prompt_restore").click(function () {
$("#main_prompt_restore").on('click', function () {
oai_settings.main_prompt = default_main_prompt;
$('#main_prompt_textarea').val(oai_settings.main_prompt);
saveSettingsDebounced();
});
$("#nsfw_prompt_restore").click(function () {
$("#nsfw_prompt_restore").on('click', function () {
oai_settings.nsfw_prompt = default_nsfw_prompt;
$('#nsfw_prompt_textarea').val(oai_settings.nsfw_prompt);
saveSettingsDebounced();
});
$("#jailbreak_prompt_restore").click(function () {
$("#jailbreak_prompt_restore").on('click', function () {
oai_settings.jailbreak_prompt = default_jailbreak_prompt;
$('#jailbreak_prompt_textarea').val(oai_settings.jailbreak_prompt);
saveSettingsDebounced();
});
$("#impersonation_prompt_restore").click(function () {
$("#impersonation_prompt_restore").on('click', function () {
oai_settings.impersonation_prompt = default_impersonation_prompt;
$('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt);
saveSettingsDebounced();
});
$("#openai_reverse_proxy").on('input', function () {
oai_settings.reverse_proxy = $(this).val();
if (oai_settings.reverse_proxy == '') {
$("#ReverseProxyWarningMessage").css('display', 'none');
} else { $("#ReverseProxyWarningMessage").css('display', 'block'); }
saveSettingsDebounced();
});
$("#api_button_openai").on('click', onConnectButtonClick);
$("#openai_reverse_proxy").on('input', onReverseProxyInput);
$("#model_openai_select").on('change', onModelChange);
$("#settings_perset_openai").on('change', onSettingsPresetChange);
$("#new_oai_preset").on('click', onNewPresetClick);
$("#delete_oai_preset").on('click', onDeletePresetClick);
$("#openai_api_usage").on('click', showApiKeyUsage);
$('#openai_logit_bias_preset').on('change', onLogitBiasPresetChange);
$('#openai_logit_bias_new_preset').on('click', createNewLogitBiasPreset);

View File

@@ -306,6 +306,10 @@ function onViewTagsListClick() {
}
function onTagDeleteClick() {
if (!confirm("Are you sure?")) {
return;
}
const id = $(this).closest('.tag_view_item').attr('id');
for (const key of Object.keys(tag_map)) {
tag_map[key] = tag_map[key].filter(x => x.id !== id);

View File

@@ -23,6 +23,10 @@ Try on Colab (runs KoboldAI backend and TavernAI Extras server alongside): <a t
https://colab.research.google.com/github/Cohee1207/SillyTavern/blob/main/colab/GPU.ipynb
Run on Repl.it:
[![Run on Repl.it](https://replit.com/badge?caption=Run+On+Repl.it)](https://replit.com/new/github/Cohee1207/SillyTavern)
## Mobile support
> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**

8
replit.nix Normal file
View File

@@ -0,0 +1,8 @@
{ pkgs }: {
deps = [
pkgs.nodejs-18_x
pkgs.nodePackages.typescript-language-server
pkgs.yarn
pkgs.replitPackages.jest
];
}

View File

@@ -2185,8 +2185,9 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
};
client.get(api_url + "/models", args, function (data, response) {
if (response.statusCode == 200) {
console.log(data);
response_getstatus_openai.send(data);//data);
response_getstatus_openai.send(data);
const modelIds = data?.data?.map(x => x.id)?.sort();
console.log('Available OpenAI models:', modelIds);
}
if (response.statusCode == 401) {
console.log('Access Token is incorrect.');
@@ -2259,6 +2260,22 @@ app.post("/openai_usage", jsonParser, async function (request, response) {
}
});
app.post("/deletepreset_openai", jsonParser, function (request, response) {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
}
const name = request.body.name;
const pathToFile = path.join(directories.openAI_Settings, `${name}.settings`);
if (fs.existsSync(pathToFile)) {
fs.rmSync(pathToFile);
return response.send({ ok: true });
}
return response.send({ error: true });
});
app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
if (!request.body) return response_generate_openai.sendStatus(400);
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();

9
start.sh Normal file → Executable file
View File

@@ -20,5 +20,14 @@ then
esac
fi
# if running on replit patch whitelist
if [ ! -z "$REPL_ID" ]; then
echo -e "Running on Repl.it... \nPatching Whitelist..."
sed -i 's|whitelistMode = true|whitelistMode = false|g' "config.conf"
fi
echo "Installing Node Modules..."
npm i
echo "Entering SillyTavern..."
node server.js