Merge branch 'staging' into parser-v2

This commit is contained in:
LenAnderson
2024-04-07 16:06:21 -04:00
18 changed files with 617 additions and 414 deletions

View File

@@ -11,7 +11,7 @@ const MODULE_NAME = 'expressions';
const UPDATE_INTERVAL = 2000;
const STREAMING_UPDATE_INTERVAL = 6000;
const TALKINGCHECK_UPDATE_INTERVAL = 500;
const FALLBACK_EXPRESSION = 'joy';
const DEFAULT_FALLBACK_EXPRESSION = 'joy';
const DEFAULT_EXPRESSIONS = [
'talkinghead',
'admiration',
@@ -58,6 +58,14 @@ function isTalkingHeadEnabled() {
return extension_settings.expressions.talkinghead && !extension_settings.expressions.local;
}
/**
* Returns the fallback expression if explicitly chosen, otherwise the default one
* @returns {string} expression name
*/
function getFallbackExpression() {
return extension_settings.expressions.fallback_expression ?? DEFAULT_FALLBACK_EXPRESSION;
}
/**
* Toggles Talkinghead mode on/off.
*
@@ -157,7 +165,8 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
const sprites = spriteCache[spriteFolderName];
const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`);
const defaultSpritePath = sprites.find(x => x.label === FALLBACK_EXPRESSION)?.path;
const defaultExpression = getFallbackExpression();
const defaultSpritePath = sprites.find(x => x.label === defaultExpression)?.path;
const noSprites = sprites.length === 0;
if (expressionImage.length > 0) {
@@ -568,7 +577,7 @@ function handleImageChange() {
// 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;
const targetExpression = last ? last : getFallbackExpression();
setExpression(charName, targetExpression, true);
}
}
@@ -691,8 +700,8 @@ async function moduleWorker() {
const force = !!context.groupId;
// Character won't be angry on you for swiping
if (currentLastMessage.mes == '...' && expressionsList.includes(FALLBACK_EXPRESSION)) {
expression = FALLBACK_EXPRESSION;
if (currentLastMessage.mes == '...' && expressionsList.includes(getFallbackExpression())) {
expression = getFallbackExpression();
}
await sendExpressionCall(spriteFolderName, expression, force, vnMode);
@@ -885,6 +894,22 @@ async function setSpriteSetCommand(_, folder) {
moduleWorker();
}
async function classifyCommand(_, text) {
if (!text) {
console.log('No text provided');
return '';
}
if (!modules.includes('classify') && !extension_settings.expressions.local) {
toastr.warning('Text classification is disabled or not available');
return '';
}
const label = getExpressionLabel(text);
console.debug(`Classification result for "${text}": ${label}`);
return label;
}
async function setSpriteSlashCommand(_, spriteId) {
if (!spriteId) {
console.log('No sprite id provided');
@@ -949,7 +974,7 @@ function sampleClassifyText(text) {
async function getExpressionLabel(text) {
// Return if text is undefined, saving a costly fetch request
if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) {
return FALLBACK_EXPRESSION;
return getFallbackExpression();
}
text = sampleClassifyText(text);
@@ -988,7 +1013,7 @@ async function getExpressionLabel(text) {
}
} catch (error) {
console.log(error);
return FALLBACK_EXPRESSION;
return getFallbackExpression();
}
}
@@ -1092,6 +1117,11 @@ async function getSpritesList(name) {
}
}
async function renderAdditionalExpressionSettings() {
renderCustomExpressions();
await renderFallbackExpressionPicker();
}
function renderCustomExpressions() {
if (!Array.isArray(extension_settings.expressions.custom)) {
extension_settings.expressions.custom = [];
@@ -1112,6 +1142,23 @@ function renderCustomExpressions() {
}
}
async function renderFallbackExpressionPicker() {
const expressions = await getExpressionsList();
const defaultPicker = $('#expression_fallback');
defaultPicker.empty();
const fallbackExpression = getFallbackExpression();
for (const expression of expressions) {
const option = document.createElement('option');
option.value = expression;
option.text = expression;
option.selected = expression == fallbackExpression;
defaultPicker.append(option);
}
}
async function getExpressionsList() {
// Return cached list if available
if (Array.isArray(expressionsList)) {
@@ -1349,7 +1396,7 @@ async function onClickExpressionAddCustom() {
// Add custom expression into settings
extension_settings.expressions.custom.push(expressionName);
renderCustomExpressions();
await renderAdditionalExpressionSettings();
saveSettingsDebounced();
// Force refresh sprites list
@@ -1376,7 +1423,11 @@ async function onClickExpressionRemoveCustom() {
// Remove custom expression from settings
const index = extension_settings.expressions.custom.indexOf(selectedExpression);
extension_settings.expressions.custom.splice(index, 1);
renderCustomExpressions();
if (selectedExpression == getFallbackExpression()) {
toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`);
extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION;
}
await renderAdditionalExpressionSettings();
saveSettingsDebounced();
// Force refresh sprites list
@@ -1385,6 +1436,14 @@ async function onClickExpressionRemoveCustom() {
moduleWorker();
}
function onExpressionFallbackChanged() {
const expression = this.value;
if (expression) {
extension_settings.expressions.fallback_expression = expression;
saveSettingsDebounced();
}
}
async function handleFileUpload(url, formData) {
try {
const data = await jQuery.ajax({
@@ -1632,7 +1691,7 @@ async function fetchImagesNoCache() {
return await Promise.allSettled(promises);
}
(function () {
(async function () {
function addExpressionImage() {
const html = `
<div id="expression-wrapper">
@@ -1652,7 +1711,7 @@ async function fetchImagesNoCache() {
element.hide();
$('body').append(element);
}
function addSettings() {
async function addSettings() {
$('#extensions_settings').append(renderExtensionTemplate(MODULE_NAME, 'settings'));
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
@@ -1680,10 +1739,11 @@ async function fetchImagesNoCache() {
}
});
renderCustomExpressions();
await renderAdditionalExpressionSettings();
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
$('#expression_fallback').on('change', onExpressionFallbackChanged)
}
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.
@@ -1716,7 +1776,7 @@ async function fetchImagesNoCache() {
addExpressionImage();
addVisualNovelMode();
addSettings();
await addSettings();
const wrapper = new ModuleWorkerWrapper(moduleWorker);
const updateFunction = wrapper.update.bind(wrapper);
setInterval(updateFunction, UPDATE_INTERVAL);
@@ -1758,5 +1818,6 @@ async function fetchImagesNoCache() {
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('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.');
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], ' Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.', true, true);
registerSlashCommand('classify', classifyCommand, [], '<span class="monospace">(text)</span> performs an emotion classification of the given text and returns a label.', true, true);
})();

View File

@@ -18,6 +18,11 @@
<input id="image_type_toggle" type="checkbox">
<span>Image Type - talkinghead (extras)</span>
</label>
<div class="expression_fallback_block m-b-1 m-t-1">
<label for="expression_fallback">Default / Fallback Expression</label>
<small>Set the default and fallback expression being used when no matching expression is found.</small>
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
</div>
<div class="expression_custom_block m-b-1 m-t-1">
<label for="expression_custom">Custom Expressions</label>
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>

View File

@@ -2524,7 +2524,7 @@ async function generateComfyImage(prompt, negativePrompt) {
}
let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt));
workflow = workflow.replace('"%negative_prompt%"', JSON.stringify(negativePrompt));
workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
workflow = workflow.replaceAll('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
placeholders.forEach(ph => {
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
});

View File

@@ -465,6 +465,7 @@ async function processAudioJobQueue() {
playAudioData(currentAudioJob);
talkingAnimation(true);
} catch (error) {
toastr.error(error.toString());
console.error(error);
audioQueueProcessorReady = true;
}
@@ -581,8 +582,9 @@ async function processTtsQueue() {
toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`);
throw `Unable to attain voiceId for ${char}`;
}
tts(text, voiceId, char);
await tts(text, voiceId, char);
} catch (error) {
toastr.error(error.toString());
console.error(error);
currentTtsJob = null;
}
@@ -654,6 +656,7 @@ function onRefreshClick() {
initVoiceMap();
updateVoiceMap();
}).catch(error => {
toastr.error(error.toString());
console.error(error);
setTtsStatus(error, false);
});