Compare commits

...

23 Commits

Author SHA1 Message Date
Himickoff 36addb2a46
Merge 95d3304146 into 443ef36653 2024-07-26 18:07:05 -04:00
Cohee 443ef36653
Merge pull request #2563 from Succubyss/timestamp-minor-refactor
refactor: timestampToMoment, parseTimestamp
2024-07-26 22:58:00 +03:00
Cohee 475eca6fca Force save new chats without 1st message 2024-07-26 22:12:14 +03:00
Cohee ff5d5fcc5e Forbid double initialization on opening a chat with 1 message 2024-07-26 21:56:36 +03:00
Cohee a2661b2c48
Merge pull request #2560 from SillyTavern/dupe-persona
Dupe persona
2024-07-26 19:47:51 +03:00
Cohee 9cf53c6a55 Handle null values for missing persona descriptor fields 2024-07-26 19:45:45 +03:00
Cohee 6f58f9c599
Merge pull request #2562 from SillyTavern/dvh-units 2024-07-26 19:34:03 +03:00
Succubyss af227a0191 refactor: timestampToMoment, parseTimestamp
parseTimestamp now returns an ISO 8601 string instead of a moment (currently, only timestampToMoment uses parseTimestamp)
timestampToMoment now does the parsed timestamp conversion itself
2024-07-26 11:29:41 -05:00
Wolfsblvt 63512c208f Fix popup not respecting <null> for text 2024-07-26 18:17:32 +02:00
Cohee 0253ef9cfd Merge branch 'staging' into dvh-units 2024-07-26 11:09:49 +00:00
Cohee 0f84388e9f Revert "Add widget resize"
This reverts commit 11155770e4.
2024-07-26 11:09:39 +00:00
Cohee 472b08d0e5 Update popups to new 2024-07-25 23:57:00 +03:00
Cohee 00d74ec683 #2558 Add persona duping 2024-07-25 23:44:34 +03:00
Cohee 11155770e4 Add widget resize 2024-07-15 00:44:50 +03:00
Cohee b7a1474d7b Switch to dynamic viewport units 2024-07-15 00:42:16 +03:00
Himickoff 95d3304146 Translate comments to en 2024-07-14 17:27:09 +07:00
Himickoff 378be40210 Add more ru comments 2024-07-14 02:35:20 +07:00
Himickoff 00350df82d Fix for other languages
But again small problem with decodig
2024-07-14 01:29:33 +07:00
Himickoff 9cdf2216a2 Now formating apply only for russian language 2024-07-14 00:58:59 +07:00
Himickoff c0d05040e6 Fix syntax 2024-07-14 00:42:05 +07:00
Himickoff 898657dde4 Improved rus google translate 2024-07-14 00:32:19 +07:00
Himickoff 8e17117854 Fix quartes
Google translator, when translating direct speech, for some reason adds an additional quotation mark, which breaks the syntax
1.Before submitting text for translation, we replace all quotation marks with special markers (__OPEN_QUOTE__ and __CLOSE_QUOTE__).
This ensures that quotes are not changed or deleted during translation.
2.Similarly, we replace asterisks with markers (__OPEN_STAR__ and __CLOSE_STAR__).
3.After receiving the translated text, we replace all markers back to the corresponding characters.
2024-07-13 19:13:08 +07:00
Himickoff bf682be17a Translate fix
Fix problem when Googole Transtale replace "" on «» during translation, what broke the syntax
2024-07-13 18:42:30 +07:00
16 changed files with 262 additions and 152 deletions

View File

@ -89,7 +89,7 @@
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 9998;
top: 0;
}

View File

@ -7,8 +7,8 @@
z-index: 999999;
width: 100vw;
height: 100vh;
width: 100svw;
height: 100svh;
width: 100dvw;
height: 100dvh;
background-color: var(--SmartThemeBlurTintColor);
color: var(--SmartThemeBodyColor);
/*for some reason the full screen blur does not work on iOS*/

View File

