mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #1790 from Technologicat/talkinghead-fixes-feb2024
Talkinghead fixes feb2024
This commit is contained in:
@ -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.');
|
||||||
})();
|
})();
|
||||||
|
Reference in New Issue
Block a user