This commit is contained in:
RossAscends
2023-04-29 23:41:30 +09:00
15 changed files with 261 additions and 224 deletions

View File

@@ -1,3 +1,4 @@
.git
node_modules
npm-debug.log
readme*

View File

@@ -3,8 +3,10 @@ FROM node:19.1.0-alpine3.16
# Arguments
ARG APP_HOME=/home/node/app
# Install system dependencies
RUN apk add gcompat tini
# Ensure proper handling of kernel signals
RUN apk add tini
ENTRYPOINT [ "tini", "--" ]
# Create app directory
@@ -14,7 +16,7 @@ WORKDIR ${APP_HOME}
COPY package*.json ./
RUN \
echo "*** Install npm packages ***" && \
npm install
npm install && npm cache clean --force
# Bundle app source
COPY . ./
@@ -25,8 +27,8 @@ RUN \
mv "./public/characters" "./public/characters.default" && \
mv "./public/chats" "./public/chats.default" && \
mv "./public/User Avatars" "./public/User Avatars.default" && \
mv "./public/settings.json" "./public/settings.json.default" && \
mv "./public/settings.json" "./public/settings.json.default" && \
\
echo "*** Create symbolic links to config directory ***" && \
ln -s "${APP_HOME}/config/characters" "${APP_HOME}/public/characters" && \
ln -s "${APP_HOME}/config/chats" "${APP_HOME}/public/chats" && \
@@ -38,7 +40,6 @@ RUN \
echo "*** Cleanup ***" && \
mv "./docker/docker-entrypoint.sh" "./" && \
rm -rf "./docker" && \
rm -rf "./.git" && \
echo "*** Make docker-entrypoint.sh executable ***" && \
chmod +x "./docker-entrypoint.sh" && \
echo "*** Convert line endings to Unix format ***" && \

View File

@@ -6,10 +6,10 @@
"metadata": {},
"source": [
"**Links**<br>\n",
"Cohee's TavernAI fork Github https://github.com/Cohee1207/SillyTavern<br>\n",
"Cohee's TavernAI Extras Github https://github.com/Cohee1207/TavernAI-extras/<br>\n",
"TavernAI Discord https://discord.gg/zmK2gmr45t<br>\n",
"Questions? Hit me up on Discord: Cohee#1207"
"SillyTavern GitHub: https://github.com/Cohee1207/SillyTavern<br>\n",
"Extensions API GitHub: https://github.com/Cohee1207/TavernAI-extras/<br>\n",
"SillyTavern community Discord (support and discussion): https://discord.gg/RZdyAEUPvj<br>\n",
"Contact the maintainer directly: Cohee#1207"
]
},
{

2
faq.md
View File

@@ -132,7 +132,7 @@ When using Poe, be careful, it's implemented in a hacky way. If you don't get an
iPhones and iPads are not capable of running the whole Tavern app, but since it's just a web interface, you can run it on another computer on your home wifi, and then access in your mobile browser. Refer to https://github.com/Cohee1207/SillyTavern#remote-connections
For Android users, in addition to the above, you can run the whole Tavern directly on your phone, without needing a PC, using the Termux app. Refer to https://rentry.org/TAI_Termux .
For Android users, in addition to the above, you can run the whole Tavern directly on your phone, without needing a PC, using the Termux app. Refer to https://rentry.org/STAI-Termux .
## Q: How can I download pre-made characters to chat with?

9
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"device-detector-js": "^3.0.3",
"exifreader": "^4.12.0",
"express": "^4.18.2",
"gpt3-tokenizer": "^1.1.5",
@@ -922,6 +923,14 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/device-detector-js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/device-detector-js/-/device-detector-js-3.0.3.tgz",
"integrity": "sha512-jM89LJAvP6uOd84at8OlD9dWP8KeYCCHUde0RT0HQo/stdoRH4b54Xl/fntx2nEXCmqiFhmo+/cJetS2VGUHPw==",
"engines": {
"node": ">= 8.11.4"
}
},
"node_modules/dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",

View File

@@ -6,6 +6,7 @@
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"device-detector-js": "^3.0.3",
"exifreader": "^4.12.0",
"express": "^4.18.2",
"gpt3-tokenizer": "^1.1.5",

View File