@ -1,7 +1,7 @@
#logprobsViewer {
overflow-y: auto;
max-width: 90svw;
max-height: 90svh;
max-width: 90dvw;
max-height: 90dvh;
min-width: 100px;
min-height: 50px;
border-radius: 10px;
@ -16,7 +16,7 @@
top: 0;
margin: 0;
right: unset;
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
}
.logprobs_panel_header {

View File

@ -1,6 +1,8 @@
/*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/
@media screen and (max-width: 1000px) {
#send_form.compact #leftSendForm, #send_form.compact #rightSendForm {
#send_form.compact #leftSendForm,
#send_form.compact #rightSendForm {
flex-wrap: nowrap;
width: unset;
}
@ -34,9 +36,9 @@
right: 0;
width: fit-content;
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
@ -102,7 +104,7 @@
min-width: unset;
width: 100%;
max-height: calc(100vh - 45px);
max-height: calc(100svh - 45px);
max-height: calc(100dvh - 45px);
position: fixed;
left: 0;
top: 5px;
@ -130,15 +132,15 @@
#top-bar {
position: fixed;
width: 100vw;
width: 100svw;
width: 100dvw;
}
#bg1,
#bg_custom {
height: 100vh !important;
height: 100svh !important;
height: 100dvh !important;
width: 100vw !important;
width: 100svw !important;
width: 100dvw !important;
background-position: center;
}
@ -146,13 +148,7 @@
#sheld,
#character_popup,
.drawer-content
/* ,
#world_popup */
{
/*max-height: calc(100vh - 36px);
max-height: calc(100svh - 36px);*/
.drawer-content {
width: 100% !important;
margin: 0 auto;
max-width: 100%;
@ -223,10 +219,10 @@
#floatingPrompt,
#cfgConfig,
#logprobsViewer,
#movingDivs > div {
/* 100vh are fallback units for browsers that don't support svh */
#movingDivs>div {
/* 100vh are fallback units for browsers that don't support dvh */
height: calc(100vh - 45px);
height: calc(100svh - 45px);
height: calc(100dvh - 45px);
min-width: 100% !important;
width: 100% !important;
max-width: 100% !important;
@ -249,7 +245,7 @@
#floatingPrompt,
#cfgConfig,
#logprobsViewer,
#movingDivs > div {
#movingDivs>div {
height: min-content;
}
@ -286,9 +282,9 @@
body.waifuMode #sheld {
height: 40vh;
height: 40svh;
height: 40dvh;
top: 60vh;
top: 60svh;
top: 60dvh;
bottom: 0 !important;
}
@ -325,16 +321,16 @@
body.waifuMode .zoomed_avatar {
width: fit-content;
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
}
.scrollableInner {
overflow-y: auto;
overflow-x: hidden;
max-height: calc(100vh - 90px);
max-height: calc(100svh - 90px);
max-height: calc(100dvh - 90px);
}
.horde_multiple_hint {
@ -370,9 +366,9 @@
body:not(.waifuMode) .zoomed_avatar {
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
@ -453,9 +449,9 @@
min-height: unset;
max-height: unset;
width: 100vw;
width: 100svw;
width: 100dvw;
height: calc(100vh - 36px);
height: calc(100svh - 36px);
height: calc(100dvh - 36px);
padding-right: max(env(safe-area-inset-right), 0px);
padding-left: max(env(safe-area-inset-left), 0px);
padding-bottom: 0;
@ -485,10 +481,10 @@
top: 0;
margin: 0 auto;
height: calc(100vh - 70px);
height: calc(100svh - 70px);
height: calc(100dvh - 70px);
width: calc(100% - 5px);
max-height: calc(100vh - 70px);
max-height: calc(100svh - 70px);
max-height: calc(100dvh - 70px);
max-width: calc(100% - 5px);
}

View File

@ -7,5 +7,5 @@ body.safari .popup.large_dialogue_popup .popup-body {
body.safari .popup .popup-body {
height: fit-content;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
}

View File

@ -16,8 +16,8 @@ dialog {
display: flex;
flex-direction: column;
max-height: calc(100svh - 2em);
max-width: calc(100svw - 2em);
max-height: calc(100dvh - 2em);
max-width: calc(100dvw - 2em);
min-height: fit-content;
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
@ -103,7 +103,7 @@ body.no-blur .popup[open]::backdrop {
.popup #toast-container {
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */
height: 100svh;
height: 100dvh;
top: calc(50% + var(--topBarBlockSize));
left: 50%;
transform: translate(-50%, -50%);
@ -115,7 +115,7 @@ body.no-blur .popup[open]::backdrop {
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-height: 75dvh;
max-width: 100%;
}

View File

@ -364,7 +364,7 @@ body.waifuMode #top-bar {
body.waifuMode #sheld {
height: 40vh;
height: 40svh;
height: 40dvh;
top: calc(100% - 40vh);
bottom: 0;
}

View File

@ -6440,6 +6440,9 @@
<button class="menu_button set_default_persona" title="Select this as default persona for the new chats." data-i18n="[title]Select this as default persona for the new chats.">
<i class="fa-fw fa-solid fa-crown"></i>
</button>
<button class="menu_button duplicate_persona" title="Duplicate persona" data-i18n="[title]Duplicate persona">
<i class="fa-fw fa-solid fa-clone"></i>
</button>
<button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona">
<i class="fa-fw fa-solid fa-trash-alt"></i>
</button>

View File

@ -6053,9 +6053,10 @@ async function getChatResult() {
const message = getFirstMessage();
if (message.mes) {
chat.push(message);
await saveChatConditional();
freshChat = true;
}
// Make sure the chat appears on the server
await saveChatConditional();
}
await loadItemizedPrompts(getCurrentChatId());
await printMessages();
@ -6107,7 +6108,7 @@ export async function openCharacterChat(file_name) {
chat_metadata = {};
await getChat();
$('#selected_chat_pole').val(file_name);
await createOrEditCharacter();
await createOrEditCharacter(new CustomEvent('newChat'));
}
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
@ -9837,8 +9838,8 @@ jQuery(async function () {
hideMenu();
});
$('#newChatFromManageScreenButton').on('click', function () {
doNewChat({ deleteCurrentChat: false });
$('#newChatFromManageScreenButton').on('click', async function () {
await doNewChat({ deleteCurrentChat: false });
$('#select_chat_cross').trigger('click');
});

View File

@ -231,8 +231,8 @@
flex-direction: column;
}
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
min-height: 50dvh;
height: 50dvh;
}
}
.popup:has(#qr--modalEditor) {

View File

@ -297,8 +297,8 @@
}
>#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
min-height: 50dvh;
height: 50dvh;
}
}
}

