|
|
export { translate }; |
|
|
|
|
|
import { |
|
|
eventSource, |
|
|
event_types, |
|
|
getRequestHeaders, |
|
|
reloadCurrentChat, |
|
|
saveSettingsDebounced, |
|
|
substituteParams, |
|
|
updateMessageBlock, |
|
|
} from '../../../script.js'; |
|
|
import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
|
|
import { POPUP_TYPE, callGenericPopup } from '../../popup.js'; |
|
|
import { updateReasoningUI } from '../../reasoning.js'; |
|
|
import { secret_state } from '../../secrets.js'; |
|
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
|
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
|
|
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
|
|
import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
|
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
|
|
import { splitRecursive } from '../../utils.js'; |
|
|
|
|
|
export const autoModeOptions = { |
|
|
NONE: 'none', |
|
|
RESPONSES: 'responses', |
|
|
INPUT: 'inputs', |
|
|
BOTH: 'both', |
|
|
}; |
|
|
|
|
|
const incomingTypes = [autoModeOptions.RESPONSES, autoModeOptions.BOTH]; |
|
|
const outgoingTypes = [autoModeOptions.INPUT, autoModeOptions.BOTH]; |
|
|
|
|
|
const defaultSettings = { |
|
|
target_language: 'en', |
|
|
internal_language: 'en', |
|
|
provider: 'google', |
|
|
auto_mode: autoModeOptions.NONE, |
|
|
deepl_endpoint: 'free', |
|
|
}; |
|
|
|
|
|
const languageCodes = { |
|
|
'Afrikaans': 'af', |
|
|
'Albanian': 'sq', |
|
|
'Amharic': 'am', |
|
|
'Arabic': 'ar', |
|
|
'Armenian': 'hy', |
|
|
'Azerbaijani': 'az', |
|
|
'Basque': 'eu', |
|
|
'Belarusian': 'be', |
|
|
'Bengali': 'bn', |
|
|
'Bosnian': 'bs', |
|
|
'Bulgarian': 'bg', |
|
|
'Catalan': 'ca', |
|
|
'Cebuano': 'ceb', |
|
|
'Chinese (Simplified)': 'zh-CN', |
|
|
'Chinese (Traditional)': 'zh-TW', |
|
|
'Corsican': 'co', |
|
|
'Croatian': 'hr', |
|
|
'Czech': 'cs', |
|
|
'Danish': 'da', |
|
|
'Dutch': 'nl', |
|
|
'English': 'en', |
|
|
'Esperanto': 'eo', |
|
|
'Estonian': 'et', |
|
|
'Finnish': 'fi', |
|
|
'French': 'fr', |
|
|
'Frisian': 'fy', |
|
|
'Galician': 'gl', |
|
|
'Georgian': 'ka', |
|
|
'German': 'de', |
|
|
'Greek': 'el', |
|
|
'Gujarati': 'gu', |
|
|
'Haitian Creole': 'ht', |
|
|
'Hausa': 'ha', |
|
|
'Hawaiian': 'haw', |
|
|
'Hebrew': 'iw', |
|
|
'Hindi': 'hi', |
|
|
'Hmong': 'hmn', |
|
|
'Hungarian': 'hu', |
|
|
'Icelandic': 'is', |
|
|
'Igbo': 'ig', |
|
|
'Indonesian': 'id', |
|
|
'Irish': 'ga', |
|
|
'Italian': 'it', |
|
|
'Japanese': 'ja', |
|
|
'Javanese': 'jw', |
|
|
'Kannada': 'kn', |
|
|
'Kazakh': 'kk', |
|
|
'Khmer': 'km', |
|
|
'Korean': 'ko', |
|
|
'Kurdish': 'ku', |
|
|
'Kyrgyz': 'ky', |
|
|
'Lao': 'lo', |
|
|
'Latin': 'la', |
|
|
'Latvian': 'lv', |
|
|
'Lithuanian': 'lt', |
|
|
'Luxembourgish': 'lb', |
|
|
'Macedonian': 'mk', |
|
|
'Malagasy': 'mg', |
|
|
'Malay': 'ms', |
|
|
'Malayalam': 'ml', |
|
|
'Maltese': 'mt', |
|
|
'Maori': 'mi', |
|
|
'Marathi': 'mr', |
|
|
'Mongolian': 'mn', |
|
|
'Myanmar (Burmese)': 'my', |
|
|
'Nepali': 'ne', |
|
|
'Norwegian': 'no', |
|
|
'Nyanja (Chichewa)': 'ny', |
|
|
'Pashto': 'ps', |
|
|
'Persian': 'fa', |
|
|
'Polish': 'pl', |
|
|
'Portuguese (Portugal)': 'pt-PT', |
|
|
'Portuguese (Brazil)': 'pt-BR', |
|
|
'Punjabi': 'pa', |
|
|
'Romanian': 'ro', |
|
|
'Russian': 'ru', |
|
|
'Samoan': 'sm', |
|
|
'Scots Gaelic': 'gd', |
|
|
'Serbian': 'sr', |
|
|
'Sesotho': 'st', |
|
|
'Shona': 'sn', |
|
|
'Sindhi': 'sd', |
|
|
'Sinhala (Sinhalese)': 'si', |
|
|
'Slovak': 'sk', |
|
|
'Slovenian': 'sl', |
|
|
'Somali': 'so', |
|
|
'Spanish': 'es', |
|
|
'Sundanese': 'su', |
|
|
'Swahili': 'sw', |
|
|
'Swedish': 'sv', |
|
|
'Tagalog (Filipino)': 'tl', |
|
|
'Tajik': 'tg', |
|
|
'Tamil': 'ta', |
|
|
'Telugu': 'te', |
|
|
'Thai': 'th', |
|
|
'Turkish': 'tr', |
|
|
'Ukrainian': 'uk', |
|
|
'Urdu': 'ur', |
|
|
'Uzbek': 'uz', |
|
|
'Vietnamese': 'vi', |
|
|
'Welsh': 'cy', |
|
|
'Xhosa': 'xh', |
|
|
'Yiddish': 'yi', |
|
|
'Yoruba': 'yo', |
|
|
'Zulu': 'zu', |
|
|
}; |
|
|
|
|
|
const KEY_REQUIRED = ['deepl', 'libre']; |
|
|
const LOCAL_URL = ['libre', 'oneringtranslator', 'deeplx', 'lingva']; |
|
|
|
|
|
function showKeysButton() { |
|
|
const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider); |
|
|
const providerOptionalUrl = LOCAL_URL.includes(extension_settings.translate.provider); |
|
|
$('#translate_key_button').toggle(providerRequiresKey).data('key', extension_settings.translate.provider); |
|
|
$('#translate_key_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider])); |
|
|
$('#translate_url_button').toggle(providerOptionalUrl).data('key', extension_settings.translate.provider + '_url'); |
|
|
$('#translate_url_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + '_url'])); |
|
|
$('#deepl_api_endpoint').toggle(extension_settings.translate.provider === 'deepl'); |
|
|
} |
|
|
|
|
|
function loadSettings() { |
|
|
for (const key in defaultSettings) { |
|
|
if (!Object.hasOwn(extension_settings.translate, key)) { |
|
|
extension_settings.translate[key] = defaultSettings[key]; |
|
|
} |
|
|
} |
|
|
|
|
|
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', 'true'); |
|
|
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', 'true'); |
|
|
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', 'true'); |
|
|
$('#deepl_api_endpoint').val(extension_settings.translate.deepl_endpoint).toggle(extension_settings.translate.provider === 'deepl'); |
|
|
showKeysButton(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isGeneratingSwipe(messageId) { |
|
|
return $(`#chat .mes[mesid="${messageId}"] .mes_text`).text() === '...'; |
|
|
} |
|
|
|
|
|
async function translateImpersonate() { |
|
|
const sendTextArea = $('#send_textarea'); |
|
|
const text = sendTextArea.val().toString(); |
|
|
const translatedText = await translate(text, extension_settings.translate.target_language); |
|
|
sendTextArea.val(translatedText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateIncomingMessage(messageId) { |
|
|
const context = getContext(); |
|
|
const message = context.chat[messageId]; |
|
|
|
|
|
if (!message) { |
|
|
return; |
|
|
} |
|
|
|
|
|
if (typeof message.extra !== 'object') { |
|
|
message.extra = {}; |
|
|
} |
|
|
|
|
|
if (isGeneratingSwipe(messageId)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
const textToTranslate = substituteParams(message.mes, context.name1, message.name); |
|
|
const translation = await translate(textToTranslate, extension_settings.translate.target_language); |
|
|
message.extra.display_text = translation; |
|
|
|
|
|
updateMessageBlock(Number(messageId), message); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateIncomingMessageReasoning(messageId) { |
|
|
const context = getContext(); |
|
|
const message = context.chat[messageId]; |
|
|
|
|
|
if (!message) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (typeof message.extra !== 'object') { |
|
|
message.extra = {}; |
|
|
} |
|
|
|
|
|
if (!message.extra.reasoning || isGeneratingSwipe(messageId)) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
const textToTranslate = substituteParams(message.extra.reasoning, context.name1, message.name); |
|
|
const translation = await translate(textToTranslate, extension_settings.translate.target_language); |
|
|
message.extra.reasoning_display_text = translation; |
|
|
|
|
|
updateReasoningUI(Number(messageId)); |
|
|
return true; |
|
|
} |
|
|
|
|
|
async function translateProviderOneRing(text, lang) { |
|
|
let from_lang = lang == extension_settings.translate.internal_language |
|
|
? extension_settings.translate.target_language |
|
|
: extension_settings.translate.internal_language; |
|
|
|
|
|
const response = await fetch('/api/translate/onering', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, from_lang: from_lang, to_lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderLibre(text, lang) { |
|
|
const response = await fetch('/api/translate/libre', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderGoogle(text, lang) { |
|
|
const response = await fetch('/api/translate/google', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderLingva(text, lang) { |
|
|
const response = await fetch('/api/translate/lingva', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderDeepl(text, lang) { |
|
|
if (!secret_state.deepl) { |
|
|
throw new Error('No DeepL API key'); |
|
|
} |
|
|
|
|
|
const endpoint = extension_settings.translate.deepl_endpoint || 'free'; |
|
|
const response = await fetch('/api/translate/deepl', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang, endpoint: endpoint }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderDeepLX(text, lang) { |
|
|
const response = await fetch('/api/translate/deeplx', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderBing(text, lang) { |
|
|
const response = await fetch('/api/translate/bing', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ text: text, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateProviderYandex(text, lang) { |
|
|
let chunks = []; |
|
|
const chunkSize = 5000; |
|
|
if (text.length <= chunkSize) { |
|
|
chunks.push(text); |
|
|
} else { |
|
|
chunks = splitRecursive(text, chunkSize); |
|
|
} |
|
|
const response = await fetch('/api/translate/yandex', { |
|
|
method: 'POST', |
|
|
headers: getRequestHeaders(), |
|
|
body: JSON.stringify({ chunks: chunks, lang: lang }), |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
const result = await response.text(); |
|
|
return result; |
|
|
} |
|
|
|
|
|
throw new Error(response.statusText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function chunkedTranslate(text, lang, translateFn, chunkSize = 5000) { |
|
|
if (text.length <= chunkSize) { |
|
|
return await translateFn(text, lang); |
|
|
} |
|
|
|
|
|
const chunks = splitRecursive(text, chunkSize); |
|
|
|
|
|
let result = ''; |
|
|
for (const chunk of chunks) { |
|
|
result += await translateFn(chunk, lang); |
|
|
} |
|
|
return result; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translate(text, lang, provider = null) { |
|
|
try { |
|
|
if (text == '') { |
|
|
return ''; |
|
|
} |
|
|
|
|
|
if (!lang) { |
|
|
lang = extension_settings.translate.target_language; |
|
|
} |
|
|
|
|
|
if (!provider) { |
|
|
provider = extension_settings.translate.provider; |
|
|
} |
|
|
|
|
|
|
|
|
const chunks = text.split(/!\[.*?]\([^)]*\)/); |
|
|
const links = [...text.matchAll(/!\[.*?]\([^)]*\)/g)]; |
|
|
|
|
|
let result = ''; |
|
|
for (let i = 0; i < chunks.length; i++) { |
|
|
result += await translateInner(chunks[i], lang, provider); |
|
|
if (i < links.length) result += links[i][0]; |
|
|
} |
|
|
|
|
|
return result; |
|
|
} catch (error) { |
|
|
console.log(error); |
|
|
toastr.error(String(error), 'Failed to translate message'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateInner(text, lang, provider) { |
|
|
if (text == '') { |
|
|
return ''; |
|
|
} |
|
|
if (!provider) { |
|
|
provider = extension_settings.translate.provider; |
|
|
} |
|
|
switch (provider) { |
|
|
case 'libre': |
|
|
return await translateProviderLibre(text, lang); |
|
|
case 'google': |
|
|
return await chunkedTranslate(text, lang, translateProviderGoogle, 5000); |
|
|
case 'lingva': |
|
|
return await chunkedTranslate(text, lang, translateProviderLingva, 5000); |
|
|
case 'deepl': |
|
|
return await translateProviderDeepl(text, lang); |
|
|
case 'deeplx': |
|
|
return await chunkedTranslate(text, lang, translateProviderDeepLX, 1500); |
|
|
case 'oneringtranslator': |
|
|
return await translateProviderOneRing(text, lang); |
|
|
case 'bing': |
|
|
return await chunkedTranslate(text, lang, translateProviderBing, 1000); |
|
|
case 'yandex': |
|
|
return await translateProviderYandex(text, lang); |
|
|
default: |
|
|
console.error('Unknown translation provider', provider); |
|
|
return text; |
|
|
} |
|
|
} |
|
|
|
|
|
async function translateOutgoingMessage(messageId) { |
|
|
const context = getContext(); |
|
|
const message = context.chat[messageId]; |
|
|
|
|
|
if (typeof message.extra !== 'object') { |
|
|
message.extra = {}; |
|
|
} |
|
|
|
|
|
const originalText = message.mes; |
|
|
message.extra.display_text = originalText; |
|
|
message.mes = await translate(originalText, extension_settings.translate.internal_language); |
|
|
updateMessageBlock(messageId, message); |
|
|
|
|
|
console.log('translateOutgoingMessage', messageId); |
|
|
} |
|
|
|
|
|
function shouldTranslate(types) { |
|
|
return types.includes(extension_settings.translate.auto_mode); |
|
|
} |
|
|
|
|
|
function createEventHandler(translateFunction, shouldTranslateFunction) { |
|
|
return async (data) => { |
|
|
if (shouldTranslateFunction()) { |
|
|
await translateFunction(data); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
async function onTranslateInputMessageClick() { |
|
|
const textarea = document.getElementById('send_textarea'); |
|
|
|
|
|
if (!(textarea instanceof HTMLTextAreaElement)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!textarea.value) { |
|
|
toastr.warning('Enter a message first'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const toast = toastr.info('Input Message is translating', 'Please wait...'); |
|
|
const translatedText = await translate(textarea.value, extension_settings.translate.internal_language); |
|
|
textarea.value = translatedText; |
|
|
textarea.dispatchEvent(new Event('input', { bubbles: true })); |
|
|
toastr.clear(toast); |
|
|
} |
|
|
|
|
|
|
|
|
let translateChatExecuting = false; |
|
|
|
|
|
async function onTranslateChatClick() { |
|
|
if (translateChatExecuting) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
translateChatExecuting = true; |
|
|
const context = getContext(); |
|
|
const chat = context.chat; |
|
|
|
|
|
toastr.info(`${chat.length} message(s) queued for translation.`, 'Please wait...'); |
|
|
|
|
|
for (let i = 0; i < chat.length; i++) { |
|
|
await translateIncomingMessageReasoning(i); |
|
|
await translateIncomingMessage(i); |
|
|
} |
|
|
|
|
|
await context.saveChat(); |
|
|
} catch (error) { |
|
|
console.log(error); |
|
|
toastr.error('Failed to translate chat'); |
|
|
} finally { |
|
|
translateChatExecuting = false; |
|
|
} |
|
|
} |
|
|
|
|
|
async function onTranslationsClearClick() { |
|
|
const popupHtml = await renderExtensionTemplateAsync('translate', 'deleteConfirmation'); |
|
|
const confirm = await callGenericPopup(popupHtml, POPUP_TYPE.CONFIRM); |
|
|
|
|
|
if (!confirm) { |
|
|
return; |
|
|
} |
|
|
|
|
|
const context = getContext(); |
|
|
const chat = context.chat; |
|
|
|
|
|
for (const mes of chat) { |
|
|
if (mes.extra) { |
|
|
delete mes.extra.display_text; |
|
|
delete mes.extra.reasoning_display_text; |
|
|
} |
|
|
} |
|
|
|
|
|
await context.saveChat(); |
|
|
await reloadCurrentChat(); |
|
|
} |
|
|
|
|
|
async function translateMessageEdit(messageId) { |
|
|
const context = getContext(); |
|
|
const chat = context.chat; |
|
|
const message = chat[messageId]; |
|
|
|
|
|
let anyChange = false; |
|
|
if (message.is_system || (extension_settings.translate.auto_mode == autoModeOptions.NONE && message.extra?.display_text)) { |
|
|
delete message.extra.display_text; |
|
|
updateMessageBlock(messageId, message); |
|
|
anyChange = true; |
|
|
} else if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) { |
|
|
await translateIncomingMessage(messageId); |
|
|
anyChange = true; |
|
|
} |
|
|
|
|
|
if (anyChange) { |
|
|
await context.saveChat(); |
|
|
} |
|
|
} |
|
|
|
|
|
async function translateMessageReasoningEdit(messageId) { |
|
|
const context = getContext(); |
|
|
const chat = context.chat; |
|
|
const message = chat[messageId]; |
|
|
|
|
|
let anyChange = false; |
|
|
if (message.is_system || (extension_settings.translate.auto_mode == autoModeOptions.NONE && message.extra?.reasoning_display_text)) { |
|
|
delete message.extra.reasoning_display_text; |
|
|
updateReasoningUI(Number(messageId)); |
|
|
anyChange = true; |
|
|
} else if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) { |
|
|
anyChange = await translateIncomingMessageReasoning(messageId); |
|
|
} |
|
|
|
|
|
if (anyChange) { |
|
|
await context.saveChat(); |
|
|
} |
|
|
} |
|
|
|
|
|
async function removeReasoningDisplayText(messageId) { |
|
|
const context = getContext(); |
|
|
const message = context.chat[messageId]; |
|
|
if (message.extra?.reasoning_display_text) { |
|
|
delete message.extra.reasoning_display_text; |
|
|
updateReasoningUI(Number(messageId)); |
|
|
await context.saveChat(); |
|
|
} |
|
|
} |
|
|
|
|
|
async function onMessageTranslateClick() { |
|
|
const context = getContext(); |
|
|
const messageId = $(this).closest('.mes').attr('mesid'); |
|
|
const message = context.chat[messageId]; |
|
|
|
|
|
|
|
|
let alreadyTranslated = false; |
|
|
if (message?.extra?.display_text) { |
|
|
delete message.extra.display_text; |
|
|
updateMessageBlock(Number(messageId), message); |
|
|
alreadyTranslated = true; |
|
|
} |
|
|
if (message?.extra?.reasoning_display_text) { |
|
|
delete message.extra.reasoning_display_text; |
|
|
updateReasoningUI(Number(messageId)); |
|
|
alreadyTranslated = true; |
|
|
} |
|
|
|
|
|
|
|
|
if (!alreadyTranslated) { |
|
|
await translateIncomingMessageReasoning(messageId); |
|
|
await translateIncomingMessage(messageId); |
|
|
} |
|
|
|
|
|
await context.saveChat(); |
|
|
} |
|
|
|
|
|
const handleIncomingMessage = createEventHandler(async (messageId) => { |
|
|
await translateIncomingMessageReasoning(messageId); |
|
|
await translateIncomingMessage(messageId); |
|
|
}, () => shouldTranslate(incomingTypes)); |
|
|
const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes)); |
|
|
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes)); |
|
|
const handleMessageEdit = createEventHandler(translateMessageEdit, () => true); |
|
|
const handleMessageReasoningEdit = createEventHandler(translateMessageReasoningEdit, () => true); |
|
|
const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayText, () => true); |
|
|
|
|
|
globalThis.translate = translate; |
|
|
|
|
|
jQuery(async () => { |
|
|
const html = await renderExtensionTemplateAsync('translate', 'index'); |
|
|
const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons'); |
|
|
|
|
|
$('#translate_wand_container').append(buttonHtml); |
|
|
$('#translation_container').append(html); |
|
|
$('#translate_chat').on('click', onTranslateChatClick); |
|
|
$('#translate_input_message').on('click', onTranslateInputMessageClick); |
|
|
$('#translation_clear').on('click', onTranslationsClearClick); |
|
|
|
|
|
for (const [key, value] of Object.entries(languageCodes)) { |
|
|
$('#translation_target_language').append(`<option value="${value}">${key}</option>`); |
|
|
} |
|
|
|
|
|
$('#translation_auto_mode').on('change', (event) => { |
|
|
if (!(event.target instanceof HTMLSelectElement)) { |
|
|
return; |
|
|
} |
|
|
extension_settings.translate.auto_mode = event.target.value; |
|
|
saveSettingsDebounced(); |
|
|
}); |
|
|
$('#translation_provider').on('change', (event) => { |
|
|
if (!(event.target instanceof HTMLSelectElement)) { |
|
|
return; |
|
|
} |
|
|
extension_settings.translate.provider = event.target.value; |
|
|
showKeysButton(); |
|
|
saveSettingsDebounced(); |
|
|
}); |
|
|
$('#translation_target_language').on('change', (event) => { |
|
|
if (!(event.target instanceof HTMLSelectElement)) { |
|
|
return; |
|
|
} |
|
|
extension_settings.translate.target_language = event.target.value; |
|
|
saveSettingsDebounced(); |
|
|
}); |
|
|
$('#deepl_api_endpoint').on('change', (event) => { |
|
|
if (!(event.target instanceof HTMLSelectElement)) { |
|
|
return; |
|
|
} |
|
|
extension_settings.translate.deepl_endpoint = event.target.value; |
|
|
saveSettingsDebounced(); |
|
|
}); |
|
|
$(document).on('click', '.mes_translate', onMessageTranslateClick); |
|
|
|
|
|
[event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach((eventType) => { |
|
|
eventSource.on(eventType, ( key) => { |
|
|
if (key === extension_settings.translate.provider) { |
|
|
$('#translate_key_button').toggleClass('success', !!secret_state[extension_settings.translate.provider]); |
|
|
} |
|
|
if (key === `${extension_settings.translate.provider}_url`) { |
|
|
$('#translate_url_button').toggleClass('success', !!secret_state[`${extension_settings.translate.provider}_url`]); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
loadSettings(); |
|
|
|
|
|
eventSource.makeFirst(event_types.CHARACTER_MESSAGE_RENDERED, handleIncomingMessage); |
|
|
eventSource.makeFirst(event_types.USER_MESSAGE_RENDERED, handleOutgoingMessage); |
|
|
eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage); |
|
|
eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady); |
|
|
eventSource.on(event_types.MESSAGE_UPDATED, handleMessageEdit); |
|
|
eventSource.on(event_types.MESSAGE_REASONING_EDITED, handleMessageReasoningEdit); |
|
|
eventSource.on(event_types.MESSAGE_REASONING_DELETED, handleMessageReasoningDelete); |
|
|
|
|
|
document.body.classList.add('translate'); |
|
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
|
|
name: 'translate', |
|
|
helpString: 'Translate text to a target language. If target language is not provided, the value from the extension settings will be used.', |
|
|
namedArgumentList: [ |
|
|
new SlashCommandNamedArgument('target', 'The target language code to translate to', ARGUMENT_TYPE.STRING, false, false, '', Object.values(languageCodes)), |
|
|
SlashCommandNamedArgument.fromProps({ |
|
|
name: 'provider', |
|
|
description: 'The translation provider to use. If not provided, the value from the extension settings will be used.', |
|
|
typeList: [ARGUMENT_TYPE.STRING], |
|
|
isRequired: false, |
|
|
acceptsMultiple: false, |
|
|
enumProvider: () => Array.from(document.getElementById('translation_provider').querySelectorAll('option')).map((option) => new SlashCommandEnumValue(option.value, option.text, enumTypes.name, enumIcons.server)), |
|
|
}), |
|
|
], |
|
|
unnamedArgumentList: [ |
|
|
new SlashCommandArgument('The text to translate', ARGUMENT_TYPE.STRING, true, false, ''), |
|
|
], |
|
|
callback: async (args, value) => { |
|
|
const target = args?.target && Object.values(languageCodes).includes(String(args.target)) |
|
|
? String(args.target) |
|
|
: extension_settings.translate.target_language; |
|
|
const provider = args?.provider || extension_settings.translate.provider; |
|
|
return await translate(String(value), target, provider); |
|
|
}, |
|
|
returns: ARGUMENT_TYPE.STRING, |
|
|
})); |
|
|
}); |
|
|
|