AI Engine Form Stop Button

Ever wanted to stop a form that’s clearly going down the wrong track? While this won’t save you tokens on the back-end, it will let your users stop forms mid-stream so they can resubmit immediately.

Installation

Add the following JavaScript to a post or page with an AI Engine form on it:

const aiestop = {};

// Map to store controllers for each fetch request
aiestop.fetchControllers = new Map();

aiestop.fetchControllersChange = function() {}

// Function to create a new AbortController and store it using a unique identifier
aiestop.createAbortSignal = function() {
    const id = Date.now() + Math.random(); // Create a unique ID
    const controller = new AbortController();
    aiestop.fetchControllers.set(id, controller);
    aiestop.fetchControllersChange(); // Call to update UI
    return { signal: controller.signal, id };
}

// Function to abort the fetch using the unique identifier
aiestop.abortFetch = function(id) {
    if (aiestop.fetchControllers.has(id)) {
        aiestop.fetchControllers.get(id).abort();
        aiestop.fetchControllers.delete(id); // Clean up the controller after aborting
        aiestop.fetchControllersChange(); // Call to update UI
    }
}

// Function to abort the fetch using the unique identifier
aiestop.removeController = function(id) {
    if (aiestop.fetchControllers.has(id)) {
        aiestop.fetchControllers.delete(id); // Clean up the controller after aborting
        aiestop.fetchControllersChange(); // Call to update UI
    }
}


// Function to abort all fetches
aiestop.abortAllFetches = function() {
    for (const [id, controller] of aiestop.fetchControllers.entries()) {
        controller.abort(); // Abort each fetch
        aiestop.removeController(id);
    }
}

aiestopOriginalFetch = window.fetch;
// Override the global fetch function
window.fetch = async (url, options = {}) => {
    const stoppable = (url.endsWith('/wp-json/mwai-ui/v1/forms/submit'));
    let signal = null;
    let id = null;
    if (stoppable) {
        ({ signal, id } = aiestop.createAbortSignal());
        options.signal = signal;
    } else {
        return aiestopOriginalFetch(url, options);
    }

    try {
        const response = await aiestopOriginalFetch(url, options);
        if (signal && signal.aborted) {
            throw new DOMException('The user aborted a request.', 'AbortError');
        }

        // Handle streaming data
        if (stoppable && response.body) {
            const reader = response.body.getReader();
            const stream = new ReadableStream({
                start(controller) {
                    function push() {
                        reader.read().then(({ done, value }) => {
                            if (done) {
                                controller.close();
                aiestop.removeController(id);
                                return;
                            }
                            controller.enqueue(value);
                            push();
                        }).catch(error => {
                            console.error('Stream reading failed:', error);
                            controller.error(error);
                        });
                    }
                    push();
                },
                cancel() {
                    reader.cancel();
                    console.log('Stream cancelled by the user');
                }
            });

            if (signal && signal.aborted) {
                stream.cancel();
                throw new DOMException('The user aborted a request.', 'AbortError');
            }

            return new Response(stream, { headers: response.headers });
        }

        if (stoppable) {
            aiestop.removeController(id);
        } // Clean up the controller after successful fetch
        return response;
    } catch (error) {
        if (error.name === 'AbortError') {
            console.log('Fetch aborted');
        } else {
            throw error;
        }
    }
};

document.addEventListener('DOMContentLoaded', function() {
    const stopBtn = document.createElement('button');
    stopBtn.textContent = 'Stop Generating';
    stopBtn.className = 'stop-generating-btn';
    stopBtn.style.display = 'none'; // Button is hidden by default
    document.body.appendChild(stopBtn);

    stopBtn.addEventListener('click', function() {
        aiestop.abortAllFetches();
    });

    // Function to check and display the button
    function updateButtonVisibility() {
        if (aiestop.fetchControllers.size > 0) {
            stopBtn.style.display = 'block';
        } else {
            stopBtn.style.display = 'none';
        }
    }

    // Call updateButtonVisibility initially and whenever fetchControllers changes
    updateButtonVisibility();
    aiestop.fetchControllersChange = updateButtonVisibility;
});

Then add this CSS:

.stop-generating-btn {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    padding: 10px 20px;
    background-color: red;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    z-index: 1000;
}

Let’s enhance your business! I can help you develop custom AI engine extensions or broader AI strategies.
Contact me.