Merge pull request #1790 from Technologicat/talkinghead-fixes-feb2024

Talkinghead fixes feb2024
This commit is contained in:
Cohee
2024-02-05 01:33:39 +02:00
committed by GitHub

View File

@ -58,6 +58,17 @@ function isTalkingHeadEnabled() {
return extension_settings.expressions.talkinghead && !extension_settings.expressions.local; return extension_settings.expressions.talkinghead && !extension_settings.expressions.local;
} }
/**
* Toggles Talkinghead mode on/off.
*
* Implements the `/th` slash command, which is meant to be bound to a Quick Reply button
* as a quick way to switch Talkinghead on or off (e.g. to conserve GPU resources when AFK
* for a long time).
*/
function toggleTalkingHeadCommand(_) {
setTalkingHeadState(!extension_settings.expressions.talkinghead);
}
function isVisualNovelMode() { function isVisualNovelMode() {
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId); return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
} }
@ -389,13 +400,14 @@ function onExpressionsShowDefaultInput() {
} }
/** /**
* Stops animating a talkinghead. * Stops animating Talkinghead.
*/ */
async function unloadTalkingHead() { async function unloadTalkingHead() {
if (!modules.includes('talkinghead')) { if (!modules.includes('talkinghead')) {
console.debug('talkinghead module is disabled'); console.debug('talkinghead module is disabled');
return; return;
} }
console.debug('expressions: Stopping Talkinghead');
try { try {
const url = new URL(getApiUrl()); const url = new URL(getApiUrl());
@ -418,6 +430,7 @@ async function loadTalkingHead() {
console.debug('talkinghead module is disabled'); console.debug('talkinghead module is disabled');
return; return;
} }
console.debug('expressions: Starting Talkinghead');
const spriteFolderName = getSpriteFolderName(); const spriteFolderName = getSpriteFolderName();
@ -528,8 +541,7 @@ function handleImageChange() {
return; return;
} }
if (isTalkingHeadEnabled()) { if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
// Method get IP of endpoint
const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`; const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`;
$('#expression-holder').css({ display: '' }); $('#expression-holder').css({ display: '' });
if (imgElement.src !== talkingheadResultFeedSrc) { if (imgElement.src !== talkingheadResultFeedSrc) {
@ -545,20 +557,26 @@ function handleImageChange() {
} }
}) })
.catch(error => { .catch(error => {
console.error(error); // Log the error if necessary console.error(error);
}); });
} }
} }
} else { } else {
imgElement.src = ''; //remove incase char doesnt have expressions imgElement.src = ''; // remove in case char doesn't have expressions
setExpression(getContext().name2, FALLBACK_EXPRESSION, true);
// When switching Talkinghead off, force-set the character to the last known expression, if any.
// This preserves the same expression Talkinghead had at the moment it was switched off.
const charName = getContext().name2;
const last = lastExpression[charName];
const targetExpression = last ? last : FALLBACK_EXPRESSION;
setExpression(charName, targetExpression, true);
} }
} }
async function moduleWorker() { async function moduleWorker() {
const context = getContext(); const context = getContext();
// Hide and disable talkinghead while in local mode // Hide and disable Talkinghead while in local mode
$('#image_type_block').toggle(!extension_settings.expressions.local); $('#image_type_block').toggle(!extension_settings.expressions.local);
if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) { if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) {
@ -691,7 +709,7 @@ async function moduleWorker() {
} }
/** /**
* Starts/stops talkinghead talking animation. * Starts/stops Talkinghead talking animation.
* *
* Talking starts only when all the following conditions are met: * Talking starts only when all the following conditions are met:
* - The LLM is currently streaming its output. * - The LLM is currently streaming its output.
@ -700,10 +718,13 @@ async function moduleWorker() {
* *
* In all other cases, talking stops. * In all other cases, talking stops.
* *
* A talkinghead API call is made only when the talking state changes. * A Talkinghead API call is made only when the talking state changes.
*
* Note that also the TTS system, if enabled, starts/stops the Talkinghead talking animation.
* See `talkingAnimation` in `SillyTavern/public/scripts/extensions/tts/index.js`.
*/ */
async function updateTalkingState() { async function updateTalkingState() {
// Don't bother if talkinghead is disabled or not loaded. // Don't bother if Talkinghead is disabled or not loaded.
if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) { if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
return; return;
} }
@ -727,7 +748,7 @@ async function updateTalkingState() {
newTalkingState = false; newTalkingState = false;
} }
try { try {
// Call the talkinghead API only if the talking state changed. // Call the Talkinghead API only if the talking state changed.
if (newTalkingState !== lastTalkingState) { if (newTalkingState !== lastTalkingState) {
console.debug(`updateTalkingState: calling ${url.pathname}`); console.debug(`updateTalkingState: calling ${url.pathname}`);
await doExtrasFetch(url); await doExtrasFetch(url);
@ -787,6 +808,7 @@ function getSpriteFolderName(characterMessage = null, characterName = null) {
} }
function setTalkingHeadState(newState) { function setTalkingHeadState(newState) {
console.debug(`expressions: New talkinghead state: ${newState}`);
extension_settings.expressions.talkinghead = newState; // Store setting extension_settings.expressions.talkinghead = newState; // Store setting
saveSettingsDebounced(); saveSettingsDebounced();
@ -871,12 +893,12 @@ async function setSpriteSlashCommand(_, spriteId) {
spriteId = spriteId.trim().toLowerCase(); spriteId = spriteId.trim().toLowerCase();
// In talkinghead mode, don't check for the existence of the sprite // In Talkinghead mode, don't check for the existence of the sprite
// (emotion names are the same as for sprites, but it only needs "talkinghead.png"). // (emotion names are the same as for sprites, but it only needs "talkinghead.png").
const currentLastMessage = getLastCharacterMessage(); const currentLastMessage = getLastCharacterMessage();
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name); const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
let label = spriteId; let label = spriteId;
if (!isTalkingHeadEnabled()) { if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
await validateImages(spriteFolderName); await validateImages(spriteFolderName);
// Fuzzy search for sprite // Fuzzy search for sprite
@ -1144,7 +1166,7 @@ async function getExpressionsList() {
} }
async function setExpression(character, expression, force) { async function setExpression(character, expression, force) {
if (!isTalkingHeadEnabled()) { if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
console.debug('entered setExpressions'); console.debug('entered setExpressions');
await validateImages(character); await validateImages(character);
const img = $('img.expression'); const img = $('img.expression');
@ -1255,8 +1277,8 @@ async function setExpression(character, expression, force) {
document.getElementById('expression-holder').style.display = ''; document.getElementById('expression-holder').style.display = '';
} else { } else {
// Set the talkinghead emotion to the specified expression // Set the Talkinghead emotion to the specified expression
// TODO: For now, talkinghead emote only supported when VN mode is off; see also updateVisualNovelMode. // TODO: For now, Talkinghead emote only supported when VN mode is off; see also updateVisualNovelMode.
try { try {
let result = await isTalkingHeadAvailable(); let result = await isTalkingHeadAvailable();
if (result) { if (result) {
@ -1409,8 +1431,8 @@ async function onClickExpressionUpload(event) {
// Reset the input // Reset the input
e.target.form.reset(); e.target.form.reset();
// In talkinghead mode, when a new talkinghead image is uploaded, refresh the live char. // In Talkinghead mode, when a new talkinghead image is uploaded, refresh the live char.
if (isTalkingHeadEnabled() && id === 'talkinghead') { if (id === 'talkinghead' && isTalkingHeadEnabled() && modules.includes('talkinghead')) {
await loadTalkingHead(); await loadTalkingHead();
} }
}; };
@ -1520,6 +1542,11 @@ async function onClickExpressionUploadPackButton() {
// Reset the input // Reset the input
e.target.form.reset(); e.target.form.reset();
// In Talkinghead mode, refresh the live char.
if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
await loadTalkingHead();
}
}; };
$('#expression_upload_pack') $('#expression_upload_pack')
@ -1657,6 +1684,34 @@ async function fetchImagesNoCache() {
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
} }
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.
// We currently do this via loading/unloading. Could be improved by adding new pause/unpause endpoints to Extras.
document.addEventListener("visibilitychange", function (event) {
let pageIsVisible;
if (document.hidden) {
console.debug('expressions: SillyTavern is now hidden');
pageIsVisible = false;
} else {
console.debug('expressions: SillyTavern is now visible');
pageIsVisible = true;
}
if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
isTalkingHeadAvailable().then(result => {
if (result) {
if (pageIsVisible) {
loadTalkingHead();
} else {
unloadTalkingHead();
}
handleImageChange(); // Change image as needed
} else {
//console.log("talkinghead does not exist.");
}
});
}
});
addExpressionImage(); addExpressionImage();
addVisualNovelMode(); addVisualNovelMode();
addSettings(); addSettings();
@ -1664,7 +1719,7 @@ async function fetchImagesNoCache() {
const updateFunction = wrapper.update.bind(wrapper); const updateFunction = wrapper.update.bind(wrapper);
setInterval(updateFunction, UPDATE_INTERVAL); setInterval(updateFunction, UPDATE_INTERVAL);
moduleWorker(); moduleWorker();
// For setting the talkinghead talking animation on/off quickly enough for realtime use, we need another timer on a shorter schedule. // For setting the Talkinghead talking animation on/off quickly enough for realtime use, we need another timer on a shorter schedule.
const wrapperTalkingState = new ModuleWorkerWrapper(updateTalkingState); const wrapperTalkingState = new ModuleWorkerWrapper(updateTalkingState);
const updateTalkingStateFunction = wrapperTalkingState.update.bind(wrapperTalkingState); const updateTalkingStateFunction = wrapperTalkingState.update.bind(wrapperTalkingState);
setInterval(updateTalkingStateFunction, TALKINGCHECK_UPDATE_INTERVAL); setInterval(updateTalkingStateFunction, TALKINGCHECK_UPDATE_INTERVAL);
@ -1701,4 +1756,5 @@ async function fetchImagesNoCache() {
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">(spriteId)</span> force sets the sprite for the current character', true, true); registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">(spriteId)</span> force sets the sprite for the current character', true, true);
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true); registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '<span class="monospace">(charName)</span> Returns the last set sprite / expression for the named character.', true, true); registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '<span class="monospace">(charName)</span> Returns the last set sprite / expression for the named character.', true, true);
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], ' Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.');
})(); })();