View File

@ -1,5 +1,4 @@
import {
callPopup,
characters,
chat,
chat_metadata,
@ -22,7 +21,7 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor
import { debounce_timeout } from './constants.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { selected_group } from './group-chats.js';
import { POPUP_TYPE, Popup } from './popup.js';
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
let savePersonasPage = 0;
const GRID_STORAGE_KEY = 'Personas_GridView';
@ -332,15 +331,14 @@ async function changeUserAvatar(e) {
* @returns {Promise} Promise that resolves when the persona is set
*/
export async function createPersona(avatarId) {
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>Cancel if you\'re just uploading an avatar.', 'input', '');
const personaName = await Popup.show.input('Enter a name for this persona:', 'Cancel if you\'re just uploading an avatar.', '');
if (!personaName) {
console.debug('User cancelled creating a persona');
return;
}
await delay(500);
const personaDescription = await callPopup('<h3>Enter a description for this persona:</h3>You can always add or change it later.', 'input', '', { rows: 4 });
const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 });
initPersona(avatarId, personaName, personaDescription);
if (power_user.persona_show_notifications) {
@ -349,7 +347,7 @@ export async function createPersona(avatarId) {
}
async function createDummyPersona() {
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>', 'input', '');
const personaName = await Popup.show.input('Enter a name for this persona:', null);
if (!personaName) {
console.debug('User cancelled creating dummy persona');
@ -508,15 +506,20 @@ async function bindUserNameToPersona(e) {
return;
}
let personaUnbind = false;
const existingPersona = power_user.personas[avatarId];
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || '');
const personaName = await Popup.show.input(
'Enter a name for this persona:',
'(If empty name is provided, this will unbind the name from this avatar)',
existingPersona || '',
{ onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } });
// If the user clicked cancel, don't do anything
if (personaName === false) {
if (personaName === null && !personaUnbind) {
return;
}
if (personaName.length > 0) {
if (personaName && personaName.length > 0) {
// If the user clicked ok and entered a name, bind the name to the persona
console.log(`Binding persona ${avatarId} to name ${personaName}`);
power_user.personas[avatarId] = personaName;
@ -643,7 +646,12 @@ async function lockPersona() {
);
}
power_user.personas[user_avatar] = name1;
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
power_user.persona_descriptions[user_avatar] = {
description: '',
position: persona_description_positions.IN_PROMPT,
depth: DEFAULT_DEPTH,
role: DEFAULT_ROLE,
};
}
chat_metadata['persona'] = user_avatar;
@ -672,7 +680,7 @@ async function deleteUserAvatar(e) {
return;
}
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
const confirm = await Popup.show.confirm('Are you sure you want to delete this avatar?', 'All information associated with its linked persona will be lost.');
if (!confirm) {
console.debug('User cancelled deleting avatar');
@ -806,7 +814,7 @@ async function setDefaultPersona(e) {
const personaName = power_user.personas[avatarId];
if (avatarId === currentDefault) {
const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm');
const confirm = await Popup.show.confirm('Are you sure you want to remove the default persona?', personaName);
if (!confirm) {
console.debug('User cancelled removing default persona');
@ -819,8 +827,7 @@ async function setDefaultPersona(e) {
}
delete power_user.default_persona;
} else {
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
const confirm = await Popup.show.confirm(`Are you sure you want to set "${personaName}" as the default persona?`, 'This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.');
if (!confirm) {
console.debug('User cancelled setting default persona');
@ -978,7 +985,7 @@ async function onPersonasRestoreInput(e) {
}
async function syncUserNameToPersona() {
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
const confirmation = await Popup.show.confirm('Are you sure?', `All user-sent messages in this chat will be attributed to ${name1}.`);
if (!confirmation) {
return;
@ -1001,6 +1008,42 @@ export function retriggerFirstMessageOnEmptyChat() {
}
}
/**
* Duplicates a persona.
* @param {string} avatarId
* @returns {Promise<void>}
*/
async function duplicatePersona(avatarId) {
const personaName = power_user.personas[avatarId];
if (!personaName) {
toastr.warning('Chosen avatar is not a persona');
return;
}
const confirm = await Popup.show.confirm('Are you sure you want to duplicate this persona?', personaName);
if (!confirm) {
console.debug('User cancelled duplicating persona');
return;
}
const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
const descriptor = power_user.persona_descriptions[avatarId];
power_user.personas[newAvatarId] = personaName;
power_user.persona_descriptions[newAvatarId] = {
description: descriptor?.description ?? '',
position: descriptor?.position ?? persona_description_positions.IN_PROMPT,
depth: descriptor?.depth ?? DEFAULT_DEPTH,
role: descriptor?.role ?? DEFAULT_ROLE,
};
await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
await getUserAvatars(true, newAvatarId);
saveSettingsDebounced();
}
export function initPersonas() {
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
$(document).on('click', '.set_default_persona', setDefaultPersona);
@ -1059,6 +1102,18 @@ export function initPersonas() {
$('#avatar_upload_file').trigger('click');
});
$(document).on('click', '#user_avatar_block .duplicate_persona', function (e) {
e.stopPropagation();
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
if (!avatarId) {
console.log('no imgfile');
return;
}
duplicatePersona(avatarId);
});
$(document).on('click', '#user_avatar_block .set_persona_image', function (e) {
e.stopPropagation();
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');

View File

@ -73,8 +73,8 @@ const showPopupHelper = {
/**
* Asynchronously displays an input popup with the given header and text, and returns the user's input.
*
* @param {string} header - The header text for the popup.
* @param {string} text - The main text for the popup.
* @param {string?} header - The header text for the popup.
* @param {string?} text - The main text for the popup.
* @param {string} [defaultValue=''] - The default value for the input field.
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<string?>} A Promise that resolves with the user's input.
@ -591,15 +591,15 @@ class PopupUtils {
/**
* Builds popup content with header and text below
*
* @param {string} header - The header to be added to the text
* @param {string} text - The main text content
* @param {string?} header - The header to be added to the text
* @param {string?} text - The main text content
*/
static BuildTextWithHeader(header, text) {
if (!header) {
return text;
}
return `<h3>${header}</h3>
${text}`;
${text ?? ''}`; // Convert no text to empty string
}
}

View File

@ -704,6 +704,22 @@ export function isOdd(number) {
return number % 2 !== 0;
}
/**
* Compare two moment objects for sorting.
* @param {moment.Moment} a The first moment object.
* @param {moment.Moment} b The second moment object.
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
*/
export function sortMoments(a, b) {
if (a.isBefore(b)) {
return 1;
} else if (a.isAfter(b)) {
return -1;
} else {
return 0;
}
}
const dateCache = new Map();
/**
@ -717,26 +733,27 @@ export function timestampToMoment(timestamp) {
return dateCache.get(timestamp);
}
const moment = parseTimestamp(timestamp);
dateCache.set(timestamp, moment);
return moment;
const iso8601 = parseTimestamp(timestamp);
const objMoment = iso8601 ? moment(iso8601) : moment.invalid();
dateCache.set(timestamp, objMoment);
return objMoment;
}
/**
* Parses a timestamp and returns a moment object representing the parsed date and time.
* @param {string|number} timestamp - The timestamp to parse. It can be a string or a number.
* @returns {moment.Moment} - A moment object representing the parsed date and time. If the timestamp is invalid, an invalid moment object is returned.
* @returns {string} - If the timestamp is valid, returns an ISO 8601 string.
*/
function parseTimestamp(timestamp) {
if (!timestamp) return moment.invalid();
if (!timestamp) return;
// Unix time (legacy TAI / tags)
if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) {
const number = Number(timestamp);
if (isNaN(number)) return moment.invalid();
if (!isFinite(number)) return moment.invalid();
if (number < 0) return moment.invalid();
return moment(number);
const unixTime = Number(timestamp);
const isValid = Number.isFinite(unixTime) && !Number.isNaN(unixTime) && unixTime >= 0;
if (!isValid) return;
return new Date(unixTime).toISOString();
}
let dtFmt = [];
@ -760,32 +777,12 @@ function parseTimestamp(timestamp) {
// 2024-6-5 @14h 56m 50s 682ms
dtFmt.push({ callback: convertFromHumanized, pattern: /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/ });
let iso8601;
for (const x of dtFmt) {
let rgxMatch = timestamp.match(x.pattern);
if (!rgxMatch) continue;
iso8601 = x.callback(...rgxMatch);
break;
}
// If one of the patterns matched, return a valid moment object, otherwise return an invalid moment object
return iso8601 ? moment(iso8601) : moment.invalid();
}
/**
* Compare two moment objects for sorting.
* @param {moment.Moment} a The first moment object.
* @param {moment.Moment} b The second moment object.
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
*/
export function sortMoments(a, b) {
if (a.isBefore(b)) {
return 1;
} else if (a.isAfter(b)) {
return -1;
} else {
return 0;
return x.callback(...rgxMatch);
}
return;
}
/** Split string to parts no more than length in size.

View File

@ -137,7 +137,6 @@ body {
width: 100%;
/*fallback for JS load*/
height: 100vh;
height: 100svh;
height: 100dvh;
/*defaults as 100%, then reassigned via JS as pixels, will work on PC and Android*/
/*height: calc(var(--doc-height) - 1px);*/
@ -451,7 +450,7 @@ code {
border-radius: 5px;
background-color: var(--black70a);
padding: 0 3px;
/* max-width: calc(100svw - 95px); */
/* max-width: calc(100dvw - 95px); */
line-height: var(--mainFontSize);
color: var(--white70a);
}
@ -535,13 +534,13 @@ body.reduced-motion #bg_custom {
flex-direction: column;
/* -1px to give sheld some wiggle room to bounce off tobar when moving*/
height: calc(100vh - var(--topBarBlockSize) - 1px);
height: calc(100svh - var(--topBarBlockSize) - 1px);
max-height: calc(100svh - var(--topBarBlockSize) - 1px);
height: calc(100dvh - var(--topBarBlockSize) - 1px);
max-height: calc(100dvh - var(--topBarBlockSize) - 1px);
overflow-x: hidden;
/* max-width: 50vw; */
position: absolute;
left: calc((100vw - var(--sheldWidth))/2);
left: calc((100svw - var(--sheldWidth))/2);
left: calc((100dvw - var(--sheldWidth))/2);
top: var(--topBarBlockSize);
margin: 0 auto;
left: 0;
@ -1160,12 +1159,12 @@ textarea {
font-family: var(--mainFontFamily);
padding: 5px 10px;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
}
textarea.autoSetHeight {
max-height: 50vh;
max-height: 50svh;
max-height: 50dvh;
}
input,
@ -1179,7 +1178,7 @@ select {
min-height: calc(var(--bottomFormBlockSize) + 2px);
height: calc(var(--bottomFormBlockSize) + 2px);
max-height: 50vh;
max-height: 50svh;
max-height: 50dvh;
word-wrap: break-word;
resize: vertical;
display: block;
@ -2111,14 +2110,14 @@ textarea::placeholder {
@media screen and (max-width: 1000px) {
#form_create textarea {
flex-grow: 1;
min-height: 20svh;
min-height: 20dvh;
}
}
@media screen and (min-width: 1001px) {
#description_textarea {
height: 29vh;
height: 29svh;
height: 29dvh;
}
#firstmessage_textarea {
@ -2394,8 +2393,8 @@ input[type="file"] {
#floatingPrompt,
#cfgConfig {
overflow-y: auto;
max-width: 90svw;
max-height: 90svh;
max-width: 90dvw;
max-height: 90dvh;
min-width: 100px;
min-height: 100px;
border-radius: 10px;
@ -2411,7 +2410,7 @@ input[type="file"] {
top: 0;
margin: 0;
right: unset;
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
}
@ -2768,7 +2767,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
flex-wrap: wrap;
width: calc(var(--sheldWidth) - 10px);
max-width: 100vw;
max-width: 100svw;
max-width: 100dvw;
justify-content: space-evenly;
}
@ -3032,6 +3031,10 @@ grammarly-extension {
opacity: 1;
}
.avatar-container .avatar-buttons .menu_button {
padding: 3px;
}
/* Ross should be able to handle this later */
/*.big-avatars .avatar-buttons{
justify-content: center;
@ -3123,7 +3126,7 @@ grammarly-extension {
#dialogue_popup {
width: 500px;
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
position: absolute;
z-index: 9999;
margin-left: auto;
@ -3139,7 +3142,7 @@ grammarly-extension {
background-color: var(--SmartThemeBlurTintColor);
border-radius: 10px;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
display: flex;
flex-direction: column;
overflow-y: hidden;
@ -3153,9 +3156,9 @@ grammarly-extension {
.large_dialogue_popup {
height: 90vh !important;
height: 90svh !important;
height: 90dvh !important;
max-width: 90vw !important;
max-width: 90svw !important;
max-width: 90dvw !important;
}
.wide_dialogue_popup {
@ -3307,7 +3310,7 @@ grammarly-extension {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 9999;
top: 0;
}
@ -3315,9 +3318,9 @@ grammarly-extension {
#bgtest {
display: none;
width: 100vw;
width: 100svw;
width: 100dvw;
height: 100vh;
height: 100svh;
height: 100dvh;
position: absolute;
z-index: -100;
background-color: red;
@ -3954,7 +3957,7 @@ input[type="range"]::-webkit-slider-thumb {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 2058;
}
@ -3967,11 +3970,11 @@ input[type="range"]::-webkit-slider-thumb {
min-width: 100px;
max-width: var(--sheldWidth);
height: calc(100vh - 84px);
height: calc(100svh - 84px);
height: calc(100dvh - 84px);
min-height: calc(100vh - 84px);
min-height: calc(100svh - 84px);
min-height: calc(100dvh - 84px);
max-height: calc(100vh - 84px);
max-height: calc(100svh - 84px);
max-height: calc(100dvh - 84px);
position: absolute;
z-index: 4001;
margin-left: auto;
@ -4050,7 +4053,7 @@ h5 {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 4100;
top: 0;
background-color: var(--black70a);
@ -4064,7 +4067,7 @@ h5 {
max-width: var(--sheldWidth);
height: min-content;
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
min-height: 100px;
position: absolute;
z-index: 2066;
@ -4384,14 +4387,14 @@ a {
overflow-wrap: break-word;
white-space: normal;
max-width: calc(((100vw - 500px) / 2) - 10px);
max-width: calc(((100svw - 500px) / 2) - 10px);
max-width: calc(((100dvw - 500px) / 2) - 10px);
position: absolute;
z-index: 9999;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
/*unsure why, but this prevents scrollbars*/
height: 49vh;
height: 49svh;
height: 49dvh;
padding: 5px;
overflow-y: auto;
@ -4427,11 +4430,11 @@ a {
#right-nav-panel {
width: calc((100vw - var(--sheldWidth) - 2px) /2);
width: calc((100svw - var(--sheldWidth) - 2px) /2);
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
height: calc(100vh - var(--topBarBlockSize));
height: calc(100svh - var(--topBarBlockSize));
height: calc(100dvh - var(--topBarBlockSize));
position: fixed;
top: 0;
margin: 0;
@ -4484,7 +4487,7 @@ a {
border-radius: 10px;
max-width: 100%;
max-height: 40vh;
max-height: 40svh;
max-height: 40dvh;
image-rendering: -webkit-optimize-contrast;
}
@ -4576,18 +4579,18 @@ body:not(.caption) .mes_img_caption {
.img_enlarged_container pre {
max-height: 25vh;
max-height: 25svh;
max-height: 25dvh;
flex-shrink: 0;
overflow: auto;
}
.popup:has(.img_enlarged.zoomed).large_dialogue_popup {
height: 100vh !important;
height: 100svh !important;
height: 100dvh !important;
max-height: 100vh !important;
max-height: 100svh !important;
max-height: 100dvh !important;
max-width: 100vw !important;
max-width: 100svw !important;
max-width: 100dvw !important;
padding: 0;
}
@ -4774,7 +4777,7 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
width: var(--sheldWidth);
overflow-y: auto;
max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
max-height: calc(100svh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
max-height: calc(100dvh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
display: none;
position: absolute;
top: var(--topBarBlockSize);
@ -4809,11 +4812,11 @@ body:not(.movingUI) .drawer-content.maximized {
.fillLeft {
width: calc((100vw - var(--sheldWidth) - 2px) /2);
width: calc((100svw - var(--sheldWidth) - 2px) /2);
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
height: calc(100vh - var(--topBarBlockSize));
height: calc(100svh - var(--topBarBlockSize));
height: calc(100dvh - var(--topBarBlockSize));
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
position: fixed;
top: 0;
margin: 0;
@ -5063,7 +5066,7 @@ body:not(.movingUI) .drawer-content.maximized {
width: 100%;
/* margin-inline: 10px; */
max-height: 90vh;
max-width: 90svh;
max-width: 90dvh;
}
.zoomed_avatar img {

View File

@ -4,6 +4,9 @@ const express = require('express');
const { readSecret, SECRET_KEYS } = require('./secrets');
const { getConfigValue, uuidv4 } = require('../util');
const { jsonParser } = require('../express-common');
const iconv = require('iconv-lite');
const { generateRequestUrl, normaliseResponse } = require('google-translate-api-browser');
const bodyParser = require('body-parser');
const DEEPLX_URL_DEFAULT = 'http://127.0.0.1:1188/translate';
const ONERING_URL_DEFAULT = 'http://127.0.0.1:4990/translate';
@ -65,39 +68,88 @@ router.post('/libre', jsonParser, async (request, response) => {
}
});
router.post('/google', jsonParser, async (request, response) => {
try {
const { generateRequestUrl, normaliseResponse } = require('google-translate-api-browser');
const text = request.body.text;
let text = request.body.text;
const lang = request.body.lang;
if (!text || !lang) {
return response.sendStatus(400);
}
console.log('Input text: ' + text);
console.log('Input text:', text);
//console.log('----------');
// Apply formatting only if the translation language is Russian
if (lang === 'ru') {
// Replace quotation marks and asterisks with special markers before translating
const openQuote = '__OPEN_QUOTE__ ';
const closeQuote = ' __CLOSE_QUOTE__';
const openStar = '__OPEN_STAR__ ';
const closeStar = ' __CLOSE_STAR__';
// A very rare case
const very_rare_execption = '__OPEN_STAR__, ';
// Use counters to alternate between opening and closing markers
let quoteCounter = 0;
let starCounter = 0;
text = text.replace(/"/g, () => (quoteCounter++ % 2 === 0 ? openQuote : closeQuote));
text = text.replace(/\*/g, () => (starCounter++ % 2 === 0 ? openStar : closeStar));
//console.log('Pre-Input text:', text);
//console.log('----------');
}
const url = generateRequestUrl(text, { to: lang });
https.get(url, (resp) => {
let data = '';
let data = [];
resp.on('data', (chunk) => {
data += chunk;
data.push(chunk);
});
resp.on('end', () => {
try {
const result = normaliseResponse(JSON.parse(data));
console.log('Translated text: ' + result.text);
return response.send(result.text);
let result;
if (lang === 'ru') {
// Decoding for Russian language
const decodedData = iconv.decode(Buffer.concat(data), 'utf-8');
result = normaliseResponse(JSON.parse(decodedData));
} else {
// For other languages, we use the data as is
result = normaliseResponse(JSON.parse(Buffer.concat(data).toString()));
}
//console.log('Pre-Translated text:', result.text);
//console.log('----------');
let fixedText = result.text;
// Restore formatting only if the target language is Russian
if (lang === 'ru') {
fixedText = result.text
.replace(new RegExp('__OPEN_QUOTE__ ', 'g'), '"')
.replace(new RegExp(' __CLOSE_QUOTE__', 'g'), '"')
.replace(new RegExp('__OPEN_STAR__ ', 'g'), '*')
.replace(new RegExp(' __CLOSE_STAR__', 'g'), '*')
.replace(new RegExp('__OPEN_STAR__, ', 'g'), '*');
}
console.log('Translated text:', fixedText);
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
return response.send(fixedText);
} catch (error) {
console.log('Translation error', error);
return response.sendStatus(500);
}
});
}).on('error', (err) => {
console.log('Translation error: ' + err.message);
console.log('Translation error:', err.message);
return response.sendStatus(500);
});
} catch (error) {
@ -106,6 +158,9 @@ router.post('/google', jsonParser, async (request, response) => {
}
});
router.post('/yandex', jsonParser, async (request, response) => {
const chunks = request.body.chunks;
const lang = request.body.lang;