HailuoAI Minimax Video Prompt Queue and Bulk Download

Allows queuing multiple prompts and images for HailuoAI Video generator and adds bulk download functionality with enhanced queue control features. Visit https://github.com/BillarySquintin/Billy_Scripts/

// ==UserScript==
// @name         HailuoAI Minimax Video Prompt Queue and Bulk Download
// @namespace    http://tampermonkey.net/
// @version      3.15
// @description  Allows queuing multiple prompts and images for HailuoAI Video generator and adds bulk download functionality with enhanced queue control features. Visit https://github.com/BillarySquintin/Billy_Scripts/
// @author       Billary
// @match        https://hailuoai.video/*
// @grant        none
// @license MIT
// ==/UserScript==


(function() {
    'use strict';

    if (!window.location.href.startsWith('https://hailuoai.video/')) {
        return;
    }

    // Create a container for the buttons
    const buttonContainer = document.createElement('div');
    buttonContainer.style.position = 'fixed';
    buttonContainer.style.top = '10px';
    buttonContainer.style.left = '0';
    buttonContainer.style.right = '0';
    buttonContainer.style.zIndex = '1000';
    buttonContainer.style.textAlign = 'center';
    buttonContainer.style.display = 'flex';
    buttonContainer.style.justifyContent = 'center';
    buttonContainer.style.alignItems = 'center';
    document.body.appendChild(buttonContainer);

    // Create the Selected Videos Counter
    const selectedCounter = document.createElement('div');
    selectedCounter.textContent = '';
    selectedCounter.style.backgroundColor = '#007BFF';
    selectedCounter.style.color = 'white';
    selectedCounter.style.padding = '5px 10px';
    selectedCounter.style.borderRadius = '5px';
    selectedCounter.style.marginRight = '10px';
    selectedCounter.style.display = 'none'; // Initially hidden
    buttonContainer.appendChild(selectedCounter);

    // Create the Prompt Queue button
    const queueButton = document.createElement("button");
    queueButton.innerHTML = "Prompt Queue";
    queueButton.style.backgroundColor = "#4CAF50";
    queueButton.style.color = "white";
    queueButton.style.border = "none";
    queueButton.style.padding = "10px";
    queueButton.style.borderRadius = "5px";
    queueButton.style.cursor = "pointer";
    queueButton.style.margin = '0 5px';
    buttonContainer.appendChild(queueButton);

    // Create the Bulk Download button
    const bulkDownloadButton = document.createElement("button");
    bulkDownloadButton.innerHTML = "Bulk Download";
    bulkDownloadButton.style.backgroundColor = "#4CAF50";
    bulkDownloadButton.style.color = "white";
    bulkDownloadButton.style.border = "none";
    bulkDownloadButton.style.padding = "10px";
    bulkDownloadButton.style.borderRadius = "5px";
    bulkDownloadButton.style.cursor = "pointer";
    bulkDownloadButton.style.margin = '0 5px';
    buttonContainer.appendChild(bulkDownloadButton);

    // Create the Toggle Autoplay button
    const toggleAutoplayButton = document.createElement("button");
    toggleAutoplayButton.innerHTML = "Disable Autoplay";
    toggleAutoplayButton.style.backgroundColor = "#4CAF50";
    toggleAutoplayButton.style.color = "white";
    toggleAutoplayButton.style.border = "none";
    toggleAutoplayButton.style.padding = "10px";
    toggleAutoplayButton.style.borderRadius = "5px";
    toggleAutoplayButton.style.cursor = "pointer";
    toggleAutoplayButton.style.margin = '0 5px';
    buttonContainer.appendChild(toggleAutoplayButton);

    // Create the Pause All Videos button
    const pauseAllButton = document.createElement("button");
    pauseAllButton.innerHTML = "Pause All Videos";
    pauseAllButton.style.backgroundColor = "#4CAF50";
    pauseAllButton.style.color = "white";
    pauseAllButton.style.border = "none";
    pauseAllButton.style.padding = "10px";
    pauseAllButton.style.borderRadius = "5px";
    pauseAllButton.style.cursor = "pointer";
    pauseAllButton.style.margin = '0 5px';
    buttonContainer.appendChild(pauseAllButton);

    // Function to generate a random delay between min and max milliseconds
    function getRandomDelay(minMs, maxMs) {
        return Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
    }

    // Variable to track autoplay state
    let isAutoplayEnabled = true;

    // Function to update video autoplay settings
    function updateVideoAutoplay() {
        let videoElements = document.querySelectorAll('div.video-cards video');
        videoElements.forEach(function(video) {
            if (isAutoplayEnabled) {
                video.setAttribute('autoplay', '');
                video.muted = true;
            } else {
                video.removeAttribute('autoplay');
                video.pause();
            }
        });
    }

    // Function to handle video hover events
    function handleVideoHover(event) {
        if (!isAutoplayEnabled) {
            event.preventDefault();
            event.stopPropagation();
        }
    }

    // Function to add hover event listeners to video cards
    function addHoverListeners() {
        let videoCards = document.querySelectorAll('div.video-cards');
        videoCards.forEach(function(card) {
            if (!card.getAttribute('data-hover-listener-added')) {
                card.addEventListener('mouseenter', handleVideoHover, true);
                card.addEventListener('mouseover', handleVideoHover, true);
                card.setAttribute('data-hover-listener-added', 'true');
            }
        });
    }

    // Observer to watch for new video elements being added to the page
    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                updateVideoAutoplay();
                addHoverListeners();
            }
        });
    });

    // Start observing the body for changes in the child elements
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial setup
    updateVideoAutoplay();
    addHoverListeners();

    // Event listener for Toggle Autoplay button
    toggleAutoplayButton.addEventListener('click', function() {
        isAutoplayEnabled = !isAutoplayEnabled;
        toggleAutoplayButton.innerHTML = isAutoplayEnabled ? "Disable Autoplay" : "Enable Autoplay";
        updateVideoAutoplay();
    });

    // Event listener for Pause All Videos button
    pauseAllButton.addEventListener('click', function() {
        let videoElements = document.querySelectorAll('div.video-cards video');
        videoElements.forEach(function(video) {
            video.pause();
        });
    });

    // Create a container div for the prompt queue
    var container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.top = '50px';
    container.style.left = '50%';
    container.style.transform = 'translateX(-50%)';
    container.style.zIndex = '10000';
    container.style.backgroundColor = 'white';
    container.style.border = '1px solid black';
    container.style.padding = '10px';
    container.style.maxWidth = '400px';
    container.style.overflowY = 'auto';
    container.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.5)';
    container.style.borderRadius = '8px';
    container.style.display = 'none';

    // Create a close button for the container
    var closeButton = document.createElement('button');
    closeButton.textContent = 'X';
    closeButton.style.position = 'absolute';
    closeButton.style.top = '5px';
    closeButton.style.right = '5px';
    closeButton.style.background = 'transparent';
    closeButton.style.border = 'none';
    closeButton.style.fontSize = '16px';
    closeButton.style.cursor = 'pointer';
    closeButton.addEventListener('click', function() {
        container.style.display = 'none';
    });
    container.appendChild(closeButton);

    // Create a label for the textarea
    var label = document.createElement('label');
    label.textContent = 'Enter your prompts here (JSON format for images), separated by "---":';
    label.style.display = 'block';
    label.style.marginBottom = '5px';
    container.appendChild(label);

    // Create a textarea for prompts
    var textarea = document.createElement('textarea');
    textarea.rows = 10;
    textarea.cols = 40;
    textarea.placeholder = 'Enter your prompts here, separated by "---"';
    textarea.style.width = '100%';
    textarea.style.boxSizing = 'border-box';
    container.appendChild(textarea);

    // Create a label and input for Repeat Count
    var repeatLabel = document.createElement('label');
    repeatLabel.textContent = 'Repeat Count (default 1, "~" for infinity):';
    repeatLabel.style.display = 'block';
    repeatLabel.style.marginTop = '10px';
    container.appendChild(repeatLabel);

    var repeatInput = document.createElement('input');
    repeatInput.type = 'text';
    repeatInput.value = '1';
    repeatInput.style.width = '100%';
    container.appendChild(repeatInput);

    // Create a label and input for Starting Prompt Number
    var startPromptLabel = document.createElement('label');
    startPromptLabel.textContent = 'Starting Prompt Number (default 1):';
    startPromptLabel.style.display = 'block';
    startPromptLabel.style.marginTop = '10px';
    container.appendChild(startPromptLabel);

    var startPromptInput = document.createElement('input');
    startPromptInput.type = 'number';
    startPromptInput.value = '1';
    startPromptInput.min = '1';
    startPromptInput.style.width = '100%';
    container.appendChild(startPromptInput);

    // Create a label to display the current batch number
    var batchLabel = document.createElement('label');
    batchLabel.textContent = 'Current Batch: 0';
    batchLabel.style.display = 'block';
    batchLabel.style.marginTop = '10px';
    container.appendChild(batchLabel);

    // Create a label to display the current prompt number
    var promptCounterLabel = document.createElement('label');
    promptCounterLabel.textContent = 'Current Prompt: 0 / 0';
    promptCounterLabel.style.display = 'block';
    promptCounterLabel.style.marginTop = '10px';
    container.appendChild(promptCounterLabel);

    // Create control buttons (Pause, Back, Hold)
    var controlButtonsContainer = document.createElement('div');
    controlButtonsContainer.style.marginTop = '10px';
    container.appendChild(controlButtonsContainer);

    var pauseButton = document.createElement('button');
    pauseButton.textContent = 'Pause Queue';
    pauseButton.style.marginRight = '10px';
    pauseButton.style.padding = '5px 10px';
    pauseButton.style.cursor = 'pointer';
    controlButtonsContainer.appendChild(pauseButton);

    var backButton = document.createElement('button');
    backButton.textContent = 'Go Back One';
    backButton.style.marginRight = '10px';
    backButton.style.padding = '5px 10px';
    backButton.style.cursor = 'pointer';
    controlButtonsContainer.appendChild(backButton);

    var holdButton = document.createElement('button');
    holdButton.textContent = 'Hold Current Prompt';
    holdButton.style.padding = '5px 10px';
    holdButton.style.cursor = 'pointer';
    controlButtonsContainer.appendChild(holdButton);

    // Create an "Add Images" button
    var addImagesButton = document.createElement('button');
    addImagesButton.textContent = 'Add Images';
    addImagesButton.style.marginTop = '10px';
    addImagesButton.style.marginRight = '10px';
    addImagesButton.style.padding = '5px 10px';
    addImagesButton.style.cursor = 'pointer';
    container.appendChild(addImagesButton);

    // Create a start button
    var startButton = document.createElement('button');
    startButton.textContent = 'Start Queue';
    startButton.style.marginTop = '10px';
    startButton.style.padding = '5px 10px';
    startButton.style.cursor = 'pointer';
    container.appendChild(startButton);

    // Append the container to the body
    document.body.appendChild(container);

    // Add an event listener to the queueButton to toggle the prompt queue container
    queueButton.addEventListener('click', function() {
        container.style.display = (container.style.display === 'none') ? 'block' : 'none';
    });

    // Store images added via "Add Images" button
    var imagesMap = new Map();

    // Handle adding images through file selection
    addImagesButton.addEventListener('click', function() {
        var fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.jpg,.jpeg,.png';
        fileInput.multiple = true;
        fileInput.style.display = 'none';

        fileInput.addEventListener('change', function(event) {
            var files = event.target.files;
            if (files.length === 0) return;

            Array.from(files).forEach(function(file) {
                imagesMap.set(file.name, file);
            });

            alert('Images have been added. Please ensure the filenames in your JSON input match the selected images.');
        });

        fileInput.click();
    });

    // Initialize variables
    var promptQueue = [];
    var originalPromptQueue = [];
    var totalRepeatCount = 1;
    var currentRepeat = 0;
    var startingPromptIndex = 1;
    var currentPromptNumber = 0;
    var totalPromptCount = 0;
    var totalPrompts = 0;
    var isPaused = false;
    var isHolding = false;

    // Now, when the start button is clicked, we start processing the prompts
    startButton.addEventListener('click', function() {
        // Clear previous queue
        promptQueue = [];

        var content = textarea.value.trim();

        if (!content) {
            alert('Please enter prompts or image-prompt pairs in the textarea.');
            return;
        }

        var repeatCountInput = repeatInput.value.trim();
        var repeatCount = 1;
        if (repeatCountInput === '~') {
            repeatCount = Infinity;
        } else {
            repeatCount = parseInt(repeatCountInput, 10);
            if (isNaN(repeatCount) || repeatCount < 1) {
                repeatCount = 1;
            }
        }

        var startPromptInputValue = parseInt(startPromptInput.value.trim(), 10);
        startingPromptIndex = isNaN(startPromptInputValue) || startPromptInputValue < 1 ? 1 : startPromptInputValue;

        var items = content.split('---').map(item => item.trim()).filter(item => item.length > 0);

        totalPrompts = items.length;

        processTextareaItems(items, function() {
            if(promptQueue.length === 0) {
                alert('No valid prompts or images to process.');
                return;
            }

            // Duplicate the promptQueue based on repeat count
            totalRepeatCount = repeatCount;
            currentRepeat = 0;

            originalPromptQueue = promptQueue.slice(); // Make a copy of the original prompts

            // Initialize prompt counters
            totalPromptCount = originalPromptQueue.length;

            // Adjust the prompt queue based on the starting prompt index
            startingPromptIndex = Math.min(startingPromptIndex, promptQueue.length);
            startingPromptIndex = Math.max(1, startingPromptIndex);
            promptQueue = promptQueue.slice(startingPromptIndex - 1);

            // Update the batch label
            batchLabel.textContent = 'Current Batch: ' + (currentRepeat + 1);

            // Set the current prompt number
            currentPromptNumber = startingPromptIndex;

            updatePromptCounterLabel();

            // Disable the start button to prevent multiple clicks
            startButton.disabled = true;

            // Start processing the prompts
            processPrompts();
        });
    });

    // Function to update the prompt counter label
    function updatePromptCounterLabel() {
        promptCounterLabel.textContent = 'Current Prompt: ' + currentPromptNumber + ' / ' + totalPromptCount;
    }

    // Function to process items from textarea
    function processTextareaItems(items, callback) {
        items.forEach(function(item) {
            try {
                // Try to parse item as JSON
                var jsonItem = JSON.parse(item);
                if (Array.isArray(jsonItem)) {
                    jsonItem.forEach(function(entry) {
                        processJsonEntry(entry);
                    });
                } else if (typeof jsonItem === 'object' && jsonItem !== null) {
                    // Single JSON object
                    processJsonEntry(jsonItem);
                } else {
                    alert('Invalid JSON format. Expected an object or an array of objects.');
                }
            } catch (e) {
                // Not JSON, treat as text prompt
                promptQueue.push({ type: 'text', content: item });
            }
        });

        callback();
    }

    // Function to check for the specific error message
    //function checkForErrorMessage(callback) {
    //    // Check for the error message element
    //    var errorMessageElement = document.querySelector('div.adm-auto-center-content');
    //    if (errorMessageElement && errorMessageElement.textContent.includes('An error occurred while generating the content, please try again')) {
    //        callback(true);
    //    } else {
    //        callback(false);
    //    }
    //}

    function processJsonEntry(entry) {
        if (entry.filename && entry.prompt) {
            // Check if the image has been added via "Add Images"
            if (imagesMap.has(entry.filename)) {
                promptQueue.push({
                    type: 'image',
                    file: imagesMap.get(entry.filename),
                    prompt: entry.prompt
                });
            } else {
                alert(`Image "${entry.filename}" has not been added via "Add Images". Please add it.`);
            }
        } else {
            alert('Invalid JSON entry. Each entry must have "filename" and "prompt".');
        }
    }

    // Function to set the value of a textarea in a way that React recognizes
    function setNativeValue(element, value) {
        const lastValue = element.value;
        element.value = value;
        const event = new Event('input', { bubbles: true });
        // Hack to make React notice the change
        const tracker = element._valueTracker;
        if (tracker) {
            tracker.setValue(lastValue);
        }
        element.dispatchEvent(event);
    }

    // Updated processPrompts function with random delay
    function processPrompts() {
        // If paused or holding, wait and retry
        if (isPaused || isHolding) {
            setTimeout(processPrompts, 1000);
            return;
        }

        if (promptQueue.length === 0) {
            currentRepeat++;
            if (currentRepeat < totalRepeatCount || totalRepeatCount === Infinity) {
                // Reset the promptQueue to original and start over
                promptQueue = originalPromptQueue.slice();
                // Update the batch label
                batchLabel.textContent = 'Current Batch: ' + (currentRepeat + 1);
                // Reset the current prompt number
                currentPromptNumber = 1;
                updatePromptCounterLabel();
                processPrompts();
            } else {
                alert('All prompts have been processed.');
                startButton.disabled = false;
                // Reset the batch label and prompt counter
                batchLabel.textContent = 'Current Batch: 0';
                promptCounterLabel.textContent = 'Current Prompt: 0 / 0';
            }
            return;
        }

        var item = promptQueue.shift();

        // Update current prompt number
        updatePromptCounterLabel();

        // Wait for queue space before proceeding
        waitForQueueSpace(function() {
            // Remove any uploaded image before processing the next prompt
            removeUploadedImage();

            if (item.type === 'text') {
                processTextPrompt(item.content, function() {
                    currentPromptNumber++;
                    // Wait between 10 and 45 seconds before proceeding to the next prompt
                    var delay = getRandomDelay(10000, 45000);
                    console.log('Waiting for ' + (delay / 1000) + ' seconds before processing the next prompt.');
                    setTimeout(processPrompts, delay);
                });
            } else if (item.type === 'image') {
                revealUploadPane(function() {
                    processImagePrompt(item, function() {
                        currentPromptNumber++;
                        // Wait between 10 and 45 seconds before proceeding to the next prompt
                        var delay = getRandomDelay(10000, 45000);
                        console.log('Waiting for ' + (delay / 1000) + ' seconds before processing the next prompt.');
                        setTimeout(processPrompts, delay);
                    });
                });
            }
        });
    }

    // Updated waitForQueueSpace function
    function waitForQueueSpace(callback) {
        var checkQueue = setInterval(function() {
            // If paused or holding, wait and retry
            if (isPaused || isHolding) {
                return; // Do nothing, stay in the interval
            }

            var submitButtonLoading = document.querySelector('img[src="/assets/img/dark-loading.png"]');

            var submitButtonReady = false;

            var queueCounterFound = false;
            var queueTextCurrentSize = 0;
            var queueTextMaxSize = 5; // Default to 5 if not found

            // Check if submit button is not loading
            if (!submitButtonLoading) {
                submitButtonReady = true;
            } else {
                console.log('Submit button is loading. Waiting...');
            }

            // Try to get queue size from the queue counter
            var queueCounterElements = document.querySelectorAll('div.bg-clip-text');

            queueCounterElements.forEach(function(element) {
                var textContent = element.textContent.replace(/\s+/g, '');
                var numbers = textContent.match(/\d+/g);

                if (numbers && numbers.length === 2) {
                    queueTextCurrentSize = parseInt(numbers[0], 10);
                    queueTextMaxSize = parseInt(numbers[1], 10);
                    queueCounterFound = true;
                }
            });

            // If we didn't find the counter with numbers, try to get queue size from the message text
            if (!queueCounterFound) {
                var messageElement = document.querySelector('div.font-medium');
                if (messageElement) {
                    var messageText = messageElement.textContent.trim();
                    var match = messageText.match(/(\d+)\s+jobs\s+in\s+queue/);
                    if (match) {
                        queueTextCurrentSize = parseInt(match[1], 10);
                        queueTextMaxSize = 5; // Assuming max queue size is 5
                        queueCounterFound = true;
                    } else if (messageText.includes('you can queue')) {
                        // Assuming queue is empty when the message says 'you can queue 5 jobs at once'
                        queueTextCurrentSize = 0;
                        queueTextMaxSize = 5;
                        queueCounterFound = true;
                    }
                } else {
                    console.log('Queue message element not found.');
                }
            }

            // Count number of queued videos (waiting to be processed) by class 'video-gen-loading'
            var queuedVideoElements = document.querySelectorAll('div.video-gen-loading');
            var queuedVideoCount = queuedVideoElements.length;

            // Count number of processing videos (currently being processed) by elements with 'role="progressbar"'
            var processingVideoElements = document.querySelectorAll('[role="progressbar"]');
            var processingVideoCount = processingVideoElements.length;

            // Output the counts for debugging
            console.log('Submit button ready:', submitButtonReady);
            console.log('Queue counter found:', queueCounterFound);
            console.log('Queue counter size:', queueTextCurrentSize + '/' + queueTextMaxSize);
            console.log('Queued video count:', queuedVideoCount);
            console.log('Processing video count:', processingVideoCount);

            // Decide on the current queue size
            var totalQueueSize = queuedVideoCount + processingVideoCount;
            var maxQueueSize = Math.max(queueTextMaxSize, 5);

            if (totalQueueSize < maxQueueSize && submitButtonReady) {
                clearInterval(checkQueue);
                // Wait for a random delay before proceeding
                var delay = getRandomDelay(10000, 45000);
                console.log('Waiting for ' + (delay / 1000) + ' seconds before submitting the next prompt.');
                setTimeout(callback, delay);
            } else {
                console.log('Queue is full (' + totalQueueSize + '/' + maxQueueSize + ') or submit button not ready. Waiting...');
            }
        }, 2000);
    }



    // Function to check for error messages
    function checkForErrorMessage(callback) {
        var errorElement = document.querySelector('.web-toast.error');
        if (errorElement) {
            callback(true);
            // Optionally remove the error element to prevent duplicate detections
            errorElement.remove();
        } else {
            callback(false);
        }
    }

    // Updated processTextPrompt function with retry logic
    function processTextPrompt(prompt, callback, retryCount = 0) {
        var maxRetries = 3;
        var promptTextarea = document.querySelector('.description_wrap textarea');
        if(!promptTextarea) {
            alert('Could not find prompt textarea on the page.');
            startButton.disabled = false;
            return;
        }

        setNativeValue(promptTextarea, prompt);

        waitForSubmitButton(function(submitButton) {
            submitButton.click();
            // Wait for potential error message
            setTimeout(function() {
                checkForErrorMessage(function(hasError) {
                    if (hasError) {
                        if (retryCount < maxRetries) {
                            console.log('Error occurred. Retrying prompt in 3 seconds. Retry count: ' + (retryCount + 1));
                            setTimeout(function() {
                                processTextPrompt(prompt, callback, retryCount + 1);
                            }, 3000);
                        } else {
                            console.log('Max retries reached for this prompt. Moving to next prompt.');
                            callback();
                        }
                    } else {
                        // Proceed to the next prompt
                        callback();
                    }
                });
            }, 3000);
        });
    }

    // Updated processImagePrompt function with submission error handling
    function processImagePrompt(item, callback, retryCount = 0) {
        var maxRetries = 3;
        var promptTextarea = document.querySelector('.description_wrap textarea');
        if (!promptTextarea) {
            alert('Could not find prompt textarea on the page.');
            startButton.disabled = false;
            return;
        }

        setNativeValue(promptTextarea, item.prompt);

        uploadImage(item.file, function(success) {
            if (!success) {
                alert('Failed to upload image.');
                callback();
                return;
            }

            waitForSubmitButton(function(submitButton) {
                submitButton.click();
                // Wait for potential error message
                setTimeout(function() {
                    checkForErrorMessage(function(hasError) {
                        if (hasError) {
                            if (retryCount < maxRetries) {
                                console.log('Error occurred. Retrying prompt in 3 seconds. Retry count: ' + (retryCount + 1));
                                setTimeout(function() {
                                    // Remove the image and attempt to upload again
                                    removeUploadedImage();
                                    // Open upload pane again if needed
                                    revealUploadPane(function() {
                                        processImagePrompt(item, callback, retryCount + 1);
                                    });
                                }, 3000);
                            } else {
                                console.log('Max retries reached for this prompt. Removing image and moving to next prompt.');
                                removeUploadedImage();
                                callback();
                            }
                        } else {
                            // Proceed to the next prompt
                            callback();
                        }
                    });
                }, 3000);
            });
        });
    }

    // Function to wait until the image has finished uploading
    function waitForImageUpload(callback) {
        var maxRetries = 30; // Adjust retries as necessary
        var retries = 0;
        var checkUpload = setInterval(function() {
            var uploadedImage = document.querySelector('img[alt="uploaded image"]');
            if (uploadedImage) {
                // Get the closest parent div with class 'relative'
                var parentDiv = uploadedImage.closest('div.relative');
                if (parentDiv) {
                    // Check if there is a child div.absolute (the overlay with the spinner or error)
                    var overlayDiv = parentDiv.querySelector('div.absolute');
                    if (overlayDiv) {
                        // Check if the retry button is present
                        var retryButton = overlayDiv.querySelector('svg.fill-\\[\\#F55762\\]');
                        if (retryButton) {
                            console.log('Image upload failed. Retrying...');
                            // Click the retry button
                            var retryButtonParent = retryButton.closest('div.cursor-pointer');
                            if (retryButtonParent) {
                                retryButtonParent.click();
                            } else {
                                console.log('Retry button parent not found.');
                            }
                        } else {
                            console.log('Image is still uploading...');
                        }
                    } else {
                        clearInterval(checkUpload);
                        console.log('Image upload successful.');
                        callback(true);
                    }
                } else {
                    console.log('Cannot find parent div of the uploaded image.');
                    retries++;
                }
            } else {
                console.log('Uploaded image element not found.');
                retries++;
            }

            if (retries >= maxRetries) {
                clearInterval(checkUpload);
                console.log('Image upload timed out.');
                callback(false);
            }
        }, 2000);
    }

    // Function to remove any uploaded image
    function removeUploadedImage() {
        var removeButton = document.querySelector('span.hover\\:cursor-pointer');
        if (removeButton) {
            removeButton.click();
            console.log('Uploaded image removed.');
        } else {
            console.log('No uploaded image to remove.');
        }
    }

    // Updated revealUploadPane function
    function revealUploadPane(callback) {
        // Updated selector to match the new element
        var uploadPaneOpener = document.querySelector('span.inline-block.group-hover\\:hidden');
        if (uploadPaneOpener) {
            uploadPaneOpener.click();
            // Wait for the upload pane to appear
            setTimeout(function() {
                callback();
            }, 1000); // Adjust the delay if necessary
        } else {
            alert('Could not find the button to open the upload pane.');
            startButton.disabled = false;
        }
    }

    // Function to upload an image
    function uploadImage(file, callback, uploadRetryCount = 0) {
        var maxUploadRetries = 3;
        var uploadInput = document.querySelector('.ant-upload input[type="file"]');
        if (!uploadInput) {
            console.log('Could not find image upload input on the page.');
            if (uploadRetryCount < maxUploadRetries) {
                console.log('Retrying to find the upload input. Retry count: ' + (uploadRetryCount + 1));
                setTimeout(function() {
                    uploadImage(file, callback, uploadRetryCount + 1);
                }, 1000);
            } else {
                alert('Failed to find image upload input after multiple attempts.');
                callback(false);
            }
            return;
        }

        var dataTransfer = new DataTransfer();
        dataTransfer.items.add(file);
        uploadInput.files = dataTransfer.files;

        uploadInput.dispatchEvent(new Event('change', { bubbles: true }));

        // Wait for image to finish uploading
        waitForImageUpload(function(success) {
            if (success) {
                callback(true);
            } else {
                if (uploadRetryCount < maxUploadRetries) {
                    console.log('Image upload failed. Removing image and retrying upload. Retry count: ' + (uploadRetryCount + 1));
                    removeUploadedImage();
                    uploadImage(file, callback, uploadRetryCount + 1);
                } else {
                    alert('Image upload failed after multiple attempts.');
                    callback(false);
                }
            }
        });
    }

    // Function to check if the submit button is available
    function isSubmitButtonAvailable() {
        var submitButton = getSubmitButton();
        return submitButton !== null;
    }

    // Function to get the submit button
    function getSubmitButton() {
        var buttons = document.querySelectorAll('div.cursor-pointer');
        for (var i = 0; i < buttons.length; i++) {
            var img = buttons[i].querySelector('img[alt="hilo ai video create icon"]');
            if (img) {
                return buttons[i];
            }
        }
        return null;
    }

    // Function to wait for the submit button to be available
    function waitForSubmitButton(callback) {
        var checkButton = setInterval(function() {
            if (isSubmitButtonAvailable()) {
                clearInterval(checkButton);
                var submitButton = getSubmitButton();
                callback(submitButton);
            }
        }, 1000);
    }

    // Event listeners for control buttons
    pauseButton.addEventListener('click', function() {
        isPaused = !isPaused;
        pauseButton.textContent = isPaused ? 'Resume Queue' : 'Pause Queue';
    });

    backButton.addEventListener('click', function() {
        if (currentPromptNumber > 1) {
            // Move back one prompt
            currentPromptNumber -= 2; // Subtract 2 because processPrompts will increment it by 1
            promptQueue.unshift(originalPromptQueue[currentPromptNumber]); // Re-insert the previous prompt at the beginning
            updatePromptCounterLabel();
        } else {
            alert('Already at the first prompt.');
        }
    });

    holdButton.addEventListener('click', function() {
        isHolding = !isHolding;
        holdButton.textContent = isHolding ? 'Release Hold' : 'Hold Current Prompt';
    });

    // BULK DOWNLOAD FUNCTIONALITY STARTS HERE

    // Create a container div for the bulk download
    var bulkDownloadContainer = document.createElement('div');
    bulkDownloadContainer.style.position = 'fixed';
    bulkDownloadContainer.style.top = '50px';
    bulkDownloadContainer.style.left = '50%';
    bulkDownloadContainer.style.transform = 'translateX(-50%)';
    bulkDownloadContainer.style.zIndex = '10000';
    bulkDownloadContainer.style.backgroundColor = 'white';
    bulkDownloadContainer.style.border = '1px solid black';
    bulkDownloadContainer.style.padding = '10px';
    bulkDownloadContainer.style.maxWidth = '400px';
    bulkDownloadContainer.style.overflowY = 'auto';
    bulkDownloadContainer.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.5)';
    bulkDownloadContainer.style.borderRadius = '8px';
    bulkDownloadContainer.style.display = 'none';

    // Create a close button for the bulk download container
    var bulkCloseButton = document.createElement('button');
    bulkCloseButton.textContent = 'X';
    bulkCloseButton.style.position = 'absolute';
    bulkCloseButton.style.top = '5px';
    bulkCloseButton.style.right = '5px';
    bulkCloseButton.style.background = 'transparent';
    bulkCloseButton.style.border = 'none';
    bulkCloseButton.style.fontSize = '16px';
    bulkCloseButton.style.cursor = 'pointer';
    bulkCloseButton.addEventListener('click', function() {
        bulkDownloadContainer.style.display = 'none';
    });
    bulkDownloadContainer.appendChild(bulkCloseButton);

    // Create "Download All Visible Videos" button
    var downloadAllButton = document.createElement('button');
    downloadAllButton.textContent = 'Download All Visible Videos';
    downloadAllButton.style.marginTop = '10px';
    downloadAllButton.style.padding = '5px 10px';
    downloadAllButton.style.cursor = 'pointer';
    bulkDownloadContainer.appendChild(downloadAllButton);

    // Create "Select All Visible" button
    var selectAllButton = document.createElement('button');
    selectAllButton.textContent = 'Select All Visible';
    selectAllButton.style.marginTop = '10px';
    selectAllButton.style.marginRight = '10px';
    selectAllButton.style.padding = '5px 10px';
    selectAllButton.style.cursor = 'pointer';
    bulkDownloadContainer.appendChild(selectAllButton);

    // Create "Deselect All" button
    var deselectAllButton = document.createElement('button');
    deselectAllButton.textContent = 'Deselect All';
    deselectAllButton.style.marginTop = '10px';
    deselectAllButton.style.marginRight = '10px';
    deselectAllButton.style.padding = '5px 10px';
    deselectAllButton.style.cursor = 'pointer';
    bulkDownloadContainer.appendChild(deselectAllButton);

    // Create "Select Videos Between" button
    var selectBetweenButton = document.createElement('button');
    selectBetweenButton.textContent = 'Select Videos Between';
    selectBetweenButton.style.marginTop = '10px';
    selectBetweenButton.style.marginRight = '10px';
    selectBetweenButton.style.padding = '5px 10px';
    selectBetweenButton.style.cursor = 'pointer';
    bulkDownloadContainer.appendChild(selectBetweenButton);

    // Create "Download Selected" button
    var downloadSelectedButton = document.createElement('button');
    downloadSelectedButton.textContent = 'Download Selected';
    downloadSelectedButton.style.marginTop = '10px';
    downloadSelectedButton.style.padding = '5px 10px';
    downloadSelectedButton.style.cursor = 'pointer';
    bulkDownloadContainer.appendChild(downloadSelectedButton);

    // Append the bulk download container to the body
    document.body.appendChild(bulkDownloadContainer);

    // Add event listener to the bulkDownloadButton to toggle the bulk download container
    bulkDownloadButton.addEventListener('click', function() {
        bulkDownloadContainer.style.display = (bulkDownloadContainer.style.display === 'none') ? 'block' : 'none';
        // Add checkboxes to videos
        addCheckboxesToVideos();
        // Update the selected counter
        updateSelectedCounter();
    });

    // Function to add checkboxes to videos
    function addCheckboxesToVideos() {
        let videoElements = document.querySelectorAll('div.video-cards');
        videoElements.forEach(function(videoElement, index) {
            // Check if the checkbox is already added
            if (!videoElement.querySelector('.video-checkbox')) {
                // Create a checkbox
                let checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.className = 'video-checkbox';
                checkbox.style.position = 'absolute';
                checkbox.style.top = '10px';
                checkbox.style.left = '10px';
                checkbox.style.zIndex = '100';
                checkbox.style.transform = 'scale(2)'; // Make the checkbox twice as big
                // Add event listener to update the counter when checkbox state changes
                checkbox.addEventListener('change', updateSelectedCounter);
                // Append the checkbox to the video element
                videoElement.style.position = 'relative'; // Ensure the video element is positioned relative
                videoElement.appendChild(checkbox);
            }
        });
    }

    // Function to update the selected counter
    function updateSelectedCounter() {
        let checkboxes = document.querySelectorAll('.video-checkbox');
        let selectedCount = 0;
        checkboxes.forEach(function(checkbox) {
            if (checkbox.checked) {
                selectedCount++;
            }
        });
        if (selectedCount > 0) {
            selectedCounter.textContent = 'Selected Videos: ' + selectedCount;
            selectedCounter.style.display = 'block';
        } else {
            selectedCounter.style.display = 'none';
        }
    }

    // "Select All Visible" button event listener
    selectAllButton.addEventListener('click', function() {
        let checkboxes = document.querySelectorAll('.video-checkbox');
        checkboxes.forEach(function(checkbox) {
            checkbox.checked = true;
        });
        updateSelectedCounter();
    });

    // "Deselect All" button event listener
    deselectAllButton.addEventListener('click', function() {
        let checkboxes = document.querySelectorAll('.video-checkbox');
        checkboxes.forEach(function(checkbox) {
            checkbox.checked = false;
        });
        updateSelectedCounter();
    });

    // "Select Videos Between" button event listener
    selectBetweenButton.addEventListener('click', function() {
        let checkboxes = Array.from(document.querySelectorAll('.video-checkbox'));
        let selectedIndexes = checkboxes.map((checkbox, index) => checkbox.checked ? index : -1).filter(index => index !== -1);

        if (selectedIndexes.length < 2) {
            alert('Please select at least two videos to use this feature.');
            return;
        }

        let start = Math.min(...selectedIndexes);
        let end = Math.max(...selectedIndexes);

        for (let i = start; i <= end; i++) {
            checkboxes[i].checked = true;
        }
        updateSelectedCounter();
    });

    // "Download Selected" button event listener
    downloadSelectedButton.addEventListener('click', function() {
        // Disable the button to prevent multiple clicks
        downloadSelectedButton.disabled = true;
        downloadSelectedButton.innerHTML = "Processing...";

        // Start processing the selected videos
        processSelectedVideos(function() {
            // Re-enable the button after processing
            downloadSelectedButton.disabled = false;
            downloadSelectedButton.innerHTML = "Download Selected";
        });
    });

    // "Download All Visible Videos" button event listener
    downloadAllButton.addEventListener('click', function() {
        // Disable the button to prevent multiple clicks
        downloadAllButton.disabled = true;
        downloadAllButton.innerHTML = "Processing...";

        // Start processing the videos
        processVideos(function() {
            // Re-enable the button after processing
            downloadAllButton.disabled = false;
            downloadAllButton.innerHTML = "Download All Visible Videos";
        });
    });

    // Updated processVideos function with random delay
    function processVideos(callback) {
        // Array to hold the video data
        let videoDataArray = [];

        // Get all video elements
        let videoElements = Array.from(document.querySelectorAll('div.video-cards'));

        if (videoElements.length === 0) {
            alert('No videos found on the page.');
            callback();
            return;
        }

        // Helper function to process each video element with random delay
        function processVideoElement(index) {
            if (index >= videoElements.length) {
                // All videos processed, save the data and call the callback
                saveVideoData(videoDataArray);
                callback();
                return;
            }

            const videoElement = videoElements[index];

            // Extract the prompt
            let promptElement = videoElement.querySelector('.line-clamp-2');
            let prompt = promptElement ? promptElement.textContent.trim() : '';

            // Get the download button
            let downloadButton = videoElement.querySelector('div.flex.items-center.mb-2\\.5.cursor-pointer');

            if (downloadButton) {
                // Simulate a click on the download button to trigger the download logic
                downloadButton.click();

                // Wait for a brief moment to allow any JavaScript to execute
                setTimeout(function() {
                    // Attempt to retrieve the raw video URL
                    let rawVideoUrl = null;

                    // Get the video tag and replace watermark URL
                    let videoTag = videoElement.querySelector('video');
                    if (videoTag) {
                        let watermarkedUrl = videoTag.getAttribute('src');
                        if (watermarkedUrl) {
                            rawVideoUrl = watermarkedUrl.replace('video_watermark', 'video_raw');
                        }
                    }

                    if (rawVideoUrl) {
                        // Add the data to the array
                        videoDataArray.push({
                            prompt: prompt,
                            raw_video_url: rawVideoUrl
                        });
                    }

                    // Move to the next video after a random delay between 500ms and 2500ms
                    var delay = getRandomDelay(500, 2500);
                    console.log('Waiting for ' + delay + ' milliseconds before processing the next video.');
                    setTimeout(function() {
                        processVideoElement(index + 1);
                    }, delay);
                }, 500); // Initial 500ms delay after clicking download button
            } else {
                console.log('Download button not found for a video.');
                // Move to the next video immediately if no download button
                processVideoElement(index + 1);
            }
        }

        // Start processing the first video element
        processVideoElement(0);
    }

    // Updated processSelectedVideos function with random delay
    function processSelectedVideos(callback) {
        // Array to hold the video data
        let videoDataArray = [];

        // Get all video elements with selected checkboxes
        let videoElements = Array.from(document.querySelectorAll('div.video-cards'));
        let selectedVideoElements = videoElements.filter(function(videoElement) {
            let checkbox = videoElement.querySelector('.video-checkbox');
            return checkbox && checkbox.checked;
        });

        if (selectedVideoElements.length === 0) {
            alert('No videos selected for download.');
            callback();
            return;
        }

        // Helper function to process each video element with random delay
        function processVideoElement(index) {
            if (index >= selectedVideoElements.length) {
                // All videos processed, save the data and call the callback
                saveVideoData(videoDataArray);
                callback();
                return;
            }

            const videoElement = selectedVideoElements[index];

            // Extract the prompt
            let promptElement = videoElement.querySelector('.line-clamp-2');
            let prompt = promptElement ? promptElement.textContent.trim() : '';

            // Get the download button
            let downloadButton = videoElement.querySelector('div.flex.items-center.mb-2\\.5.cursor-pointer');

            if (downloadButton) {
                // Simulate a click on the download button to trigger the download logic
                downloadButton.click();

                // Wait for a brief moment to allow any JavaScript to execute
                setTimeout(function() {
                    // Attempt to retrieve the raw video URL
                    let rawVideoUrl = null;

                    // Get the video tag and replace watermark URL
                    let videoTag = videoElement.querySelector('video');
                    if (videoTag) {
                        let watermarkedUrl = videoTag.getAttribute('src');
                        if (watermarkedUrl) {
                            rawVideoUrl = watermarkedUrl.replace('video_watermark', 'video_raw');
                        }
                    }

                    if (rawVideoUrl) {
                        // Add the data to the array
                        videoDataArray.push({
                            prompt: prompt,
                            raw_video_url: rawVideoUrl
                        });
                    }

                    // Move to the next video after a random delay between 500ms and 2500ms
                    var delay = getRandomDelay(500, 2500);
                    console.log('Waiting for ' + delay + ' milliseconds before processing the next video.');
                    setTimeout(function() {
                        processVideoElement(index + 1);
                    }, delay);
                }, 500); // Initial 500ms delay after clicking download button
            } else {
                console.log('Download button not found for a video.');
                // Move to the next video immediately if no download button
                processVideoElement(index + 1);
            }
        }

        // Start processing the first selected video element
        processVideoElement(0);
    }

    // Function to save the video data as a JSON file
    function saveVideoData(data) {
        if (data.length === 0) {
            alert('No video data to save.');
            return;
        }

        // Convert the data to JSON format
        let jsonData = JSON.stringify(data, null, 2);

        // Create a blob from the JSON data
        let blob = new Blob([jsonData], { type: 'application/json' });

        // Create a download link
        let url = URL.createObjectURL(blob);
        let a = document.createElement('a');
        a.href = url;
        a.download = 'video_data.json';

        // Append the link to the body
        document.body.appendChild(a);

        // Programmatically click the link to trigger the download
        a.click();

        // Remove the link from the document
        document.body.removeChild(a);

        // Revoke the object URL
        URL.revokeObjectURL(url);

        alert('Video data has been saved as video_data.json');
    }

})();