mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev
This commit is contained in:
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js, conf, json}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
@@ -98,7 +98,7 @@
|
||||
"!git clone https://github.com/Cohee1207/tts_samples\n",
|
||||
"!npm install -g localtunnel\n",
|
||||
"!pip install -r requirements-complete.txt\n",
|
||||
"!pip install tensorflow==2.11\n",
|
||||
"!pip install tensorflow==2.12\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"cmd = f\"python server.py {' '.join(params)}\"\n",
|
||||
|
@@ -10,6 +10,11 @@ const enableExtensions = true; //Enables support for TavernAI-extras project
|
||||
const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine.
|
||||
const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend.
|
||||
|
||||
|
||||
// If true, Allows insecure settings for listen, whitelist, and authentication.
|
||||
// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting.
|
||||
const securityOverride = false;
|
||||
|
||||
module.exports = {
|
||||
port,
|
||||
whitelist,
|
||||
@@ -21,4 +26,5 @@ module.exports = {
|
||||
listen,
|
||||
disableThumbnails,
|
||||
allowKeysExposure,
|
||||
securityOverride,
|
||||
};
|
||||
|
@@ -1,12 +1,13 @@
|
||||
version: "3"
|
||||
services:
|
||||
tavernai:
|
||||
sillytavern:
|
||||
build: ..
|
||||
container_name: tavernai
|
||||
hostname: tavernai
|
||||
image: tavernai/tavernai:latest
|
||||
container_name: sillytavern
|
||||
hostname: sillytavern
|
||||
image: cohee1207/sillytavern:latest
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "./config:/home/node/app/config"
|
||||
restart: unless-stopped
|
||||
- "./config.conf:/home/node/app/config.conf"
|
||||
restart: unless-stopped
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
|
@@ -42,7 +42,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
||||
},
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.0",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip ."
|
||||
|
BIN
public/backgrounds/_black.jpg
Normal file
BIN
public/backgrounds/_black.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
public/backgrounds/_white.jpg
Normal file
BIN
public/backgrounds/_white.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
@@ -370,15 +370,6 @@
|
||||
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<label for="oai_breakdown" class="checkbox_label widthFreeExpand">
|
||||
<input id="oai_breakdown" type="checkbox" />
|
||||
Token Breakdown
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
Display a breakdown of the tokens used in the request.
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title">
|
||||
Context Size (tokens)
|
||||
@@ -2557,11 +2548,6 @@
|
||||
<div id="chat">
|
||||
</div>
|
||||
<div id="form_sheld">
|
||||
<div id="token_breakdown" style="display:none;">
|
||||
<div>
|
||||
<!-- Token Breakdown Goes Here -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialogue_del_mes">
|
||||
<div id="dialogue_del_mes_ok" class="menu_button">Delete</div>
|
||||
<div id="dialogue_del_mes_cancel" class="menu_button">Cancel</div>
|
||||
|
197
public/script.js
197
public/script.js
@@ -49,6 +49,7 @@ import {
|
||||
editGroup,
|
||||
deleteGroupChat,
|
||||
renameGroupChat,
|
||||
importGroupChat,
|
||||
} from "./scripts/group-chats.js";
|
||||
|
||||
import {
|
||||
@@ -291,11 +292,22 @@ const system_messages = {
|
||||
is_system: true,
|
||||
is_name: true,
|
||||
mes: [
|
||||
'Hi there! The following chat formatting commands are supported:',
|
||||
'<ol>',
|
||||
'<li><tt>{{text}}</tt> – sets a one-time behavioral bias for the AI. Resets when you send the next message.</li>',
|
||||
'</ol>',
|
||||
].join('')
|
||||
`Hi there! The following chat formatting commands are supported:
|
||||
<ul>
|
||||
<li><tt>{{text}}</tt> - sets a one-time behavioral bias for the AI. Resets when you send the next message.
|
||||
</li>
|
||||
</ul>
|
||||
Hotkeys/Keybinds:
|
||||
<ul>
|
||||
<li><tt>Up</tt> = Edit last message in chat</li>
|
||||
<li><tt>Ctrl+Up</tt> = Edit last USER message in chat</li>
|
||||
<li><tt>Left</tt> = swipe left</li>
|
||||
<li><tt>Right</tt> = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</li>
|
||||
<li><tt>Ctrl+Left</tt> = view locally stored variables (in the browser console window)</li>
|
||||
<li><tt>Enter</tt> (with chat bar selected) = send your message to AI</li>
|
||||
<li><tt>Ctrl+Enter</tt> = Regenerate the last AI response</li>
|
||||
</ul>`
|
||||
]
|
||||
},
|
||||
welcome:
|
||||
{
|
||||
@@ -304,42 +316,42 @@ const system_messages = {
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
mes: [
|
||||
'<h2>Welcome to <span id="version_display_welcome">SillyTavern</span>!</h2>',
|
||||
'<div id="version_display_welcome"></div>',
|
||||
'<h3>Want to Update to the latest version?</h3>',
|
||||
"Read the <a href='/notes/update.html' target='_blank'>instructions here</a>. Also located in your installation's base folder",
|
||||
'<hr class="sysHR">',
|
||||
'<h3>In order to begin chatting:</h3>',
|
||||
'<ol>',
|
||||
'<li>Connect to one of the supported generation APIs (the plug icon)</li>',
|
||||
'<li>Create or pick a character from the list (the top-right namecard icon)</li>',
|
||||
'</ol>',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Where to download more characters?</h3>',
|
||||
'<i>(Not endorsed, your discretion is advised)</i>',
|
||||
'<ol>',
|
||||
'<li><a target="_blank" href="https://discord.gg/pygmalionai">Pygmalion AI Discord</a></li>',
|
||||
'<li><a target="_blank" href="https://www.characterhub.org/">CharacterHub (NSFW)</a></li>',
|
||||
'</ol>',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Where can I get help?</h3>',
|
||||
'Before going any further, check out the following resources:',
|
||||
'<ol>',
|
||||
'<li><a target="_blank" href="/notes/readme.md">Introduction to SillyTavern</a></li>',
|
||||
'<li><a target="_blank" href="/notes/faq.md">SillyTavern FAQ</a></li>',
|
||||
'<li><a target="_blank" href="/notes">SillyTavern Guidebook</a></li>',
|
||||
'<li><a target="_blank" href="https://github.com/Cohee1207/TavernAI-extras/blob/main/README.md">Extras API Docs</a></li>',
|
||||
'<li><a target="_blank" href="https://docs.alpindale.dev/">Pygmalion AI Docs</a></li>',
|
||||
'</ol>',
|
||||
'Type <tt>/?</tt> in any chat to get help on message formatting commands.',
|
||||
'<hr class="sysHR">',
|
||||
'<h3>Still have questions or suggestions left?</h3>',
|
||||
'<a target="_blank" href="https://discord.gg/RZdyAEUPvj">SillyTavern Community Discord</a>',
|
||||
'<br/>',
|
||||
'<a target="_blank" href="https://github.com/Cohee1207/SillyTavern/issues">Post a GitHub issue.</a>',
|
||||
'<br/>',
|
||||
'<a target="_blank" href="https://github.com/Cohee1207/SillyTavern#questions-or-suggestions">Contact the developers.</a>',
|
||||
].join('')
|
||||
`<h2>Welcome to <span id="version_display_welcome">SillyTavern</span>!</h2>
|
||||
<div id="version_display_welcome"></div>
|
||||
<h3>Want to Update to the latest version?</h3>
|
||||
Read the <a href='/notes/update.html' target='_blank'>instructions here</a>. Also located in your installation's base folder
|
||||
<hr class="sysHR">
|
||||
<h3>In order to begin chatting:</h3>
|
||||
<ol>
|
||||
<li>Connect to one of the supported generation APIs (the plug icon)</li>
|
||||
<li>Create or pick a character from the list (the top-right namecard icon)</li>
|
||||
</ol>
|
||||
<hr class="sysHR">
|
||||
<h3>Where to download more characters?</h3>
|
||||
<i>(Not endorsed, your discretion is advised)</i>
|
||||
<ol>
|
||||
<li><a target="_blank" href="https://discord.gg/pygmalionai">Pygmalion AI Discord</a></li>
|
||||
<li><a target="_blank" href="https://www.characterhub.org/">CharacterHub (NSFW)</a></li>
|
||||
</ol>
|
||||
<hr class="sysHR">
|
||||
<h3>Where can I get help?</h3>
|
||||
Before going any further, check out the following resources:
|
||||
<ol>
|
||||
<li><a target="_blank" href="/notes/readme.md">Introduction to SillyTavern</a></li>
|
||||
<li><a target="_blank" href="/notes/faq.md">SillyTavern FAQ</a></li>
|
||||
<li><a target="_blank" href="/notes">SillyTavern Guidebook</a></li>
|
||||
<li><a target="_blank" href="https://github.com/Cohee1207/TavernAI-extras/blob/main/README.md">Extras API Docs</a></li>
|
||||
<li><a target="_blank" href="https://docs.alpindale.dev/">Pygmalion AI Docs</a></li>
|
||||
</ol>
|
||||
Type <tt>/?</tt> in any chat to get help on message formatting commands.
|
||||
<hr class="sysHR">
|
||||
<h3>Still have questions or suggestions left?</h3>
|
||||
<a target="_blank" href="https://discord.gg/RZdyAEUPvj">SillyTavern Community Discord</a>
|
||||
<br>
|
||||
<a target="_blank" href="https://github.com/Cohee1207/SillyTavern/issues">Post a GitHub issue.</a>
|
||||
<br>
|
||||
<a target="_blank" href="https://github.com/Cohee1207/SillyTavern#questions-or-suggestions">Contact the developers.</a>
|
||||
`].join('')
|
||||
},
|
||||
group: {
|
||||
name: systemUserName,
|
||||
@@ -408,7 +420,7 @@ async function getClientVersion() {
|
||||
let displayVersion = `SillyTavern ${data.pkgVersion}`;
|
||||
|
||||
if (data.gitRevision && data.gitBranch) {
|
||||
displayVersion += ` '${data.gitBranch}' (${data.gitRevision})`;
|
||||
displayVersion += ` '${data.gitBranch}'(${data.gitRevision})`;
|
||||
}
|
||||
|
||||
$('#version_display').text(displayVersion);
|
||||
@@ -447,7 +459,7 @@ function getTokenCount(str, padding = undefined) {
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: `/tokenize_llama`,
|
||||
url: `/ tokenize_llama`,
|
||||
data: JSON.stringify({ text: str }),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
@@ -2420,9 +2432,6 @@ function getMaxContextSize() {
|
||||
}
|
||||
|
||||
function parseTokenCounts(counts, thisPromptBits) {
|
||||
const breakdown_bar = $('#token_breakdown div:first-child');
|
||||
breakdown_bar.empty();
|
||||
|
||||
const total = Object.values(counts).filter(x => !Number.isNaN(x)).reduce((acc, val) => acc + val, 0);
|
||||
|
||||
thisPromptBits.push({
|
||||
@@ -2436,22 +2445,6 @@ function parseTokenCounts(counts, thisPromptBits) {
|
||||
oaiConversationTokens: Object.entries(counts)[7][1],
|
||||
oaiTotalTokens: total,
|
||||
});
|
||||
|
||||
Object.entries(counts).forEach(([type, value]) => {
|
||||
if (value === 0) {
|
||||
return;
|
||||
}
|
||||
const percent_value = (value / total) * 100;
|
||||
const color = uniqolor(type, { saturation: 50, lightness: 75, }).color;
|
||||
const bar = document.createElement('div');
|
||||
bar.style.width = `${percent_value}%`;
|
||||
bar.classList.add('token_breakdown_segment');
|
||||
bar.style.backgroundColor = color + 'AA';
|
||||
bar.style.borderColor = color + 'FF';
|
||||
bar.innerText = value;
|
||||
bar.title = `${type}: ${percent_value.toFixed(2)}%`;
|
||||
breakdown_bar.append(bar);
|
||||
});
|
||||
}
|
||||
|
||||
function adjustChatsSeparator(mesSendString) {
|
||||
@@ -3398,6 +3391,7 @@ async function read_avatar_load(input) {
|
||||
await delay(durationSaveEdit);
|
||||
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'pragma': 'no-cache',
|
||||
'cache-control': 'no-cache',
|
||||
@@ -3569,10 +3563,8 @@ function changeMainAPI() {
|
||||
// Hide common settings for OpenAI
|
||||
if (selectedVal == "openai") {
|
||||
$("#common-gen-settings-block").css("display", "none");
|
||||
//$("#token_breakdown").css("display", "flex");
|
||||
} else {
|
||||
$("#common-gen-settings-block").css("display", "block");
|
||||
//$("#token_breakdown").css("display", "none");
|
||||
}
|
||||
// Hide amount gen for poe
|
||||
if (selectedVal == "poe") {
|
||||
@@ -3962,7 +3954,7 @@ async function getPastCharacterChats() {
|
||||
return data;
|
||||
}
|
||||
|
||||
async function displayPastChats() {
|
||||
export async function displayPastChats() {
|
||||
$("#select_chat_div").empty();
|
||||
|
||||
const group = selected_group ? groups.find(x => x.id === selected_group) : null;
|
||||
@@ -3999,7 +3991,7 @@ async function displayPastChats() {
|
||||
|
||||
$("#select_chat_div").append(template);
|
||||
|
||||
if (currentChat === fileName.replace(".jsonl", "")) {
|
||||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||||
}
|
||||
}
|
||||
@@ -4044,7 +4036,6 @@ async function getStatusNovel() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function selectRightMenuWithAnimation(selectedMenuId) {
|
||||
const displayModes = {
|
||||
'rm_info_block': 'flex',
|
||||
@@ -4065,13 +4056,7 @@ function selectRightMenuWithAnimation(selectedMenuId) {
|
||||
easing: animation_easing,
|
||||
complete: function () { },
|
||||
});
|
||||
|
||||
|
||||
|
||||
// $(menu).find('#groupCurrentMemberListToggle').click();
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4113,10 +4098,8 @@ function select_rm_info(type, charId) {
|
||||
setTimeout(function () {
|
||||
$(`#rm_characters_block [title="${charId + '.png'}"]`).parent().removeClass('flash animated');
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
setRightTabSelectedClass();
|
||||
|
||||
prev_selected_char = charId;
|
||||
@@ -4446,6 +4429,27 @@ export async function saveChatConditional() {
|
||||
}
|
||||
}
|
||||
|
||||
async function importCharacterChat(formData) {
|
||||
await jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importchat",
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.res) {
|
||||
await displayPastChats();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$("#create_button").removeAttr("disabled");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateViewMessageIds() {
|
||||
$('#chat').find(".mes").each(function (index, element) {
|
||||
$(element).attr("mesid", index);
|
||||
@@ -6278,12 +6282,13 @@ $(document).ready(function () {
|
||||
$("#chat_import_file").click();
|
||||
});
|
||||
|
||||
$("#chat_import_file").on("change", function (e) {
|
||||
$("#chat_import_file").on("change", async function (e) {
|
||||
var file = e.target.files[0];
|
||||
//console.log(1);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = file.name.match(/\.(\w+)$/);
|
||||
if (
|
||||
!ext ||
|
||||
@@ -6292,33 +6297,23 @@ $(document).ready(function () {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_group && file.name.endsWith('.json')) {
|
||||
toastr.warning("Only SillyTavern's own format is supported for group chat imports. Sorry!");
|
||||
return;
|
||||
}
|
||||
|
||||
var format = ext[1].toLowerCase();
|
||||
$("#chat_import_file_type").val(format);
|
||||
//console.log(format);
|
||||
|
||||
var formData = new FormData($("#form_import_chat").get(0));
|
||||
//console.log('/importchat entered with: '+formData);
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importchat",
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
$("#select_chat_div").html("");
|
||||
$("#load_select_chat_div").css("display", "block");
|
||||
//$('#create_button').attr('value','Creating...');
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (data) {
|
||||
//console.log(data);
|
||||
if (data.res) {
|
||||
displayPastChats();
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
$("#create_button").removeAttr("disabled");
|
||||
},
|
||||
});
|
||||
$("#select_chat_div").html("");
|
||||
$("#load_select_chat_div").css("display", "block");
|
||||
|
||||
if (selected_group) {
|
||||
await importGroupChat(formData);
|
||||
} else {
|
||||
await importCharacterChat(formData);
|
||||
}
|
||||
});
|
||||
|
||||
$("#rm_button_group_chats").click(function () {
|
||||
|
@@ -47,6 +47,7 @@ import {
|
||||
select_selected_character,
|
||||
cancelTtsPlay,
|
||||
isMultigenEnabled,
|
||||
displayPastChats,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
|
||||
|
||||
@@ -292,6 +293,12 @@ async function getGroups() {
|
||||
if (group.past_metadata == undefined) {
|
||||
group.past_metadata = {};
|
||||
}
|
||||
if (typeof group.chat_id === 'number') {
|
||||
group.chat_id = String(group.chat_id);
|
||||
}
|
||||
if (Array.isArray(group.chats) && group.chats.some(x => typeof x === 'number')) {
|
||||
group.chats = group.chats.map(x => String(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1282,6 +1289,34 @@ export async function deleteGroupChat(groupId, chatId) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function importGroupChat(formData) {
|
||||
await jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importgroupchat",
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.res) {
|
||||
const chatId = data.res;
|
||||
const group = groups.find(x => x.id == selected_group);
|
||||
|
||||
if (group) {
|
||||
group.chats.push(chatId);
|
||||
await editGroup(selected_group, true, true);
|
||||
await displayPastChats();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$("#create_button").removeAttr("disabled");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveGroupBookmarkChat(groupId, name, metadata) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
|
@@ -102,7 +102,6 @@ const default_settings = {
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
oai_breakdown: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -127,7 +126,6 @@ const oai_settings = {
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
oai_breakdown: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -460,7 +458,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
handler_instance.log();
|
||||
return [
|
||||
openai_msgs_tosend,
|
||||
oai_settings.oai_breakdown ? handler_instance.counts : false,
|
||||
handler_instance.counts,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -562,13 +560,19 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body.getReader();
|
||||
let getMessage = "";
|
||||
let messageBuffer = "";
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
let response = decoder.decode(value);
|
||||
|
||||
tryParseStreamingError(response);
|
||||
|
||||
let eventList = response.split("\n");
|
||||
|
||||
// ReadableStream's buffer is not guaranteed to contain full SSE messages as they arrive in chunks
|
||||
// We need to buffer chunks until we have one or more full messages (separated by double newlines)
|
||||
messageBuffer += response;
|
||||
let eventList = messageBuffer.split("\n\n");
|
||||
// Last element will be an empty string or a leftover partial message
|
||||
messageBuffer = eventList.pop();
|
||||
|
||||
for (let event of eventList) {
|
||||
if (!event.startsWith("data"))
|
||||
@@ -745,7 +749,6 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.nsfw_first !== undefined) oai_settings.nsfw_first = !!settings.nsfw_first;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.jailbreak_system !== undefined) oai_settings.jailbreak_system = !!settings.jailbreak_system;
|
||||
if (settings.oai_breakdown !== undefined) oai_settings.oai_breakdown = !!settings.oai_breakdown;
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
|
||||
@@ -761,7 +764,6 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#wrap_in_quotes').prop('checked', oai_settings.wrap_in_quotes);
|
||||
$('#nsfw_first').prop('checked', oai_settings.nsfw_first);
|
||||
$('#jailbreak_system').prop('checked', oai_settings.jailbreak_system);
|
||||
$('#oai_breakdown').prop('checked', oai_settings.oai_breakdown);
|
||||
|
||||
if (settings.main_prompt !== undefined) oai_settings.main_prompt = settings.main_prompt;
|
||||
if (settings.nsfw_prompt !== undefined) oai_settings.nsfw_prompt = settings.nsfw_prompt;
|
||||
@@ -881,7 +883,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
jailbreak_system: settings.jailbreak_system,
|
||||
impersonation_prompt: settings.impersonation_prompt,
|
||||
bias_preset_selected: settings.bias_preset_selected,
|
||||
oai_breakdown: settings.oai_breakdown,
|
||||
reverse_proxy: settings.reverse_proxy,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1140,12 +1142,12 @@ function onSettingsPresetChange() {
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
nsfw_first: ['#nsfw_first', 'nsfw_first', true],
|
||||
jailbreak_system: ['#jailbreak_system', 'jailbreak_system', true],
|
||||
oai_breakdown: ['#oai_breakdown', 'oai_breakdown', true],
|
||||
main_prompt: ['#main_prompt_textarea', 'main_prompt', false],
|
||||
nsfw_prompt: ['#nsfw_prompt_textarea', 'nsfw_prompt', false],
|
||||
jailbreak_prompt: ['#jailbreak_prompt_textarea', 'jailbreak_prompt', false],
|
||||
impersonation_prompt: ['#impersonation_prompt_textarea', 'impersonation_prompt', false],
|
||||
bias_preset_selected: ['#openai_logit_bias_preset', 'bias_preset_selected', false],
|
||||
reverse_proxy: ['#openai_reverse_proxy', 'reverse_proxy', false],
|
||||
};
|
||||
|
||||
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
|
||||
@@ -1313,16 +1315,6 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#oai_breakdown").on('change', function () {
|
||||
oai_settings.oai_breakdown = !!$(this).prop("checked");
|
||||
if (!oai_settings.oai_breakdown) {
|
||||
$("#token_breakdown").css('display', 'none');
|
||||
} else {
|
||||
$("#token_breakdown").css('display', 'flex');
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
// auto-select a preset based on character/group name
|
||||
$(document).on("click", ".character_select", function () {
|
||||
const chid = $(this).attr('chid');
|
||||
|
@@ -427,19 +427,6 @@ code {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#token_breakdown div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.token_breakdown_segment {
|
||||
min-width: 40px !important;
|
||||
border: solid 2px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
#loading_mes {
|
||||
display: none;
|
||||
width: 40px;
|
||||
|
@@ -65,6 +65,8 @@ Get in touch with the developers directly:
|
||||
* Character emotional expressions
|
||||
* Auto-Summary of the chat history
|
||||
* Sending images to chat, and the AI interpreting the content.
|
||||
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
|
||||
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
|
||||
|
||||
## UI Extensions 🚀
|
||||
|
||||
@@ -76,6 +78,8 @@ Get in touch with the developers directly:
|
||||
| D&D Dice | A set of 7 classic D&D dice for all your dice rolling needs.<br><br>*I used to roll the dice.<br>Feel the fear in my enemies' eyes* | None | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/226199925-a066c6fc-745e-4a2b-9203-1cbffa481b14.png"> |
|
||||
| Author's Note | Built-in extension that allows you to append notes that will be added to the context and steer the story and character in a specific direction. Because it's sent after the character description, it has a lot of weight. Thanks Ali឵#2222 for pitching the idea! | None |  |
|
||||
| Character Backgrounds | Built-in extension to assign unique backgrounds to specific chats or groups. | None | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/233494454-bfa7c9c7-4faa-4d97-9c69-628fd96edd92.png"> |
|
||||
| Stable Diffusion | Use local of cloud-based Stable Diffusion webUI API to generate images. 5 presets included ('you', 'your face', 'me', 'the story', and 'the last message'. Free mode also supported via `/sd (anything_here_)` command in the chat input bar. Most common StableDiffusion generation settings are customizable within the SillyTavern UI. | None | <img style="max-width:200px" alt="image" src="https://files.catbox.moe/ppata8.png"> |
|
||||
| Text-to-Speech | AI-generated voice will read back character messages on demand, or automatically read new messages they arrive. Supports ElevenLabs, Silero, and your device's TTS service. | None | <img style="max-width:200px" alt="image" src="https://files.catbox.moe/o3wxkk.png"> |
|
||||
|
||||
## UI/CSS/Quality of Life tweaks by RossAscends
|
||||
|
||||
|
90
server.js
90
server.js
@@ -63,7 +63,9 @@ const utf8Encode = new TextEncoder();
|
||||
const utf8Decode = new TextDecoder('utf-8', { ignoreBOM: true });
|
||||
const commandExistsSync = require('command-exists').sync;
|
||||
|
||||
const characterCardParser = require('./src/character-card-parser.js');
|
||||
const config = require(path.join(process.cwd(), './config.conf'));
|
||||
|
||||
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
|
||||
|
||||
const whitelistPath = path.join(process.cwd(), "./whitelist.txt");
|
||||
@@ -913,61 +915,7 @@ async function tryReadImage(img_url, crop) {
|
||||
}
|
||||
|
||||
async function charaRead(img_url, input_format) {
|
||||
let format;
|
||||
if (input_format === undefined) {
|
||||
if (img_url.indexOf('.webp') !== -1) {
|
||||
format = 'webp';
|
||||
} else {
|
||||
format = 'png';
|
||||
}
|
||||
} else {
|
||||
format = input_format;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case 'webp':
|
||||
try {
|
||||
const exif_data = await ExifReader.load(fs.readFileSync(img_url));
|
||||
let char_data;
|
||||
|
||||
if (exif_data['UserComment']['description']) {
|
||||
let description = exif_data['UserComment']['description'];
|
||||
if (description === 'Undefined' && exif_data['UserComment'].value && exif_data['UserComment'].value.length === 1) {
|
||||
description = exif_data['UserComment'].value[0];
|
||||
}
|
||||
try {
|
||||
json5.parse(description);
|
||||
char_data = description;
|
||||
} catch {
|
||||
const byteArr = description.split(",").map(Number);
|
||||
const uint8Array = new Uint8Array(byteArr);
|
||||
const char_data_string = utf8Decode.decode(uint8Array);
|
||||
char_data = char_data_string;
|
||||
}
|
||||
} else {
|
||||
console.log('No description found in EXIF data.');
|
||||
return false;
|
||||
}
|
||||
return char_data;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return false;
|
||||
}
|
||||
case 'png':
|
||||
const buffer = fs.readFileSync(img_url);
|
||||
const chunks = extract(buffer);
|
||||
|
||||
const textChunks = chunks.filter(function (chunk) {
|
||||
return chunk.name === 'tEXt';
|
||||
}).map(function (chunk) {
|
||||
return PNGtext.decode(chunk.data);
|
||||
});
|
||||
var base64DecodedData = Buffer.from(textChunks[0].text, 'base64').toString('utf8');
|
||||
return base64DecodedData;//textChunks[0].text;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return characterCardParser.parse(img_url, input_format);
|
||||
}
|
||||
|
||||
app.post("/getcharacters", jsonParser, function (request, response) {
|
||||
@@ -1754,6 +1702,17 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
return response.sendStatus(400);
|
||||
});
|
||||
|
||||
app.post("/importgroupchat", urlencodedParser, function (request, response) {
|
||||
try {
|
||||
const filedata = request.file;
|
||||
const chatname = humanizedISO8601DateTime();
|
||||
fs.copyFileSync(`./uploads/${filedata.filename}`, (`${directories.groupChats}/${chatname}.jsonl`));
|
||||
return response.send({ res: chatname });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.send({ error: true });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
@@ -1763,9 +1722,8 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let avatar_url = (request.body.avatar_url).replace('.png', '');
|
||||
let ch_name = request.body.character_name;
|
||||
if (filedata) {
|
||||
|
||||
if (format === 'json') {
|
||||
fs.readFile('./uploads/' + filedata.filename, 'utf8', (err, data) => {
|
||||
fs.readFile(`./uploads/${filedata.filename}`, 'utf8', (err, data) => {
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
@@ -1782,7 +1740,6 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
user_name: 'You',
|
||||
character_name: ch_name,
|
||||
create_date: humanizedISO8601DateTime(),
|
||||
|
||||
},
|
||||
...history.msgs.map(
|
||||
(message) => ({
|
||||
@@ -1803,7 +1760,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
|
||||
const errors = [];
|
||||
newChats.forEach(chat => fs.writeFile(
|
||||
chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
|
||||
`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()} imported.jsonl`,
|
||||
chat.map(JSON.stringify).join('\n'),
|
||||
'utf8',
|
||||
(err) => err ?? errors.push(err)
|
||||
@@ -1832,8 +1789,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let jsonData = json5.parse(line);
|
||||
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
//console.log(humanizedISO8601DateTime()+':/importchat copying chat as '+ch_name+' - '+humanizedISO8601DateTime()+'.jsonl');
|
||||
fs.copyFile('./uploads/' + filedata.filename, chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + '.jsonl', (err) => { //added character name and replaced Date.now() with humanizedISO8601DateTime
|
||||
fs.copyFile(`./uploads/${filedata.filename}`, (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
if (err) {
|
||||
response.send({ error: true });
|
||||
return console.log(err);
|
||||
@@ -1849,9 +1805,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
rl.close();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||
@@ -1919,7 +1873,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
const crop = tryParse(request.query.crop);
|
||||
let rawImg = await jimp.read(pathToUpload);
|
||||
|
||||
if (typeof crop == 'object') {
|
||||
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
|
||||
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
|
||||
}
|
||||
|
||||
@@ -2746,8 +2700,12 @@ const setupTasks = async function () {
|
||||
}
|
||||
|
||||
if (listen && !config.whitelistMode && !config.basicAuthMode) {
|
||||
console.error('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.');
|
||||
process.exit(1);
|
||||
if (config.securityOverride)
|
||||
console.warn("Security has been override. If it's not a trusted network, change the settings.");
|
||||
else {
|
||||
console.error('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (true === cliArguments.ssl)
|
||||
|
72
src/character-card-parser.js
Normal file
72
src/character-card-parser.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const fs = require('fs');
|
||||
const json5 = require('json5');
|
||||
const ExifReader = require('exifreader');
|
||||
|
||||
const extract = require('png-chunks-extract');
|
||||
const PNGtext = require('png-chunk-text');
|
||||
|
||||
const utf8Decode = new TextDecoder('utf-8', { ignoreBOM: true });
|
||||
|
||||
const parse = async (cardUrl, format) => {
|
||||
let fileFormat;
|
||||
if (format === undefined) {
|
||||
if (cardUrl.indexOf('.webp') !== -1)
|
||||
fileFormat = 'webp';
|
||||
else
|
||||
fileFormat = 'png';
|
||||
}
|
||||
else
|
||||
fileFormat = format;
|
||||
|
||||
switch (fileFormat) {
|
||||
case 'webp':
|
||||
try {
|
||||
const exif_data = await ExifReader.load(fs.readFileSync(cardUrl));
|
||||
let char_data;
|
||||
|
||||
if (exif_data['UserComment']['description']) {
|
||||
let description = exif_data['UserComment']['description'];
|
||||
if (description === 'Undefined' && exif_data['UserComment'].value && exif_data['UserComment'].value.length === 1) {
|
||||
description = exif_data['UserComment'].value[0];
|
||||
}
|
||||
|
||||
try {
|
||||
json5.parse(description);
|
||||
char_data = description;
|
||||
} catch {
|
||||
const byteArr = description.split(",").map(Number);
|
||||
const uint8Array = new Uint8Array(byteArr);
|
||||
const char_data_string = utf8Decode.decode(uint8Array);
|
||||
char_data = char_data_string;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('No description found in EXIF data.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return char_data;
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return false;
|
||||
}
|
||||
case 'png':
|
||||
const buffer = fs.readFileSync(cardUrl);
|
||||
const chunks = extract(buffer);
|
||||
|
||||
const textChunks = chunks.filter(function (chunk) {
|
||||
return chunk.name === 'tEXt';
|
||||
}).map(function (chunk) {
|
||||
return PNGtext.decode(chunk.data);
|
||||
});
|
||||
|
||||
return Buffer.from(textChunks[0].text, 'base64').toString('utf8');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parse: parse
|
||||
};
|
Reference in New Issue
Block a user