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/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/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');