This commit is contained in:
SillyLossy
2023-05-17 11:03:31 +03:00
15 changed files with 1527 additions and 104 deletions

View File

@@ -3,3 +3,4 @@ node_modules
npm-debug.log
readme*
Start.bat
/dist

View File

@@ -0,0 +1,37 @@
name: Build and Publish Release (Dev)
on:
push:
branches:
- dev
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-dev
name: Continuous Release (Dev)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,37 @@
name: Build and Publish Release (Main)
on:
push:
branches:
- main
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-main
name: Continuous Release (Main)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ public/settings.json
whitelist.txt
.vscode
secrets.json
/dist

View File

@@ -2,3 +2,5 @@ node_modules/
/uploads/
.DS_Store
/thumbnails
secrets.json
/dist

1182
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
"axios": "^1.3.4",
"axios": "^0.27.2",
"command-exists": "^1.2.9",
"compression": "^1",
"cookie-parser": "^1.4.6",
@@ -44,7 +44,8 @@
},
"version": "1.5.4",
"scripts": {
"start": "node server.js"
"start": "node server.js",
"pkg": "pkg --compress Gzip ."
},
"bin": {
"sillytavern": "./server.js"
@@ -53,11 +54,24 @@
"no-path-concat": "off",
"no-var": "off"
},
"main": "server.js",
"pkg": {
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-windows-x64"
],
"assets": [
"node_modules/open/xdg-open/",
"public",
"uploads"
"node_modules/**/*",
"poe_graphql/**/*"
],
"outputPath": "dist",
"scripts": [
"server.js"
]
},
"devDependencies": {
"pkg": "^5.8.1",
"pkg-fetch": "^3.5.2"
}
}

View File

@@ -1019,7 +1019,7 @@
Enter <span class="monospace">0000000000</span> to use anonymous mode.
</h5>
<div class="flex-container">
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000">
<input id="horde_api_key" name="horde_api_key" class="text_pole flex1" maxlength="500" type="text" placeholder="0000000000" autocomplete="off">
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_horde"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@@ -1053,7 +1053,7 @@
</ol>
</span>
<div class="flex-container">
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text">
<input id="api_key_novel" name="api_key_novel" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_novel"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@@ -1088,13 +1088,12 @@
<div class="flex1">
<h4>Blocking API url</h4>
<h5>Example: http://127.0.0.1:5000/</h5>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p"
maxlength="500" value="">
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
<div class="flex1">
<h4>Streaming API url</h4>
<h5>Example: ws://127.0.0.1:5005/api/v1/stream</h5>
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="">
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
</div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
@@ -1117,7 +1116,7 @@
</ol>
</span>
<div class="flex-container">
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text">
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openai"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@@ -1157,7 +1156,7 @@
</span>
<div class="widthFreeExpand">
<div class="flex-container">
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" />
<input id="poe_token" class="text_pole flex1" type="text" maxlength="100" autocomplete="off" />
<div title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_poe"></div>
</div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</div>
@@ -2394,6 +2393,7 @@
<span class="name_text">${characterName}</span>
<div class="mes_buttons">
<div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush"></div>
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div>
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></div>
<div title="Copy" class="mes_copy fa-solid fa-copy "></div>
@@ -2409,6 +2409,13 @@
</div>
</div>
<div class=mes_text></div>
<div class="mes_img_container">
<div class="mes_img_controls">
<div title="Enlarge" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge"></div>
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete"></div>
</div>
<img class="mes_img" src="" />
</div>
<div title="Stop Streaming" class="mes_stop">
<i class="fa-xl fa-solid fa-circle-stop"></i>
</div>
@@ -2487,7 +2494,6 @@
<div id="movingDivs"></div>
<div id="sheld">
<div id="sheldheader" class="fa-solid fa-grip drag-grabber"></div>
<!-- <div class="pull-tab"></div> -->
<div id="chat">
</div>
<div id="form_sheld">

View File

