mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bf7f04e3b2 | ||
|
5df7d2d1dc | ||
|
83c875d8dc | ||
|
72b7b7cab2 | ||
|
55f38f69d6 | ||
|
0633d16622 | ||
|
35cb1f6182 | ||
|
a18c20305e | ||
|
d542ec0d81 | ||
|
6ad0be9597 | ||
|
0de09e9da0 | ||
|
bb187d9920 | ||
|
711dbdcc15 | ||
|
5215e6e437 | ||
|
01c27bc9a9 | ||
|
b35d8a4324 | ||
|
6c6f5b7f1a | ||
|
cff5cd0928 | ||
|
fb1b02571e | ||
|
412fad002d | ||
|
6ad2492ef6 | ||
|
d3b0ba02b6 | ||
|
d80fff3b5e |
@@ -23,7 +23,7 @@ COPY . ./
|
||||
|
||||
# Copy default chats, characters and user avatars to <folder>.default folder
|
||||
RUN \
|
||||
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,settings.json" && \
|
||||
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,settings.json" && \
|
||||
\
|
||||
echo "*** Store default $RESOURCES in <folder>.default ***" && \
|
||||
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
|
||||
|
@@ -3,6 +3,7 @@ pushd %~dp0
|
||||
git --version > nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Git is not installed on this system. Skipping update.
|
||||
echo If you installed with a zip file, you will need to download the new zip and install it manually.
|
||||
) else (
|
||||
call git pull --rebase --autostash
|
||||
if %errorlevel% neq 0 (
|
||||
|
@@ -4,7 +4,7 @@ services:
|
||||
build: ..
|
||||
container_name: sillytavern
|
||||
hostname: sillytavern
|
||||
image: cohee1207/sillytavern:latest
|
||||
image: sillytavern/sillytavern:latest
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.4",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
|
@@ -46,7 +46,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
||||
},
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.4",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
|
@@ -2512,7 +2512,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
//console.log('generate ending');
|
||||
} //generate ends
|
||||
|
||||
function getBiasStrings(textareaText) {
|
||||
export function getBiasStrings(textareaText) {
|
||||
let promptBias = '';
|
||||
let messageBias = extractMessageBias(textareaText);
|
||||
|
||||
@@ -2551,7 +2551,7 @@ export function replaceBiasMarkup(str) {
|
||||
return (str ?? '').replace(/{{(\*?.*\*?)}}/g, '');
|
||||
}
|
||||
|
||||
async function sendMessageAsUser(textareaText, messageBias) {
|
||||
export async function sendMessageAsUser(textareaText, messageBias) {
|
||||
chat[chat.length] = {};
|
||||
chat[chat.length - 1]['name'] = name1;
|
||||
chat[chat.length - 1]['is_user'] = true;
|
||||
@@ -3294,14 +3294,14 @@ export function isMultigenEnabled() {
|
||||
return power_user.multigen && (main_api == 'textgenerationwebui' || main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'novel');
|
||||
}
|
||||
|
||||
function activateSendButtons() {
|
||||
export function activateSendButtons() {
|
||||
is_send_press = false;
|
||||
$("#send_but").css("display", "flex");
|
||||
$("#send_textarea").attr("disabled", false);
|
||||
hideStopButton();
|
||||
}
|
||||
|
||||
function deactivateSendButtons() {
|
||||
export function deactivateSendButtons() {
|
||||
$("#send_but").css("display", "none");
|
||||
showStopButton();
|
||||
}
|
||||
@@ -4248,27 +4248,40 @@ function select_rm_info(type, charId, previousCharId = null) {
|
||||
getCharacters();
|
||||
selectRightMenuWithAnimation('rm_characters_block');
|
||||
|
||||
if (type === 'char_import' || type === 'char_create') {
|
||||
setTimeout(function () {
|
||||
if (type === 'char_import' || type === 'char_create') {
|
||||
const element = $(`#rm_characters_block [title="${charId}"]`).parent().get(0);
|
||||
console.log(element);
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
const element = $(`#rm_characters_block [title="${charId}"]`).get(0);
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
|
||||
$(`#rm_characters_block [title="${charId}"]`).parent().addClass('flash animated');
|
||||
setTimeout(function () {
|
||||
$(`#rm_characters_block [title="${charId}"]`).parent().removeClass('flash animated');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if (type === 'group_create') {
|
||||
//for groups, ${charId} = data.id from group-chats.js createGroup()
|
||||
const element = $(`#rm_characters_block [grid="${charId}"]`).get(0);
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
$(`#rm_characters_block [grid="${charId}"]`).addClass('flash animated');
|
||||
setTimeout(function () {
|
||||
$(`#rm_characters_block [grid="${charId}"]`).removeClass('flash animated');
|
||||
}, 5000);
|
||||
}
|
||||
try {
|
||||
if (element !== undefined || element !== null) {
|
||||
$(element).addClass('flash animated');
|
||||
setTimeout(function () {
|
||||
$(element).removeClass('flash animated');
|
||||
}, 5000);
|
||||
} else { console.log('didnt find the element'); }
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'group_create') {
|
||||
//for groups, ${charId} = data.id from group-chats.js createGroup()
|
||||
const element = $(`#rm_characters_block [grid="${charId}"]`).get(0);
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
try {
|
||||
if (element !== undefined || element !== null) {
|
||||
$(element).addClass('flash animated');
|
||||
setTimeout(function () {
|
||||
$(element).removeClass('flash animated');
|
||||
}, 5000);
|
||||
} else { console.log('didnt find the element'); }
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
setRightTabSelectedClass();
|
||||
|
||||
if (previousCharId) {
|
||||
@@ -5106,6 +5119,12 @@ function importCharacter(file) {
|
||||
$(document).ready(function () {
|
||||
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
||||
|
||||
setTimeout(function () {
|
||||
$("#groupControlsToggle").trigger('click');
|
||||
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
|
||||
}, 200);
|
||||
|
||||
|
||||
$("#rm_print_characters_block").on('scroll',
|
||||
debounce(updateVisibleDivs, 5));
|
||||
|
||||
|
@@ -144,6 +144,7 @@ async function createNewBookmark() {
|
||||
}
|
||||
}
|
||||
|
||||
await delay(250);
|
||||
let name = await getBookmarkName();
|
||||
|
||||
if (!name) {
|
||||
@@ -257,7 +258,7 @@ async function convertSoloToGroupChat() {
|
||||
}
|
||||
|
||||
// Skip messages we don't care about
|
||||
if (message.is_user || message.is_system) {
|
||||
if (message.is_user || message.is_system || message.extra?.type === system_message_types.NARRATOR || message.force_avatar !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -51,6 +51,22 @@ function getChatSyncState() {
|
||||
|
||||
const context = getContext();
|
||||
const chatState = chatStateFlags[currentChatId] || [];
|
||||
|
||||
// if the chat length has decreased, it means that some messages were deleted
|
||||
if (chatState.length > context.chat.length) {
|
||||
for (let i = context.chat.length; i < chatState.length; i++) {
|
||||
// if the synced message was deleted, notify the user
|
||||
if (chatState[i]) {
|
||||
toastr.warning(
|
||||
'Purge your ChromaDB to remove it from there too. See the "Smart Context" tab in the Extensions menu for more information.',
|
||||
'Message deleted from chat, but it still exists inside the ChromaDB database.',
|
||||
{ timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatState.length = context.chat.length;
|
||||
for (let i = 0; i < chatState.length; i++) {
|
||||
if (chatState[i] === undefined) {
|
||||
@@ -76,6 +92,7 @@ async function loadSettings() {
|
||||
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).trigger('input');
|
||||
$('#chromadb_split_length').val(extension_settings.chromadb.split_length).trigger('input');
|
||||
$('#chromadb_file_split_length').val(extension_settings.chromadb.file_split_length).trigger('input');
|
||||
$('#chromadb_freeze').prop('checked', extension_settings.chromadb.freeze);
|
||||
}
|
||||
|
||||
function onStrategyChange() {
|
||||
@@ -119,6 +136,10 @@ function checkChatId(chat_id) {
|
||||
}
|
||||
|
||||
async function addMessages(chat_id, messages) {
|
||||
if (extension_settings.chromadb.freeze) {
|
||||
return { count: 0 };
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/chromadb';
|
||||
|
||||
@@ -328,12 +349,13 @@ async function onSelectInjectFile(e) {
|
||||
const text = await getFileText(file);
|
||||
|
||||
const split = splitRecursive(text, extension_settings.chromadb.file_split_length).filter(onlyUnique);
|
||||
const baseDate = Date.now();
|
||||
|
||||
const messages = split.map(m => ({
|
||||
const messages = split.map((m, i) => ({
|
||||
id: `${file.name}-${split.indexOf(m)}`,
|
||||
role: 'system',
|
||||
content: m,
|
||||
date: Date.now(),
|
||||
date: baseDate + i,
|
||||
meta: JSON.stringify({
|
||||
name: file.name,
|
||||
is_user: false,
|
||||
@@ -380,7 +402,7 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
if (currentChatId) {
|
||||
const messagesToStore = chat.slice(0, -extension_settings.chromadb.keep_context);
|
||||
|
||||
if (messagesToStore.length > 0) {
|
||||
if (messagesToStore.length > 0 || extension_settings.chromadb.freeze) {
|
||||
await addMessages(currentChatId, messagesToStore);
|
||||
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
@@ -431,6 +453,11 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
}
|
||||
}
|
||||
|
||||
function onFreezeInput() {
|
||||
extension_settings.chromadb.freeze = $('#chromadb_freeze').is(':checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
const settingsHtml = `
|
||||
<div class="chromadb_settings">
|
||||
@@ -454,6 +481,10 @@ jQuery(async () => {
|
||||
<input id="chromadb_split_length" type="range" min="${defaultSettings.split_length_min}" max="${defaultSettings.split_length_max}" step="${defaultSettings.split_length_step}" value="${defaultSettings.split_length}" />
|
||||
<label for="chromadb_file_split_length">Max length for each 'memory' pulled from imported text files: (<span id="chromadb_file_split_length_value"></span>) characters</label>
|
||||
<input id="chromadb_file_split_length" type="range" min="${defaultSettings.file_split_length_min}" max="${defaultSettings.file_split_length_max}" step="${defaultSettings.file_split_length_step}" value="${defaultSettings.file_split_length}" />
|
||||
<label class="checkbox_label" for="chromadb_freeze" title="Pauses the automatic synchronization of new messages with ChromaDB. Older messages and injections will still be pulled as usual." >
|
||||
<input type="checkbox" id="chromadb_freeze" />
|
||||
<span>Freeze ChromaDB state</span>
|
||||
</label>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div id="chromadb_inject" title="Upload custom textual data to use in the context of the current chat" class="menu_button">
|
||||
<i class="fa-solid fa-file-arrow-up"></i>
|
||||
@@ -472,7 +503,7 @@ jQuery(async () => {
|
||||
<span>Purge Chat from the DB</span>
|
||||
</div>
|
||||
</div>
|
||||
<small><i>Since ChromaDB state is not persisted to disk by default, you'll need to inject text data every time the Extras API server is restarted.</i></small>
|
||||
<small><i>Local ChromaDB now persists to disk by default. The default folder is .chroma_db, and you can set a different folder with the --chroma-folder argument. If you are using the Extras Colab notebook, you will need to inject the text data every time the Extras API server is restarted.</i></small>
|
||||
</div>
|
||||
<form><input id="chromadb_inject_file" type="file" accept="text/plain" hidden></form>
|
||||
<form><input id="chromadb_import_file" type="file" accept="application/json" hidden></form>
|
||||
@@ -490,6 +521,7 @@ jQuery(async () => {
|
||||
$('#chromadb_import_file').on('change', onSelectImportFile);
|
||||
$('#chromadb_purge').on('click', onPurgeClick);
|
||||
$('#chromadb_export').on('click', onExportClick);
|
||||
$('#chromadb_freeze').on('input', onFreezeInput);
|
||||
await loadSettings();
|
||||
|
||||
// Not sure if this is needed, but it's here just in case
|
||||
|
@@ -110,6 +110,7 @@ async function moduleWorker() {
|
||||
|
||||
// We're currently swiping or streaming. Don't generate voice
|
||||
if (
|
||||
!message ||
|
||||
message.mes === '...' ||
|
||||
message.mes === '' ||
|
||||
(context.streamingProcessor && !context.streamingProcessor.isFinished)
|
||||
|
@@ -1,5 +1,74 @@
|
||||
export { SystemTtsProvider }
|
||||
|
||||
/**
|
||||
* Chunkify
|
||||
* Google Chrome Speech Synthesis Chunking Pattern
|
||||
* Fixes inconsistencies with speaking long texts in speechUtterance objects
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Peter Woolley and Brett Zamir
|
||||
* Modified by Haaris for bug fixes
|
||||
*/
|
||||
|
||||
var speechUtteranceChunker = function (utt, settings, callback) {
|
||||
settings = settings || {};
|
||||
var newUtt;
|
||||
var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text);
|
||||
if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec
|
||||
newUtt = utt;
|
||||
newUtt.text = txt;
|
||||
newUtt.addEventListener('end', function () {
|
||||
if (speechUtteranceChunker.cancel) {
|
||||
speechUtteranceChunker.cancel = false;
|
||||
}
|
||||
if (callback !== undefined) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
var chunkLength = (settings && settings.chunkLength) || 160;
|
||||
var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} ');
|
||||
var chunkArr = txt.match(pattRegex);
|
||||
|
||||
if (chunkArr == null || chunkArr[0] === undefined || chunkArr[0].length <= 2) {
|
||||
//call once all text has been spoken...
|
||||
if (callback !== undefined) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var chunk = chunkArr[0];
|
||||
newUtt = new SpeechSynthesisUtterance(chunk);
|
||||
var x;
|
||||
for (x in utt) {
|
||||
if (utt.hasOwnProperty(x) && x !== 'text') {
|
||||
newUtt[x] = utt[x];
|
||||
}
|
||||
}
|
||||
newUtt.lang = utt.lang;
|
||||
newUtt.voice = utt.voice;
|
||||
newUtt.addEventListener('end', function () {
|
||||
if (speechUtteranceChunker.cancel) {
|
||||
speechUtteranceChunker.cancel = false;
|
||||
return;
|
||||
}
|
||||
settings.offset = settings.offset || 0;
|
||||
settings.offset += chunk.length;
|
||||
speechUtteranceChunker(utt, settings, callback);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.modifier) {
|
||||
settings.modifier(newUtt);
|
||||
}
|
||||
console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues.
|
||||
//placing the speak invocation inside a callback fixes ordering and onend issues.
|
||||
setTimeout(function () {
|
||||
speechSynthesis.speak(newUtt);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
class SystemTtsProvider {
|
||||
//########//
|
||||
// Config //
|
||||
@@ -142,7 +211,12 @@ class SystemTtsProvider {
|
||||
utterance.pitch = this.settings.pitch || 1;
|
||||
utterance.onend = () => resolve(silence);
|
||||
utterance.onerror = () => reject();
|
||||
speechSynthesis.speak(utterance);
|
||||
speechUtteranceChunker(utterance, {
|
||||
chunkLength: 200,
|
||||
}, function () {
|
||||
//some code to execute when done
|
||||
console.log('System TTS done');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -48,6 +48,11 @@ import {
|
||||
cancelTtsPlay,
|
||||
isMultigenEnabled,
|
||||
displayPastChats,
|
||||
sendMessageAsUser,
|
||||
getBiasStrings,
|
||||
saveChatConditional,
|
||||
deactivateSendButtons,
|
||||
activateSendButtons,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
|
||||
|
||||
@@ -414,7 +419,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
group_generation_id = Date.now();
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
let messagesBefore = chat.length;
|
||||
let lastMessageText = lastMessage.mes;
|
||||
let lastMessageText = lastMessage?.mes || '';
|
||||
let activationText = "";
|
||||
let isUserInput = false;
|
||||
let isGenerationDone = false;
|
||||
@@ -491,17 +496,23 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
|
||||
if (activatedMembers.length === 0) {
|
||||
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
||||
throw new Error('All group members are disabled');
|
||||
|
||||
// Send user message as is
|
||||
const bias = getBiasStrings(userInput);
|
||||
await sendMessageAsUser(userInput, bias.messageBias);
|
||||
await saveChatConditional();
|
||||
$('#send_textarea').val('');
|
||||
}
|
||||
|
||||
// now the real generation begins: cycle through every activated character
|
||||
for (const chId of activatedMembers) {
|
||||
deactivateSendButtons();
|
||||
isGenerationDone = false;
|
||||
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
|
||||
setCharacterId(chId);
|
||||
setCharacterName(characters[chId].name)
|
||||
|
||||
await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
|
||||
Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
|
||||
|
||||
if (type !== "swipe" && type !== "impersonate" && !isMultigenEnabled() && !isStreamingEnabled()) {
|
||||
// update indicator and scroll down
|
||||
@@ -509,13 +520,14 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
.find(".typing_indicator_name")
|
||||
.text(characters[chId].name);
|
||||
$("#chat").append(typingIndicator);
|
||||
typingIndicator.show(250, function () {
|
||||
typingIndicator.show(200, function () {
|
||||
typingIndicator.get(0).scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: This is awful. Refactor this
|
||||
while (true) {
|
||||
deactivateSendButtons();
|
||||
if (isGenerationAborted) {
|
||||
throw new Error('Group generation aborted');
|
||||
}
|
||||
@@ -595,11 +607,10 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
// hide and reapply the indicator to the bottom of the list
|
||||
typingIndicator.hide(250);
|
||||
typingIndicator.hide(200);
|
||||
$("#chat").append(typingIndicator);
|
||||
|
||||
is_group_generating = false;
|
||||
@@ -607,6 +618,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
setSendButtonState(false);
|
||||
setCharacterId(undefined);
|
||||
setCharacterName('');
|
||||
activateSendButtons();
|
||||
showSwipeButtons();
|
||||
}
|
||||
}
|
||||
@@ -714,7 +726,8 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
|
||||
}
|
||||
|
||||
// pick 1 at random if no one was activated
|
||||
while (activatedMembers.length === 0) {
|
||||
let retries = 0;
|
||||
while (activatedMembers.length === 0 && ++retries <= members.length) {
|
||||
const randomIndex = Math.floor(Math.random() * members.length);
|
||||
const character = characters.find((x) => x.avatar === members[randomIndex]);
|
||||
|
||||
@@ -959,6 +972,9 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_scenario").show();
|
||||
} else {
|
||||
$("#rm_group_submit").show();
|
||||
if ($("#groupAddMemberListToggle .inline-drawer-content").css('display') !== 'block') {
|
||||
$("#groupAddMemberListToggle").trigger('click');
|
||||
}
|
||||
$("#rm_group_delete").hide();
|
||||
$("#rm_group_scenario").hide();
|
||||
}
|
||||
|
@@ -56,6 +56,7 @@ export {
|
||||
let openai_msgs = [];
|
||||
let openai_msgs_example = [];
|
||||
let openai_messages_count = 0;
|
||||
let openai_narrator_messages_count = 0;
|
||||
|
||||
let is_get_status_openai = false;
|
||||
let is_api_button_press_openai = false;
|
||||
@@ -175,6 +176,7 @@ function setOpenAIMessages(chat) {
|
||||
let j = 0;
|
||||
// clean openai msgs
|
||||
openai_msgs = [];
|
||||
openai_narrator_messages_count = 0;
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
let role = chat[j]['is_user'] ? 'user' : 'assistant';
|
||||
let content = chat[j]['mes'];
|
||||
@@ -182,6 +184,7 @@ function setOpenAIMessages(chat) {
|
||||
// 100% legal way to send a message as system
|
||||
if (chat[j].extra?.type === system_message_types.NARRATOR) {
|
||||
role = 'system';
|
||||
openai_narrator_messages_count++;
|
||||
}
|
||||
|
||||
// for groups or sendas command - prepend a character's name
|
||||
@@ -457,7 +460,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
||||
}
|
||||
}
|
||||
|
||||
openai_messages_count = openai_msgs_tosend.filter(x => x.role == "user" || x.role == "assistant").length;
|
||||
openai_messages_count = openai_msgs_tosend.filter(x => x.role == "user" || x.role == "assistant").length + openai_narrator_messages_count;
|
||||
// reverse the messages array because we had the newest at the top to remove the oldest,
|
||||
// now we want proper order
|
||||
openai_msgs_tosend.reverse();
|
||||
|
@@ -162,7 +162,7 @@ export function saveCaretPosition(element) {
|
||||
end: range.endOffset
|
||||
};
|
||||
|
||||
console.log('Caret saved', position);
|
||||
console.debug('Caret saved', position);
|
||||
|
||||
return position;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export function restoreCaretPosition(element, position) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Caret restored', position);
|
||||
console.debug('Caret restored', position);
|
||||
|
||||
// Create a new range object
|
||||
const range = new Range();
|
||||
|
32
readme.md
32
readme.md
@@ -63,29 +63,19 @@ Get in touch with the developers directly:
|
||||
* Soft prompts selector for KoboldAI
|
||||
* Prompt generation formatting tweaking
|
||||
* webp character card interoperability (PNG is still an internal format)
|
||||
* Extensibility support via [SillyLossy's TAI-extras](https://github.com/Cohee1207/TavernAI-extras) plugins
|
||||
* Author's Note / Character Bias
|
||||
* 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 🚀
|
||||
## Extensions
|
||||
|
||||
| Name | Description | Required <a href="https://github.com/Cohee1207/TavernAI-extras#modules" target="_blank">Extra Modules</a> | Screenshot |
|
||||
| ---------------- | ---------------------------------| ---------------------------- | ---------- |
|
||||
| Image Captioning | Send a cute picture to your bot!<br><br>Picture select option will appear beside the "Message send" button. | `caption` | <img src="https://user-images.githubusercontent.com/18619528/224161576-ddfc51cd-995e-44ec-bf2d-d2477d603f0c.png" style="max-width:200px" /> |
|
||||
| Character Expressions | See your character reacting to your messages!<br><br>**You need to provide your own character images!**<br><br>1. Create a folder in TavernAI called `public/characters/<name>`, where `<name>` is the name of your character.<br>2. For the base emotion classification model, put six PNG files there with the following names: `joy.png`, `anger.png`, `fear.png`, `love.png`, `sadness.png`, `surprise.png`. Other models may provide other options.<br>3. Images only display in desktop mode. | `classify` | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/223765089-34968217-6862-47e0-85da-7357370f8de6.png"> |
|
||||
| Memory | Chatbot long-term memory simulation using automatic message context summarization. | `summarize` | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/223766279-88a46481-1fa6-40c5-9724-6cdd6f587233.png"> |
|
||||
| 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"> |
|
||||
| Chat Translation | Automatically translates incoming and/or outgoing messages into the chosen language. | None | Pending |
|
||||
| Token Counter | Simple way to calculate the number of tokens in any text with selected tokenizer. | None | Pending |
|
||||
| Smart Context<br><br>*Infinity Context / Object Permanence* | **What it doesn't do:** Magically increase your context size.<br>**What it does:** Optimizes the arrangement of your message history within the context space for more effective use.<br><br>Imagine two variables:<br>X: How many original chat messages to keep<br>Y: Maximum number of ChromaDB 'memories' to inject<br><br>When the chat reaches the threshold of X messages, additional messages will no longer be included in the context chronologically. Instead, they will be selected from the history based on their similarity to your recent inputs (limited to a maximum of Y), which should provide more relevant information than simply disregarding past messages. Adjust these values according to your average number of in-context entries for optimal performance. | `chromadb` | Pending |
|
||||
SillyTavern has an extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
|
||||
|
||||
* Author's Note / Character Bias
|
||||
* 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)
|
||||
|
||||
Full list of included extenisons and tutorials how to use them can be found on [Wiki](https://github.com/SillyTavern/SillyTavern/wiki).
|
||||
|
||||
## UI/CSS/Quality of Life tweaks by RossAscends
|
||||
|
||||
|
Reference in New Issue
Block a user