LetzAI Advanced Settings

AAdvanced features for fast and convenient use of the LetzAI service.

// ==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.');
        });
    }

})();