diff --git a/public/script.js b/public/script.js
index bfe9fe57d..3b3de2e7e 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3548,7 +3548,7 @@ export function getBiasStrings(textareaText, type) {
*/
function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
const isNarratorType = chatItem?.extra?.type === system_message_types.NARRATOR;
- const characterName = (selected_group || chatItem.force_avatar) ? chatItem.name : name2;
+ const characterName = chatItem?.name ? chatItem.name : name2;
const itemName = chatItem.is_user ? chatItem['name'] : characterName;
const shouldPrependName = !isNarratorType;
diff --git a/public/scripts/extensions/assets/confirm.html b/public/scripts/extensions/assets/confirm.html
new file mode 100644
index 000000000..1dd52f26c
--- /dev/null
+++ b/public/scripts/extensions/assets/confirm.html
@@ -0,0 +1,9 @@
+
+ Are you sure you want to connect to '{{url}}'?
+
+
+
+
diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js
index 721be64f5..78ea75061 100644
--- a/public/scripts/extensions/assets/index.js
+++ b/public/scripts/extensions/assets/index.js
@@ -4,11 +4,11 @@ TODO:
//const DEBUG_TONY_SAMA_FORK_MODE = true
import { getRequestHeaders, callPopup } from "../../../script.js";
-import { deleteExtension, extensionNames, installExtension } from "../../extensions.js";
-import { isValidUrl } from "../../utils.js";
+import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from "../../extensions.js";
+import { getStringHash, isValidUrl } from "../../utils.js";
export { MODULE_NAME };
-const MODULE_NAME = 'Assets';
+const MODULE_NAME = 'assets';
const DEBUG_PREFIX = " ";
let previewAudio = null;
let ASSETS_JSON_URL = "https://raw.githubusercontent.com/SillyTavern/SillyTavern-Content/main/index.json"
@@ -75,18 +75,18 @@ function downloadAssetsList(url) {
label.addClass("fa-check");
this.classList.remove('asset-download-button-loading');
element.on("click", assetDelete);
- element.on("mouseenter", function(){
+ element.on("mouseenter", function () {
label.removeClass("fa-check");
label.addClass("fa-trash");
label.addClass("redOverlayGlow");
- }).on("mouseleave", function(){
+ }).on("mouseleave", function () {
label.addClass("fa-check");
label.removeClass("fa-trash");
label.removeClass("redOverlayGlow");
});
};
- const assetDelete = async function() {
+ const assetDelete = async function () {
element.off("click");
await deleteAsset(assetType, asset["id"]);
label.removeClass("fa-check");
@@ -102,11 +102,11 @@ function downloadAssetsList(url) {
label.toggleClass("fa-download");
label.toggleClass("fa-check");
element.on("click", assetDelete);
- element.on("mouseenter", function(){
+ element.on("mouseenter", function () {
label.removeClass("fa-check");
label.addClass("fa-trash");
label.addClass("redOverlayGlow");
- }).on("mouseleave", function(){
+ }).on("mouseleave", function () {
label.addClass("fa-check");
label.removeClass("fa-trash");
label.removeClass("redOverlayGlow");
@@ -274,24 +274,35 @@ async function updateCurrentAssets() {
// This function is called when the extension is loaded
jQuery(async () => {
// This is an example of loading HTML from a file
- const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
+ const windowHtml = $(renderExtensionTemplate(MODULE_NAME, 'window', {}));
const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
assetsJsonUrl.val(ASSETS_JSON_URL);
const connectButton = windowHtml.find('#assets-connect-button');
connectButton.on("click", async function () {
- const confirmation = await callPopup(`Are you sure you want to connect to '${assetsJsonUrl.val()}'?`, 'confirm')
+ const url = String(assetsJsonUrl.val());
+ const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`;
+ const skipConfirm = localStorage.getItem(rememberKey) === 'true';
+
+ const template = renderExtensionTemplate(MODULE_NAME, 'confirm', { url });
+ const confirmation = skipConfirm || await callPopup(template, 'confirm');
+
if (confirmation) {
try {
+ if (!skipConfirm) {
+ const rememberValue = Boolean($('#assets-remember').prop('checked'));
+ localStorage.setItem(rememberKey, String(rememberValue));
+ }
+
console.debug(DEBUG_PREFIX, "Confimation, loading assets...");
- downloadAssetsList(assetsJsonUrl.val());
+ downloadAssetsList(url);
connectButton.removeClass("fa-plug-circle-exclamation");
connectButton.removeClass("redOverlayGlow");
connectButton.addClass("fa-plug-circle-check");
} catch (error) {
console.error('Error:', error);
- toastr.error(`Cannot get assets list from ${assetsJsonUrl.val()}`);
+ toastr.error(`Cannot get assets list from ${url}`);
connectButton.removeClass("fa-plug-circle-check");
connectButton.addClass("fa-plug-circle-exclamation");
connectButton.removeClass("redOverlayGlow");
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index aa1b40def..627742b73 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -1,4 +1,4 @@
-import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
+import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced, this_chid } from "../../../script.js";
import { dragElement, isMobile } from "../../RossAscends-mods.js";
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
import { loadMovingUIState, power_user } from "../../power-user.js";
@@ -493,24 +493,6 @@ async function moduleWorker() {
return;
}
- // character changed
- if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
- removeExpression();
- spriteCache = {};
-
- //clear expression
- let imgElement = document.getElementById('expression-image');
- if (imgElement && imgElement instanceof HTMLImageElement) {
- imgElement.src = "";
- }
-
- //set checkbox to global var
- $('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
- if (extension_settings.expressions.talkinghead) {
- setTalkingHeadState(extension_settings.expressions.talkinghead);
- }
- }
-
const vnMode = isVisualNovelMode();
const vnWrapperVisible = $('#visual-novel-wrapper').is(':visible');
@@ -531,7 +513,7 @@ async function moduleWorker() {
}
const currentLastMessage = getLastCharacterMessage();
- let spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
+ let spriteFolderName = context.groupId ? getSpriteFolderName(currentLastMessage, currentLastMessage.name) : getSpriteFolderName();
// character has no expressions or it is not loaded
if (Object.keys(spriteCache).length === 0) {
@@ -1492,6 +1474,25 @@ function setExpressionOverrideHtml(forceClear = false) {
moduleWorker();
dragElement($("#expression-holder"))
eventSource.on(event_types.CHAT_CHANGED, () => {
+ // character changed
+ const context = getContext();
+ if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
+ removeExpression();
+ spriteCache = {};
+
+ //clear expression
+ let imgElement = document.getElementById('expression-image');
+ if (imgElement && imgElement instanceof HTMLImageElement) {
+ imgElement.src = "";
+ }
+
+ //set checkbox to global var
+ $('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
+ if (extension_settings.expressions.talkinghead) {
+ setTalkingHeadState(extension_settings.expressions.talkinghead);
+ }
+ }
+
setExpressionOverrideHtml();
if (isVisualNovelMode()) {
diff --git a/public/scripts/horde.js b/public/scripts/horde.js
index ffdf3a630..f62d5c18d 100644
--- a/public/scripts/horde.js
+++ b/public/scripts/horde.js
@@ -73,6 +73,11 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
for (const model of selectedModels) {
for (const worker of workers) {
if (model.cluster == worker.cluster && worker.models.includes(model.name)) {
+ // Skip workers that are not trusted if the option is enabled
+ if (horde_settings.trusted_workers_only && !worker.trusted) {
+ continue;
+ }
+
availableWorkers.push(worker);
}
}
@@ -92,6 +97,14 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
return { maxContextLength, maxLength };
}
+function setContextSizePreview() {
+ if (horde_settings.models.length) {
+ adjustHordeGenerationParams(max_context, amount_gen);
+ } else {
+ $("#adjustedHordeParams").text(`Context: --, Response: --`);
+ }
+}
+
async function generateHorde(prompt, params, signal, reportProgress) {
validateHordeModel();
delete params.prompt;
@@ -213,6 +226,8 @@ async function getHordeModels() {
if (horde_settings.models.length && models.filter(m => horde_settings.models.includes(m.name)).length === 0) {
horde_settings.models = [];
}
+
+ setContextSizePreview();
}
function loadHordeSettings(settings) {
@@ -263,26 +278,19 @@ jQuery(function () {
$("#horde_auto_adjust_response_length").on("input", function () {
horde_settings.auto_adjust_response_length = !!$(this).prop("checked");
- if (horde_settings.models.length) {
- adjustHordeGenerationParams(max_context, amount_gen)
- } else {
- $("#adjustedHordeParams").text(`Context: --, Response: --`)
- }
+ setContextSizePreview();
saveSettingsDebounced();
});
$("#horde_auto_adjust_context_length").on("input", function () {
horde_settings.auto_adjust_context_length = !!$(this).prop("checked");
- if (horde_settings.models.length) {
- adjustHordeGenerationParams(max_context, amount_gen);
- } else {
- $("#adjustedHordeParams").text(`Context: --, Response: --`)
- }
+ setContextSizePreview();
saveSettingsDebounced();
});
$("#horde_trusted_workers_only").on("input", function () {
horde_settings.trusted_workers_only = !!$(this).prop("checked");
+ setContextSizePreview();
saveSettingsDebounced();
})
@@ -313,3 +321,4 @@ jQuery(function () {
});
}
})
+
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 2d8ffbd47..45bce64bd 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -1242,7 +1242,7 @@ export function renderStoryString(params) {
output = output.trimStart();
// add a newline to the end of the story string if it doesn't have one
- if (!output.endsWith('\n')) {
+ if (output.length > 0 && !output.endsWith('\n')) {
output += '\n';
}
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index fd65cae34..861bbe0c5 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -21,9 +21,12 @@ import {
reloadCurrentChat,
sendMessageAsUser,
name1,
+ Generate,
+ this_chid,
+ setCharacterName,
} from "../script.js";
import { getMessageTimeStamp } from "./RossAscends-mods.js";
-import { resetSelectedGroup } from "./group-chats.js";
+import { resetSelectedGroup, selected_group } from "./group-chats.js";
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
import { chat_styles, power_user } from "./power-user.js";
import { autoSelectPersona } from "./personas.js";
@@ -131,6 +134,7 @@ parser.addCommand('flat', setFlatModeCallback, ['default'], ' – sets the messa
parser.addCommand('continue', continueChatCallback, ['cont'], ' – continues the last message in the chat', true, true);
parser.addCommand('go', goToCharacterCallback, ['char'], '(name) – opens up a chat with the character by its name', true, true);
parser.addCommand('sysgen', generateSystemMessage, [], '(prompt) – generates a system message using a specified prompt', true, true);
+parser.addCommand('ask', askCharacter, [], '(prompt) – asks a specified character card a prompt', true, true);
parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '(name) – deletes all messages attributed to a specified name', true, true);
parser.addCommand('send', sendUserMessageCallback, ['add'], '(text) – adds a user message to the chat log without triggering a generation', true, true);
@@ -138,6 +142,88 @@ const NARRATOR_NAME_KEY = 'narrator_name';
const NARRATOR_NAME_DEFAULT = 'System';
export const COMMENT_NAME_DEFAULT = 'Note';
+async function askCharacter(_, text) {
+ // Prevent generate recursion
+ $('#send_textarea').val('');
+
+ // Not supported in group chats
+ // TODO: Maybe support group chats?
+ if (selected_group) {
+ toastr.error("Cannot run this command in a group chat!");
+ return;
+ }
+
+ if (!text) {
+ console.warn('WARN: No text provided for /ask command')
+ }
+
+ const parts = text.split('\n');
+ if (parts.length <= 1) {
+ toastr.warning('Both character name and message are required. Separate them with a new line.');
+ return;
+ }
+
+ // Grabbing the message
+ const name = parts.shift().trim();
+ let mesText = parts.join('\n').trim();
+ const prevChId = this_chid;
+
+ // Find the character
+ const chId = characters.findIndex((e) => e.name === name);
+ if (!characters[chId] || chId === -1) {
+ toastr.error("Character not found.");
+ return;
+ }
+
+ // Override character and send a user message
+ setCharacterId(chId);
+
+ // TODO: Maybe look up by filename instead of name
+ const character = characters[chId];
+ let force_avatar, original_avatar;
+
+ if (character && character.avatar !== 'none') {
+ force_avatar = getThumbnailUrl('avatar', character.avatar);
+ original_avatar = character.avatar;
+ }
+ else {
+ force_avatar = default_avatar;
+ original_avatar = default_avatar;
+ }
+
+ setCharacterName(character.name);
+
+ sendMessageAsUser(mesText)
+
+ const restoreCharacter = () => {
+ setCharacterId(prevChId);
+ setCharacterName(characters[prevChId].name);
+
+ // Only force the new avatar if the character name is the same
+ // This skips if an error was fired
+ const lastMessage = chat[chat.length - 1];
+ if (lastMessage && lastMessage?.name === character.name) {
+ lastMessage.force_avatar = force_avatar;
+ lastMessage.original_avatar = original_avatar;
+ }
+
+ // Kill this callback once the event fires
+ eventSource.removeListener(event_types.CHARACTER_MESSAGE_RENDERED, restoreCharacter)
+ }
+
+ // Run generate and restore previous character on error
+ try {
+ toastr.info(`Asking ${character.name} something...`);
+ await Generate('ask_command')
+ } catch {
+ restoreCharacter()
+ }
+
+ // Restore previous character once message renders
+ // Hack for generate
+ eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, restoreCharacter);
+}
+
async function sendUserMessageCallback(_, text) {
if (!text) {
console.warn('WARN: No text provided for /send command');