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 npm-debug.log
readme* readme*
Start.bat 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 whitelist.txt
.vscode .vscode
secrets.json secrets.json
/dist

View File

@@ -2,3 +2,5 @@ node_modules/
/uploads/ /uploads/
.DS_Store .DS_Store
/thumbnails /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": { "dependencies": {
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
"axios": "^1.3.4", "axios": "^0.27.2",
"command-exists": "^1.2.9", "command-exists": "^1.2.9",
"compression": "^1", "compression": "^1",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
@@ -44,7 +44,8 @@
}, },
"version": "1.5.4", "version": "1.5.4",
"scripts": { "scripts": {
"start": "node server.js" "start": "node server.js",
"pkg": "pkg --compress Gzip ."
}, },
"bin": { "bin": {
"sillytavern": "./server.js" "sillytavern": "./server.js"
@@ -53,11 +54,24 @@
"no-path-concat": "off", "no-path-concat": "off",
"no-var": "off" "no-var": "off"
}, },
"main": "server.js",
"pkg": { "pkg": {
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-windows-x64"
],
"assets": [ "assets": [
"node_modules/open/xdg-open/", "node_modules/**/*",
"public", "poe_graphql/**/*"
"uploads" ],
"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. Enter <span class="monospace">0000000000</span> to use anonymous mode.
</h5> </h5>
<div class="flex-container"> <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 title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_horde"></div>
</div> </div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</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> </ol>
</span> </span>
<div class="flex-container"> <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 title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_novel"></div>
</div> </div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</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"> <div class="flex1">
<h4>Blocking API url</h4> <h4>Blocking API url</h4>
<h5>Example: http://127.0.0.1:5000/</h5> <h5>Example: http://127.0.0.1:5000/</h5>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" <input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
maxlength="500" value="">
</div> </div>
<div class="flex1"> <div class="flex1">
<h4>Streaming API url</h4> <h4>Streaming API url</h4>
<h5>Example: ws://127.0.0.1:5005/api/v1/stream</h5> <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>
</div> </div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect"> <input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
@@ -1117,7 +1116,7 @@
</ol> </ol>
</span> </span>
<div class="flex-container"> <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 title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openai"></div>
</div> </div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</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> </span>
<div class="widthFreeExpand"> <div class="widthFreeExpand">
<div class="flex-container"> <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 title="Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_poe"></div>
</div> </div>
<div class="neutral_warning">For privacy reasons, your API key will be hidden after you reload the page.</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> <span class="name_text">${characterName}</span>
<div class="mes_buttons"> <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="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div>
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></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> <div title="Copy" class="mes_copy fa-solid fa-copy "></div>
@@ -2409,6 +2409,13 @@
</div> </div>
</div> </div>
<div class=mes_text></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"> <div title="Stop Streaming" class="mes_stop">
<i class="fa-xl fa-solid fa-circle-stop"></i> <i class="fa-xl fa-solid fa-circle-stop"></i>
</div> </div>
@@ -2487,7 +2494,6 @@
<div id="movingDivs"></div> <div id="movingDivs"></div>
<div id="sheld"> <div id="sheld">
<div id="sheldheader" class="fa-solid fa-grip drag-grabber"></div> <div id="sheldheader" class="fa-solid fa-grip drag-grabber"></div>
<!-- <div class="pull-tab"></div> -->
<div id="chat"> <div id="chat">
</div> </div>
<div id="form_sheld"> <div id="form_sheld">

View File

@@ -261,7 +261,7 @@ let fav_ch_checked = false;
//initialize global var for future cropped blobs //initialize global var for future cropped blobs
let currentCroppedAvatar = ''; let currentCroppedAvatar = '';
const durationSaveEdit = 200; const durationSaveEdit = 500;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit); const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit); const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
const getStatusDebounced = debounce(() => getStatus(), 90000); const getStatusDebounced = debounce(() => getStatus(), 90000);
@@ -948,7 +948,7 @@ function printMessages() {
function clearChat() { function clearChat() {
count_view_mes = 0; count_view_mes = 0;
extension_prompts = {}; extension_prompts = {};
$("#chat").html(""); $("#chat").children().remove();
} }
function deleteLastMessage() { function deleteLastMessage() {
@@ -1030,13 +1030,14 @@ function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias,
return mes; return mes;
} }
function appendImageToMessage(mes, messageElement) { export function appendImageToMessage(mes, messageElement) {
if (mes.extra?.image) { if (mes.extra?.image) {
const image = document.createElement("img"); const image = messageElement.find('.mes_img');
image.src = mes.extra?.image; const isInline = !!mes.extra?.inline_image;
image.title = mes.extra?.title || mes.title; image.attr('src', mes.extra?.image);
image.classList.add("img_extra"); image.attr('title', mes.extra?.title || mes.title);
messageElement.find(".mes_text").prepend(image); 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 () { window["SillyTavern"].getContext = function () {
return { return {
chat: chat, chat: chat,
@@ -6433,6 +6468,9 @@ $(document).ready(function () {
$('.code-copied').css({ 'display': 'none' }); $('.code-copied').css({ 'display': 'none' });
}); });
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$(window).on('beforeunload', () => { $(window).on('beforeunload', () => {
cancelTtsPlay(); cancelTtsPlay();
if (streamingProcessor) { 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) //Auto-connect to API (when set to kobold, API URL exists, and auto_connect is true)
function RA_autoconnect(PrevApi) { 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); setTimeout(RA_autoconnect, 100);
return; return;
} }
@@ -404,7 +405,6 @@ function RA_autoconnect(PrevApi) {
} }
if (!connection_made) { if (!connection_made) {
RA_AC_retries++; RA_AC_retries++;
retry_delay = Math.min(retry_delay * 2, 30000); // double retry delay up to to 30 secs 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'); //console.log('connection attempts: ' + RA_AC_retries + ' delay: ' + (retry_delay / 1000) + 's');

View File

@@ -7,7 +7,8 @@ import {
callPopup, callPopup,
getRequestHeaders, getRequestHeaders,
event_types, event_types,
eventSource eventSource,
appendImageToMessage
} from "../../../script.js"; } from "../../../script.js";
import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js"; import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js"; import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
@@ -41,24 +42,22 @@ const triggerWords = {
[generationMode.CHARACTER]: ['you'], [generationMode.CHARACTER]: ['you'],
[generationMode.USER]: ['me'], [generationMode.USER]: ['me'],
[generationMode.SCENARIO]: ['scene'], [generationMode.SCENARIO]: ['scene'],
[generationMode.FREE]: ['raw_last'],
[generationMode.NOW]: ['last'], [generationMode.NOW]: ['last'],
[generationMode.FACE]: ['face'], [generationMode.FACE]: ['face'],
} }
const quietPrompts = { 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 //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:']", [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 //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.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.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 = [ 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.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.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.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>', '</ul>',
`Anything else would trigger a "free mode" to make SD generate whatever you prompted.<Br> `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.`, example: '/sd apple tree' would generate a picture of an apple tree.`,
@@ -340,7 +340,14 @@ function processReply(str) {
return 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) { if (!trigger || trigger.trim().length === 0) {
console.log('Trigger word empty, aborting'); console.log('Trigger word empty, aborting');
return; return;
@@ -361,7 +368,7 @@ async function generatePicture(_, trigger) {
const context = getContext(); const context = getContext();
try { try {
const prompt = processReply(await new Promise( const prompt = trigger == 'raw_last' ? message || getRawLastMessage(context) : processReply(await new Promise(
async function promptPromise(resolve, reject) { async function promptPromise(resolve, reject) {
try { try {
await context.generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, }); 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); console.log('Processed Stable Diffusion prompt:', prompt);
if (extension_settings.sd.horde) { if (extension_settings.sd.horde) {
await generateHordeImage(prompt); await generateHordeImage(prompt, callback);
} else { } else {
await generateExtrasImage(prompt); await generateExtrasImage(prompt, callback);
} }
} catch (err) { } catch (err) {
console.trace(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()); const url = new URL(getApiUrl());
url.pathname = '/api/image'; url.pathname = '/api/image';
const result = await fetch(url, { const result = await fetch(url, {
@@ -414,13 +421,13 @@ async function generateExtrasImage(prompt) {
if (result.ok) { if (result.ok) {
const data = await result.json(); const data = await result.json();
const base64Image = `data:image/jpeg;base64,${data.image}`; const base64Image = `data:image/jpeg;base64,${data.image}`;
sendMessage(prompt, base64Image); callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
} else { } else {
callPopup('Image generation has failed. Please try again.', 'text'); callPopup('Image generation has failed. Please try again.', 'text');
} }
} }
async function generateHordeImage(prompt) { async function generateHordeImage(prompt, callback) {
const result = await fetch('/horde_generateimage', { const result = await fetch('/horde_generateimage', {
method: 'POST', method: 'POST',
headers: getRequestHeaders(), headers: getRequestHeaders(),
@@ -441,7 +448,7 @@ async function generateHordeImage(prompt) {
if (result.ok) { if (result.ok) {
const data = await result.text(); const data = await result.text();
const base64Image = `data:image/webp;base64,${data}`; const base64Image = `data:image/webp;base64,${data}`;
sendMessage(prompt, base64Image); callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
} else { } else {
callPopup('Image generation has failed. Please try again.', 'text'); callPopup('Image generation has failed. Please try again.', 'text');
} }
@@ -468,6 +475,7 @@ async function sendMessage(prompt, image) {
} }
function addSDGenButtons() { function addSDGenButtons() {
const buttonHtml = ` const buttonHtml = `
<div id="sd_gen" class="fa-solid fa-paintbrush" title="Trigger Stable Diffusion" /></div> <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_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_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_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> </ul>
</div>`; </div>`;
$('#send_but_sheld').prepend(buttonHtml); $('#send_but_sheld').prepend(buttonHtml);
$('#send_but_sheld').prepend(waitButtonHtml); $('#send_but_sheld').prepend(waitButtonHtml);
$(document.body).append(dropdownHtml) $(document.body).append(dropdownHtml);
const messageButton = $('.sd_message_gen');
const button = $('#sd_gen'); const button = $('#sd_gen');
const waitButton = $("#sd_gen_wait"); const waitButton = $("#sd_gen_wait");
const dropdown = $('#sd_dropdown'); const dropdown = $('#sd_dropdown');
waitButton.hide(); waitButton.hide();
dropdown.hide(); dropdown.hide();
button.hide(); button.hide();
messageButton.hide();
let popper = Popper.createPopper(button.get(0), dropdown.get(0), { 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) { $(document).on('click touchend', function (e) {
const target = $(e.target); const target = $(e.target);
if (target.is(dropdown)) return; if (target.is(dropdown)) return;
@@ -519,14 +532,47 @@ function addSDGenButtons() {
async function moduleWorker() { async function moduleWorker() {
const context = getContext(); const context = getContext();
context.onlineStatus === 'no_connection' if (context.onlineStatus === 'no_connection') {
? $('#sd_gen').hide(200) $('#sd_gen').hide(200);
: $('#sd_gen').show(200) $('.sd_message_gen').hide();
}
else {
$('#sd_gen').show(200);
$('.sd_message_gen').show();
}
} }
addSDGenButtons(); addSDGenButtons();
setInterval(moduleWorker, UPDATE_INTERVAL); 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 () { $("#sd_dropdown [id]").on("click", function () {
var id = $(this).attr("id"); var id = $(this).attr("id");
if (id == "sd_you") { if (id == "sd_you") {
@@ -553,6 +599,11 @@ $("#sd_dropdown [id]").on("click", function () {
console.log("doing /sd last"); console.log("doing /sd last");
generatePicture('sd', 'last'); generatePicture('sd', 'last');
} }
else if (id == "sd_raw_last") {
console.log("doing /sd raw last");
generatePicture('sd', 'raw_last');
}
}); });
jQuery(async () => { jQuery(async () => {
@@ -618,4 +669,5 @@ jQuery(async () => {
}); });
await loadSettings(); 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.`; 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 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 = { const poe_settings = {
@@ -220,7 +220,7 @@ async function onConnectClick() {
return; return;
} }
if ( is_poe_button_press) { if (is_poe_button_press) {
console.log('Poe API button is pressed'); console.log('Poe API button is pressed');
return; return;
} }

View File

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

View File

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

View File

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