@@ -112,9 +112,13 @@
<div id="openai_api-presets">
<div>
<h4>OpenAI Presets</h4>
<select id="settings_perset_openai">
<option value="gui">Default</option>
</select>
<div class="openai_preset_buttons">
<select id="settings_perset_openai">
<option value="gui">Default</option>
</select>
<i id="update_preset" class="menu_button fa-solid fa-save" title="Update current preset"></i>
<i id="new_preset" class="menu_button fa-solid fa-plus" title="Create new preset"></i>
</div>
</div>
<div>
<h4>OpenAI Model</h4>
@@ -125,10 +129,6 @@
<option value="gpt-4-32k">gpt-4-32k</option>
</select>
</div>
<div class="range-block openai_preset_buttons">
<input id="update_preset" class="menu_button" type="button" value="Update current preset">
<input id="new_preset" class="menu_button" type="button" value="Create new preset">
</div>
</div>
<div id="textgenerationwebui_api-presets">
<h3>Text Gen WebUI (ooba) presets</h3>
@@ -1346,6 +1346,16 @@
<input id="auto_scroll_chat_to_bottom" type="checkbox" />
Auto-scroll Chat
</label>
<div class="flex-container flexFlowColumn">
<h4>
Send on Enter
</h4>
<select id="send_on_enter">
<option value="-1">Always disabled</option>
<option value="0">Automatic (desktop)</option>
<option value="1">Always enabled</option>
</select>
</div>
</div>
</div>

View File

