Merge branch 'main' into pkg

This commit is contained in:
SillyLossy
2023-05-14 22:54:53 +03:00
12 changed files with 232 additions and 98 deletions

View File

@@ -1,7 +1,7 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
title: "[Feature Request] "
labels: ''
assignees: ''

View File

@@ -16,6 +16,7 @@ Method 1 - GIT
We always recommend users install using 'git'. Here's why:
When you have installed via `git clone`, all you have to do to update is type `git pull` in a command line in the ST folder.
Alternatively, if the command prompt gives you problems (and you have GitHub Desktop installed), you can use the 'Repository' menu and select 'Pull'.
The updates are applied automatically and safely.
Method 2 - ZIP

View File

@@ -10,6 +10,19 @@
"SillyTavern community Discord (support and discussion): https://discord.gg/RZdyAEUPvj"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#@title <-- Tap this if you run on Mobile { display-mode: \"form\" }\n",
"#Taken from KoboldAI colab\n",
"%%html\n",
"<b>Press play on the audio player to keep the tab alive. (Uses only 13MB of data)</b><br/>\n",
"<audio src=\"https://henk.tech/colabkobold/silence.m4a\" controls>"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -42,10 +55,13 @@
"#@markdown Enables Silero text-to-speech module\n",
"extras_enable_sd = True #@param {type:\"boolean\"}\n",
"#@markdown Enables SD picture generation\n",
"SD_Model = \"ckpt/anything-v4.5-vae-swapped\" #@param [ \"ckpt/anything-v4.5-vae-swapped\", \"philz1337/clarity\", \"ckpt/sd15\" ]\n",
"SD_Model = \"ckpt/anything-v4.5-vae-swapped\" #@param [ \"ckpt/anything-v4.5-vae-swapped\", \"hakurei/waifu-diffusion\", \"philz1337/clarity\", \"prompthero/openjourney\", \"ckpt/sd15\", \"stabilityai/stable-diffusion-2-1-base\" ]\n",
"#@markdown * ckpt/anything-v4.5-vae-swapped - anime style model\n",
"#@markdown * hakurei/waifu-diffusion - anime style model\n",
"#@markdown * philz1337/clarity - realistic style model\n",
"#@markdown * prompthero/openjourney - midjourney style model\n",
"#@markdown * ckpt/sd15 - base SD 1.5\n",
"#@markdown * stabilityai/stable-diffusion-2-1-base - base SD 2.1\n",
"\n",
"import subprocess\n",
"\n",
@@ -79,6 +95,7 @@
"%cd /\n",
"!git clone https://github.com/Cohee1207/SillyTavern-extras\n",
"%cd /SillyTavern-extras\n",
"!git clone https://github.com/Cohee1207/tts_samples\n",
"!npm install -g localtunnel\n",
"!pip install -r requirements-complete.txt\n",
"!pip install tensorflow==2.11\n",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.5.1",
"version": "1.5.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.5.1",
"version": "1.5.3",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",

View File

