// ==UserScript==
// @name LetzAI Advanced Settings
// @namespace https://x.com/SavitarStorm
// @version 1.6
// @description AAdvanced features for fast and convenient use of the LetzAI service.
// @author @SavitarStorm @Tano
// @match *://letz.ai/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const PROMPT_TEXT_AREA_SELECTOR = '#TextArea';
const MY_MODELS_STORAGE_KEY = 'myCustomLetzAIModelsList';
const SETTINGS_CONTAINER_SELECTOR = 'div.wrapgenerationsettings';
const PROMPT_FORM_SELECTOR = '.outerwrapprompt form';
function setNativeValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
}
function dispatchEvents(element, events = ['input', 'change']) {
events.forEach(eventType => {
const event = new Event(eventType, { bubbles: true });
element.dispatchEvent(event);
});
}
function addDimensionButtons(settingsContainer) {
const dimensionsHeader = Array.from(settingsContainer.querySelectorAll('h4')).find(h => h.textContent.trim() === 'Dimensions');
if (!dimensionsHeader) {
return;
}
const dimensionsBlock = dimensionsHeader.parentElement;
if (!dimensionsBlock) {
return;
}
if (document.getElementById('myCustomDimensionButtonsContainer')) return;
const customDimensions = [
{ label: '3:4 (1440x1920)', width: 1440, height: 1920 },
{ label: '4:3 (1920x1440)', width: 1920, height: 1440 },
{ label: '9:16 (1080x1920)', width: 1080, height: 1920 },
{ label: '16:9 (1920x1080)', width: 1920, height: 1080 },
{ label: '1:1 (1920x1920)', width: 1920, height: 1920 },
{ label: '5:4 (1920x1536)', width: 1920, height: 1536 },
{ label: '4:5 (1536x1920)', width: 1536, height: 1920 },
{ label: '2:3 (1280x1920)', width: 1280, height: 1920 },
{ label: '3:2 (1920x1280)', width: 1920, height: 1280 },
{ label: '1:2 (960x1920)', width: 960, height: 1920 },
{ label: '2:1 (1920x960)', width: 1920, height: 960 },
{ label: 'Square (1600x1600)', width: 1600, height: 1600 },
];
const buttonsContainer = document.createElement('div');
buttonsContainer.id = 'myCustomDimensionButtonsContainer';
buttonsContainer.style.marginTop = '10px';
buttonsContainer.style.display = 'flex';
buttonsContainer.style.flexWrap = 'wrap';
customDimensions.forEach(dim => {
const button = document.createElement('div');
button.classList.add('stdbuttonsmall');
button.textContent = dim.label;
button.style.marginRight = '5px';
button.style.marginBottom = '5px';
button.style.cursor = 'pointer';
button.addEventListener('click', () => {
updateDimensions(dim.width, dim.height, dim.ifMaxHeight1920 === false);
});
buttonsContainer.appendChild(button);
});
const widthHeightButtonsContainer = dimensionsBlock.querySelector('.widthheightbuttons');
if (widthHeightButtonsContainer) {
dimensionsBlock.insertBefore(buttonsContainer, widthHeightButtonsContainer);
} else {
dimensionsBlock.appendChild(buttonsContainer);
}
console.log('LetzAI Custom Script: Dimension buttons added.');
}
function updateDimensions(width, height, allowExceed1920 = true) {
const widthNumberInput = document.getElementById('width-number');
const heightNumberInput = document.getElementById('height-number');
const widthSlider = document.getElementById('width-slider');
const heightSlider = document.getElementById('height-slider');
if (widthNumberInput && heightNumberInput && widthSlider && heightSlider) {
const defaultMax = 3840;
const sliderMaxWidth = parseInt(widthSlider.max) || 1920;
const sliderMaxHeight = parseInt(heightSlider.max) || 1920;
const maxWidth = allowExceed1920 ? defaultMax : sliderMaxWidth;
const maxHeight = allowExceed1920 ? defaultMax : sliderMaxHeight;
const finalWidth = Math.min(width, maxWidth);
const finalHeight = Math.min(height, maxHeight);
console.log(`LetzAI Custom Script: Attempting to set dimensions ${finalWidth}x${finalHeight}`);
setNativeValue(widthNumberInput, finalWidth.toString());
dispatchEvents(widthNumberInput, ['input', 'change']);
if (typeof widthNumberInput.focus === 'function') widthNumberInput.focus();
if (typeof widthNumberInput.blur === 'function') widthNumberInput.blur();
setNativeValue(heightNumberInput, finalHeight.toString());
dispatchEvents(heightNumberInput, ['input', 'change']);
if (typeof heightNumberInput.focus === 'function') heightNumberInput.focus();
if (typeof heightNumberInput.blur === 'function') heightNumberInput.blur();
setNativeValue(widthSlider, finalWidth.toString());
dispatchEvents(widthSlider, ['input', 'change']);
setNativeValue(heightSlider, finalHeight.toString());
dispatchEvents(heightSlider, ['change', 'input']);
console.log(`LetzAI Custom Script: Dimensions visually set to ${finalWidth}x${finalHeight}. Verify generation.`);
} else {
console.error('LetzAI Custom Script: Dimension setting elements not found (width/height number/slider).');
}
}
let savedModels = [];
const modelsButtonsContainerId = 'myCustomModelsButtonsContainer';
function loadModels() {
const modelsJson = GM_getValue(MY_MODELS_STORAGE_KEY, '[]');
try {
savedModels = JSON.parse(modelsJson);
} catch (e) {
console.error('LetzAI Custom Script: Error loading models, using empty list.', e);
savedModels = [];
}
}
function saveModels() {
GM_setValue(MY_MODELS_STORAGE_KEY, JSON.stringify(savedModels));
}
function addModelToList(modelString) {
if (modelString && !savedModels.includes(modelString)) {
savedModels.push(modelString);
saveModels();
renderModelButtons();
return true;
}
return false;
}
function removeModelFromList(modelString) {
savedModels = savedModels.filter(m => m !== modelString);
saveModels();
renderModelButtons();
}
function renderModelButtons() {
let container = document.getElementById(modelsButtonsContainerId);
const promptForm = document.querySelector(PROMPT_FORM_SELECTOR);
if (!promptForm) {
return;
}
if (!container) {
container = document.createElement('div');
container.id = modelsButtonsContainerId;
container.style.marginTop = '10px';
container.style.display = 'flex';
container.style.flexWrap = 'wrap';
const mentionInputMain = promptForm.querySelector('#mentionInput-main');
if(mentionInputMain && mentionInputMain.nextSibling) {
promptForm.insertBefore(container, mentionInputMain.nextSibling);
} else if (mentionInputMain) {
promptForm.appendChild(container);
} else {
const firstChildOfForm = promptForm.firstChild;
if (firstChildOfForm) {
promptForm.insertBefore(container, firstChildOfForm);
} else {
promptForm.appendChild(container);
}
}
console.log('LetzAI Custom Script: Model buttons container created.');
}
container.innerHTML = '';
if (savedModels.length === 0) {
const noModelsLabel = document.createElement('p');
noModelsLabel.textContent = 'No saved models.';
noModelsLabel.style.fontSize = '12px';
noModelsLabel.style.color = 'grey';
noModelsLabel.style.width = '100%';
noModelsLabel.style.margin = '0';
container.appendChild(noModelsLabel);
return;
}
savedModels.forEach(model => {
const buttonWrapper = document.createElement('div');
buttonWrapper.style.display = 'flex';
buttonWrapper.style.alignItems = 'center';
buttonWrapper.style.marginRight = '5px';
buttonWrapper.style.marginBottom = '5px';
const modelButton = document.createElement('div');
modelButton.classList.add('stdbuttonsmall');
modelButton.textContent = model;
modelButton.style.cursor = 'pointer';
modelButton.title = 'Insert model';
modelButton.addEventListener('click', () => {
insertModelIntoPrompt(model);
});
buttonWrapper.appendChild(modelButton);
const removeButton = document.createElement('span');
removeButton.textContent = '❌';
removeButton.style.cursor = 'pointer';
removeButton.style.marginLeft = '3px';
removeButton.style.fontSize = '10px';
removeButton.title = 'Remove model';
removeButton.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Delete model "${model}"?`)) {
removeModelFromList(model);
}
});
buttonWrapper.appendChild(removeButton);
container.appendChild(buttonWrapper);
});
}
function insertModelIntoPrompt(modelString) {
const textArea = document.querySelector(PROMPT_TEXT_AREA_SELECTOR);
if (!textArea) {
alert('Prompt text area not found!');
return;
}
const currentText = textArea.value;
let newText = currentText;
const start = textArea.selectionStart;
const end = textArea.selectionEnd;
if (document.activeElement === textArea && typeof start === 'number' && typeof end === 'number') {
newText = currentText.substring(0, start) + modelString + currentText.substring(end);
const newCursorPos = start + modelString.length;
requestAnimationFrame(() => {
textArea.selectionStart = textArea.selectionEnd = newCursorPos;
});
} else {
if (currentText.trim() && !currentText.endsWith(' ') && !currentText.endsWith('\n')) {
newText += ' ';
}
newText += modelString;
}
setNativeValue(textArea, newText);
dispatchEvents(textArea, ['input', 'change']);
textArea.focus();
}
function addModelManagementSection(settingsContainer) {
if (document.getElementById('myModelManagementSectionAdded')) return;
const modelSectionDiv = document.createElement('div');
modelSectionDiv.id = 'myModelManagementSectionAdded';
modelSectionDiv.style.marginTop = '20px';
modelSectionDiv.style.paddingTop = '15px';
modelSectionDiv.style.borderTop = '1px solid var(--bordercolor, #444)';
modelSectionDiv.classList.add('left');
const title = document.createElement('h4');
title.textContent = 'Model Management';
modelSectionDiv.appendChild(title);
const addModelButton = document.createElement('div');
addModelButton.classList.add('stdbuttonsmall');
addModelButton.textContent = 'Add Model to List';
addModelButton.style.cursor = 'pointer';
addModelButton.style.display = 'inline-block';
addModelButton.addEventListener('click', () => {
const modelStringInput = prompt('Enter model name (e.g., @fix or LORA_NAME):');
if (modelStringInput && modelStringInput.trim() !== '') {
const modelToAdd = modelStringInput.trim();
if (addModelToList(modelToAdd)) {
alert(`Model "${modelToAdd}" added.`);
} else {
alert(`Model "${modelToAdd}" is already in the list or the input is invalid.`);
}
}
});
modelSectionDiv.appendChild(addModelButton);
settingsContainer.appendChild(modelSectionDiv);
console.log('LetzAI Custom Script: Model management section added.');
}
let initializationComplete = false;
const maxAttempts = 40;
let attempts = 0;
const intervalTime = 500;
loadModels();
function checkAndInjectElements() {
const settingsContainer = document.querySelector(SETTINGS_CONTAINER_SELECTOR);
const promptForm = document.querySelector(PROMPT_FORM_SELECTOR);
let elementsWereAddedNow = false;
if (settingsContainer) {
if (!document.getElementById('myModelManagementSectionAdded')) {
addModelManagementSection(settingsContainer);
elementsWereAddedNow = true;
}
if (!document.getElementById('myCustomDimensionButtonsContainer')) {
addDimensionButtons(settingsContainer);
elementsWereAddedNow = true;
}
}
if (promptForm) {
if (!document.getElementById(modelsButtonsContainerId)) {
renderModelButtons();
elementsWereAddedNow = true;
} else {
// Optional: Uncomment to always redraw buttons if form exists
// renderModelButtons();
}
}
if (elementsWereAddedNow) {
console.log('LetzAI Custom Script: Elements added/updated.');
}
return settingsContainer && promptForm;
}
const initInterval = setInterval(() => {
if (checkAndInjectElements()) {
clearInterval(initInterval);
initializationComplete = true;
console.log('LetzAI Custom Script: Initial initialization complete. Starting MutationObserver.');
setupObserver();
} else {
attempts++;
if (attempts >= maxAttempts) {
clearInterval(initInterval);
console.error('LetzAI Custom Script: Failed to find main containers ('+SETTINGS_CONTAINER_SELECTOR+', '+PROMPT_FORM_SELECTOR+') for initialization after ' + maxAttempts + ' attempts.');
}
}
}, intervalTime);
function setupObserver() {
const observerTargetNode = document.body;
if (!observerTargetNode) {
console.error('LetzAI Custom Script: Failed to find document.body for MutationObserver.');
return;
}
const observerConfig = {
childList: true,
subtree: true
};
const callback = function(mutationsList, observer) {
const settingsContainer = document.querySelector(SETTINGS_CONTAINER_SELECTOR);
if (settingsContainer) {
addModelManagementSection(settingsContainer);
addDimensionButtons(settingsContainer);
}
const promptForm = document.querySelector(PROMPT_FORM_SELECTOR);
const modelsContainer = document.getElementById(modelsButtonsContainerId);
if (promptForm && !modelsContainer) {
console.log('LetzAI Observer: Model buttons container missing, attempting redraw.');
renderModelButtons();
}
};
const observer = new MutationObserver(callback);
observer.observe(observerTargetNode, observerConfig);
console.log('LetzAI Custom Script: MutationObserver started and watching for DOM changes.');
window.addEventListener('beforeunload', () => {
observer.disconnect();
console.log('LetzAI Custom Script: MutationObserver stopped.');
});
}
})();