+
entries
diff --git a/public/script.js b/public/script.js
index 96b2aa0e2..cff2194da 100644
--- a/public/script.js
+++ b/public/script.js
@@ -1055,14 +1055,14 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
const bias = messageFormating(mes.extra?.bias ?? "");
let params = {
- mesId: count_view_mes,
- characterName: characterName,
- isUser: mes.is_user,
- avatarImg: avatarImg,
- bias: bias,
- isSystem: isSystem,
- title: title,
- ...formatGenerationTimer(mes.gen_started, mes.gen_finished),
+ mesId: count_view_mes,
+ characterName: characterName,
+ isUser: mes.is_user,
+ avatarImg: avatarImg,
+ bias: bias,
+ isSystem: isSystem,
+ title: title,
+ ...formatGenerationTimer(mes.gen_started, mes.gen_finished),
};
const HTMLForEachMes = getMessageFromTemplate(params);
@@ -4313,7 +4313,11 @@ $(document).ready(function () {
duration: 200,
easing: animation_easing,
});
- setTimeout(function () { $("#shadow_popup").css("display", "none"); }, 200);
+ setTimeout(function () {
+ $("#shadow_popup").css("display", "none");
+ $("#dialogue_popup").removeClass('large_dialogue_popup');
+ }, 200);
+
// $("#shadow_popup").css("opacity:", 0.0);
if (popup_type == "del_bg") {
delBackground(bg_file_for_del.attr("bgfile"));
@@ -4421,13 +4425,16 @@ $(document).ready(function () {
if (popup_type == 'input') {
dialogueResolve($("#dialogue_popup_input").val());
$("#dialogue_popup_input").val('');
+
}
else {
dialogueResolve(true);
+
}
dialogueResolve = null;
}
+
});
$("#dialogue_popup_cancel").click(function (e) {
$("#shadow_popup").transition({
@@ -4435,7 +4442,11 @@ $(document).ready(function () {
duration: 200,
easing: animation_easing,
});
- setTimeout(function () { $("#shadow_popup").css("display", "none"); }, 200);
+ setTimeout(function () {
+ $("#shadow_popup").css("display", "none");
+ $("#dialogue_popup").removeClass('large_dialogue_popup');
+ }, 200);
+
//$("#shadow_popup").css("opacity:", 0.0);
popup_type = "";
@@ -4443,6 +4454,7 @@ $(document).ready(function () {
dialogueResolve(false);
dialogueResolve = null;
}
+
});
$("#add_bg_button").change(function () {
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index eafd3f9db..a99e381af 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -153,6 +153,7 @@ function createNewTag(tagName) {
const tag = {
id: random_id(),
name: tagName,
+ color: '',
};
tags.push(tag);
return tag;
@@ -165,6 +166,10 @@ function appendTagToList(listElement, tag, { removable, editable, selectable })
let tagElement = $('#tag_template .tag').clone();
tagElement.attr('id', tag.id);
+
+ tagElement.css('color', 'var(--SmartThemeBodyColor)');
+ tagElement.css('background-color', tag.color);
+
tagElement.find('.tag_name').text(tag.name);
const removeButton = tagElement.find(".tag_remove");
removable ? removeButton.show() : removeButton.hide();
@@ -278,7 +283,7 @@ function applyTagsOnGroupSelect() {
function createTagInput(inputSelector, listSelector) {
$(inputSelector)
- .autocomplete({
+ .autocomplete({
source: (i, o) => findTag(i, o, listSelector),
select: (e, u) => selectTag(e, u, listSelector),
minLength: 0,
@@ -287,21 +292,37 @@ function createTagInput(inputSelector, listSelector) {
}
function onViewTagsListClick() {
+ $('#dialogue_popup').addClass('large_dialogue_popup');
const list = document.createElement('div');
const everything = Object.values(tag_map).flat();
- $(list).append('
Tags
Click on the tag name to edit it.')
+ $(list).append('
Tags
Click on the tag name to edit it.');
+ $(list).append('
Click on color box to assign new color.');
for (const tag of tags) {
const count = everything.filter(x => x == tag.id).length;
const template = $('#tag_view_template .tag_view_item').clone();
-
template.attr('id', tag.id);
template.find('.tag_view_counter_value').text(count);
template.find('.tag_view_name').text(tag.name);
+ template.find('.tag_view_name').addClass('tag');
+ template.find('.tag_view_name').css('background-color', tag.color);
+ const colorPickerId = tag.name + "-tag-color";
+ template.find('.tagColorPickerHolder').html(
+ `
`
+ );
+ template.find('.tag-color').attr('id', colorPickerId);
list.appendChild(template.get(0));
- }
+ setTimeout(function () {
+ document.querySelector(`#${colorPickerId}`).addEventListener('change', (evt) => {
+ onTagColorize(evt);
+ });
+ }, 100);
+
+ $(colorPickerId).color = tag.color;
+
+ }
callPopup(list.outerHTML, 'text');
}
@@ -330,6 +351,18 @@ function onTagRenameInput() {
saveSettingsDebounced();
}
+function onTagColorize(evt) {
+ console.log(evt);
+ const id = $(evt.target).closest('.tag_view_item').attr('id');
+ const newColor = evt.detail.rgba;
+ $(evt.target).parent().parent().find('.tag_view_name').css('background-color', newColor);
+ $(`.tag[id="${id}"]`).css('background-color', newColor);
+ const tag = tags.find(x => x.id === id);
+ tag.color = newColor;
+ console.log(tag);
+ saveSettingsDebounced();
+}
+
$(document).ready(() => {
createTagInput('#tagInput', '#tagList');
createTagInput('#groupTagInput', '#groupTagList');
diff --git a/public/style.css b/public/style.css
index 060d405cc..be9ac9a3c 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1355,6 +1355,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
#dialogue_popup {
width: 500px;
+ max-width: 90svw;
position: absolute;
z-index: 9999;
margin-left: auto;
@@ -1376,6 +1377,16 @@ input[type=search]:focus::-webkit-search-cancel-button {
overflow-y: hidden;
}
+.large_dialogue_popup {
+ height: 90svh;
+ max-width: 90svw;
+}
+
+.height100pSpaceEvenly {
+ align-content: space-evenly;
+ height: 100%;
+}
+
#dialogue_popup_holder {
display: flex;
flex-direction: column;
@@ -2438,7 +2449,7 @@ h5 {
.tag_view_name {
text-align: left;
- flex: 2;
+ /* flex: 2; */
}
.tag_view_counter {
@@ -2448,6 +2459,7 @@ h5 {
.tag_delete {
padding-right: 0;
+ color: var(--SmartThemeBodyColor) !important;
}
.tag {
@@ -2455,9 +2467,9 @@ h5 {
border-style: solid;
border-width: 1px;
box-sizing: border-box;
- color: var(--SmartThemeQuoteColor);
+ color: var(--SmartThemeBodyColor);
background-color: var(--black30a);
- border-color: var(--white30a);
+ border-color: var(--white50a);
padding: 0.2rem 0.3rem;
font-size: calc(var(--mainFontSize) - 5%);
display: flex;
@@ -2467,11 +2479,10 @@ h5 {
width: fit-content;
min-width: 0;
text-shadow: none !important;
+
}
-.tag.selected {
- border-color: var(--white70a);
-}
+
.tag_remove {
cursor: pointer;
@@ -2490,6 +2501,10 @@ h5 {
margin: 5px 0;
}
+#tagList .tag {
+ opacity: 0.6;
+}
+
.tags.tags_inline {
opacity: 0.6;
column-gap: 0.2rem;
@@ -2517,6 +2532,13 @@ h5 {
#rm_tag_filter .tag {
cursor: pointer;
+ opacity: 0.6;
+ filter: brightness(0.8);
+}
+
+.tag.selected {
+ opacity: 1 !important;
+ filter: none !important;
}
body .ui-autocomplete {
From e639666b34a8b3c53d221e5b2ef601c7c0d94edb Mon Sep 17 00:00:00 2001
From: SillyLossy
Date: Wed, 3 May 2023 21:02:23 +0300
Subject: [PATCH 02/32] Move chat renaming logic to server side. Add "quiet"
reply generation mode.
---
public/script.js | 122 +++++++++++++++++++++++-----------
public/scripts/group-chats.js | 19 ++++++
public/scripts/openai.js | 13 ++--
public/scripts/poe.js | 5 +-
server.js | 23 +++++++
5 files changed, 138 insertions(+), 44 deletions(-)
diff --git a/public/script.js b/public/script.js
index 96b2aa0e2..0a0e7d067 100644
--- a/public/script.js
+++ b/public/script.js
@@ -47,6 +47,7 @@ import {
openGroupChat,
editGroup,
deleteGroupChat,
+ renameGroupChat,
} from "./scripts/group-chats.js";
import {
@@ -102,7 +103,7 @@ import {
import { debounce, delay } from "./scripts/utils.js";
import { extension_settings, loadExtensionSettings } from "./scripts/extensions.js";
-import { executeSlashCommands, getSlashCommandsHelp } from "./scripts/slash-commands.js";
+import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js";
import {
tag_map,
tags,
@@ -1169,7 +1170,7 @@ function getStoppingStrings(isImpersonate, addSpace) {
}
function processCommands(message, type) {
- if (type == "regenerate" || type == "swipe") {
+ if (type == "regenerate" || type == "swipe" || type == 'quiet') {
return null;
}
@@ -1477,7 +1478,7 @@ class StreamingProcessor {
}
}
-async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_prompt } = {}) {
+async function Generate(type, { automatic_trigger, force_name2, resolve, reject, quiet_prompt } = {}) {
//console.log('Generate entered');
setGenerationProgress(0);
tokens_already_generated = 0;
@@ -1519,19 +1520,16 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
if (online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id') {
let textareaText;
- if (type !== 'regenerate' && type !== "swipe" && !isImpersonate) {
+ if (type !== 'regenerate' && type !== "swipe" && type !== 'quiet' && !isImpersonate) {
is_send_press = true;
textareaText = $("#send_textarea").val();
- //console.log('Not a Regenerate call, so posting normall with input of: ' +textareaText);
$("#send_textarea").val('').trigger('input');
-
} else {
- //console.log('Regenerate call detected')
textareaText = "";
if (chat.length && chat[chat.length - 1]['is_user']) {//If last message from You
}
- else if (type !== "swipe" && !isImpersonate) {
+ else if (type !== 'quiet' && type !== "swipe" && !isImpersonate) {
chat.length = chat.length - 1;
count_view_mes -= 1;
$('#chat').children().last().hide(500, function () {
@@ -1584,7 +1582,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
//*********************************
//PRE FORMATING STRING
//*********************************
- if (textareaText != "" && !automatic_trigger) {
+ if (textareaText != "" && !automatic_trigger && type !== 'quiet') {
chat[chat.length] = {};
chat[chat.length - 1]['name'] = name1;
chat[chat.length - 1]['is_user'] = true;
@@ -1633,7 +1631,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
if (main_api === 'openai') {
- setOpenAIMessages(coreChat);
+ setOpenAIMessages(coreChat, quiet_prompt);
setOpenAIMessageExamples(mesExamplesArray);
}
@@ -1726,7 +1724,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
// Extension added strings
const allAnchors = getAllExtensionPrompts();
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
- const zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
+ let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
@@ -1747,7 +1745,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
anchorBottom,
charPersonality,
promptBias,
- allAnchors
+ allAnchors,
+ quiet_prompt,
].join('').replace(/\r/gm, '');
return getTokenCount(encodeString, padding_tokens) < this_max_context;
}
@@ -1899,7 +1898,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
charPersonality,
generatedPromtCache,
promptBias,
- allAnchors
+ allAnchors,
+ quiet_prompt,
].join('').replace(/\r/gm, '');
let thisPromtContextSize = getTokenCount(prompt, padding_tokens);
@@ -1968,6 +1968,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
}
}
+ // Add quiet generation prompt at depth 0
+ if (quiet_prompt && quiet_prompt.length) {
+ finalPromt += `\n${quiet_prompt}`;
+ }
+
finalPromt = finalPromt.replace(/\r/gm, '');
if (power_user.collapse_newlines) {
@@ -1977,7 +1982,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
let this_amount_gen = parseInt(amount_gen); // how many tokens the AI will be requested to generate
let this_settings = koboldai_settings[koboldai_setting_names[preset_settings]];
- if (isMultigenEnabled()) {
+ if (isMultigenEnabled() && type !== 'quiet') {
// if nothing has been generated yet..
if (tokens_already_generated === 0) {
// if the max gen setting is > 50...(
@@ -2041,25 +2046,25 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
let prompt = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type);
setInContextMessages(openai_messages_count, type);
- if (isStreamingEnabled()) {
- streamingProcessor.generator = await sendOpenAIRequest(prompt, streamingProcessor.abortController.signal);
+ if (isStreamingEnabled() && type !== 'quiet') {
+ streamingProcessor.generator = await sendOpenAIRequest(type, prompt, streamingProcessor.abortController.signal);
}
else {
- sendOpenAIRequest(prompt).then(onSuccess).catch(onError);
+ sendOpenAIRequest(type, prompt).then(onSuccess).catch(onError);
}
}
else if (main_api == 'kobold' && horde_settings.use_horde) {
generateHorde(finalPromt, generate_data).then(onSuccess).catch(onError);
}
else if (main_api == 'poe') {
- if (isStreamingEnabled()) {
+ if (isStreamingEnabled() && type !== 'quiet') {
streamingProcessor.generator = await generatePoe(type, finalPromt, streamingProcessor.abortController.signal);
}
else {
generatePoe(type, finalPromt).then(onSuccess).catch(onError);
}
}
- else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming) {
+ else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming && type !== 'quiet') {
streamingProcessor.generator = await generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal);
}
else {
@@ -2078,7 +2083,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
}); //end of "if not data error"
}
- if (isStreamingEnabled()) {
+ if (isStreamingEnabled() && type !== 'quiet') {
hideSwipeButtons();
let getMessage = await streamingProcessor.generate();
@@ -2103,7 +2108,6 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
}
function onSuccess(data) {
-
is_send_press = false;
if (!data.error) {
//const getData = await response.json();
@@ -2113,7 +2117,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
//Pygmalion run again
// to make it continue generating so long as it's under max_amount and hasn't signaled
// an end to the character's response via typing "You:" or adding ""
- if (isMultigenEnabled()) {
+ if (isMultigenEnabled() && type !== 'quiet') {
message_already_generated += getMessage;
promptBias = '';
if (shouldContinueMultigen(getMessage)) {
@@ -2147,6 +2151,9 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
if (isImpersonate) {
$('#send_textarea').val(getMessage).trigger('input');
}
+ else if (type == 'quiet') {
+ resolve(getMessage);
+ }
else {
if (!isMultigenEnabled()) {
({ type, getMessage } = saveReply(type, getMessage, this_mes_is_name, title));
@@ -2156,7 +2163,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
}
}
activateSendButtons();
- playMessageSound();
+
+ if (type !== 'quiet') {
+ playMessageSound();
+ }
+
generate_loop_counter = 0;
} else {
++generate_loop_counter;
@@ -2189,6 +2200,10 @@ async function Generate(type, { automatic_trigger, force_name2, quiet, quiet_pro
};
function onError(jqXHR, exception) {
+ if (type == 'quiet') {
+ reject(exception);
+ }
+
$("#send_textarea").removeAttr('disabled');
is_send_press = false;
activateSendButtons();
@@ -2648,7 +2663,7 @@ async function saveChat(chat_name, withMetadata) {
},
...chat,
];
- jQuery.ajax({
+ return jQuery.ajax({
type: "POST",
url: "/savechat",
data: JSON.stringify({
@@ -3764,6 +3779,7 @@ window["SillyTavern"].getContext = function () {
activateSendButtons,
deactivateSendButtons,
saveReply,
+ registerSlashCommand: registerSlashCommand,
};
};
@@ -4647,34 +4663,64 @@ $(document).ready(function () {
$("#renameCharButton").on('click', renameCharacter);
$(document).on("click", ".renameChatButton", async function () {
- var old_filenamefull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
-
- var old_filename = old_filenamefull.substring(0, old_filenamefull.length - 6);
+ const old_filenamefull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
+ const old_filename = old_filenamefull.replace('.jsonl', '');
const popupText = `Enter the new name for the chat:
- !!Using an existing filename will overwrite that file!!
+ !!Using an existing filename will produce an error!!
+ This will break the link between bookmark chats.
No need to add '.jsonl' at the end.
`;
- let newName = await callPopup(popupText, 'input', old_filename);
+ const newName = await callPopup(popupText, 'input', old_filename);
- if (!newName) {
+ if (!newName || newName == old_filename) {
console.log('no new name found, aborting');
return;
}
- const newMetadata = { main_chat: characters[this_chid].chat };
- await saveChat(newName, newMetadata);
- await saveChat(); //is this second save needed?
- chat_file_for_del = old_filenamefull;
- popup_type = 'del_chat';
+ const body = {
+ is_group: !!selected_group,
+ avatar_url: characters[this_chid]?.avatar,
+ original_file: `${old_filename}.jsonl`,
+ renamed_file: `${newName}.jsonl`,
+ }
- setTimeout(function () {
- callPopup('Confirm Delete of Old File After Rename');
- }, 200);
+ try {
+ const response = await fetch('/renamechat', {
+ method: 'POST',
+ body: JSON.stringify(body),
+ headers: getRequestHeaders(),
+ });
+ if (!response.ok) {
+ throw new Error('Unsuccessful request.');
+ }
+ const data = response.json();
+ if (data.error) {
+ throw new Error('Server returned an error.');
+ }
+ if (selected_group) {
+ await renameGroupChat(selected_group, old_filename, newName);
+ }
+ else {
+ if (characters[this_chid].chat == old_filename) {
+ characters[this_chid].chat = newName;
+ saveCharacterDebounced();
+ }
+ }
+
+ reloadCurrentChat();
+
+ await delay(250);
+ $("#option_select_chat").trigger('click');
+ $("#options").hide();
+ } catch {
+ await delay(500);
+ await callPopup('An error has occurred. Chat was not renamed.', 'text');
+ }
});
$("#talkativeness_slider").on("input", function () {
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index f47859d2f..bac5a58e1 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -1159,6 +1159,25 @@ export async function openGroupChat(groupId, chatId) {
await getGroupChat(groupId);
}
+export async function renameGroupChat(groupId, oldChatId, newChatId) {
+ const group = groups.find(x => x.id === groupId);
+
+ if (!group || !group.chats.includes(oldChatId)) {
+ return;
+ }
+
+ if (group.chat_id === oldChatId) {
+ group.chat_id = newChatId;
+ }
+
+ group.chats.splice(group.chats.indexOf(oldChatId), 1);
+ group.chats.push(newChatId);
+ group.past_metadata[newChatId] = (group.past_metadata[oldChatId] || {});
+ delete group.past_metadata[oldChatId];
+
+ await editGroup(groupId, true, true);
+}
+
export async function deleteGroupChat(groupId, chatId) {
const group = groups.find(x => x.id === groupId);
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 617887654..92f4f0d50 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -144,7 +144,7 @@ function setOpenAIOnlineStatus(value) {
is_get_status_openai = value;
}
-function setOpenAIMessages(chat) {
+function setOpenAIMessages(chat, quietPrompt) {
let j = 0;
// clean openai msgs
openai_msgs = [];
@@ -176,6 +176,10 @@ function setOpenAIMessages(chat) {
openai_msgs.splice(i, 0, { "role": 'system', 'content': anchor.trim() })
}
}
+
+ if (quietPrompt) {
+ openai_msgs.splice(0, 0, { role: 'system', content: quietPrompt });
+ }
}
function setOpenAIMessageExamples(mesExamplesArray) {
@@ -481,7 +485,7 @@ function checkQuotaError(data) {
}
}
-async function sendOpenAIRequest(openai_msgs_tosend, signal) {
+async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
// Provide default abort signal
if (!signal) {
signal = new AbortController().signal;
@@ -492,6 +496,7 @@ async function sendOpenAIRequest(openai_msgs_tosend, signal) {
}
let logit_bias = {};
+ const stream = type !== 'quiet' && oai_settings.stream_openai;
if (oai_settings.bias_preset_selected
&& Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
@@ -507,7 +512,7 @@ async function sendOpenAIRequest(openai_msgs_tosend, signal) {
"frequency_penalty": parseFloat(oai_settings.freq_pen_openai),
"presence_penalty": parseFloat(oai_settings.pres_pen_openai),
"max_tokens": oai_settings.openai_max_tokens,
- "stream": oai_settings.stream_openai,
+ "stream": stream,
"reverse_proxy": oai_settings.reverse_proxy,
"logit_bias": logit_bias,
};
@@ -520,7 +525,7 @@ async function sendOpenAIRequest(openai_msgs_tosend, signal) {
signal: signal,
});
- if (oai_settings.stream_openai) {
+ if (stream) {
return async function* streamData() {
const decoder = new TextDecoder();
const reader = response.body.getReader();
diff --git a/public/scripts/poe.js b/public/scripts/poe.js
index e676e06b2..41c4cb437 100644
--- a/public/scripts/poe.js
+++ b/public/scripts/poe.js
@@ -115,7 +115,8 @@ async function generatePoe(type, finalPrompt, signal) {
console.log('Could not jailbreak the bot');
}
- const isImpersonate = type == 'impersonate';
+ const isImpersonate = type === 'impersonate';
+ const isQuiet = type === 'quiet';
if (poe_settings.character_nudge && !isImpersonate) {
let characterNudge = '\n' + substituteParams(poe_settings.character_nudge_message);
@@ -136,7 +137,7 @@ async function generatePoe(type, finalPrompt, signal) {
finalPrompt = sentences.join('');
}
- const reply = await sendMessage(finalPrompt, true, signal);
+ const reply = await sendMessage(finalPrompt, !isQuiet, signal);
got_reply = true;
return reply;
}
diff --git a/server.js b/server.js
index cf65b9b59..82bdab96f 100644
--- a/server.js
+++ b/server.js
@@ -666,6 +666,29 @@ app.post("/createcharacter", urlencodedParser, function (request, response) {
}
});
+app.post('/renamechat', jsonParser, async function (request, response) {
+ if (!request.body || !request.body.original_file || !request.body.renamed_file) {
+ return response.sendStatus(400);
+ }
+
+ const pathToFolder = request.body.is_group
+ ? directories.groupChats
+ : path.join(directories.chats, String(request.body.avatar_url).replace('.png', ''));
+ const pathToOriginalFile = path.join(pathToFolder, request.body.original_file);
+ const pathToRenamedFile = path.join(pathToFolder, request.body.renamed_file);
+ console.log('Old chat name', pathToOriginalFile);
+ console.log('New chat name', pathToRenamedFile);
+
+ if (!fs.existsSync(pathToOriginalFile) || fs.existsSync(pathToRenamedFile)) {
+ console.log('Either Source or Destination files are not available');
+ return response.status(400).send({ error: true });
+ }
+
+ console.log('Successfully renamed.');
+ fs.renameSync(pathToOriginalFile, pathToRenamedFile);
+ return response.send({ ok: true });
+});
+
app.post("/renamecharacter", jsonParser, async function (request, response) {
if (!request.body.avatar_url || !request.body.new_name) {
return response.sendStatus(400);
From 99e8d3612e35005d89664e6a24b6ae7c859df82a Mon Sep 17 00:00:00 2001
From: SillyLossy
Date: Wed, 3 May 2023 21:02:53 +0300
Subject: [PATCH 03/32] Add pre-alpha version of Stable Diffusion extension.
---
.../extensions/stable-diffusion/index.js | 136 ++++++++++++++++++
.../extensions/stable-diffusion/manifest.json | 13 ++
.../extensions/stable-diffusion/style.css | 0
3 files changed, 149 insertions(+)
create mode 100644 public/scripts/extensions/stable-diffusion/index.js
create mode 100644 public/scripts/extensions/stable-diffusion/manifest.json
create mode 100644 public/scripts/extensions/stable-diffusion/style.css
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
new file mode 100644
index 000000000..ea9719fcf
--- /dev/null
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -0,0 +1,136 @@
+import { substituteParams } from "../../../script.js";
+import { getApiUrl, getContext } from "../../extensions.js";
+import { stringFormat } from "../../utils.js";
+
+// Wraps a string into monospace font-face span
+const m = x => `${x}`;
+// Joins an array of strings with ' / '
+const j = a => a.join(' / ');
+
+const generationMode = {
+ CHARACTER: 0,
+ USER: 1,
+ SCENARIO: 2,
+ FREE: 3,
+}
+
+const triggerWords = {
+ [generationMode.CHARACTER]: ['yourself', 'you', 'bot', 'AI', 'character'],
+ [generationMode.USER]: ['me', 'user', 'myself'],
+ [generationMode.SCENARIO]: ['scenario', 'world', 'surroundings', 'scenery'],
+}
+
+const quietPrompts = {
+ [generationMode.CHARACTER]: "Please provide a detailed description of {{char}}'s appearance",
+ [generationMode.USER]: "Please provide a detailed description of {{user}}'s appearance",
+ [generationMode.SCENARIO]: 'Please provide a detailed description of your surroundings and what you are doing right now',
+ [generationMode.FREE]: 'Please provide a detailed and vivid description of {0}',
+}
+
+
+const helpString = [
+ `${m('what')} – requests an SD generation. Supported "what" arguments:`,
+ '',
+ `- ${m(j(triggerWords[generationMode.CHARACTER]))} – AI character image
`,
+ `- ${m(j(triggerWords[generationMode.USER]))} – user character image
`,
+ `- ${m(j(triggerWords[generationMode.SCENARIO]))} – world scenario image
`,
+ '
',
+ `Anything else would trigger a "free mode" with AI describing whatever you prompted.`
+].join('
');
+
+function getGenerationType(prompt) {
+ for (const [key, values] of Object.entries(triggerWords)) {
+ for (const value of values) {
+ if (value.toLowerCase() === prompt.toLowerCase().trim()) {
+ return key;
+ }
+ }
+ }
+
+ return generationMode.FREE;
+}
+
+function getQuietPrompt(mode, trigger) {
+ return substituteParams(stringFormat(quietPrompts[mode], trigger));
+}
+
+function processReply(str) {
+ str = str.replaceAll('"', '')
+ str = str.replaceAll('“', '')
+ str = str.replaceAll('\n', ' ')
+ str = str.trim();
+
+ return str;
+}
+
+async function generatePicture(_, trigger) {
+ if (!trigger || trigger.trim().length === 0) {
+ console.log('Trigger word empty, aborting');
+ return;
+ }
+
+ trigger = trigger.trim();
+ const generationMode = getGenerationType(trigger);
+ console.log('Generation mode', generationMode, 'triggered with', trigger);
+ const quiet_prompt = getQuietPrompt(generationMode, trigger);
+ const context = getContext();
+
+ try {
+ const prompt = processReply(await new Promise(
+ async function promptPromise(resolve, reject) {
+ try {
+ await context.generate('quiet', { resolve, reject, quiet_prompt });
+ }
+ catch {
+ reject();
+ }
+ }));
+
+ context.deactivateSendButtons();
+
+ const url = new URL(getApiUrl());
+ url.pathname = '/api/image';
+ const result = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Bypass-Tunnel-Reminder': 'bypass',
+ },
+ body: JSON.stringify({ prompt: prompt })
+ });
+
+ if (result.ok) {
+ const data = await result.json();
+ const base64Image = `data:image/jpeg;base64,${data.image}`;
+ sendMessage(prompt, base64Image);
+ }
+ } catch {
+ throw new Error('SD prompt text generation failed.')
+ }
+ finally {
+ context.activateSendButtons();
+ }
+}
+
+async function sendMessage(prompt, image) {
+ const context = getContext();
+ const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
+ const message = {
+ name: context.name2,
+ is_user: false,
+ is_name: true,
+ send_date: Date.now(),
+ mes: messageText,
+ extra: {
+ image: image,
+ title: prompt,
+ },
+ };
+ context.chat.push(message);
+ context.addOneMessage(message);
+ context.saveChat();
+}
+
+jQuery(() => {
+ getContext().registerSlashCommand('sd', generatePicture, ['picture', 'image'], helpString, true, true);
+});
\ No newline at end of file
diff --git a/public/scripts/extensions/stable-diffusion/manifest.json b/public/scripts/extensions/stable-diffusion/manifest.json
new file mode 100644
index 000000000..13f8db021
--- /dev/null
+++ b/public/scripts/extensions/stable-diffusion/manifest.json
@@ -0,0 +1,13 @@
+{
+ "display_name": "Stable Diffusion",
+ "loading_order": 10,
+ "requires": [
+ "sd"
+ ],
+ "optional": [],
+ "js": "index.js",
+ "css": "style.css",
+ "author": "Cohee#1207",
+ "version": "1.0.0",
+ "homePage": "https://github.com/Cohee1207/SillyTavern"
+}
\ No newline at end of file
diff --git a/public/scripts/extensions/stable-diffusion/style.css b/public/scripts/extensions/stable-diffusion/style.css
new file mode 100644
index 000000000..e69de29bb
From 437cf59a4841fa802453cfc8f447bdb37a7edf41 Mon Sep 17 00:00:00 2001
From: SillyLossy
Date: Wed, 3 May 2023 23:49:57 +0300
Subject: [PATCH 04/32] Add more options to SD plugin
---
public/scripts/extensions.js | 1 +
.../scripts/extensions/expressions/index.js | 1 +
.../extensions/stable-diffusion/index.js | 213 +++++++++++++++++-
.../extensions/stable-diffusion/style.css | 3 +
4 files changed, 208 insertions(+), 10 deletions(-)
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index a049c9319..51d412242 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -25,6 +25,7 @@ const extension_settings = {
expressions: {},
dice: {},
tts: {},
+ sd: {},
};
let modules = [];
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index 6e299bd21..89b789139 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -381,4 +381,5 @@ function onClickExpressionImage() {
addExpressionImage();
addSettings();
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
+ moduleWorkerWrapper();
})();
\ No newline at end of file
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index ea9719fcf..b17977e17 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -1,5 +1,5 @@
-import { substituteParams } from "../../../script.js";
-import { getApiUrl, getContext } from "../../extensions.js";
+import { substituteParams, saveSettingsDebounced } from "../../../script.js";
+import { getApiUrl, getContext, extension_settings, defaultRequestArgs } from "../../extensions.js";
import { stringFormat } from "../../utils.js";
// Wraps a string into monospace font-face span
@@ -7,6 +7,11 @@ const m = x => `${x}`;
// Joins an array of strings with ' / '
const j = a => a.join(' / ');
+const postHeaders = {
+'Content-Type': 'application/json',
+'Bypass-Tunnel-Reminder': 'bypass',
+};
+
const generationMode = {
CHARACTER: 0,
USER: 1,
@@ -27,7 +32,6 @@ const quietPrompts = {
[generationMode.FREE]: 'Please provide a detailed and vivid description of {0}',
}
-
const helpString = [
`${m('what')} – requests an SD generation. Supported "what" arguments:`,
'',
@@ -35,9 +39,152 @@ const helpString = [
`- ${m(j(triggerWords[generationMode.USER]))} – user character image
`,
`- ${m(j(triggerWords[generationMode.SCENARIO]))} – world scenario image
`,
'
',
- `Anything else would trigger a "free mode" with AI describing whatever you prompted.`
+ `Anything else would trigger a "free mode" with AI describing whatever you prompted.`,
].join('
');
+const defaultSettings = {
+ // CFG Scale
+ scale_min: 1,
+ scale_max: 30,
+ scale_step: 0.5,
+ scale: 7,
+
+ // Sampler steps
+ steps_min: 1,
+ steps_max: 150,
+ steps_step: 1,
+ steps: 20,
+
+ // Image dimensions (Width & Height)
+ dimension_min: 64,
+ dimension_max: 2048,
+ dimension_step: 64,
+ width: 512,
+ height: 512,
+
+ prompt_prefix: 'best quality, absurdres, masterpiece, detailed, intricate, colorful,',
+ negative_prompt: 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry',
+ sampler: 'DDIM',
+ model: '',
+}
+
+async function loadSettings() {
+ if (Object.keys(extension_settings.sd).length === 0) {
+ Object.assign(extension_settings.sd, defaultSettings);
+ }
+
+ $('#sd_scale').val(extension_settings.sd.scale).trigger('input');
+ $('#sd_steps').val(extension_settings.sd.steps).trigger('input');
+ $('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
+ $('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input');
+ $('#sd_width').val(extension_settings.sd.width).trigger('input');
+ $('#sd_height').val(extension_settings.sd.height).trigger('input');
+
+ await Promise.all([loadSamplers, loadModels]);
+}
+
+function onScaleInput() {
+ extension_settings.sd.scale = Number($('#sd_scale').val());
+ $('#sd_scale_value').text(extension_settings.sd.scale.toFixed(1));
+ saveSettingsDebounced();
+}
+
+function onStepsInput() {
+ extension_settings.sd.steps = Number($('#sd_steps').val());
+ $('#sd_steps_value').text(extension_settings.sd.steps);
+ saveSettingsDebounced();
+}
+
+function onPromptPrefixInput() {
+ extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val();
+ saveSettingsDebounced();
+}
+
+function onNegativePromptInput() {
+ extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val();
+ saveSettingsDebounced();
+}
+
+function onSamplerChange() {
+ extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val();
+ saveSettingsDebounced();
+}
+
+function onWidthInput() {
+ extension_settings.sd.width = Number($('#sd_width').val());
+ $('#sd_width_value').text(extension_settings.sd.width);
+ saveSettingsDebounced();
+}
+
+function onHeightInput() {
+ extension_settings.sd.height = Number($('#sd_height').val());
+ $('#sd_height_value').text(extension_settings.sd.height);
+ saveSettingsDebounced();
+}
+
+async function onModelChange() {
+ extension_settings.sd.model = $('#sd_model').find(':selected').val();
+ saveSettingsDebounced();
+
+ const url = new URL(getApiUrl());
+ url.pathname = '/api/image/model';
+ const getCurrentModelResult = await fetch(url, {
+ method: 'POST',
+ headers: postHeaders,
+ body: JSON.stringify({ model: extension_settings.sd.model }),
+ });
+
+ if (getCurrentModelResult.ok) {
+ console.log('Model successfully updated on SD remote.');
+ }
+}
+
+async function loadSamplers() {
+ const url = new URL(getApiUrl());
+ url.pathname = '/api/image/samplers';
+ const result = await fetch(url, defaultRequestArgs);
+
+ if (result.ok) {
+ const data = await result.json();
+ const samplers = data.samplers;
+
+ for (const sampler of samplers) {
+ const option = document.createElement('option');
+ option.innerText = sampler;
+ option.value = sampler;
+ option.selected = sampler === extension_settings.sd.sampler;
+ $('#sd_sampler').append(option);
+ }
+ }
+}
+
+async function loadModels() {
+ const url = new URL(getApiUrl());
+ url.pathname = '/api/image/model';
+ const getCurrentModelResult = await fetch(url, defaultRequestArgs);
+
+ if (getCurrentModelResult.ok) {
+ const data = await getCurrentModelResult.json();
+ extension_settings.sd.model = data.model;
+ }
+
+ url.pathname = '/api/image/models';
+ const getModelsResult = await fetch(url, defaultRequestArgs);
+
+ if (getModelsResult.ok) {
+ const data = await getModelsResult.json();
+ const models = data.models;
+
+ for (const model of models) {
+ const option = document.createElement('option');
+ option.innerText = model;
+ option.value = model;
+ option.selected = model === extension_settings.sd.model;
+ $('#sd_model').append(option);
+ }
+ }
+}
+
function getGenerationType(prompt) {
for (const [key, values] of Object.entries(triggerWords)) {
for (const value of values) {
@@ -92,11 +239,18 @@ async function generatePicture(_, trigger) {
url.pathname = '/api/image';
const result = await fetch(url, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Bypass-Tunnel-Reminder': 'bypass',
- },
- body: JSON.stringify({ prompt: prompt })
+ headers: postHeaders,
+ body: JSON.stringify({
+ prompt: prompt,
+ sampler: extension_settings.sd.sampler,
+ steps: extension_settings.sd.steps,
+ scale: extension_settings.sd.scale,
+ model: extension_settings.sd.model,
+ width: extension_settings.sd.width,
+ height: extension_settings.sd.height,
+ prompt_prefix: extension_settings.sd.prompt_prefix,
+ negative_prompt: extension_settings.sd.negative_prompt,
+ }),
});
if (result.ok) {
@@ -131,6 +285,45 @@ async function sendMessage(prompt, image) {
context.saveChat();
}
-jQuery(() => {
+jQuery(async () => {
getContext().registerSlashCommand('sd', generatePicture, ['picture', 'image'], helpString, true, true);
+
+ const settingsHtml = `
+
+
`;
+
+ $('#extensions_settings').append(settingsHtml);
+ $('#sd_scale').on('input', onScaleInput);
+ $('#sd_steps').on('input', onStepsInput);
+ $('#sd_model').on('change', onModelChange);
+ $('#sd_sampler').on('change', onSamplerChange);
+ $('#sd_prompt_prefix').on('input', onPromptPrefixInput);
+ $('#sd_negative_prompt').on('input', onNegativePromptInput);
+ $('#sd_width').on('input', onWidthInput);
+ $('#sd_height').on('input', onHeightInput);
+ await loadSettings();
});
\ No newline at end of file
diff --git a/public/scripts/extensions/stable-diffusion/style.css b/public/scripts/extensions/stable-diffusion/style.css
index e69de29bb..0fe6446f8 100644
--- a/public/scripts/extensions/stable-diffusion/style.css
+++ b/public/scripts/extensions/stable-diffusion/style.css
@@ -0,0 +1,3 @@
+.sd_settings label {
+ display: block;
+}
\ No newline at end of file
From 9c62783e083985b4f77945b2f9253b3159a6a07e Mon Sep 17 00:00:00 2001
From: SillyLossy
Date: Thu, 4 May 2023 01:00:00 +0300
Subject: [PATCH 05/32] Editable slider labels
---
public/index.html | 130 +++++++++++++++++++++--------
public/script.js | 43 +++++++++-
public/scripts/RossAscends-mods.js | 2 +-
public/scripts/utils.js | 53 +++++++++++-
public/style.css | 7 +-
5 files changed, 197 insertions(+), 38 deletions(-)
diff --git a/public/index.html b/public/index.html
index 73bd5188c..3dc46a2e8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -153,7 +153,9 @@