mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
New validation logic of expression images
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { saveSettingsDebounced } from "../../../script.js";
|
import { saveSettingsDebounced, token } from "../../../script.js";
|
||||||
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ const DEFAULT_EXPRESSIONS = [
|
|||||||
let expressionsList = null;
|
let expressionsList = null;
|
||||||
let lastCharacter = undefined;
|
let lastCharacter = undefined;
|
||||||
let lastMessage = null;
|
let lastMessage = null;
|
||||||
let existingExpressions = [];
|
let spriteCache = {};
|
||||||
let inApiCall = false;
|
let inApiCall = false;
|
||||||
|
|
||||||
function onExpressionsShowDefaultInput() {
|
function onExpressionsShowDefaultInput() {
|
||||||
@ -58,21 +58,25 @@ function onExpressionsShowDefaultInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moduleWorker() {
|
let isWorkerBusy = false;
|
||||||
function getLastCharacterMessage() {
|
|
||||||
const reversedChat = context.chat.slice().reverse();
|
|
||||||
|
|
||||||
for (let mes of reversedChat) {
|
async function moduleWorkerWrapper() {
|
||||||
if (mes.is_user || mes.is_system) {
|
// Don't touch me I'm busy...
|
||||||
continue;
|
if (isWorkerBusy) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
return { mes: mes.mes, name: mes.name };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { mes: '', name: null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I'm free. Let's update!
|
||||||
|
try {
|
||||||
|
isWorkerBusy = true;
|
||||||
|
await moduleWorker();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isWorkerBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
|
|
||||||
// non-characters not supported
|
// non-characters not supported
|
||||||
@ -84,27 +88,34 @@ async function moduleWorker() {
|
|||||||
// character changed
|
// character changed
|
||||||
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
|
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
|
||||||
removeExpression();
|
removeExpression();
|
||||||
validateImages();
|
spriteCache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLastMessage = getLastCharacterMessage();
|
||||||
|
|
||||||
|
// character has no expressions or it is not loaded
|
||||||
|
if (Object.keys(spriteCache).length === 0) {
|
||||||
|
await validateImages(currentLastMessage.name);
|
||||||
|
lastCharacter = context.groupId || context.characterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!modules.includes('classify')) {
|
if (!modules.includes('classify')) {
|
||||||
$('.expression_settings').show();
|
$('.expression_settings').show();
|
||||||
$('.expression_settings .offline_mode').css('display', 'block');
|
$('.expression_settings .offline_mode').css('display', 'block');
|
||||||
lastCharacter = context.characterId;
|
lastCharacter = context.groupId || context.characterId;
|
||||||
|
|
||||||
|
if (context.groupId) {
|
||||||
|
await validateImages(currentLastMessage.name, true);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('.expression_settings .offline_mode').css('display', 'none');
|
$('.expression_settings .offline_mode').css('display', 'none');
|
||||||
}
|
}
|
||||||
|
|
||||||
// character has no expressions or it is not loaded
|
|
||||||
if (!context.groupId && existingExpressions.length === 0) {
|
|
||||||
lastCharacter = context.groupId || context.characterId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if last message changed
|
// check if last message changed
|
||||||
const currentLastMessage = getLastCharacterMessage();
|
|
||||||
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
||||||
&& lastMessage === currentLastMessage.mes) {
|
&& lastMessage === currentLastMessage.mes) {
|
||||||
return;
|
return;
|
||||||
@ -154,48 +165,67 @@ async function moduleWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLastCharacterMessage() {
|
||||||
|
const context = getContext();
|
||||||
|
const reversedChat = context.chat.slice().reverse();
|
||||||
|
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
if (mes.is_user || mes.is_system) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mes: mes.mes, name: mes.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mes: '', name: null };
|
||||||
|
}
|
||||||
|
|
||||||
function removeExpression() {
|
function removeExpression() {
|
||||||
lastMessage = null;
|
lastMessage = null;
|
||||||
|
$('img.expression').off('error');
|
||||||
$('img.expression').prop('src', '');
|
$('img.expression').prop('src', '');
|
||||||
$('img.expression').removeClass('default');
|
$('img.expression').removeClass('default');
|
||||||
$('.expression_settings').hide();
|
$('.expression_settings').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
let imagesValidating = false;
|
async function validateImages(character, forceRedrawCached) {
|
||||||
|
if (!character) {
|
||||||
async function validateImages() {
|
|
||||||
if (imagesValidating) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesValidating = true;
|
const labels = await getExpressionsList();
|
||||||
existingExpressions = [];
|
|
||||||
const context = getContext();
|
if (spriteCache[character]) {
|
||||||
|
if (forceRedrawCached && $('#image_list').data('name') !== character) {
|
||||||
|
console.log('force redrawing character sprites list')
|
||||||
|
drawSpritesList(character, labels, spriteCache[character]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprites = await getSpritesList(character);
|
||||||
|
let validExpressions = drawSpritesList(character, labels, sprites);
|
||||||
|
spriteCache[character] = validExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSpritesList(character, labels, sprites) {
|
||||||
|
let validExpressions = [];
|
||||||
$('.expression_settings').show();
|
$('.expression_settings').show();
|
||||||
$('#image_list').empty();
|
$('#image_list').empty();
|
||||||
|
$('#image_list').data('name', character);
|
||||||
|
labels.sort().forEach((item) => {
|
||||||
|
const sprite = sprites.find(x => x.label == item);
|
||||||
|
|
||||||
if (!context.characterId) {
|
if (sprite) {
|
||||||
imagesValidating = false;
|
validExpressions.push(sprite);
|
||||||
return;
|
$('#image_list').append(getListItem(item, sprite.path, 'success'));
|
||||||
}
|
|
||||||
|
|
||||||
const IMAGE_LIST = (await getExpressionsList()).map(x => ({ name: x, file: `${x}.png` }));
|
|
||||||
IMAGE_LIST.forEach((item) => {
|
|
||||||
const image = document.createElement('img');
|
|
||||||
image.src = `/characters/${context.name2}/${item.file}`;
|
|
||||||
image.classList.add('debug-image');
|
|
||||||
image.width = '0px';
|
|
||||||
image.height = '0px';
|
|
||||||
image.onload = function () {
|
|
||||||
existingExpressions.push(item.name);
|
|
||||||
$('#image_list').append(getListItem(item.file, image.src, 'success'));
|
|
||||||
}
|
}
|
||||||
image.onerror = function () {
|
else {
|
||||||
$('#image_list').append(getListItem(item.file, '/img/No-Image-Placeholder.svg', 'failure'));
|
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure'));
|
||||||
}
|
}
|
||||||
$('#image_list').prepend(image);
|
|
||||||
});
|
});
|
||||||
imagesValidating = false;
|
return validExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getListItem(item, imageSrc, textClass) {
|
function getListItem(item, imageSrc, textClass) {
|
||||||
@ -207,9 +237,28 @@ function getListItem(item, imageSrc, textClass) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getSpritesList(name) {
|
||||||
|
console.log('getting sprites list');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`, {
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-Token': token,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let sprites = result.ok ? (await result.json()) : [];
|
||||||
|
return sprites;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getExpressionsList() {
|
async function getExpressionsList() {
|
||||||
console.log('getting expressions list');
|
console.log('getting expressions list');
|
||||||
// get something for offline mode (6 default images)
|
// get something for offline mode (default images)
|
||||||
if (!modules.includes('classify')) {
|
if (!modules.includes('classify')) {
|
||||||
console.log('classify not available, loading default');
|
console.log('classify not available, loading default');
|
||||||
return DEFAULT_EXPRESSIONS;
|
return DEFAULT_EXPRESSIONS;
|
||||||
@ -246,13 +295,14 @@ async function getExpressionsList() {
|
|||||||
|
|
||||||
async function setExpression(character, expression, force) {
|
async function setExpression(character, expression, force) {
|
||||||
console.log('entered setExpressions');
|
console.log('entered setExpressions');
|
||||||
const filename = `${expression}.png`;
|
await validateImages(character);
|
||||||
const img = $('img.expression');
|
const img = $('img.expression');
|
||||||
|
|
||||||
|
const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression));
|
||||||
console.log('checking for expression images to show..');
|
console.log('checking for expression images to show..');
|
||||||
if (force || (existingExpressions.includes(expression))) {
|
if (sprite) {
|
||||||
console.log('setting expression from character images folder');
|
console.log('setting expression from character images folder');
|
||||||
const imgUrl = `/characters/${character}/${filename}`;
|
img.attr('src', sprite.path);
|
||||||
img.attr('src', imgUrl);
|
|
||||||
img.removeClass('default');
|
img.removeClass('default');
|
||||||
img.off('error');
|
img.off('error');
|
||||||
img.on('error', function () {
|
img.on('error', function () {
|
||||||
@ -270,7 +320,7 @@ async function setExpression(character, expression, force) {
|
|||||||
|
|
||||||
function setDefault() {
|
function setDefault() {
|
||||||
console.log('setting default');
|
console.log('setting default');
|
||||||
const defImgUrl = `/img/default-expressions/${filename}`;
|
const defImgUrl = `/img/default-expressions/${expression}.png`;
|
||||||
//console.log(defImgUrl);
|
//console.log(defImgUrl);
|
||||||
img.attr('src', defImgUrl);
|
img.attr('src', defImgUrl);
|
||||||
img.addClass('default');
|
img.addClass('default');
|
||||||
@ -284,11 +334,11 @@ function onClickExpressionImage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = getContext();
|
const expression = $(this).attr('id');
|
||||||
const expression = $(this).attr('id').replace('.png', '');
|
const name = getLastCharacterMessage().name;
|
||||||
|
|
||||||
if ($(this).find('.failure').length === 0) {
|
if ($(this).find('.failure').length === 0) {
|
||||||
setExpression(context.name2, expression, true);
|
setExpression(name, expression, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,5 +379,5 @@ function onClickExpressionImage() {
|
|||||||
|
|
||||||
addExpressionImage();
|
addExpressionImage();
|
||||||
addSettings();
|
addSettings();
|
||||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
|
||||||
})();
|
})();
|
29
server.js
29
server.js
@ -1943,6 +1943,35 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
|
|||||||
return response.send(extensions);
|
return response.send(extensions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/get_sprites', jsonParser, function (request, response) {
|
||||||
|
const name = request.query.name;
|
||||||
|
const spritesPath = path.join(directories.characters, name);
|
||||||
|
let sprites = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) {
|
||||||
|
sprites = fs.readdirSync(spritesPath)
|
||||||
|
.filter(file => {
|
||||||
|
const mimeType = mime.lookup(file);
|
||||||
|
return mimeType && mimeType.startsWith('image/');
|
||||||
|
})
|
||||||
|
.map((file) => {
|
||||||
|
const pathToSprite = path.join(spritesPath, file);
|
||||||
|
return {
|
||||||
|
label: path.parse(pathToSprite).name.toLowerCase(),
|
||||||
|
path: `/characters/${name}/${file}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
return response.send(sprites);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function getThumbnailFolder(type) {
|
function getThumbnailFolder(type) {
|
||||||
let thumbnailFolder;
|
let thumbnailFolder;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user