mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-28 18:00:49 +01:00
Merge pull request #3403 from SillyTavern/support-multiple-expressions
Support multiple expressions
This commit is contained in:
commit
fb06e7afa1
8
public/global.d.ts
vendored
8
public/global.d.ts
vendored
@ -40,4 +40,12 @@ declare global {
|
|||||||
searchInputCssClass?: string;
|
searchInputCssClass?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a text to a target language using a translation provider.
|
||||||
|
* @param text Text to translate
|
||||||
|
* @param lang Target language
|
||||||
|
* @param provider Translation provider
|
||||||
|
*/
|
||||||
|
async function translate(text: string, lang: string, provider: string = null): Promise<string>;
|
||||||
}
|
}
|
||||||
|
@ -1602,7 +1602,6 @@
|
|||||||
"Character Expressions": "Expressions de personnages",
|
"Character Expressions": "Expressions de personnages",
|
||||||
"Translate text to English before classification": "Traduire le texte en anglais avant de le classer",
|
"Translate text to English before classification": "Traduire le texte en anglais avant de le classer",
|
||||||
"Show default images (emojis) if sprite missing": "Afficher les images par défaut (emojis) si le sprite est manquant",
|
"Show default images (emojis) if sprite missing": "Afficher les images par défaut (emojis) si le sprite est manquant",
|
||||||
"Image Type - talkinghead (extras)": "Type d'image - talkinghead (extras)",
|
|
||||||
"Classifier API": "API de classification",
|
"Classifier API": "API de classification",
|
||||||
"Select the API for classifying expressions.": "Sélectionnez l'API pour classer les expressions.",
|
"Select the API for classifying expressions.": "Sélectionnez l'API pour classer les expressions.",
|
||||||
"Main API": "API principale",
|
"Main API": "API principale",
|
||||||
|
@ -1467,7 +1467,6 @@
|
|||||||
"menu within": "내의 메뉴",
|
"menu within": "내의 메뉴",
|
||||||
"Translate text to English before classification": "분류 전에 텍스트를 영어로 번역합니다.",
|
"Translate text to English before classification": "분류 전에 텍스트를 영어로 번역합니다.",
|
||||||
"Show default images (emojis) if sprite missing": "해당하는 스프라이트가 없으면 기본 이미지 (이모지들)을 표시합니다.",
|
"Show default images (emojis) if sprite missing": "해당하는 스프라이트가 없으면 기본 이미지 (이모지들)을 표시합니다.",
|
||||||
"Image Type - talkinghead (extras)": "이미지 유형 - 토킹 헤드 (부가 사항)",
|
|
||||||
"Classifier API": "분류를 위한 API",
|
"Classifier API": "분류를 위한 API",
|
||||||
"Select the API for classifying expressions.": "감정 이미지들을 분류할 API를 선택하세요.",
|
"Select the API for classifying expressions.": "감정 이미지들을 분류할 API를 선택하세요.",
|
||||||
"Local": "로컬",
|
"Local": "로컬",
|
||||||
|
@ -1349,7 +1349,6 @@
|
|||||||
"Character Expressions": "角色表情",
|
"Character Expressions": "角色表情",
|
||||||
"Translate text to English before classification": "分类之前将文本翻译成英文",
|
"Translate text to English before classification": "分类之前将文本翻译成英文",
|
||||||
"Show default images (emojis) if sprite missing": "如果表情包缺失,则显示默认图像(表情符号)",
|
"Show default images (emojis) if sprite missing": "如果表情包缺失,则显示默认图像(表情符号)",
|
||||||
"Image Type - talkinghead (extras)": "图像类型 - 说话头像(附加内容)",
|
|
||||||
"Classifier API": "分类器 API",
|
"Classifier API": "分类器 API",
|
||||||
"Select the API for classifying expressions.": "选择用于对表达式进行分类的API。",
|
"Select the API for classifying expressions.": "选择用于对表达式进行分类的API。",
|
||||||
"Main API": "主要 API",
|
"Main API": "主要 API",
|
||||||
|
@ -1653,7 +1653,6 @@
|
|||||||
"HuggingFace Token": "HuggingFace 符元",
|
"HuggingFace Token": "HuggingFace 符元",
|
||||||
"Image Captioning": "圖片註解",
|
"Image Captioning": "圖片註解",
|
||||||
"Generate Caption": "產生圖片註解",
|
"Generate Caption": "產生圖片註解",
|
||||||
"Image Type - talkinghead (extras)": "圖片類型 - talkinghead(額外選項)",
|
|
||||||
"Injection Position": "插入位置",
|
"Injection Position": "插入位置",
|
||||||
"Injection position. Relative (to other prompts in prompt manager) or In-chat @ Depth.": "插入位置(與提示詞管理器中的其他提示相比)或聊天中的深度位置。",
|
"Injection position. Relative (to other prompts in prompt manager) or In-chat @ Depth.": "插入位置(與提示詞管理器中的其他提示相比)或聊天中的深度位置。",
|
||||||
"Injection Template": "插入範本",
|
"Injection Template": "插入範本",
|
||||||
|
@ -154,8 +154,18 @@ export const extension_settings = {
|
|||||||
refine_mode: false,
|
refine_mode: false,
|
||||||
},
|
},
|
||||||
expressions: {
|
expressions: {
|
||||||
|
/** @type {number} see `EXPRESSION_API` */
|
||||||
|
api: undefined,
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
custom: [],
|
custom: [],
|
||||||
|
showDefault: false,
|
||||||
|
translate: false,
|
||||||
|
/** @type {string} */
|
||||||
|
fallback_expression: undefined,
|
||||||
|
/** @type {string} */
|
||||||
|
llmPrompt: undefined,
|
||||||
|
allowMultiple: true,
|
||||||
|
rerollIfSame: false,
|
||||||
},
|
},
|
||||||
connectionManager: {
|
connectionManager: {
|
||||||
selectedProfile: '',
|
selectedProfile: '',
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
|||||||
<div id="{{item}}" class="expression_list_item">
|
{{#each images}}
|
||||||
|
<div class="expression_list_item interactable" data-expression="{{../expression}}" data-expression-type="{{this.type}}" data-filename="{{this.fileName}}">
|
||||||
<div class="expression_list_buttons">
|
<div class="expression_list_buttons">
|
||||||
<div class="menu_button expression_list_upload" title="Upload image">
|
<div class="menu_button expression_list_upload" title="Upload image">
|
||||||
<i class="fa-solid fa-upload"></i>
|
<i class="fa-solid fa-upload"></i>
|
||||||
@ -7,11 +8,14 @@
|
|||||||
<i class="fa-solid fa-trash"></i>
|
<i class="fa-solid fa-trash"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="expression_list_title {{textClass}}">
|
<div class="expression_list_title">
|
||||||
<span>{{item}}</span>
|
<span>{{../expression}}</span>
|
||||||
{{#if isCustom}}
|
{{#if ../isCustom}}
|
||||||
<small class="expression_list_custom">(custom)</small>
|
<small class="expression_list_custom">(custom)</small>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<img class="expression_list_image" src="{{imageSrc}}" />
|
<div class="expression_list_image_container" title="{{this.title}}">
|
||||||
|
<img class="expression_list_image" src="{{this.imageSrc}}" alt="{{this.title}}" data-epression="{{../expression}}" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
@ -6,17 +6,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline-drawer-content">
|
<div class="inline-drawer-content">
|
||||||
<label class="checkbox_label" for="expression_translate" title="Use the selected API from Chat Translation extension settings.">
|
<label class="checkbox_label" for="expression_translate" title="Use the selected API from Chat Translation extension settings." data-i18n="[title]Use the selected API from Chat Translation extension settings.">
|
||||||
<input id="expression_translate" type="checkbox">
|
<input id="expression_translate" type="checkbox">
|
||||||
<span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
|
<span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="checkbox_label" for="expressions_show_default">
|
<label class="checkbox_label" for="expressions_allow_multiple" title="A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected." data-i18n="[title]A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected.">
|
||||||
<input id="expressions_show_default" type="checkbox">
|
<input id="expressions_allow_multiple" type="checkbox">
|
||||||
<span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span>
|
<span data-i18n="Allow multiple sprites per expression">Allow multiple sprites per expression</span>
|
||||||
</label>
|
</label>
|
||||||
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
|
<label class="checkbox_label" for="expressions_reroll_if_same" title="If the same expression is used again, re-roll the sprite. This only applies to expressions that have multiple available sprites assigned." data-i18n="[title]If the same expression is used again, re-roll the sprite. This only applies to expressions that have multiple available sprites assigned.">
|
||||||
<input id="image_type_toggle" type="checkbox">
|
<input id="expressions_reroll_if_same" type="checkbox">
|
||||||
<span data-i18n="Image Type - talkinghead (extras)">Image Type - talkinghead (extras)</span>
|
<span data-i18n="Re-roll if same expression is used again">Re-roll if same sprite is used again</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="expression_api_block m-b-1 m-t-1">
|
<div class="expression_api_block m-b-1 m-t-1">
|
||||||
<label for="expression_api" data-i18n="Classifier API">Classifier API</label>
|
<label for="expression_api" data-i18n="Classifier API">Classifier API</label>
|
||||||
@ -75,8 +75,20 @@
|
|||||||
<span data-i18n="Remove all image overrides">Remove all image overrides</span>
|
<span data-i18n="Remove all image overrides">Remove all image overrides</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
|
<p class="hint">
|
||||||
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p>
|
<b data-i18n="Hint:">Hint:</b>
|
||||||
|
<i>
|
||||||
|
<span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
|
||||||
|
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt>
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i>
|
||||||
|
<span>In case of multiple files per expression, file names can contain a suffix, either separated by a dot or a
|
||||||
|
dash.
|
||||||
|
Examples: </span><tt>joy.png</tt>, <tt>joy-1.png</tt>, <tt>joy.expressive.png</tt>
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
<h3 id="image_list_header">
|
<h3 id="image_list_header">
|
||||||
<strong data-i18n="Sprite set:">Sprite set:</strong> <span id="image_list_header_name"></span>
|
<strong data-i18n="Sprite set:">Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -111,6 +111,10 @@ img.expression.default {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expression_list_image_container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.expression_list_title {
|
.expression_list_title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -126,6 +130,9 @@ img.expression.default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
.expression_list_custom {
|
||||||
|
font-size: 0.66rem;
|
||||||
|
}
|
||||||
|
|
||||||
.expression_list_buttons {
|
.expression_list_buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -162,11 +169,24 @@ img.expression.default {
|
|||||||
row-gap: 1rem;
|
row-gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image_list .success {
|
#image_list .expression_list_item[data-expression-type="success"] .expression_list_title {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image_list .failure {
|
#image_list .expression_list_item[data-expression-type="additional"] .expression_list_title {
|
||||||
|
color: darkolivegreen;
|
||||||
|
}
|
||||||
|
#image_list .expression_list_item[data-expression-type="additional"] .expression_list_title::before {
|
||||||
|
content: '➕';
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: -9px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: 0 0 0 darkolivegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image_list .expression_list_item[data-expression-type="failure"] .expression_list_title {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,3 +209,12 @@ img.expression.default {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#expressions_container:has(#expressions_allow_multiple:not(:checked)) #image_list .expression_list_item[data-expression-type="additional"],
|
||||||
|
#expressions_container:has(#expressions_allow_multiple:not(:checked)) label[for="expressions_reroll_if_same"] {
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: opacity var(--animation-duration) ease;
|
||||||
|
}
|
||||||
|
#expressions_container:has(#expressions_allow_multiple:not(:checked)) #image_list .expression_list_item[data-expression-type="additional"]:hover,
|
||||||
|
#expressions_container:has(#expressions_allow_multiple:not(:checked)) #image_list .expression_list_item[data-expression-type="additional"]:focus {
|
||||||
|
opacity: unset;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<div class="m-b-1" data-i18n="upload_expression_request">Please enter a name for the sprite (without extension).</div>
|
||||||
|
<div class="m-b-1" data-i18n="upload_expression_naming_1">
|
||||||
|
Sprite names must follow the naming schema for the selected expression: {{expression}}
|
||||||
|
</div>
|
||||||
|
<div data-i18n="upload_expression_naming_2">
|
||||||
|
For multiple expressions, the name must follow the expression name and a valid suffix. Allowed separators are '-' or dot '.'.
|
||||||
|
</div>
|
||||||
|
<span class="m-b-1" data-i18n="Examples:">Examples:</span> <tt>{{expression}}.png</tt>, <tt>{{expression}}-1.png</tt>, <tt>{{expression}}.expressive.png</tt>
|
||||||
|
{{#if clickedFileName}}
|
||||||
|
<div class="m-t-1" data-i18n="upload_expression_replace">Click 'Replace' to replace the existing expression:</div>
|
||||||
|
<tt>{{clickedFileName}}</tt>
|
||||||
|
{{/if}}
|
@ -605,7 +605,7 @@ const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () =>
|
|||||||
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
|
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
|
||||||
const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
|
const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
|
||||||
|
|
||||||
window['translate'] = translate;
|
globalThis.translate = translate;
|
||||||
|
|
||||||
jQuery(async () => {
|
jQuery(async () => {
|
||||||
const html = await renderExtensionTemplateAsync('translate', 'index');
|
const html = await renderExtensionTemplateAsync('translate', 'index');
|
||||||
|
@ -27,14 +27,12 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm
|
|||||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
||||||
import { GoogleTranslateTtsProvider } from './google-translate.js';
|
import { GoogleTranslateTtsProvider } from './google-translate.js';
|
||||||
export { talkingAnimation };
|
|
||||||
|
|
||||||
const UPDATE_INTERVAL = 1000;
|
const UPDATE_INTERVAL = 1000;
|
||||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||||
|
|
||||||
let voiceMapEntries = [];
|
let voiceMapEntries = [];
|
||||||
let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}
|
let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}
|
||||||
let talkingHeadState = false;
|
|
||||||
let lastChatId = null;
|
let lastChatId = null;
|
||||||
let lastMessage = null;
|
let lastMessage = null;
|
||||||
let lastMessageHash = null;
|
let lastMessageHash = null;
|
||||||
@ -166,27 +164,6 @@ async function moduleWorker() {
|
|||||||
updateUiAudioPlayState();
|
updateUiAudioPlayState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function talkingAnimation(switchValue) {
|
|
||||||
if (!modules.includes('talkinghead')) {
|
|
||||||
console.debug('Talking Animation module not loaded');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiUrl = getApiUrl();
|
|
||||||
const animationType = switchValue ? 'start' : 'stop';
|
|
||||||
|
|
||||||
if (switchValue !== talkingHeadState) {
|
|
||||||
try {
|
|
||||||
console.log(animationType + ' Talking Animation');
|
|
||||||
doExtrasFetch(`${apiUrl}/api/talkinghead/${animationType}_talking`);
|
|
||||||
talkingHeadState = switchValue;
|
|
||||||
} catch (error) {
|
|
||||||
// Handle the error here or simply ignore it to prevent logging
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateUiAudioPlayState();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetTtsPlayback() {
|
function resetTtsPlayback() {
|
||||||
// Stop system TTS utterance
|
// Stop system TTS utterance
|
||||||
cancelTtsPlay();
|
cancelTtsPlay();
|
||||||
@ -378,7 +355,6 @@ function onAudioControlClicked() {
|
|||||||
// Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
|
// Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
|
||||||
if (!audioElement.paused || isTtsProcessing()) {
|
if (!audioElement.paused || isTtsProcessing()) {
|
||||||
resetTtsPlayback();
|
resetTtsPlayback();
|
||||||
talkingAnimation(false);
|
|
||||||
} else {
|
} else {
|
||||||
// Default play behavior if not processing or playing is to play the last message.
|
// Default play behavior if not processing or playing is to play the last message.
|
||||||
processAndQueueTtsMessage(context.chat[context.chat.length - 1]);
|
processAndQueueTtsMessage(context.chat[context.chat.length - 1]);
|
||||||
@ -405,7 +381,6 @@ function addAudioControl() {
|
|||||||
function completeCurrentAudioJob() {
|
function completeCurrentAudioJob() {
|
||||||
audioQueueProcessorReady = true;
|
audioQueueProcessorReady = true;
|
||||||
currentAudioJob = null;
|
currentAudioJob = null;
|
||||||
talkingAnimation(false); //stop lip animation
|
|
||||||
// updateUiPlayState();
|
// updateUiPlayState();
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
}
|
}
|
||||||
@ -436,7 +411,6 @@ async function processAudioJobQueue() {
|
|||||||
audioQueueProcessorReady = false;
|
audioQueueProcessorReady = false;
|
||||||
currentAudioJob = audioJobQueue.shift();
|
currentAudioJob = audioJobQueue.shift();
|
||||||
playAudioData(currentAudioJob);
|
playAudioData(currentAudioJob);
|
||||||
talkingAnimation(true);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastr.error(error.toString());
|
toastr.error(error.toString());
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { isMobile } from '../../RossAscends-mods.js';
|
import { isMobile } from '../../RossAscends-mods.js';
|
||||||
import { getPreviewString } from './index.js';
|
import { getPreviewString } from './index.js';
|
||||||
import { talkingAnimation } from './index.js';
|
|
||||||
import { saveTtsProviderSettings } from './index.js';
|
import { saveTtsProviderSettings } from './index.js';
|
||||||
export { SystemTtsProvider };
|
export { SystemTtsProvider };
|
||||||
|
|
||||||
@ -70,7 +69,6 @@ var speechUtteranceChunker = function (utt, settings, callback) {
|
|||||||
//placing the speak invocation inside a callback fixes ordering and onend issues.
|
//placing the speak invocation inside a callback fixes ordering and onend issues.
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
speechSynthesis.speak(newUtt);
|
speechSynthesis.speak(newUtt);
|
||||||
talkingAnimation(true);
|
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -240,7 +238,6 @@ class SystemTtsProvider {
|
|||||||
//some code to execute when done
|
//some code to execute when done
|
||||||
resolve(silence);
|
resolve(silence);
|
||||||
console.log('System TTS done');
|
console.log('System TTS done');
|
||||||
talkingAnimation(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -561,9 +561,9 @@ async function retrieveFileChunks(queryText, collectionId) {
|
|||||||
*/
|
*/
|
||||||
async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overlapPercent) {
|
async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overlapPercent) {
|
||||||
try {
|
try {
|
||||||
if (settings.translate_files && typeof window['translate'] === 'function') {
|
if (settings.translate_files && typeof globalThis.translate === 'function') {
|
||||||
console.log(`Vectors: Translating file ${fileName} to English...`);
|
console.log(`Vectors: Translating file ${fileName} to English...`);
|
||||||
const translatedText = await window['translate'](fileText, 'en');
|
const translatedText = await globalThis.translate(fileText, 'en');
|
||||||
fileText = translatedText;
|
fileText = translatedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1845,14 +1845,15 @@ async function loadContextSettings() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Common function to perform fuzzy search with optional caching
|
* Common function to perform fuzzy search with optional caching
|
||||||
|
* @template T
|
||||||
* @param {string} type - Type of search from fuzzySearchCategories
|
* @param {string} type - Type of search from fuzzySearchCategories
|
||||||
* @param {any[]} data - Data array to search in
|
* @param {T[]} data - Data array to search in
|
||||||
* @param {Array<{name: string, weight: number, getFn?: (obj: any) => string}>} keys - Fuse.js keys configuration
|
* @param {Array<{name: string, weight: number, getFn?: (obj: T) => string}>} keys - Fuse.js keys configuration
|
||||||
* @param {string} searchValue - The search term
|
* @param {string} searchValue - The search term
|
||||||
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
|
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
|
||||||
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
|
* @returns {import('fuse.js').FuseResult<T>[]} Results as items with their score
|
||||||
*/
|
*/
|
||||||
function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) {
|
export function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) {
|
||||||
// Check cache if provided
|
// Check cache if provided
|
||||||
if (fuzzySearchCaches) {
|
if (fuzzySearchCaches) {
|
||||||
const cache = fuzzySearchCaches[type];
|
const cache = fuzzySearchCaches[type];
|
||||||
|
@ -125,8 +125,14 @@ router.get('/get', jsonParser, function (request, response) {
|
|||||||
.map((file) => {
|
.map((file) => {
|
||||||
const pathToSprite = path.join(spritesPath, file);
|
const pathToSprite = path.join(spritesPath, file);
|
||||||
const mtime = fs.statSync(pathToSprite).mtime?.toISOString().replace(/[^0-9]/g, '').slice(0, 14);
|
const mtime = fs.statSync(pathToSprite).mtime?.toISOString().replace(/[^0-9]/g, '').slice(0, 14);
|
||||||
|
|
||||||
|
const fileName = path.parse(pathToSprite).name.toLowerCase();
|
||||||
|
// Extract the label from the filename via regex, which can be suffixed with a sub-name, either connected with a dash or a dot.
|
||||||
|
// Examples: joy.png, joy-1.png, joy.expressive.png
|
||||||
|
const label = fileName.match(/^(.+?)(?:[-\\.].*?)?$/)?.[1] ?? fileName;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: path.parse(pathToSprite).name.toLowerCase(),
|
label: label,
|
||||||
path: `/characters/${name}/${file}` + (mtime ? `?t=${mtime}` : ''),
|
path: `/characters/${name}/${file}` + (mtime ? `?t=${mtime}` : ''),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -141,8 +147,9 @@ router.get('/get', jsonParser, function (request, response) {
|
|||||||
router.post('/delete', jsonParser, async (request, response) => {
|
router.post('/delete', jsonParser, async (request, response) => {
|
||||||
const label = request.body.label;
|
const label = request.body.label;
|
||||||
const name = request.body.name;
|
const name = request.body.name;
|
||||||
|
const spriteName = request.body.spriteName || label;
|
||||||
|
|
||||||
if (!label || !name) {
|
if (!spriteName || !name) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +165,7 @@ router.post('/delete', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
// Remove existing sprite with the same label
|
// Remove existing sprite with the same label
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (path.parse(file).name === label) {
|
if (path.parse(file).name === spriteName) {
|
||||||
fs.rmSync(path.join(spritesPath, file));
|
fs.rmSync(path.join(spritesPath, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,6 +228,7 @@ router.post('/upload', urlencodedParser, async (request, response) => {
|
|||||||
const file = request.file;
|
const file = request.file;
|
||||||
const label = request.body.label;
|
const label = request.body.label;
|
||||||
const name = request.body.name;
|
const name = request.body.name;
|
||||||
|
const spriteName = request.body.spriteName || label;
|
||||||
|
|
||||||
if (!file || !label || !name) {
|
if (!file || !label || !name) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
@ -243,12 +251,12 @@ router.post('/upload', urlencodedParser, async (request, response) => {
|
|||||||
|
|
||||||
// Remove existing sprite with the same label
|
// Remove existing sprite with the same label
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (path.parse(file).name === label) {
|
if (path.parse(file).name === spriteName) {
|
||||||
fs.rmSync(path.join(spritesPath, file));
|
fs.rmSync(path.join(spritesPath, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = label + path.parse(file.originalname).ext;
|
const filename = spriteName + path.parse(file.originalname).ext;
|
||||||
const spritePath = path.join(file.destination, file.filename);
|
const spritePath = path.join(file.destination, file.filename);
|
||||||
const pathToFile = path.join(spritesPath, filename);
|
const pathToFile = path.join(spritesPath, filename);
|
||||||
// Copy uploaded file to sprites folder
|
// Copy uploaded file to sprites folder
|
||||||
|
Loading…
x
Reference in New Issue
Block a user