@@ -161,7 +161,6 @@ export {
name2,
is_send_press,
api_server_textgenerationwebui,
count_view_mes,
max_context,
chat_metadata,
streamingProcessor,
@@ -1224,14 +1223,7 @@ function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
function baseChatReplace(value, name1, name2) {
if (value !== undefined && value.length > 0) {
if (is_pygmalion) {
value = value.replace(/{{user}}:/gi, "You:");
value = value.replace(/<USER>:/gi, "You:");
}
value = value.replace(/{{user}}/gi, name1);
value = value.replace(/{{char}}/gi, name2);
value = value.replace(/<USER>/gi, name1);
value = value.replace(/<BOT>/gi, name2);
value = substituteParams(value, is_pygmalion ? "You:" : name1, name2);
if (power_user.collapse_newlines) {
value = collapseNewlines(value);
@@ -1468,7 +1460,6 @@ async function Generate(type, automatic_trigger, force_name2) {
else if (type !== "swipe" && !isImpersonate) {
chat.length = chat.length - 1;
count_view_mes -= 1;
openai_msgs.pop();
$('#chat').children().last().hide(500, function () {
$(this).remove();
});
@@ -1495,6 +1486,7 @@ async function Generate(type, automatic_trigger, force_name2) {
// Compute anchors
const topAnchorDepth = 8;
const bottomAnchorThreshold = 8;
let anchorTop = '';
let anchorBottom = '';
if (!is_pygmalion) {
@@ -1554,14 +1546,20 @@ async function Generate(type, automatic_trigger, force_name2) {
is_pygmalion ? '<START>' : `This is how ${name2} should talk`;
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}\n${block.trim()}\n`);
// First message in fresh 1-on-1 chat reacts to user/character settings changes
if (chat.length) {
chat[0].mes = substituteParams(chat[0].mes);
}
// Collect messages with usable content
let coreChat = chat.filter(x => !x.is_system);
if (type === 'swipe') {
coreChat.pop();
}
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
if (main_api === 'openai') {
const oai_chat = [...chat].filter(x => !x.is_system);
if (type == 'swipe') {
oai_chat.pop();
}
setOpenAIMessages(oai_chat);
setOpenAIMessages(coreChat);
setOpenAIMessageExamples(mesExamplesArray);
}
@@ -1574,7 +1572,7 @@ async function Generate(type, automatic_trigger, force_name2) {
} else {
storyString += appendToStoryString(charDescription, '');
if (count_view_mes < topAnchorDepth) {
if (coreChat.length < topAnchorDepth) {
storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : name2 + "'s personality: ");
}
@@ -1592,31 +1590,19 @@ async function Generate(type, automatic_trigger, force_name2) {
//////////////////////////////////
console.log('emptying chat2');
let chat2 = [];
console.log('pre-replace chat.length = ' + chat.length);
for (let i = chat.length - 1, j = 0; i >= 0; i--, j++) {
let charName = selected_group ? chat[j].name : name2;
if (j == 0) {
chat[j]['mes'] = chat[j]['mes'].replace(/{{user}}/gi, name1);
chat[j]['mes'] = chat[j]['mes'].replace(/{{char}}/gi, charName);
chat[j]['mes'] = chat[j]['mes'].replace(/<USER>/gi, name1);
chat[j]['mes'] = chat[j]['mes'].replace(/<BOT>/gi, charName);
}
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
let charName = selected_group ? coreChat[j].name : name2;
let this_mes_ch_name = '';
if (chat[j]['is_user']) {
if (coreChat[j]['is_user']) {
this_mes_ch_name = name1;
} else {
this_mes_ch_name = charName;
}
if (chat[j]['is_name']) {
chat2[i] = this_mes_ch_name + ': ' + chat[j]['mes'] + '\n';
if (coreChat[j]['is_name']) {
chat2[i] = this_mes_ch_name + ': ' + coreChat[j]['mes'] + '\n';
} else {
chat2[i] = chat[j]['mes'] + '\n';
}
// system messages produce no text
if (chat[j]['is_system']) {
chat2[i] = '';
chat2[i] = coreChat[j]['mes'] + '\n';
}
// replace bias markup
@@ -1624,7 +1610,6 @@ async function Generate(type, automatic_trigger, force_name2) {
chat2[i] = (chat2[i] ?? '').replace(/{{(\*?.+?\*?)}}/g, '');
//console.log('replacing chat2 {}s');
}
//console.log('post replace chat.length = ' + chat.length);
//chat2 = chat2.reverse();
// Determine token limit
@@ -1669,17 +1654,8 @@ async function Generate(type, automatic_trigger, force_name2) {
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
const zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
/////////////////////// swipecode
if (type == 'swipe') {
//console.log('pre swipe shift: ' + chat2.length);
//console.log('shifting swipe chat2');
chat2.shift();
}
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
console.log('post swipe shift:' + chat2.length);
// hack for regeneration of the first message
if (chat2.length == 0) {
chat2.push('');
@@ -1745,39 +1721,37 @@ async function Generate(type, automatic_trigger, force_name2) {
generatedPromtCache += cycleGenerationPromt;
if (generatedPromtCache.length == 0) {
if (main_api === 'openai') {
generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, anchorBottom);
generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, bottomAnchorThreshold, anchorBottom);
}
console.log('generating prompt');
chatString = "";
arrMes = arrMes.reverse();
let is_add_personality = false;
arrMes.forEach(function (item, i, arr) {//For added anchors and others
if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {
if (i === arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {
if (textareaText == "") {
item = item.substr(0, item.length - 1);
}
}
if (i === arrMes.length - topAnchorDepth && count_view_mes >= topAnchorDepth && !is_add_personality) {
is_add_personality = true;
if (i === arrMes.length - topAnchorDepth && !is_pygmalion) {
//chatString = chatString.substr(0,chatString.length-1);
//anchorAndPersonality = "[Genre: roleplay chat][Tone: very long messages with descriptions]";
let personalityAndAnchor = [charPersonality, anchorTop].filter(x => x).join(' ');
if (personalityAndAnchor && !is_pygmalion) {
if (personalityAndAnchor) {
item += "[" + personalityAndAnchor + ']\n';
}
}
if (i >= arrMes.length - 1 && count_view_mes > 8 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":" && !is_pygmalion) {//For add anchor in end
if (i === arrMes.length - 1 && coreChat.length > bottomAnchorThreshold && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":" && !is_pygmalion) {//For add anchor in end
item = item.substr(0, item.length - 1);
//chatString+=postAnchor+"\n";//"[Writing style: very long messages]\n";
item = item + anchorBottom + "\n";
}
if (is_pygmalion) {
if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":") {//for add name2 when user sent
if (i === arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":") {//for add name2 when user sent
item = item + name2 + ":";
}
if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {//for add name2 when continue
if (i === arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {//for add name2 when continue
if (textareaText == "") {
item = item + '\n' + name2 + ":";
}

View File

@@ -17,6 +17,7 @@ import {
import {
power_user,
send_on_enter_options,
} from "./power-user.js";
import { LoadLocal, SaveLocal, ClearLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
@@ -49,10 +50,6 @@ var connection_made = false;
var retry_delay = 100;
var RA_AC_retries = 1;
let isVirtualKB = false;
let lastKeyDownTime = 0;
let lastKeyUpTime = 0;
const observerConfig = { childList: true, subtree: true };
const observer = new MutationObserver(function (mutations) {
@@ -104,6 +101,36 @@ waitForElement("#expression-image", 10000).then(function () {
console.log("expression holder not loaded yet");
});
// Device detection
const deviceInfo = await getDeviceInfo();
async function getDeviceInfo() {
try {
const deviceInfo = await (await fetch('/deviceinfo')).json();
console.log("Device type: " + deviceInfo?.device?.type);
return deviceInfo;
}
catch {
console.log("Couldn't load device info. Defaulting to desktop");
return { device: { type: 'desktop' } };
}
}
function isMobile() {
const mobileTypes = ['smartphone', 'tablet', 'phablet', 'feature phone', 'portable media player'];
return mobileTypes.includes(deviceInfo?.device?.type);
}
function shouldSendOnEnter() {
switch (power_user.send_on_enter) {
case send_on_enter_options.DISABLED:
return false;
case send_on_enter_options.AUTO:
return !isMobile();
case send_on_enter_options.ENABLED:
return true;
}
}
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
@@ -675,33 +702,17 @@ $("document").ready(function () {
return false;
}
document.addEventListener("keydown", () => {
lastKeyDownTime = new Date().getTime();
})
document.addEventListener('keyup', (event) => {
lastKeyUpTime = new Date().getTime();
isVirtualKeyboard(event);
})
function isVirtualKeyboard(event) {
var keyTiming = lastKeyUpTime - lastKeyDownTime; // array to store times
if (keyTiming <= 40) {
console.log(`detected VKB (${keyTiming}ms)`);
return;
}
if (keyTiming > 40) {
console.log(`detected PhysKB (${keyTiming}ms)`);
processHotkeys(event);
}
}
$(document).on('keydown', function (event) {
processHotkeys(event);
});
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
function processHotkeys(event) {
const sendOnEnter = shouldSendOnEnter();
//Enter to send when send_textarea in focus
if ($(':focus').attr('id') === 'send_textarea') {
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter" && is_send_press == false) {
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter" && is_send_press == false && sendOnEnter) {
event.preventDefault();
Generate();
}
@@ -742,7 +753,6 @@ $("document").ready(function () {
}
}
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
console.log('got ctrl+uparrow input');
if (
@@ -777,7 +787,5 @@ $("document").ready(function () {
}
}
}
}
});

View File

@@ -7,7 +7,6 @@
import {
saveSettingsDebounced,
substituteParams,
count_view_mes,
checkOnlineStatus,
setOnlineStatus,
getExtensionPrompt,
@@ -148,10 +147,6 @@ function setOpenAIMessages(chat) {
// clean openai msgs
openai_msgs = [];
for (let i = chat.length - 1; i >= 0; i--) {
// first greeting message
if (j == 0) {
chat[j]['mes'] = substituteParams(chat[j]['mes']);
}
let role = chat[j]['is_user'] ? 'user' : 'assistant';
let content = chat[j]['mes'];
@@ -160,12 +155,6 @@ function setOpenAIMessages(chat) {
content = `${chat[j].name}: ${content}`;
}
// system messages produce no content
if (chat[j]['is_system']) {
role = 'system';
content = '';
}
// replace bias markup
//content = (content ?? '').replace(/{.*}/g, '');
content = (content ?? '').replace(/{{(\*?.+?\*?)}}/g, '');
@@ -199,19 +188,17 @@ function setOpenAIMessageExamples(mesExamplesArray) {
}
}
function generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, anchorBottom) {
function generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, bottomAnchorThreshold, anchorBottom) {
openai_msgs = openai_msgs.reverse();
let is_add_personality = false;
openai_msgs.forEach(function (msg, i, arr) {//For added anchors and others
let item = msg["content"];
if (i === openai_msgs.length - topAnchorDepth && count_view_mes >= topAnchorDepth && !is_add_personality) {
is_add_personality = true;
if ((anchorTop != "" || charPersonality != "")) {
if (anchorTop != "") charPersonality += ' ';
item = `[${name2} is ${charPersonality}${anchorTop}]\n${item}`;
if (i === openai_msgs.length - topAnchorDepth) {
let personalityAndAnchor = [charPersonality, anchorTop].filter(x => x).join(' ');
if (personalityAndAnchor) {
item = `[${name2} is ${personalityAndAnchor}]\n${item}`;
}
}
if (i >= openai_msgs.length - 1 && count_view_mes > 8 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":") {//For add anchor in end
if (i === openai_msgs.length - 1 && openai_msgs.length > bottomAnchorThreshold && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":") {//For add anchor in end
item = anchorBottom + "\n" + item;
}

View File

@@ -16,6 +16,7 @@ export {
power_user,
pygmalion_options,
tokenizers,
send_on_enter_options,
};
const avatar_styles = {
@@ -46,6 +47,12 @@ const tokenizers = {
LLAMA: 3,
}
const send_on_enter_options = {
DISABLED: -1,
AUTO: 0,
ENABLED: 1,
}
let power_user = {
tokenizer: tokenizers.CLASSIC,
collapse_newlines: false,
@@ -89,6 +96,7 @@ let power_user = {
auto_scroll_chat_to_bottom: true,
auto_fix_generated_markdown: true,
send_on_enter: send_on_enter_options.AUTO,
};
let themes = [];
@@ -145,7 +153,8 @@ function fixMarkdown(text) {
// "^example *text *\n" -> "^example *text*\n"
// "^* example * text\n" -> "^*example* text\n"
// take note that the side you move the asterisk depends on where its pairing is
// i.e. both of the following strings have the same broken asterisk ' * ', but you move the first to the left and the second to the right, to match the non-broken asterisk "^example * text*\n" "^*example * text\n"
// i.e. both of the following strings have the same broken asterisk ' * ',
// but you move the first to the left and the second to the right, to match the non-broken asterisk "^example * text*\n" "^*example * text\n"
// and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
@@ -340,6 +349,7 @@ function loadPowerUserSettings(settings, data) {
$('#auto_scroll_chat_to_bottom').prop("checked", power_user.auto_scroll_chat_to_bottom);
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
$(`#pygmalion_formatting option[value=${power_user.pygmalion_formatting}]`).attr("selected", true);
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true);
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
$("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting);
@@ -415,6 +425,79 @@ function sortCharactersList(selector = '.character_select') {
}
}
async function saveTheme() {
const name = await callPopup('Enter a theme preset name:', 'input');
if (!name) {
return;
}
const theme = {
name,
blur_strength: power_user.blur_strength,
main_text_color: power_user.main_text_color,
italics_text_color: power_user.italics_text_color,
quote_text_color: power_user.quote_text_color,
fastui_bg_color: power_user.fastui_bg_color,
blur_tint_color: power_user.blur_tint_color,
shadow_color: power_user.shadow_color,
shadow_width: power_user.shadow_width,
};
const response = await fetch('/savetheme', {
method: 'POST', headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json',
},
body: JSON.stringify(theme)
});
if (response.ok) {
const themeIndex = themes.findIndex(x => x.name == name);
if (themeIndex == -1) {
themes.push(theme);
const option = document.createElement('option');
option.selected = true;
option.value = name;
option.innerText = name;
$('#themes').append(option);
}
else {
themes[themeIndex] = theme;
$(`#themes option[value="${name}"]`).attr('selected', true);
}
power_user.theme = name;
saveSettingsDebounced();
}
}
function resetMovablePanels() {
document.getElementById("sheld").style.top = '';
document.getElementById("sheld").style.left = '';
document.getElementById("sheld").style.height = '';
document.getElementById("sheld").style.width = '';
document.getElementById("left-nav-panel").style.top = '';
document.getElementById("left-nav-panel").style.left = '';
document.getElementById("left-nav-panel").style.height = '';
document.getElementById("left-nav-panel").style.width = '';
document.getElementById("right-nav-panel").style.top = '';
document.getElementById("right-nav-panel").style.left = '';
document.getElementById("right-nav-panel").style.right = '';
document.getElementById("right-nav-panel").style.height = '';
document.getElementById("right-nav-panel").style.width = '';
document.getElementById("expression-holder").style.top = '';
document.getElementById("expression-holder").style.left = '';
document.getElementById("expression-holder").style.right = '';
document.getElementById("expression-holder").style.bottom = '';
document.getElementById("expression-holder").style.height = '';
document.getElementById("expression-holder").style.width = '';
}
$(document).ready(() => {
// Settings that go to settings.json
$("#collapse-newlines-checkbox").change(function () {
@@ -498,32 +581,7 @@ $(document).ready(() => {
noShadows();
});
$("#movingUIreset").on('click', function () {
document.getElementById("sheld").style.top = '';
document.getElementById("sheld").style.left = '';
document.getElementById("sheld").style.height = '';
document.getElementById("sheld").style.width = '';
document.getElementById("left-nav-panel").style.top = '';
document.getElementById("left-nav-panel").style.left = '';
document.getElementById("left-nav-panel").style.height = '';
document.getElementById("left-nav-panel").style.width = '';
document.getElementById("right-nav-panel").style.top = '';
document.getElementById("right-nav-panel").style.left = '';
document.getElementById("right-nav-panel").style.right = '';
document.getElementById("right-nav-panel").style.height = '';
document.getElementById("right-nav-panel").style.width = '';
document.getElementById("expression-holder").style.top = '';
document.getElementById("expression-holder").style.left = '';
document.getElementById("expression-holder").style.right = '';
document.getElementById("expression-holder").style.bottom = '';
document.getElementById("expression-holder").style.height = '';
document.getElementById("expression-holder").style.width = '';
})
$("#movingUIreset").on('click', resetMovablePanels);
$(`input[name="avatar_style"]`).on('input', function (e) {
power_user.avatar_style = Number(e.target.value);
@@ -608,53 +666,7 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#ui-preset-save-button").on('click', async function () {
const name = await callPopup('Enter a theme preset name:', 'input');
if (!name) {
return;
}
const theme = {
name,
blur_strength: power_user.blur_strength,
main_text_color: power_user.main_text_color,
italics_text_color: power_user.italics_text_color,
quote_text_color: power_user.quote_text_color,
fastui_bg_color: power_user.fastui_bg_color,
blur_tint_color: power_user.blur_tint_color,
shadow_color: power_user.shadow_color,
shadow_width: power_user.shadow_width,
};
const response = await fetch('/savetheme', {
method: 'POST', headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json',
},
body: JSON.stringify(theme)
});
if (response.ok) {
const themeIndex = themes.findIndex(x => x.name == name);
if (themeIndex == -1) {
themes.push(theme);
const option = document.createElement('option');
option.selected = true;
option.value = name;
option.innerText = name;
$('#themes').append(option);
}
else {
themes[themeIndex] = theme;
$(`#themes option[value="${name}"]`).attr('selected', true);
}
power_user.theme = name;
saveSettingsDebounced();
}
});
$("#ui-preset-save-button").on('click', saveTheme);
$("#play_message_sound").on('input', function () {
power_user.play_message_sound = !!$(this).prop('checked');
@@ -709,6 +721,12 @@ $(document).ready(() => {
$("#character_popup").trigger('input');
});
$("#send_on_enter").on('change', function () {
const value = $(this).find(':selected').val();
power_user.send_on_enter = Number(value);
saveSettingsDebounced();
});
$(window).on('focus', function () {
browser_has_focus = true;
});

View File

@@ -3409,17 +3409,6 @@ toolcool-color-picker {
width: 100%;
}
#openai_api-presets {
display: flex;
flex-direction: row;
column-gap: 10px;
flex-wrap: wrap;
}
#openai_api-presets>div {
flex: 1;
}
#openai_api-presets select {
width: 100%;
}
@@ -3467,7 +3456,8 @@ toolcool-color-picker {
flex: 1;
}
.openai_logit_bias_preset_form .menu_button {
.openai_logit_bias_preset_form .menu_button,
.openai_preset_buttons .menu_button {
padding: 7px;
}
@@ -3495,12 +3485,19 @@ toolcool-color-picker {
height: 20px;
}
#openai_api-presets {
flex-direction: column;
}
.openai_preset_buttons {
display: flex;
justify-content: space-evenly;
flex-direction: row;
flex-wrap: wrap;
flex-basis: 100% !important;
align-items: baseline;
gap: 5px;
}
.openai_preset_buttons select {
flex-grow: 1;
}
.reverse_proxy_warning,

View File

@@ -27,10 +27,25 @@ https://colab.research.google.com/github/Cohee1207/SillyTavern/blob/main/colab/G
> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**
https://rentry.org/TAI_Termux
https://rentry.org/STAI-Termux
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
## Questions or suggestions?
### We now have a community Discord server!
Get support, share favorite characters and prompts:
### [Join](https://discord.gg/RZdyAEUPvj)
***
Get in touch with the developers directly:
* Discord: Cohee#1207 or RossAscends#1779
* Reddit: /u/RossAscends or /u/sillylossy
* [Post a GitHub issue](https://github.com/Cohee1207/SillyTavern/issues)
## This version includes
* A heavily modified TavernAI 1.2.8 (more than 50% of code rewritten or optimized)
* Swipes
@@ -100,7 +115,7 @@ https://rentry.org/TAI_Termux
*NOTE: This branch is intended for local install purposes, and has not been thoroughly tested on a colab or other cloud notebook service.*
### Windows
1. install [NodeJS](https://nodejs.org/en)
1. install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
2. download the zip from this GitHub repo
3. unzip it into a folder of your choice
4. run start.bat via double-clicking or in a command line.
@@ -166,11 +181,6 @@ Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
2. Send bug reports without providing any context
3. Ask the questions that were already answered numerous times
## Questions or suggestions?
Contact us on:
* Discord: Cohee#1207 or RossAscends#1779
* Reddit: /u/RossAscends or /u/sillylossy
## Screenshots
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">

View File

@@ -54,6 +54,7 @@ const json5 = require('json5');
const ExifReader = require('exifreader');
const exif = require('piexifjs');
const webp = require('webp-converter');
const DeviceDetector = require("device-detector-js");
const config = require(path.join(__dirname, './config.conf'));
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
@@ -185,8 +186,6 @@ const { invalidCsrfTokenError, generateToken, doubleCsrfProtection } = doubleCsr
getTokenFromRequest: (req) => req.headers["x-csrf-token"]
});
app.get("/csrf-token", (req, res) => {
res.json({
"token": generateToken(res)
@@ -283,6 +282,12 @@ app.get('/get_faq', function (_, response) {
app.get('/get_readme', function (_, response) {
response.sendFile(__dirname + "/readme.md");
});
app.get('/deviceinfo', function(request, response) {
const userAgent = request.header('user-agent');
const deviceDetector = new DeviceDetector();
const deviceInfo = deviceDetector.parse(userAgent);
return response.send(deviceInfo);
});
//**************Kobold api
app.post("/generate", jsonParser, async function (request, response_generate = response) {
@@ -793,11 +798,13 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
fs.writeFileSync(charactersPath + target_img + '.png', new Buffer.from(encode(chunks)));
if (response !== undefined) response.send(mes);
return true;
} catch (err) {
console.log(err);
if (response !== undefined) response.send(err);
if (response !== undefined) response.status(500).send(err);
return false;
}
}
@@ -1843,7 +1850,8 @@ app.post('/status_poe', jsonParser, async (request, response) => {
return response.send({ 'bot_names': botNames });
}
catch {
catch (err) {
console.error(err);
return response.sendStatus(401);
}
});
@@ -1864,7 +1872,8 @@ app.post('/purge_poe', jsonParser, async (request, response) => {
return response.send({ "ok": true });
}
catch {
catch (err) {
console.error(err);
return response.sendStatus(500);
}
});
@@ -2451,7 +2460,12 @@ async function convertWebp() {
await webp.dwebp(source, dest, "-o");
console.log(`Write... ${dest}`);
await charaWrite(dest, data, path.parse(dest).name);
const success = await charaWrite(dest, data, path.parse(dest).name);
if (!success) {
console.log(`Failure on ${source} -> ${dest}`);
continue;
}
console.log(`Remove... ${source}`);
fs.rmSync(source);

View File

@@ -71,11 +71,13 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
fs.writeFileSync(target_img, new Buffer.from(encode(chunks)));
if (response !== undefined) response.send(mes);
return true;
} catch (err) {
console.log(err);
if (response !== undefined) response.send(err);
if (response !== undefined) response.status(500).send(err);
return false;
}
}
@@ -102,7 +104,12 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
await webp.dwebp(source, dest, "-o")
console.log(`Write... ${dest}`)
await charaWrite(dest, data, dest)
const success = await charaWrite(dest, data, path.parse(dest).name);
if (!success) {
console.log(`Failure on ${source} -> ${dest}`);
continue;
}
console.log(`Remove... ${source}`)
fs.rmSync(source)