mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
83c875d8dc | ||
|
72b7b7cab2 | ||
|
55f38f69d6 | ||
|
0633d16622 | ||
|
35cb1f6182 | ||
|
a18c20305e | ||
|
d542ec0d81 | ||
|
6ad0be9597 | ||
|
0de09e9da0 | ||
|
bb187d9920 | ||
|
711dbdcc15 | ||
|
5215e6e437 | ||
|
01c27bc9a9 | ||
|
b35d8a4324 | ||
|
6c6f5b7f1a | ||
|
cff5cd0928 | ||
|
fb1b02571e | ||
|
412fad002d | ||
|
6ad2492ef6 | ||
|
d3b0ba02b6 | ||
|
d80fff3b5e | ||
|
b359dd1e81 | ||
|
cdaa14964e | ||
|
747567466f | ||
|
d95786fb25 | ||
|
e0fdd1513c | ||
|
3f9ccff2bc | ||
|
c9c82537a7 | ||
|
ed74eedc5b | ||
|
8fdfb272a4 | ||
|
b1927d454c |
@@ -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.0",
|
||||
"version": "1.6.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.2",
|
||||
"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.0",
|
||||
"version": "1.6.2",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
|
@@ -318,14 +318,14 @@ class Client {
|
||||
if (!viewer.availableBots) {
|
||||
throw new Error('Invalid token.');
|
||||
}
|
||||
const botList = viewer.availableBots;
|
||||
const botList = viewer.viewerBotList;
|
||||
const retries = 2;
|
||||
const bots = {};
|
||||
for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) {
|
||||
try {
|
||||
const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName}.json`;
|
||||
let r;
|
||||
|
||||
|
||||
if (this.use_cached_bots && cached_bots[url]) {
|
||||
r = cached_bots[url];
|
||||
}
|
||||
@@ -334,7 +334,7 @@ class Client {
|
||||
r = await request_with_retries(() => this.session.get(url), retries);
|
||||
cached_bots[url] = r;
|
||||
}
|
||||
|
||||
|
||||
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
|
||||
bots[chatData.defaultBotObject.nickname] = chatData;
|
||||
}
|
||||
@@ -640,4 +640,4 @@ class Client {
|
||||
|
||||
load_queries();
|
||||
|
||||
module.exports = { Client };
|
||||
module.exports = { Client };
|
||||
|
@@ -1104,6 +1104,14 @@ function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias,
|
||||
return mes;
|
||||
}
|
||||
|
||||
export function updateMessageBlock(messageId, message) {
|
||||
const messageElement = $(`#chat [mesid="${messageId}"]`);
|
||||
const text = message?.extra?.display_text ?? message.mes;
|
||||
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user));
|
||||
addCopyToCodeBlocks(messageElement)
|
||||
appendImageToMessage(message, messageElement);
|
||||
}
|
||||
|
||||
export function appendImageToMessage(mes, messageElement) {
|
||||
if (mes.extra?.image) {
|
||||
const image = messageElement.find('.mes_img');
|
||||
@@ -1117,7 +1125,7 @@ export function appendImageToMessage(mes, messageElement) {
|
||||
}
|
||||
}
|
||||
|
||||
function addCopyToCodeBlocks(messageElement) {
|
||||
export function addCopyToCodeBlocks(messageElement) {
|
||||
const codeBlocks = $(messageElement).find("pre code");
|
||||
for (let i = 0; i < codeBlocks.length; i++) {
|
||||
hljs.highlightElement(codeBlocks.get(i));
|
||||
@@ -2504,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);
|
||||
|
||||
@@ -2543,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;
|
||||
@@ -3286,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();
|
||||
}
|
||||
@@ -4013,8 +4021,8 @@ function setCharacterBlockHeight() {
|
||||
|
||||
// Common code for message editor done and auto-save
|
||||
function updateMessage(div) {
|
||||
let mesBlock = div.closest(".mes_block");
|
||||
var text = mesBlock.find(".edit_textarea").val().trim();
|
||||
const mesBlock = div.closest(".mes_block");
|
||||
const text = mesBlock.find(".edit_textarea").val().trim();
|
||||
const bias = extractMessageBias(text);
|
||||
const mes = chat[this_edit_mes_id];
|
||||
mes["mes"] = text;
|
||||
@@ -4070,7 +4078,7 @@ async function messageEditDone(div) {
|
||||
await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
|
||||
|
||||
this_edit_mes_id = undefined;
|
||||
saveChatConditional();
|
||||
await saveChatConditional();
|
||||
}
|
||||
|
||||
async function getPastCharacterChats() {
|
||||
@@ -4240,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) {
|
||||
@@ -5098,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));
|
||||
|
||||
@@ -6257,7 +6284,7 @@ $(document).ready(function () {
|
||||
|
||||
//********************
|
||||
//***Message Editor***
|
||||
$(document).on("click", ".mes_edit", function () {
|
||||
$(document).on("click", ".mes_edit", async function () {
|
||||
if (this_chid !== undefined || selected_group) {
|
||||
// Previously system messages we're allowed to be edited
|
||||
/*const message = $(this).closest(".mes");
|
||||
@@ -6268,12 +6295,8 @@ $(document).ready(function () {
|
||||
|
||||
let chatScrollPosition = $("#chat").scrollTop();
|
||||
if (this_edit_mes_id !== undefined) {
|
||||
let mes_edited = $("#chat")
|
||||
.children()
|
||||
.filter('[mesid="' + this_edit_mes_id + '"]')
|
||||
.find(".mes_block")
|
||||
.find(".mes_edit_done");
|
||||
if (edit_mes_id == count_view_mes - 1) { //if the generating swipe (...)
|
||||
let mes_edited = $(`#chat [mesid="${this_edit_mes_id}"]`).find(".mes_edit_done");
|
||||
if (Number(edit_mes_id) == count_view_mes - 1) { //if the generating swipe (...)
|
||||
if (chat[edit_mes_id]['swipe_id'] !== undefined) {
|
||||
if (chat[edit_mes_id]['swipes'].length === chat[edit_mes_id]['swipe_id']) {
|
||||
run_edit = false;
|
||||
@@ -6283,7 +6306,7 @@ $(document).ready(function () {
|
||||
hideSwipeButtons();
|
||||
}
|
||||
}
|
||||
messageEditDone(mes_edited);
|
||||
await messageEditDone(mes_edited);
|
||||
}
|
||||
$(this).closest(".mes_block").find(".mes_text").empty();
|
||||
$(this).closest(".mes_block").find(".mes_buttons").css("display", "none");
|
||||
@@ -6454,8 +6477,8 @@ $(document).ready(function () {
|
||||
showSwipeButtons();
|
||||
});
|
||||
|
||||
$(document).on("click", ".mes_edit_done", function () {
|
||||
messageEditDone($(this));
|
||||
$(document).on("click", ".mes_edit_done", async function () {
|
||||
await messageEditDone($(this));
|
||||
});
|
||||
|
||||
$("#your_name_button").click(function () {
|
||||
|
@@ -144,6 +144,7 @@ async function createNewBookmark() {
|
||||
}
|
||||
}
|
||||
|
||||
await delay(250);
|
||||
let name = await getBookmarkName();
|
||||
|
||||
if (!name) {
|
||||
|
@@ -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';
|
||||
|
||||
@@ -230,7 +251,7 @@ async function onExportClick() {
|
||||
const exportResult = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
body: JSON.stringify({ currentChatId }),
|
||||
body: JSON.stringify({ chat_id: currentChatId }),
|
||||
});
|
||||
|
||||
if (exportResult.ok) {
|
||||
@@ -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
|
||||
|
@@ -3,10 +3,10 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
getRequestHeaders,
|
||||
messageFormatting,
|
||||
reloadCurrentChat,
|
||||
saveSettingsDebounced,
|
||||
substituteParams,
|
||||
updateMessageBlock,
|
||||
} from "../../../script.js";
|
||||
import { extension_settings, getContext } from "../../extensions.js";
|
||||
|
||||
@@ -168,7 +168,7 @@ async function translateIncomingMessage(messageId) {
|
||||
const translation = await translate(textToTranslate, extension_settings.translate.target_language);
|
||||
message.extra.display_text = translation;
|
||||
|
||||
$(`#chat .mes[mesid="${messageId}"] .mes_text`).html(messageFormatting(translation, message.name, message.is_system, message.is_user));
|
||||
updateMessageBlock(messageId, message);
|
||||
}
|
||||
|
||||
async function translateProviderGoogle(text, lang) {
|
||||
@@ -211,8 +211,8 @@ async function translateOutgoingMessage(messageId) {
|
||||
|
||||
const originalText = message.mes;
|
||||
message.extra.display_text = originalText;
|
||||
$(`#chat .mes[mesid="${messageId}"] .mes_text`).html(messageFormatting(originalText, message.name, message.is_system, message.is_user));
|
||||
message.mes = await translate(originalText, extension_settings.translate.internal_language);
|
||||
updateMessageBlock(messageId, message);
|
||||
|
||||
console.log('translateOutgoingMessage', messageId);
|
||||
}
|
||||
@@ -291,6 +291,24 @@ async function translateMessageEdit(messageId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onMessageTranslateClick() {
|
||||
const context = getContext();
|
||||
const messageId = $(this).closest('.mes').attr('mesid');
|
||||
const message = context.chat[messageId];
|
||||
|
||||
// If the message is already translated, revert it back to the original text
|
||||
if (message?.extra?.display_text) {
|
||||
delete message.extra.display_text;
|
||||
updateMessageBlock(messageId, message);
|
||||
}
|
||||
// If the message is not translated, translate it
|
||||
else {
|
||||
await translateIncomingMessage(messageId);
|
||||
}
|
||||
|
||||
await context.saveChat();
|
||||
}
|
||||
|
||||
const handleIncomingMessage = createEventHandler(translateIncomingMessage, () => shouldTranslate(incomingTypes));
|
||||
const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));
|
||||
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
|
||||
@@ -352,12 +370,7 @@ jQuery(() => {
|
||||
extension_settings.translate.target_language = event.target.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$(document).on('click', '.mes_translate', function () {
|
||||
const context = getContext();
|
||||
const messageId = $(this).closest('.mes').attr('mesid');
|
||||
translateIncomingMessage(messageId);
|
||||
context.saveChat();
|
||||
});
|
||||
$(document).on('click', '.mes_translate', onMessageTranslateClick);
|
||||
|
||||
loadSettings();
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
|
33
readme.md
33
readme.md
@@ -56,35 +56,26 @@ Get in touch with the developers directly:
|
||||
* Chat bookmarks / branching (duplicates the dialogue in its current state)
|
||||
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
|
||||
* World Info support: create a rich lore or save tokens on your character card
|
||||
* Window AI browser extension support (run models like Claude, GPT 4): https://windowai.io/
|
||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
||||
* [AI Horde](https://horde.koboldai.net/) connection
|
||||
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection
|
||||
* 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