diff --git a/public/css/st-tailwind.css b/public/css/st-tailwind.css
index aa21daaee..f2c1c55e6 100644
--- a/public/css/st-tailwind.css
+++ b/public/css/st-tailwind.css
@@ -220,7 +220,7 @@
}
.monospace {
- font-family: monospace;
+ font-family: var(--monoFontFamily);
}
.expander {
diff --git a/public/index.html b/public/index.html
index e9a840353..9f43cd7ac 100644
--- a/public/index.html
+++ b/public/index.html
@@ -18,6 +18,7 @@
+
diff --git a/public/login.html b/public/login.html
index ff390be68..c52ca3d74 100644
--- a/public/login.html
+++ b/public/login.html
@@ -26,6 +26,7 @@
+
diff --git a/public/script.js b/public/script.js
index 38f069968..ebd67cbff 100644
--- a/public/script.js
+++ b/public/script.js
@@ -764,8 +764,19 @@ const per_page_default = 50;
var is_advanced_char_open = false;
-export let menu_type = ''; //what is selected in the menu
+/**
+ * The type of the right menu
+ * @typedef {'characters' | 'character_edit' | 'create' | 'group_edit' | 'group_create' | '' } MenuType
+ */
+
+/**
+ * The type of the right menu that is currently open
+ * @type {MenuType}
+ */
+export let menu_type = '';
+
export let selected_button = ''; //which button pressed
+
//create pole save
let create_save = {
name: '',
@@ -5432,8 +5443,14 @@ export function resetChatState() {
characters.length = 0;
}
+/**
+ *
+ * @param {'characters' | 'character_edit' | 'create' | 'group_edit' | 'group_create'} value
+ */
export function setMenuType(value) {
menu_type = value;
+ // Allow custom CSS to see which menu type is active
+ document.getElementById('right-nav-panel').dataset.menuType = menu_type;
}
export function setExternalAbortController(controller) {
@@ -6815,7 +6832,7 @@ export function select_selected_character(chid) {
//character select
//console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');
select_rm_create();
- menu_type = 'character_edit';
+ setMenuType('character_edit');
$('#delete_button').css('display', 'flex');
$('#export_button').css('display', 'flex');
var display_name = characters[chid].name;
@@ -6891,7 +6908,7 @@ export function select_selected_character(chid) {
}
function select_rm_create() {
- menu_type = 'create';
+ setMenuType('create');
//console.log('select_rm_Create() -- selected button: '+selected_button);
if (selected_button == 'create') {
@@ -6952,7 +6969,7 @@ function select_rm_create() {
function select_rm_characters() {
const doFullRefresh = menu_type === 'characters';
- menu_type = 'characters';
+ setMenuType('characters');
selectRightMenuWithAnimation('rm_characters_block');
printCharacters(doFullRefresh);
}
@@ -8978,7 +8995,6 @@ jQuery(async function () {
$('#rm_button_settings').click(function () {
selected_button = 'settings';
- menu_type = 'settings';
selectRightMenuWithAnimation('rm_api_block');
});
$('#rm_button_characters').click(function () {
diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js
index 806b5f308..dbf7dd4ed 100644
--- a/public/scripts/PromptManager.js
+++ b/public/scripts/PromptManager.js
@@ -685,6 +685,23 @@ class PromptManager {
this.log('Initialized');
}
+ /**
+ * Get the scroll position of the prompt manager
+ * @returns {number} - Scroll position of the prompt manager
+ */
+ #getScrollPosition() {
+ return document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTop;
+ }
+
+ /**
+ * Set the scroll position of the prompt manager
+ * @param {number} scrollPosition - The scroll position to set
+ */
+ #setScrollPosition(scrollPosition) {
+ if (scrollPosition === undefined || scrollPosition === null) return;
+ document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTo(0, scrollPosition);
+ }
+
/**
* Main rendering function
*
@@ -703,17 +720,21 @@ class PromptManager {
this.tryGenerate().finally(async () => {
this.profileEnd('filling context');
this.profileStart('render');
+ const scrollPosition = this.#getScrollPosition();
await this.renderPromptManager();
await this.renderPromptManagerListItems();
this.makeDraggable();
+ this.#setScrollPosition(scrollPosition);
this.profileEnd('render');
});
} else {
// Executed during live communication
this.profileStart('render');
+ const scrollPosition = this.#getScrollPosition();
await this.renderPromptManager();
await this.renderPromptManagerListItems();
this.makeDraggable();
+ this.#setScrollPosition(scrollPosition);
this.profileEnd('render');
}
}).catch(() => {
diff --git a/public/scripts/autocomplete/AutoComplete.js b/public/scripts/autocomplete/AutoComplete.js
index 2d5818e22..5f1dd32a9 100644
--- a/public/scripts/autocomplete/AutoComplete.js
+++ b/public/scripts/autocomplete/AutoComplete.js
@@ -482,8 +482,8 @@ export class AutoComplete {
this.domWrap.style.setProperty('--leftOffset', `max(1vw, ${rect[power_user.stscript.autocomplete.width.left].left}px)`);
this.domWrap.style.setProperty('--rightOffset', `calc(100vw - min(99vw, ${rect[power_user.stscript.autocomplete.width.right].right}px)`);
}
- this.updateDetailsPosition();
}
+ this.updateDetailsPosition();
}
/**
diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html
index 40b4e8db5..f6d95ead5 100644
--- a/public/scripts/extensions/quick-reply/html/qrEditor.html
+++ b/public/scripts/extensions/quick-reply/html/qrEditor.html
@@ -29,10 +29,14 @@
Ctrl+Enter to execute
+
+
+ Syntax highlight
+
diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js
index 33b36e6fb..f4a09906f 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReply.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReply.js
@@ -264,6 +264,13 @@ export class QuickReply {
const updateSyntax = ()=>{
messageSyntaxInner.innerHTML = hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value;
};
+ const updateSyntaxEnabled = ()=>{
+ if (JSON.parse(localStorage.getItem('qr--syntax'))) {
+ dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax');
+ } else {
+ dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax');
+ }
+ };
/**@type {HTMLInputElement}*/
const tabSize = dom.querySelector('#qr--modal-tabSize');
tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4');
@@ -282,6 +289,13 @@ export class QuickReply {
executeShortcut.addEventListener('click', () => {
localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
});
+ /**@type {HTMLInputElement}*/
+ const syntax = dom.querySelector('#qr--modal-syntax');
+ syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true');
+ syntax.addEventListener('click', () => {
+ localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
+ updateSyntaxEnabled();
+ });
/**@type {HTMLTextAreaElement}*/
const message = dom.querySelector('#qr--modal-message');
message.value = this.message;
@@ -352,8 +366,7 @@ export class QuickReply {
}
});
window.addEventListener('resize', resizeListener);
- message.style.color = 'transparent';
- message.style.background = 'transparent';
+ updateSyntaxEnabled();
message.style.setProperty('text-shadow', 'none', 'important');
/**@type {HTMLElement}*/
const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner');
diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css
index b7b8d8ffc..dfab049d8 100644
--- a/public/scripts/extensions/quick-reply/style.css
+++ b/public/scripts/extensions/quick-reply/style.css
@@ -301,6 +301,22 @@
text-align: left;
overflow: hidden;
}
+.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-messageSyntax {
+ display: none;
+}
+.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message {
+ background-color: var(--ac-style-color-background);
+ color: var(--ac-style-color-text);
+}
+.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
+ color: unset;
+ background-color: rgba(108 171 251 / 0.25);
+}
+@supports (color: rgb(from white r g b / 0.25)) {
+ .dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
+ }
+}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax {
grid-column: 1;
grid-row: 1;
@@ -315,18 +331,30 @@
height: 100%;
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
+ background-color: transparent;
+ color: transparent;
grid-column: 1;
grid-row: 1;
- caret-color: white;
- mix-blend-mode: difference;
+ caret-color: var(--ac-style-color-text);
+ overflow: auto;
}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar-thumb {
visibility: hidden;
cursor: default;
}
+.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
+ color: transparent;
+ background-color: rgba(108 171 251 / 0.25);
+}
+@supports (color: rgb(from white r g b / 0.25)) {
+ .dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
+ }
+}
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-message,
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-messageSyntaxInner {
+ font-family: var(--monoFontFamily);
padding: 0.75em;
margin: 0;
border: none;
diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less
index 5de370a4f..0d6b92a04 100644
--- a/public/scripts/extensions/quick-reply/style.less
+++ b/public/scripts/extensions/quick-reply/style.less
@@ -322,6 +322,22 @@
display: grid;
text-align: left;
overflow: hidden;
+ &.qr--noSyntax {
+ > #qr--modal-messageSyntax {
+ display: none;
+ }
+ > #qr--modal-message {
+ background-color: var(--ac-style-color-background);
+ color: var(--ac-style-color-text);
+ &::selection {
+ color: unset;
+ background-color: rgba(108 171 251 / 0.25);
+ @supports (color: rgb(from white r g b / 0.25)) {
+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
+ }
+ }
+ }
+ }
> #qr--modal-messageSyntax {
grid-column: 1;
grid-row: 1;
@@ -336,16 +352,26 @@
}
}
> #qr--modal-message {
+ background-color: transparent;
+ color: transparent;
grid-column: 1;
grid-row: 1;
- caret-color: white;
- mix-blend-mode: difference;
+ caret-color: var(--ac-style-color-text);
+ overflow: auto;
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
visibility: hidden;
cursor: default;
}
+ &::selection {
+ color: transparent;
+ background-color: rgba(108 171 251 / 0.25);
+ @supports (color: rgb(from white r g b / 0.25)) {
+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
+ }
+ }
}
#qr--modal-message, #qr--modal-messageSyntaxInner {
+ font-family: var(--monoFontFamily);
padding: 0.75em;
margin: 0;
border: none;
diff --git a/public/scripts/extensions/translate/index.js b/public/scripts/extensions/translate/index.js
index 20519db5b..36589ca3a 100644
--- a/public/scripts/extensions/translate/index.js
+++ b/public/scripts/extensions/translate/index.js
@@ -16,6 +16,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { splitRecursive } from '../../utils.js';
+import { renderTemplateAsync } from '../../templates.js';
export const autoModeOptions = {
NONE: 'none',
@@ -340,6 +341,34 @@ async function translateProviderBing(text, lang) {
throw new Error(response.statusText);
}
+/**
+ * Translates text using the Yandex Translate API
+ * @param {string} text Text to translate
+ * @param {string} lang Target language code
+ * @returns {Promise} Translated text
+ */
+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);
+}
+
/**
* Splits text into chunks and translates each chunk separately
* @param {string} text Text to translate
@@ -389,6 +418,8 @@ async function translate(text, lang) {
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', extension_settings.translate.provider);
return text;
@@ -532,56 +563,10 @@ const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
window['translate'] = translate;
-jQuery(() => {
- const html = `
-
-
-
-
-
Auto-mode
-
- None
- Translate responses
- Translate inputs
- Translate both
-
-
Provider
-
-
- Libre
- Google
- Lingva
- DeepL
- DeepLX
- Bing
- OneRingTranslator
-
-
-
-
-
Target Language
-
-
-
-
-
`;
+jQuery(async() => {
+ const html = await renderTemplateAsync('translateIndex');
- const buttonHtml = `
-
-
- Translate Chat
-
-
-
- Translate Input
-
- `;
+ const buttonHtml = await renderTemplateAsync('translateButtons');
$('#extensionsMenu').append(buttonHtml);
$('#extensions_settings2').append(html);
$('#translate_chat').on('click', onTranslateChatClick);
@@ -624,7 +609,7 @@ jQuery(() => {
'libre': 'http://127.0.0.1:5000/translate',
'lingva': 'https://lingva.ml/api/v1',
'oneringtranslator': 'http://127.0.0.1:4990/translate',
- 'deeplx': 'http://127.0.0.1:1188/translate',
+ 'deeplx': 'http://127.0.0.1:1188/translate'
};
const popupText = `${optionText} API URL Example: ${String(exampleURLs[extension_settings.translate.provider])} `;
diff --git a/public/scripts/extensions/tts/sbvits2.js b/public/scripts/extensions/tts/sbvits2.js
index ab41d4f6d..7f542ca6a 100644
--- a/public/scripts/extensions/tts/sbvits2.js
+++ b/public/scripts/extensions/tts/sbvits2.js
@@ -19,6 +19,8 @@ class SBVits2TtsProvider {
* @returns {string} Processed text
*/
processText(text) {
+ // backup for auto_split
+ text = text.replace(/\n+/g, ' ');
return text;
}
@@ -276,6 +278,8 @@ class SBVits2TtsProvider {
const [model_id, speaker_id, style] = voiceId.split('-');
const params = new URLSearchParams();
+ // restore for auto_split
+ inputText = inputText.replaceAll(' ', '\n');
params.append('text', inputText);
params.append('model_id', model_id);
params.append('speaker_id', speaker_id);
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index 6e29ea39e..6f7729ce9 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -84,15 +84,21 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
helpString: 'Get help on macros, chat formatting and commands.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
- name: 'name',
+ name: 'persona',
callback: setNameCallback,
- unnamedArgumentList: [
- new SlashCommandArgument(
- 'persona', [ARGUMENT_TYPE.STRING], true,
+ namedArgumentList: [
+ new SlashCommandNamedArgument(
+ 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
+ [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
),
],
- helpString: 'Sets user name and persona avatar (if set).',
- aliases: ['persona'],
+ unnamedArgumentList: [
+ new SlashCommandArgument(
+ 'persona name', [ARGUMENT_TYPE.STRING], true,
+ ),
+ ],
+ helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
+ aliases: ['name'],
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'sync',
@@ -2372,26 +2378,44 @@ function setFlatModeCallback() {
$('#chat_display').val(chat_styles.DEFAULT).trigger('change');
}
-function setNameCallback(_, name) {
+/**
+ * Sets a persona name and optionally an avatar.
+ * @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments
+ * @param {string} name Name to set
+ * @returns {void}
+ */
+function setNameCallback({ mode = 'all' }, name) {
if (!name) {
- toastr.warning('you must specify a name to change to');
+ toastr.warning('You must specify a name to change to');
+ return;
+ }
+
+ if (!['lookup', 'temp', 'all'].includes(mode)) {
+ toastr.warning('Mode must be one of "lookup", "temp" or "all"');
return;
}
name = name.trim();
- // If the name is a persona, auto-select it
- for (let persona of Object.values(power_user.personas)) {
- if (persona.toLowerCase() === name.toLowerCase()) {
- autoSelectPersona(name);
+ // If the name matches a persona avatar, or a name, auto-select it
+ if (['lookup', 'all'].includes(mode)) {
+ let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1];
+ if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
+ if (persona) {
+ autoSelectPersona(persona);
retriggerFirstMessageOnEmptyChat();
return;
+ } else if (mode === 'lookup') {
+ toastr.warning(`Persona ${name} not found`);
+ return;
}
}
- // Otherwise, set just the name
- setUserName(name); //this prevented quickReply usage
- retriggerFirstMessageOnEmptyChat();
+ if (['temp', 'all'].includes(mode)) {
+ // Otherwise, set just the name
+ setUserName(name); //this prevented quickReply usage
+ retriggerFirstMessageOnEmptyChat();
+ }
}
async function setNarratorName(_, text) {
diff --git a/public/scripts/templates/translateButtons.html b/public/scripts/templates/translateButtons.html
new file mode 100644
index 000000000..7f1549f40
--- /dev/null
+++ b/public/scripts/templates/translateButtons.html
@@ -0,0 +1,8 @@
+
+
+ Translate Chat
+
+
+
+ Translate Input
+
\ No newline at end of file
diff --git a/public/scripts/templates/translateIndex.html b/public/scripts/templates/translateIndex.html
new file mode 100644
index 000000000..d0f3d5ea4
--- /dev/null
+++ b/public/scripts/templates/translateIndex.html
@@ -0,0 +1,38 @@
+
+
+
+
+
Auto-mode
+
+ None
+ Translate responses
+ Translate inputs
+ Translate both
+
+
Provider
+
+
+ Libre
+ Google
+ Lingva
+ DeepL
+ DeepLX
+ Bing
+ OneRingTranslator
+ Yandex
+
+
+
+
+
Target Language
+
+
+
+
+
\ No newline at end of file
diff --git a/public/scripts/utils.js b/public/scripts/utils.js
index 5d5fe0f1d..3a0441e7f 100644
--- a/public/scripts/utils.js
+++ b/public/scripts/utils.js
@@ -495,14 +495,16 @@ export function trimToEndSentence(input, include_newline = false) {
return '';
}
+ const isEmoji = x => /(\p{Emoji_Presentation}|\p{Extended_Pictographic})/gu.test(x);
const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$', '。', '!', '?', '”', ')', '】', '’', '」', '_']); // extend this as you see fit
let last = -1;
- for (let i = input.length - 1; i >= 0; i--) {
- const char = input[i];
+ const characters = Array.from(input);
+ for (let i = characters.length - 1; i >= 0; i--) {
+ const char = characters[i];
- if (punctuation.has(char)) {
- if (i > 0 && /[\s\n]/.test(input[i - 1])) {
+ if (punctuation.has(char) || isEmoji(char)) {
+ if (i > 0 && /[\s\n]/.test(characters[i - 1])) {
last = i - 1;
} else {
last = i;
@@ -520,7 +522,7 @@ export function trimToEndSentence(input, include_newline = false) {
return input.trimEnd();
}
- return input.substring(0, last + 1).trimEnd();
+ return characters.slice(0, last + 1).join('').trimEnd();
}
export function trimToStartSentence(input) {
@@ -1275,6 +1277,9 @@ export async function waitUntilCondition(condition, timeout = 1000, interval = 1
* uuidv4(); // '3e2fd9e1-0a7a-4f6d-9aaf-8a7a4babe7eb'
*/
export function uuidv4() {
+ if ('randomUUID' in crypto) {
+ return crypto.randomUUID();
+ }
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
diff --git a/public/style.css b/public/style.css
index a74dbcbac..c24fd4288 100644
--- a/public/style.css
+++ b/public/style.css
@@ -77,6 +77,7 @@
--fontScale: 1;
--mainFontSize: calc(var(--fontScale) * 15px);
--mainFontFamily: "Noto Sans", "Noto Color Emoji", sans-serif;
+ --monoFontFamily: 'Noto Sans Mono', 'Courier New', Consolas, monospace;
/* base variable for blur strength slider calculations */
--blurStrength: 10;
@@ -444,7 +445,7 @@ small {
}
code {
- font-family: Consolas, monospace;
+ font-family: var(--monoFontFamily);
white-space: pre-wrap;
/* word-wrap: break-word; */
border: 1px solid var(--SmartThemeBorderColor);
@@ -1248,6 +1249,7 @@ select {
left: var(--leftOffset);
right: var(--rightOffset);
z-index: 10000;
+ pointer-events: none;
&.isFloating {
@@ -1656,7 +1658,7 @@ body[data-stscript-style] .hljs.language-stscript {
text-align: center;
/* opacity: 0.6; */
white-space: nowrap;
- font-family: monospace;
+ font-family: var(--monoFontFamily);
line-height: calc(1.2em / 0.8);
/* &:before { content: "["; }
&:after { content: "]"; } */
@@ -1824,7 +1826,7 @@ body[data-stscript-style] .hljs.language-stscript {
}
>.alias {
- font-family: monospace;
+ font-family: var(--monoFontFamily);
&+.alias:before {
content: ', ';
@@ -1844,7 +1846,7 @@ body[data-stscript-style] .hljs.language-stscript {
gap: 0.5em;
>.name {
- font-family: monospace;
+ font-family: var(--monoFontFamily);
white-space: nowrap;
/* color: var(--ac-color-text); */
}
@@ -1853,7 +1855,7 @@ body[data-stscript-style] .hljs.language-stscript {
display: flex;
>.arguments {
- font-family: monospace;
+ font-family: var(--monoFontFamily);
.argument {
white-space: nowrap;
@@ -1943,7 +1945,7 @@ body[data-stscript-style] .hljs.language-stscript {
}
>.returns {
- font-family: monospace;
+ font-family: var(--monoFontFamily);
color: var(--ac-color-text);
&:before {
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-100.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-100.woff2
new file mode 100644
index 000000000..0e23ef8cd
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-100.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-200.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-200.woff2
new file mode 100644
index 000000000..6b9396d6f
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-200.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-300.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-300.woff2
new file mode 100644
index 000000000..e271fd128
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-300.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-500.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-500.woff2
new file mode 100644
index 000000000..ba6135d55
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-500.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-600.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-600.woff2
new file mode 100644
index 000000000..c8ca40080
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-600.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-700.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-700.woff2
new file mode 100644
index 000000000..2b85d6aa9
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-700.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-800.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-800.woff2
new file mode 100644
index 000000000..e3cf0bbab
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-800.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-900.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-900.woff2
new file mode 100644
index 000000000..962d8cd9a
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-900.woff2 differ
diff --git a/public/webfonts/NotoSansMono/noto-sans-mono-v30-regular.woff2 b/public/webfonts/NotoSansMono/noto-sans-mono-v30-regular.woff2
new file mode 100644
index 000000000..e854afa92
Binary files /dev/null and b/public/webfonts/NotoSansMono/noto-sans-mono-v30-regular.woff2 differ
diff --git a/public/webfonts/NotoSansMono/stylesheet.css b/public/webfonts/NotoSansMono/stylesheet.css
new file mode 100644
index 000000000..80fdde3b5
--- /dev/null
+++ b/public/webfonts/NotoSansMono/stylesheet.css
@@ -0,0 +1,89 @@
+/* noto-sans-mono-100 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 100;
+ src: url('noto-sans-mono-v30-100.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-200 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 200;
+ src: url('noto-sans-mono-v30-200.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-300 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 300;
+ src: url('noto-sans-mono-v30-300.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-regular - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 400;
+ src: url('noto-sans-mono-v30-regular.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-500 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 500;
+ src: url('noto-sans-mono-v30-500.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-600 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 600;
+ src: url('noto-sans-mono-v30-600.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-700 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 700;
+ src: url('noto-sans-mono-v30-700.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-800 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 800;
+ src: url('noto-sans-mono-v30-800.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
+
+/* noto-sans-mono-900 - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
+@font-face {
+ font-display: swap;
+ font-family: 'Noto Sans Mono';
+ font-style: normal;
+ font-weight: 900;
+ src: url('noto-sans-mono-v30-900.woff2') format('woff2');
+ /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
+}
diff --git a/src/endpoints/translate.js b/src/endpoints/translate.js
index d1e8f98ae..9541fc331 100644
--- a/src/endpoints/translate.js
+++ b/src/endpoints/translate.js
@@ -2,7 +2,7 @@ const fetch = require('node-fetch').default;
const https = require('https');
const express = require('express');
const { readSecret, SECRET_KEYS } = require('./secrets');
-const { getConfigValue } = require('../util');
+const { getConfigValue, uuidv4 } = require('../util');
const { jsonParser } = require('../express-common');
const DEEPLX_URL_DEFAULT = 'http://127.0.0.1:1188/translate';
@@ -102,6 +102,54 @@ router.post('/google', jsonParser, async (request, response) => {
}
});
+router.post('/yandex', jsonParser, async (request, response) => {
+ const chunks = request.body.chunks;
+ const lang = request.body.lang;
+
+ if (!chunks || !lang) {
+ return response.sendStatus(400);
+ }
+
+ // reconstruct original text to log
+ let inputText = '';
+
+ const params = new URLSearchParams();
+ for (const chunk of chunks) {
+ params.append('text', chunk);
+ inputText += chunk;
+ }
+ params.append('lang', lang);
+ const ucid = uuidv4().replaceAll('-', '');
+
+ console.log('Input text: ' + inputText);
+
+ try {
+ const result = await fetch(`https://translate.yandex.net/api/v1/tr.json/translate?ucid=${ucid}&srv=android&format=text`, {
+ method: 'POST',
+ body: params,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ timeout: 0,
+ });
+
+ if (!result.ok) {
+ const error = await result.text();
+ console.log('Yandex error: ', result.statusText, error);
+ return response.sendStatus(500);
+ }
+
+ const json = await result.json();
+ const translated = json.text.join();
+ console.log('Translated text: ' + translated);
+
+ return response.send(translated);
+ } catch (error) {
+ console.log('Translation error: ' + error.message);
+ return response.sendStatus(500);
+ }
+});
+
router.post('/lingva', jsonParser, async (request, response) => {
try {
const baseUrl = readSecret(request.user.directories, SECRET_KEYS.LINGVA_URL);
diff --git a/src/util.js b/src/util.js
index 326ad6a36..06f64da83 100644
--- a/src/util.js
+++ b/src/util.js
@@ -293,7 +293,14 @@ const color = {
white: (mess) => color.byNum(mess, 37),
};
+/**
+ * Gets a random UUIDv4 string.
+ * @returns {string} A UUIDv4 string
+ */
function uuidv4() {
+ if ('randomUUID' in crypto) {
+ return crypto.randomUUID();
+ }
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);