@@ -40,7 +40,7 @@
"type": "git",
"url": "https://github.com/Cohee1207/SillyTavern.git"
},
"version": "1.5.1",
"version": "1.5.3",
"scripts": {
"start": "node server.js",
"pkg": "pkg ."

View File

@@ -22,6 +22,7 @@ You definitely installed via git, so just 'git pull' inside the SillyTavern dire
We always recommend users install using 'git'. Here's why:
When you have installed via 'git clone', all you have to do to update is type 'git pull' in a command line in the ST folder.
Alternatively, if the command prompt gives you problems (and you have GitHub Desktop installed), you can use the 'Repository' menu and select 'Pull'.
The updates are applied automatically and safely.
### Method 2 - ZIP

View File

@@ -1369,7 +1369,7 @@ function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
function baseChatReplace(value, name1, name2) {
if (value !== undefined && value.length > 0) {
value = substituteParams(value, is_pygmalion ? "You:" : name1, name2);
value = substituteParams(value, is_pygmalion ? "You" : name1, name2);
if (power_user.collapse_newlines) {
value = collapseNewlines(value);
@@ -1386,9 +1386,10 @@ function appendToStoryString(value, prefix) {
}
function isStreamingEnabled() {
return (main_api == 'openai' && oai_settings.stream_openai)
return ((main_api == 'openai' && oai_settings.stream_openai)
|| (main_api == 'poe' && poe_settings.streaming)
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming);
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
&& !isMultigenEnabled(); // Multigen has a quasi-streaming mode which breaks the real streaming
}
class StreamingProcessor {
@@ -1568,7 +1569,18 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
const isImpersonate = type == "impersonate";
const isInstruct = power_user.instruct.enabled;
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
// Name for the multigen prefix
const magName = isImpersonate ? (is_pygmalion ? 'You' : name1) : name2;
if (isInstruct) {
message_already_generated = formatInstructModePrompt(magName, isImpersonate);
} else {
message_already_generated = `${magName}: `;
}
// To trim after multigen ended
const magFirst = message_already_generated;
const interruptedByCommand = processCommands($("#send_textarea").val(), type);
@@ -1596,6 +1608,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
hideSwipeButtons();
}
// Set empty promise resolution functions
if (typeof resolve !== 'function') {
resolve = () => {};
}
if (typeof reject !== 'function') {
reject = () => {};
}
if (selected_group && !is_group_generating) {
generateGroupWrapper(false, type, { resolve, reject, quiet_prompt, force_chid });
return;
@@ -1994,8 +2014,16 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
const isBottom = j === mesSend.length - 1;
mesSendString += mesSend[j];
// Add quiet generation prompt at depth 0
if (isBottom && quiet_prompt && quiet_prompt.length) {
const name = is_pygmalion ? 'You' : name1;
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, true) : `\n${name}: ${quiet_prompt}`;
mesSendString += quietAppend;
}
if (isInstruct && isBottom && tokens_already_generated === 0) {
mesSendString += formatInstructModePrompt(isImpersonate);
const name = isImpersonate ? (is_pygmalion ? 'You' : name1) : name2;
mesSendString += formatInstructModePrompt(name, isImpersonate);
}
if (!isInstruct && isImpersonate && isBottom && tokens_already_generated === 0) {
@@ -2148,11 +2176,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
}
}
// Add quiet generation prompt at depth 0
if (quiet_prompt && quiet_prompt.length) {
finalPromt += `\n${quiet_prompt}`;
}
finalPromt = finalPromt.replace(/\r/gm, '');
if (power_user.collapse_newlines) {
@@ -2267,20 +2290,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
hideSwipeButtons();
let getMessage = await streamingProcessor.generate();
if (isMultigenEnabled()) {
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
message_already_generated += getMessage;
promptBias = '';
if (!streamingProcessor.isStopped && shouldContinueMultigen(getMessage)) {
streamingProcessor.isFinished = false;
runGenerate(getMessage);
console.log('returning to make generate again');
return;
}
getMessage = message_already_generated;
}
if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) {
streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage);
streamingProcessor = null;
@@ -2303,6 +2312,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
let this_mes_is_name;
({ this_mes_is_name, getMessage } = extractNameFromMessage(getMessage, force_name2, isImpersonate));
if (!isImpersonate) {
if (tokens_already_generated == 0) {
console.log("New message");
({ type, getMessage } = saveReply(type, getMessage, this_mes_is_name, title));
@@ -2311,8 +2322,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
console.log("Should append message");
({ type, getMessage } = saveReply('append', getMessage, this_mes_is_name, title));
}
} else {
let chunk = cleanUpMessage(message_already_generated, true);
let extract = extractNameFromMessage(chunk, force_name2, isImpersonate);
$('#send_textarea').val(extract.getMessage).trigger('input');
}
if (shouldContinueMultigen(getMessage)) {
if (shouldContinueMultigen(getMessage, isImpersonate)) {
hideSwipeButtons();
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
getMessage = message_already_generated;
@@ -2321,7 +2337,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
return;
}
getMessage = message_already_generated;
getMessage = message_already_generated.substring(magFirst.length);
}
//Formating
@@ -2332,6 +2348,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
if (getMessage.length > 0) {
if (isImpersonate) {
$('#send_textarea').val(getMessage).trigger('input');
generatedPromtCache = "";
}
else if (type == 'quiet') {
resolve(getMessage);
@@ -2379,13 +2396,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
showSwipeButtons();
setGenerationProgress(0);
$('.mes_buttons:last').show();
if (type !== 'quiet') {
resolve();
}
};
function onError(jqXHR, exception) {
if (type == 'quiet') {
reject(exception);
}
$("#send_textarea").removeAttr('disabled');
is_send_press = false;
activateSendButtons();
@@ -2480,12 +2498,24 @@ function getGenerateUrl() {
return generate_url;
}
function shouldContinueMultigen(getMessage) {
const nameString = is_pygmalion ? 'You:' : `${name1}:`;
return message_already_generated.indexOf(nameString) === -1 && //if there is no 'You:' in the response msg
message_already_generated.indexOf('<|endoftext|>') === -1 && //if there is no <endoftext> stamp in the response msg
tokens_already_generated < parseInt(amount_gen) && //if the gen'd msg is less than the max response length..
getMessage.length > 0; //if we actually have gen'd text at all...
function shouldContinueMultigen(getMessage, isImpersonate) {
if (power_user.instruct.enabled && power_user.instruct.stop_sequence) {
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
return false;
}
}
// stopping name string
const nameString = isImpersonate ? `${name2}:` : (is_pygmalion ? 'You:' : `${name1}:`);
// if there is no 'You:' in the response msg
const doesNotContainName = message_already_generated.indexOf(nameString) === -1;
//if there is no <endoftext> stamp in the response msg
const isNotEndOfText = message_already_generated.indexOf('<|endoftext|>') === -1;
//if the gen'd msg is less than the max response length..
const notReachedMax = tokens_already_generated < parseInt(amount_gen);
//if we actually have gen'd text at all...
const msgHasText = getMessage.length > 0;
return doesNotContainName && isNotEndOfText && notReachedMax && msgHasText;
}
function extractNameFromMessage(getMessage, force_name2, isImpersonate) {
@@ -2601,6 +2631,12 @@ function cleanUpMessage(getMessage, isImpersonate) {
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
}
}
if (power_user.instruct.enabled && power_user.instruct.input_sequence && isImpersonate) {
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
}
if (power_user.instruct.enabled && power_user.instruct.output_sequence && !isImpersonate) {
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
}
// clean-up group message from excessive generations
if (selected_group) {
getMessage = cleanGroupMessage(getMessage);
@@ -2699,6 +2735,10 @@ function saveReply(type, getMessage, this_mes_is_name, title) {
const item = chat[chat.length - 1];
if (item['swipe_id'] !== undefined) {
item['swipes'][item['swipes'].length - 1] = item['mes'];
} else {
item['swipe_id'] = 0;
item['swipes'] = [];
item['swipes'][0] = chat[chat.length - 1]['mes'];
}
return { type, getMessage };

View File

@@ -237,9 +237,17 @@ function getQuietPrompt(mode, trigger) {
function processReply(str) {
str = str.replaceAll('"', '')
str = str.replaceAll('“', '')
str = str.replaceAll('\n', ' ')
str = str.replaceAll('\n', ', ')
str = str.replace(/[^a-zA-Z0-9,:]+/g, ' ') // Replace everything except alphanumeric characters and commas with spaces
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim();
str = str
.split(',') // list split by commas
.map(x => x.trim()) // trim each entry
.filter(x => x) // remove empty entries
.join(', '); // join it back with proper spacing
return str;
}
@@ -259,7 +267,7 @@ async function generatePicture(_, trigger) {
const prompt = processReply(await new Promise(
async function promptPromise(resolve, reject) {
try {
await context.generate('quiet', { resolve, reject, quiet_prompt });
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
}
catch {
reject();
@@ -269,6 +277,8 @@ async function generatePicture(_, trigger) {
context.deactivateSendButtons();
hideSwipeButtons();
console.log('Processed Stable Diffusion prompt:', prompt);
const url = new URL(getApiUrl());
url.pathname = '/api/image';
const result = await fetch(url, {
@@ -295,7 +305,7 @@ async function generatePicture(_, trigger) {
sendMessage(prompt, base64Image);
}
} catch (err) {
console.error(err);
console.trace(err);
throw new Error('SD prompt text generation failed.')
}
finally {

View File

@@ -48,10 +48,8 @@ async function moduleWorker() {
return;
}
// Chat/character/group changed
// Chat changed
if (
(context.groupId && lastGroupId !== context.groupId) ||
context.characterId !== lastCharacterId ||
context.chatId !== lastChatId
) {
currentMessageNumber = context.chat.length ? context.chat.length : 0
@@ -241,9 +239,17 @@ async function processTtsQueue() {
console.debug('New message found, running TTS')
currentTtsJob = ttsJobQueue.shift()
const text = extension_settings.tts.narrate_dialogues_only
let text = extension_settings.tts.narrate_dialogues_only
? currentTtsJob.mes.replace(/\*[^\*]*?(\*|$)/g, '').trim() // remove asterisks content
: currentTtsJob.mes.replaceAll('*', '').trim() // remove just the asterisks
if (extension_settings.tts.narrate_quoted_only) {
const special_quotes = /[“”]/g; // Extend this regex to include other special quotes
text = text.replace(special_quotes, '"');
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
text = matches ? matches.join(' ... ... ... ') : text;
}
console.log(`TTS: ${text}`)
const char = currentTtsJob.name
try {
@@ -288,6 +294,7 @@ function loadSettings() {
extension_settings.tts.enabled
)
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only)
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only)
}
const defaultSettings = {
@@ -380,6 +387,13 @@ function onNarrateDialoguesClick() {
saveSettingsDebounced()
}
function onNarrateQuotedClick() {
extension_settings.tts.narrate_quoted_only = $('#tts_narrate_quoted').prop('checked');
saveSettingsDebounced()
}
//##############//
// TTS Provider //
//##############//
@@ -459,6 +473,10 @@ $(document).ready(function () {
<input type="checkbox" id="tts_narrate_dialogues">
Narrate dialogues only
</label>
<label class="checkbox_label" for="tts_narrate_quoted">
<input type="checkbox" id="tts_narrate_quoted">
Narrate quoted only
</label>
</div>
<label>Voice Map</label>
<textarea id="tts_voice_map" type="text" class="text_pole textarea_compact" rows="4"
@@ -481,6 +499,7 @@ $(document).ready(function () {
$('#tts_apply').on('click', onApplyClick)
$('#tts_enabled').on('click', onEnableClick)
$('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick);
$('#tts_narrate_quoted').on('click', onNarrateQuotedClick);
$('#tts_voices').on('click', onTtsVoicesClick)
$('#tts_provider_settings').on('input', onTtsProviderSettingsInput)
for (const provider in ttsProviders) {

View File

@@ -46,6 +46,7 @@ import {
menu_type,
select_selected_character,
cancelTtsPlay,
isMultigenEnabled,
} from "../script.js";
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
@@ -426,7 +427,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
let lastMessageText = lastMessage.mes;
let activationText = "";
let isUserInput = false;
let isQuietGenDone = false;
let isGenerationDone = false;
if (userInput && userInput.length && !by_auto_mode) {
isUserInput = true;
@@ -438,6 +439,23 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
}
}
const resolveOriginal = params.resolve;
const rejectOriginal = params.reject;
if (typeof params.resolve === 'function') {
params.resolve = function () {
isGenerationDone = true;
resolveOriginal.apply(this, arguments);
};
}
if (typeof params.reject === 'function') {
params.reject = function () {
isGenerationDone = true;
rejectOriginal.apply(this, arguments);
}
}
const activationStrategy = Number(group.activation_strategy ?? group_activation_strategy.NATURAL);
let activatedMembers = [];
@@ -450,16 +468,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
activatedMembers = activateListOrder(group.members.slice(0, 1));
}
const resolveOriginal = params.resolve;
const rejectOriginal = params.reject;
params.resolve = function () {
isQuietGenDone = true;
resolveOriginal.apply(this, arguments);
};
params.reject = function () {
isQuietGenDone = true;
rejectOriginal.apply(this, arguments);
}
}
else if (type === "swipe") {
activatedMembers = activateSwipe(group.members);
@@ -482,13 +490,14 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
// now the real generation begins: cycle through every character
for (const chId of activatedMembers) {
isGenerationDone = false;
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
setCharacterId(chId);
setCharacterName(characters[chId].name)
await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
if (type !== "swipe" && type !== "impersonate") {
if (type !== "swipe" && type !== "impersonate" && !isMultigenEnabled()) {
// update indicator and scroll down
typingIndicator
.find(".typing_indicator_name")
@@ -499,9 +508,10 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
});
}
// TODO: This is awful. Refactor this
while (true) {
// if not swipe - check if message generated already
if (type !== "swipe" && chat.length == messagesBefore) {
if (type !== "swipe" && !isMultigenEnabled() && chat.length == messagesBefore) {
await delay(100);
}
// if swipe - see if message changed
@@ -514,6 +524,13 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
break;
}
}
else if (isMultigenEnabled()) {
if (isGenerationDone) {
break;
} else {
await delay(100);
}
}
else {
if (lastMessageText === chat[chat.length - 1].mes) {
await delay(100);
@@ -532,6 +549,13 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
break;
}
}
else if (isMultigenEnabled()) {
if (isGenerationDone) {
break;
} else {
await delay(100);
}
}
else {
if (!$("#send_textarea").val() || $("#send_textarea").val() == userInput) {
await delay(100);
@@ -542,7 +566,15 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
}
}
else if (type === 'quiet') {
if (isQuietGenDone) {
if (isGenerationDone) {
break;
} else {
await delay(100);
}
}
else if (isMultigenEnabled()) {
if (isGenerationDone) {
messagesBefore++;
break;
} else {
await delay(100);

View File

@@ -624,10 +624,10 @@ function loadInstructMode() {
}
export function formatInstructModeChat(name, mes, isUser) {
const includeNames = power_user.instruct.names || (selected_group && !isUser);
const includeNames = power_user.instruct.names || !!selected_group;
const sequence = isUser ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
const separator = power_user.instruct.wrap ? '\n' : '';
const textArray = includeNames ? [sequence, name, ': ', mes, separator] : [sequence, mes, separator];
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separator] : [sequence, mes, separator];
const text = textArray.filter(x => x).join(separator);
return text;
}
@@ -641,10 +641,11 @@ export function formatInstructStoryString(story) {
return text;
}
export function formatInstructModePrompt(isImpersonate) {
export function formatInstructModePrompt(name, isImpersonate) {
const includeNames = power_user.instruct.names || !!selected_group;
const sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
const separator = power_user.instruct.wrap ? '\n' : '';
const text = separator + sequence;
const text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
return text;
}

View File

@@ -124,7 +124,7 @@ Get in touch with the developers directly:
### Windows
Installing via Git (reccomended for easy updating)
Installing via Git (recommended for easy updating)
Easy to follow guide with pretty pictures:
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
@@ -168,45 +168,58 @@ In order to enable viewing your keys by clicking a button in the API block:
## Remote connections
Most often this is for people who want to use SillyTavern on their mobile phones while at home.
If you want to enable other devices to connect to your TAI server, open 'config.conf' in a text editor, and change:
Most often this is for people who want to use SillyTavern on their mobile phones while their PC runs the ST server on the same wifi network.
```
const whitelistMode = true;
```
However, it can be used to allow remote connections from anywhere as well.
to
**IMPORTANT: SillyTavern is a single-user program, so anyone who logs in will be able to see all characters and chats, and be able to change any settings inside the UI.**
```
const whitelistMode = false;
```
### 1. Managing whitelisted IPs
Save the file.
Restart your TAI server.
You will now be able to connect from other devices.
### Managing whitelisted IPs
You can add or remove whitelisted IPs by editing the `whitelist` array in `config.conf`. You can also provide a `whitelist.txt` file in the same directory as `config.conf` with one IP address per line like:
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
*IP ranges are not accepted. Each IP must be listed individually like this:*
```txt
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
```
* Save the `whitelist.txt` file.
* Restart your TAI server.
The `whitelist` array in `config.conf` will be ignored if `whitelist.txt` exists.
Now devices which have the IP specified in the file will be able to connect.
***Disclaimer: Anyone else who knows your IP address and TAI port number will be able to connect as well***
*Note: `config.conf` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.*
To connect over wifi you'll need your PC's local wifi IP address
### 2. Connecting to ST from a remote device
* (For Windows: windows button > type 'cmd.exe' in the search bar> type 'ipconfig' in the console, hit Enter > "IPv4" listing)
if you want other people on the internet to connect, check [here](https://whatismyipaddress.com/) for 'IPv4'
After the whitelist has been setup, to connect over wifi you'll need the IP of the ST-hosting device.
If the ST-hosting device is on the same wifi network, you will point your remote device's browser to the ST-host's internal wifi IP:
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
If you (or someone else) wants to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
### Opening your ST to all IPs
We do not reccomend doing this, but you can open `config.conf` and change `whitelist` to `false`.
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
This is usually an insecure practice, so we require you to set a username and password when you do this.
The username and password are set in `config.conf`.
After restarting your ST server, any device will be able to connect to it, regardless of their IP as long as they know the username and password.
### Still Unable To Connect?
- Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
## Performance issues?