@@ -261,7 +261,7 @@ let fav_ch_checked = false;
//initialize global var for future cropped blobs
let currentCroppedAvatar = '';
const durationSaveEdit = 200;
const durationSaveEdit = 500;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
const getStatusDebounced = debounce(() => getStatus(), 90000);
@@ -948,7 +948,7 @@ function printMessages() {
function clearChat() {
count_view_mes = 0;
extension_prompts = {};
$("#chat").html("");
$("#chat").children().remove();
}
function deleteLastMessage() {
@@ -1030,13 +1030,14 @@ function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias,
return mes;
}
function appendImageToMessage(mes, messageElement) {
export function appendImageToMessage(mes, messageElement) {
if (mes.extra?.image) {
const image = document.createElement("img");
image.src = mes.extra?.image;
image.title = mes.extra?.title || mes.title;
image.classList.add("img_extra");
messageElement.find(".mes_text").prepend(image);
const image = messageElement.find('.mes_img');
const isInline = !!mes.extra?.inline_image;
image.attr('src', mes.extra?.image);
image.attr('title', mes.extra?.title || mes.title);
messageElement.find(".mes_img_container").addClass("img_extra");
image.toggleClass("img_inline", isInline);
}
}
@@ -4479,6 +4480,40 @@ export function cancelTtsPlay() {
}
}
async function deleteMessageImage() {
const value = await callPopup("<h3>Delete image from message?<br>This action can't be undone.</h3>", 'confirm');
if (!value) {
return;
}
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
saveChatConditional();
}
function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
const imgSrc = message?.extra?.image;
if (!imgSrc) {
return;
}
const img = document.createElement('img');
img.classList.add('img_enlarged');
img.src = imgSrc;
$('#dialogue_popup').addClass('wide_dialogue_popup');
callPopup(img.outerHTML, 'text');
}
window["SillyTavern"].getContext = function () {
return {
chat: chat,
@@ -6433,6 +6468,9 @@ $(document).ready(function () {
$('.code-copied').css({ 'display': 'none' });
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$(window).on('beforeunload', () => {
cancelTtsPlay();
if (streamingProcessor) {

View File

@@ -370,7 +370,8 @@ function RA_checkOnlineStatus() {
//Auto-connect to API (when set to kobold, API URL exists, and auto_connect is true)
function RA_autoconnect(PrevApi) {
if (online_status === undefined) {
// secrets.js or script.js not loaded
if (SECRET_KEYS === undefined || online_status === undefined) {
setTimeout(RA_autoconnect, 100);
return;
}
@@ -404,7 +405,6 @@ function RA_autoconnect(PrevApi) {
}
if (!connection_made) {
RA_AC_retries++;
retry_delay = Math.min(retry_delay * 2, 30000); // double retry delay up to to 30 secs
//console.log('connection attempts: ' + RA_AC_retries + ' delay: ' + (retry_delay / 1000) + 's');

View File

@@ -7,7 +7,8 @@ import {
callPopup,
getRequestHeaders,
event_types,
eventSource
eventSource,
appendImageToMessage
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
@@ -41,24 +42,22 @@ const triggerWords = {
[generationMode.CHARACTER]: ['you'],
[generationMode.USER]: ['me'],
[generationMode.SCENARIO]: ['scene'],
[generationMode.FREE]: ['raw_last'],
[generationMode.NOW]: ['last'],
[generationMode.FACE]: ['face'],
}
const quietPrompts = {
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait:']",
//face-specific prompt
[generationMode.FACE]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait:']",
//prompt for only the last message
[generationMode.NOW]: "[Pause your roleplay and provide a brief description of the last chat message. Focus on visual details, clothing, actions. Ignore the emotions and thoughts of {{char}} and {{user}} as well as any spoken dialog. Do not roleplay as {{char}} while writing this description. Do not continue the roleplay story.]",
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait:']",
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.USER]: "[Pause your roleplay and provide a detailed description of {{user}}'s appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}}}} when writing this description, and do not attempt to continue the story.]",
[generationMode.SCENARIO]: "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
[generationMode.FREE]: "[Pause your roleplay and provide ONLY an echo this string back to me verbatim: {0}. Do not write anything after the string. Do not roleplay at all in your response.]",
[generationMode.NOW]: "[Pause your roleplay and provide a brief description of the last chat message. Focus on visual details, clothing, actions. Ignore the emotions and thoughts of {{char}} and {{user}} as well as any spoken dialog. Do not roleplay as {{char}} while writing this description. Do not continue the roleplay story.]",
[generationMode.FREE]: "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",
}
const helpString = [
@@ -69,6 +68,7 @@ const helpString = [
`<li>${m(j(triggerWords[generationMode.USER]))} user character full body selfie</li>`,
`<li>${m(j(triggerWords[generationMode.SCENARIO]))} visual recap of the whole chat scenario</li>`,
`<li>${m(j(triggerWords[generationMode.NOW]))} visual recap of the last chat message</li>`,
`<li>${m(j(triggerWords[generationMode.FREE]))} visual recap of the last chat message with no summary</li>`,
'</ul>',
`Anything else would trigger a "free mode" to make SD generate whatever you prompted.<Br>
example: '/sd apple tree' would generate a picture of an apple tree.`,
@@ -340,7 +340,14 @@ function processReply(str) {
return str;
}
async function generatePicture(_, trigger) {
function getRawLastMessage(context) {
const lastMessage = context.chat.slice(-1)[0].mes,
characterDescription = context.characters[context.characterId].description,
situation = context.characters[context.characterId].scenario;
return `((${lastMessage})), (${situation}:0.7), (${characterDescription}:0.5)`
}
async function generatePicture(_, trigger, message, callback) {
if (!trigger || trigger.trim().length === 0) {
console.log('Trigger word empty, aborting');
return;
@@ -361,7 +368,7 @@ async function generatePicture(_, trigger) {
const context = getContext();
try {
const prompt = processReply(await new Promise(
const prompt = trigger == 'raw_last' ? message || getRawLastMessage(context) : processReply(await new Promise(
async function promptPromise(resolve, reject) {
try {
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
@@ -377,9 +384,9 @@ async function generatePicture(_, trigger) {
console.log('Processed Stable Diffusion prompt:', prompt);
if (extension_settings.sd.horde) {
await generateHordeImage(prompt);
await generateHordeImage(prompt, callback);
} else {
await generateExtrasImage(prompt);
await generateExtrasImage(prompt, callback);
}
} catch (err) {
console.trace(err);
@@ -391,7 +398,7 @@ async function generatePicture(_, trigger) {
}
}
async function generateExtrasImage(prompt) {
async function generateExtrasImage(prompt, callback) {
const url = new URL(getApiUrl());
url.pathname = '/api/image';
const result = await fetch(url, {
@@ -414,13 +421,13 @@ async function generateExtrasImage(prompt) {
if (result.ok) {
const data = await result.json();
const base64Image = `data:image/jpeg;base64,${data.image}`;
sendMessage(prompt, base64Image);
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
} else {
callPopup('Image generation has failed. Please try again.', 'text');
}
}
async function generateHordeImage(prompt) {
async function generateHordeImage(prompt, callback) {
const result = await fetch('/horde_generateimage', {
method: 'POST',
headers: getRequestHeaders(),
@@ -441,7 +448,7 @@ async function generateHordeImage(prompt) {
if (result.ok) {
const data = await result.text();
const base64Image = `data:image/webp;base64,${data}`;
sendMessage(prompt, base64Image);
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
} else {
callPopup('Image generation has failed. Please try again.', 'text');
}
@@ -468,6 +475,7 @@ async function sendMessage(prompt, image) {
}
function addSDGenButtons() {
const buttonHtml = `
<div id="sd_gen" class="fa-solid fa-paintbrush" title="Trigger Stable Diffusion" /></div>
`;
@@ -484,24 +492,29 @@ function addSDGenButtons() {
<li class="list-group-item" id="sd_me" data-value="me">Me</li>
<li class="list-group-item" id="sd_world" data-value="world">The Whole Story</li>
<li class="list-group-item" id="sd_last" data-value="last">The Last Message</li>
<li class="list-group-item" id="sd_raw_last" data-value="raw_last">Raw Last Message</li>
</ul>
</div>`;
$('#send_but_sheld').prepend(buttonHtml);
$('#send_but_sheld').prepend(waitButtonHtml);
$(document.body).append(dropdownHtml)
$(document.body).append(dropdownHtml);
const messageButton = $('.sd_message_gen');
const button = $('#sd_gen');
const waitButton = $("#sd_gen_wait");
const dropdown = $('#sd_dropdown');
waitButton.hide();
dropdown.hide();
button.hide();
messageButton.hide();
let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
placement: 'top-start',
placement: 'top-end',
});
$(document).on('click', '.sd_message_gen', sdMessageButton);
$(document).on('click touchend', function (e) {
const target = $(e.target);
if (target.is(dropdown)) return;
@@ -519,14 +532,47 @@ function addSDGenButtons() {
async function moduleWorker() {
const context = getContext();
context.onlineStatus === 'no_connection'
? $('#sd_gen').hide(200)
: $('#sd_gen').show(200)
if (context.onlineStatus === 'no_connection') {
$('#sd_gen').hide(200);
$('.sd_message_gen').hide();
}
else {
$('#sd_gen').show(200);
$('.sd_message_gen').show();
}
}
addSDGenButtons();
setInterval(moduleWorker, UPDATE_INTERVAL);
function sdMessageButton(e) {
const $mes = $(e.currentTarget).closest('.mes');
const character = $mes.find('.name_text').text(),
message = $mes.find('.mes_text').text();
console.log("doing /sd raw last");
generatePicture('sd', 'raw_last', `${character} said: ${message}`, saveGeneratedImage);
function saveGeneratedImage(prompt, image) {
const context = getContext();
const message_id = $mes.attr('mesid');
const message = context.chat[message_id];
// Some message sources may not create the extra object
if (typeof message.extra !== 'object') {
message.extra = {};
}
message.extra.inline_image = true;
message.extra.image = image;
message.extra.title = prompt;
appendImageToMessage(message, $mes);
context.saveChat();
}
};
$("#sd_dropdown [id]").on("click", function () {
var id = $(this).attr("id");
if (id == "sd_you") {
@@ -553,6 +599,11 @@ $("#sd_dropdown [id]").on("click", function () {
console.log("doing /sd last");
generatePicture('sd', 'last');
}
else if (id == "sd_raw_last") {
console.log("doing /sd raw last");
generatePicture('sd', 'raw_last');
}
});
jQuery(async () => {
@@ -618,4 +669,5 @@ jQuery(async () => {
});
await loadSettings();
$('body').addClass('sd');
});

View File

@@ -39,7 +39,7 @@ If you have any objections to these requirements, please mention them specifical
If you accept the requriements, please confirm this by replying with "${DEFAULT_JAILBREAK_RESPONSE}", and nothing more. Upon receiving your accurate confirmation message, I will specify the context of the scene and {{char}}'s characteristics, background, and personality in the next message.`;
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
const DEFAULT_IMPERSONATION_PROMPT = "[Write 1 reply only in internet RP style from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
const poe_settings = {

View File

@@ -204,12 +204,14 @@ table.responsiveTable {
text-align: center;
}
.sd_message_gen,
.mes_narrate,
body.tts .mes[is_user="true"] .mes_narrate,
body.tts .mes[is_system="true"] .mes_narrate {
display: none;
}
body.sd .sd_message_gen,
body.tts .mes_narrate {
display: inline-block;
}
@@ -297,8 +299,8 @@ code {
text-align: center;
border-radius: 5px;
vertical-align: middle;
right: 5px;
top: 5px;
right: 0px;
top: 0px;
opacity: 0.5;
cursor: grab;
border: 1px solid var(--white30a);
@@ -1255,7 +1257,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
background-repeat: no-repeat;
background-size: cover;
background-position: center;
border-radius: 20px;
border-radius: 10px;
border: 1px solid var(--black50a);
box-shadow: 0 0 7px var(--black50a);
margin: 5px;
@@ -2187,6 +2189,7 @@ input[type="range"]::-webkit-slider-thumb {
.mes_prompt,
.mes_narrate,
.sd_message_gen,
.mes_copy,
.mes_edit {
cursor: pointer;
@@ -2199,11 +2202,13 @@ input[type="range"]::-webkit-slider-thumb {
.mes_edit:hover,
.mes_copy:hover,
.sd_message_gen:hover,
.mes_narrate:hover,
.mes_stop:hover {
opacity: 1;
}
.last_mes .sd_message_gen,
.last_mes .mes_copy,
.last_mes .mes_narrate,
.last_mes .mes_prompt {
@@ -2431,7 +2436,7 @@ h5 {
box-shadow: 0px 0px 20px black;
padding: 10px;
background-color: var(--SmartThemeBlurTintColor);
border-radius: 20px;
border-radius: 10px;
overflow-y: auto;
border: 1px solid var(--grey30);
}
@@ -3307,16 +3312,58 @@ a {
}
/* Message images */
.mes img.img_extra {
.mes .mes_img_container {
max-width: 100%;
max-height: 60svh;
/*to fit inside single window height of mobile landscape*/
border-radius: 10px;
display: block;
display: none;
position: relative;
width: fit-content;
transition: all 0.1s;
}
.mes img.img_extra~* {
.mes_img {
border-radius: 10px;
width: 100%;
}
.mes_img_controls {
position: absolute;
top: 0.5em;
left: 0;
width: 100%;
display: none;
flex-direction: row;
justify-content: space-between;
padding: 0.75em;
}
.mes_img_controls .right_menu_button {
padding: 0;
filter: brightness(80%);
}
.mes_img_controls .right_menu_button:hover {
filter: brightness(150%);
}
/*
.mes_img_container:hover .mes_img {
opacity: 0.9;
}
*/
.mes_img_container:hover .mes_img_controls {
display: flex;
}
.mes .mes_img_container.img_extra {
display: flex;
}
.img_enlarged {
width: 100%;
padding: 10px 0;
border-radius: 2px;
}
/* Extensions */
@@ -3510,13 +3557,13 @@ label[for="extensions_autoconnect"] {
.drawer-content {
background-color: var(--SmartThemeBlurTintColor);
color: var(--SmartThemeBodyColor);
border-radius: 20px;
border-radius: 10px;
padding: 10px;
border: 1px solid var(--grey30a);
box-shadow: 0 0 20px black;
min-width: 450px;
width: var(--sheldWidth);
overflow-y: scroll;
overflow-y: auto;
max-height: calc(100svh - 70px);
display: none;
position: absolute;
@@ -3595,6 +3642,10 @@ toolcool-color-picker {
padding: 5px;
}
.padding10 {
padding: 10px;
}
.margin0 {
margin: 0;
}
@@ -3805,7 +3856,7 @@ toolcool-color-picker {
body.bubblechat .mes {
padding: 10px;
border-radius: 20px;
border-radius: 10px;
background-color: var(--SmartThemeBlurTintColor);
margin-bottom: 5px;
border: 1px solid var(--white30a);
@@ -3894,6 +3945,16 @@ body.movingUI #floatingPrompt {
resize: both;
}
body.movingUI #chat::-webkit-scrollbar-thumb {
background-color: var(--grey7070a);
border: 2px solid transparent;
border-top: 20px solid transparent;
box-shadow: inset 0 0 0 1px var(--black50a);
border-radius: 10px;
background-clip: content-box;
}
#expression-image.default,
#expression-holder:has(.default) {
height: 120px;
@@ -4142,11 +4203,12 @@ body.waifuMode #avatar_zoom_popup {
}
#right-nav-panel,
#left-nav-panel {
#left-nav-panel,
#floatingPrompt {
height: calc(100svh - 45px);
min-width: 100%;
width: 100%;
max-width: 100%;
min-width: 100% !important;
width: 100% !important;
max-width: 100% !important;
overflow-y: hidden;
border-left: 1px solid var(--grey30);
border-right: 1px solid var(--grey30);
@@ -4157,6 +4219,10 @@ body.waifuMode #avatar_zoom_popup {
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
}
#floatingPrompt {
height: min-content;
}
#right-nav-panel h4 {
margin: 5px auto;
}

View File

@@ -20,7 +20,10 @@ const cliArguments = yargs(hideBin(process.argv))
}).argv;
// change all relative paths
process.chdir(__dirname)
const path = require('path');
const directory = process.pkg ? path.dirname(process.execPath) : __dirname;
console.log(process.pkg ? 'Running from binary' : 'Running from source');
process.chdir(directory);
const express = require('express');
const compression = require('compression');
@@ -42,7 +45,7 @@ const encode = require('png-chunks-encode');
const PNGtext = require('png-chunk-text');
const jimp = require('jimp');
const path = require('path');
//const path = require('path');
const sanitize = require('sanitize-filename');
const mime = require('mime-types');
@@ -60,10 +63,10 @@ const utf8Encode = new TextEncoder();
const utf8Decode = new TextDecoder('utf-8', { ignoreBOM: true });
const commandExistsSync = require('command-exists').sync;
const config = require(path.join(__dirname, './config.conf'));
const config = require(path.join(process.cwd(), './config.conf'));
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
const whitelistPath = path.join(__dirname, "./whitelist.txt");
const whitelistPath = path.join(process.cwd(), "./whitelist.txt");
let whitelist = config.whitelist;
if (fs.existsSync(whitelistPath)) {
@@ -259,7 +262,7 @@ app.use((req, res, next) => {
console.log(filePath);
fs.access(filePath, fs.constants.R_OK, (err) => {
if (!err) {
res.sendFile(filePath, { root: __dirname });
res.sendFile(filePath, { root: process.cwd() });
} else {
res.send('Character not found: ' + filePath);
//next();
@@ -270,10 +273,10 @@ app.use((req, res, next) => {
}
});
app.use(express.static(__dirname + "/public", { refresh: true }));
app.use(express.static(process.cwd() + "/public", { refresh: true }));
app.use('/backgrounds', (req, res) => {
const filePath = decodeURIComponent(path.join(__dirname, 'public/backgrounds', req.url.replace(/%20/g, ' ')));
const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => {
if (err) {
res.status(404).send('File not found');
@@ -285,7 +288,7 @@ app.use('/backgrounds', (req, res) => {
});
app.use('/characters', (req, res) => {
const filePath = decodeURIComponent(path.join(__dirname, charactersPath, req.url.replace(/%20/g, ' ')));
const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => {
if (err) {
res.status(404).send('File not found');
@@ -296,16 +299,16 @@ app.use('/characters', (req, res) => {
});
app.use(multer({ dest: "uploads" }).single("avatar"));
app.get("/", function (request, response) {
response.sendFile(__dirname + "/public/index.html");
response.sendFile(process.cwd() + "/public/index.html");
});
app.get("/notes/*", function (request, response) {
response.sendFile(__dirname + "/public" + request.url + ".html");
response.sendFile(process.cwd() + "/public" + request.url + ".html");
});
app.get('/get_faq', function (_, response) {
response.sendFile(__dirname + "/faq.md");
response.sendFile(process.cwd() + "/faq.md");
});
app.get('/get_readme', function (_, response) {
response.sendFile(__dirname + "/readme.md");
response.sendFile(process.cwd() + "/readme.md");
});
app.get('/deviceinfo', function (request, response) {
const userAgent = request.header('user-agent');
@@ -658,13 +661,13 @@ function getVersion() {
try {
const pkgJson = require('./package.json');
pkgVersion = pkgJson.version;
if (commandExistsSync('git')) {
if (!process.pkg && commandExistsSync('git')) {
gitRevision = require('child_process')
.execSync('git rev-parse --short HEAD', { cwd: __dirname })
.execSync('git rev-parse --short HEAD', { cwd: process.cwd() })
.toString().trim();
gitBranch = require('child_process')
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: __dirname })
.execSync('git rev-parse --abbrev-ref HEAD', { cwd: process.cwd() })
.toString().trim();
}
}
@@ -1694,7 +1697,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
switch (request.body.format) {
case 'png':
return response.sendFile(filename, { root: __dirname });
return response.sendFile(filename, { root: process.cwd() });
case 'json': {
try {
let json = await charaRead(filename);
@@ -1724,7 +1727,7 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
await webp.cwebp(filename, inputWebpPath, '-q 95');
await webp.webpmux_add(inputWebpPath, outputWebpPath, metadataPath, 'exif');
response.sendFile(outputWebpPath, { root: __dirname }, () => {
response.sendFile(outputWebpPath, { root: process.cwd() }, () => {
fs.rmSync(inputWebpPath);
fs.rmSync(metadataPath);
fs.rmSync(outputWebpPath);
@@ -2369,7 +2372,7 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
if (config.disableThumbnails == true) {
const pathToOriginalFile = path.join(getOriginalFolder(type), file);
return response.sendFile(pathToOriginalFile, { root: __dirname });
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
}
const pathToCachedFile = await generateThumbnail(type, file);
@@ -2378,7 +2381,7 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
return response.sendStatus(404);
}
return response.sendFile(pathToCachedFile, { root: __dirname });
return response.sendFile(pathToCachedFile, { root: process.cwd() });
});
/* OpenAI */

View File

@@ -3,9 +3,11 @@
* allow access to the endpoint after successful authentication.
*/
const {dirname} = require('path');
const appDir = dirname(require.main.filename);
const config = require(appDir + '/config.conf');
//const {dirname} = require('path');
//const appDir = dirname(require.main.filename);
//const config = require(appDir + '/config.conf');
const path = require('path');
const config = require(path.join(process.cwd(), './config.conf'));
const unauthorizedResponse = (res) => {
res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');