Google AI Studio Bulk Chat Deleter

Bulk delete (mass-remove) chats from Google AI Studio in batch.

当前为 2025-11-29 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Google AI Studio Bulk Chat Deleter
// @namespace    http://tampermonkey.net/
// @version      2025-11-29
// @description  Bulk delete (mass-remove) chats from Google AI Studio in batch.
// @author       Lorenzo Alali
// @match        https://aistudio.google.com/*
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/lorenzoalali/Gemini-and-AI-Studio-Mass-Chat-Deleter
// @run-at       document-idle
// ==/UserScript==

/*
 * =======================================================================
 * --- DISCLAIMER & IMPORTANT INFORMATION ---
 *
 * This tool (and its Gemini equivalent) can be found
 * on GitHub https://github.com/lorenzoalali/Google-AI-Studio-Bulk-Delete-UserScript
 * and on Greasy Fork https://greasyfork.org/en/scripts/555870-google-ai-studio-bulk-delete
 *
 * --- USAGE AT YOUR OWN RISK ---
 * The author provides no guarantees regarding the performance, safety, or functionality of this script. You assume
 * all risks associated with its use. The author offers no support and is not responsible for any potential data
 * loss or issues that may arise.
 *
 * --- FUTURE COMPATIBILITY ---
 * This script's operation depends on the current Document Object Model (DOM) of the Google AI Studio platform.
 * Modifications to the website by Google are likely to render this script non-functional in the future. While the
 * author does not plan on providing proactive updates or support, contributions in the form of GitHub pull requests
 * are welcome.
 * =======================================================================
 */

(function () {
    'use strict';

    // --- State Management ---
    const aBULK_DELETE_ALL_KEY = 'isAiStudioBulkDeletingAll';
    const aBULK_DELETE_SELECTED_KEY = 'isAiStudioBulkDeletingSelected';
    const aBULK_DELETE_SUCCESS_KEY = 'aiStudioBulkDeleteSuccessCount';
    const aBULK_DELETE_FAIL_KEY = 'aiStudioBulkDeleteFailCount';
    const aBULK_DELETE_TOTAL_KEY = 'aiStudioBulkDeleteTotalCount';

    let isStopRequested = false;
    let isProcessing = false; // Prevent multiple loops
    let lastCheckedCheckbox = null; // For Range Selection


    // Global UI References to handle re-renders
    const uiElements = {
        allBtn: null,
        selBtn: null,
        stopBtn: null
    };

    // --- UI Helper & Styles ---
    const UI = {
        injectStyles: () => {
            if (document.getElementById('gas-bulk-delete-styles')) return;
            const style = document.createElement('style');
            style.id = 'gas-bulk-delete-styles';
            style.textContent = `
                .gas-toast-container {
                    position: fixed;
                    bottom: 20px;
                    left: 50%;
                    transform: translateX(-50%);
                    z-index: 10000;
                    display: flex;
                    flex-direction: column;
                    gap: 10px;
                    pointer-events: none;
                }
                .gas-toast {
                    background: #333;
                    color: #fff;
                    padding: 12px 24px;
                    border-radius: 8px;
                    font-family: 'Google Sans', Roboto, Arial, sans-serif;
                    font-size: 14px;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                    opacity: 0;
                    transform: translateY(20px);
                    transition: opacity 0.3s, transform 0.3s;
                    pointer-events: auto;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    min-width: 300px;
                    justify-content: center;
                }
                .gas-toast.visible {
                    opacity: 1;
                    transform: translateY(0);
                }
                .gas-toast.success { background: #0f9d58; }
                .gas-toast.error { background: #d93025; }
                .gas-toast.warning { background: #f4b400; color: #202124; }
                .gas-toast.info { background: #1a73e8; }

                .gas-modal-overlay {
                    position: fixed;
                    top: 0; left: 0; width: 100%; height: 100%;
                    background: rgba(0,0,0,0.5);
                    z-index: 10001;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    opacity: 0;
                    transition: opacity 0.2s;
                }
                .gas-modal-overlay.visible { opacity: 1; }
                .gas-modal {
                    background: white;
                    padding: 24px;
                    border-radius: 8px;
                    width: 400px;
                    max-width: 90%;
                    box-shadow: 0 1px 3px 0 rgba(60,64,67,0.3), 0 4px 8px 3px rgba(60,64,67,0.15);
                    font-family: 'Google Sans', Roboto, Arial, sans-serif;
                    transform: scale(0.95);
                    transition: transform 0.2s;
                }
                .gas-modal-overlay.visible .gas-modal { transform: scale(1); }
                .gas-modal-title {
                    font-size: 18px;
                    font-weight: 500;
                    margin-bottom: 12px;
                    color: #202124;
                }
                .gas-modal-content {
                    font-size: 14px;
                    color: #5f6368;
                    line-height: 1.5;
                    margin-bottom: 24px;
                    white-space: pre-wrap;
                }
                .gas-modal-actions {
                    display: flex;
                    justify-content: flex-end;
                    gap: 12px;
                }
                .gas-btn {
                    border: none;
                    background: none;
                    padding: 8px 16px;
                    border-radius: 4px;
                    cursor: pointer;
                    font-weight: 500;
                    font-size: 14px;
                    transition: background 0.2s;
                }
                .gas-btn:hover { background: rgba(0,0,0,0.04); }
                .gas-btn.primary {
                    background: #1a73e8;
                    color: white;
                }
                .gas-btn.primary:hover { background: #1557b0; }
                .gas-btn.danger {
                    background: #d93025;
                    color: white;
                }
                .gas-btn.danger:hover { background: #a50e0e; }
                .gas-btn.secondary {
                    background: white;
                    color: #3c4043;
                    border: 1px solid #dadce0;
                }
                .gas-btn.secondary:hover {
                    background: #f1f3f4;
                    border-color: #dadce0;
                }

                /* Progress Bar Styles */
                .gas-progress-container {
                    position: fixed;
                    bottom: 20px;
                    left: 50%;
                    transform: translateX(-50%);
                    z-index: 10000;
                    background: #333;
                    color: #fff;
                    padding: 16px 24px;
                    border-radius: 12px;
                    font-family: 'Google Sans', Roboto, Arial, sans-serif;
                    box-shadow: 0 4px 20px rgba(0,0,0,0.25);
                    display: flex;
                    flex-direction: column;
                    gap: 12px;
                    min-width: 320px;
                    opacity: 0;
                    transform: translate(-50%, 20px);
                    transition: opacity 0.3s, transform 0.3s;
                    pointer-events: auto;
                }
                .gas-progress-container.visible {
                    opacity: 1;
                    transform: translate(-50%, 0);
                }
                .gas-progress-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    font-size: 14px;
                    font-weight: 500;
                }
                .gas-progress-bar-bg {
                    width: 100%;
                    height: 6px;
                    background: rgba(255,255,255,0.2);
                    border-radius: 3px;
                    overflow: hidden;
                }
                .gas-progress-bar-fill {
                    height: 100%;
                    background: #4285f4; /* Google Blue */
                    width: 0%;
                    transition: width 0.3s ease-out;
                }
                .gas-progress-details {
                    font-size: 12px;
                    color: rgba(255,255,255,0.7);
                    text-align: right;
                }
            `;
            document.head.appendChild(style);
        },

        showToast: (message, type = 'info', duration = 3000) => {
            UI.injectStyles();
            let container = document.querySelector('.gas-toast-container');
            if (!container) {
                container = document.createElement('div');
                container.className = 'gas-toast-container';
                document.body.appendChild(container);
            }

            const toast = document.createElement('div');
            toast.className = `gas-toast ${type}`;

            const iconSpan = document.createElement('span');
            let icon = '';
            if (type === 'success') icon = '✅';
            if (type === 'error') icon = '❌';
            if (type === 'warning') icon = '⚠️';
            if (type === 'info') icon = 'ℹ️';
            iconSpan.textContent = icon;

            const msgSpan = document.createElement('span');
            msgSpan.textContent = message;

            toast.appendChild(iconSpan);
            toast.appendChild(msgSpan);
            container.appendChild(toast);

            // Trigger reflow
            toast.offsetHeight;
            toast.classList.add('visible');

            if (duration > 0) {
                setTimeout(() => {
                    toast.classList.remove('visible');
                    setTimeout(() => toast.remove(), 300);
                }, duration);
            }
            return toast;
        },

        showConfirm: (title, message, confirmText = 'Confirm', confirmType = 'primary') => {
            UI.injectStyles();
            return new Promise((resolve) => {
                const overlay = document.createElement('div');
                overlay.className = 'gas-modal-overlay';

                const modal = document.createElement('div');
                modal.className = 'gas-modal';

                const titleDiv = document.createElement('div');
                titleDiv.className = 'gas-modal-title';
                titleDiv.textContent = title;

                const contentDiv = document.createElement('div');
                contentDiv.className = 'gas-modal-content';
                contentDiv.textContent = message;

                const actionsDiv = document.createElement('div');
                actionsDiv.className = 'gas-modal-actions';

                const cancelBtn = document.createElement('button');
                cancelBtn.className = 'gas-btn secondary cancel-btn';
                cancelBtn.textContent = 'Cancel';

                const confirmBtn = document.createElement('button');
                confirmBtn.className = `gas-btn ${confirmType} confirm-btn`;
                confirmBtn.textContent = confirmText;

                actionsDiv.appendChild(cancelBtn);
                actionsDiv.appendChild(confirmBtn);

                modal.appendChild(titleDiv);
                modal.appendChild(contentDiv);
                modal.appendChild(actionsDiv);
                overlay.appendChild(modal);
                document.body.appendChild(overlay);

                // Trigger reflow
                overlay.offsetHeight;
                overlay.classList.add('visible');

                const close = (result) => {
                    overlay.classList.remove('visible');
                    setTimeout(() => overlay.remove(), 200);
                    resolve(result);
                };

                cancelBtn.addEventListener('click', () => close(false));
                confirmBtn.addEventListener('click', () => close(true));
                // Close on click outside
                overlay.addEventListener('click', (e) => {
                    if (e.target === overlay) close(false);
                });
            });
        },

        showProgress: (message, total) => {
            UI.injectStyles();
            // Remove existing if any
            UI.hideProgress();

            const container = document.createElement('div');
            container.className = 'gas-progress-container';
            container.id = 'gas-progress-ui';

            const header = document.createElement('div');
            header.className = 'gas-progress-header';

            const msgSpan = document.createElement('span');
            msgSpan.id = 'gas-progress-text';
            msgSpan.textContent = message;

            const percentSpan = document.createElement('span');
            percentSpan.id = 'gas-progress-percent';
            percentSpan.textContent = '0%';

            header.appendChild(msgSpan);
            header.appendChild(percentSpan);

            const barBg = document.createElement('div');
            barBg.className = 'gas-progress-bar-bg';

            const barFill = document.createElement('div');
            barFill.className = 'gas-progress-bar-fill';
            barFill.id = 'gas-progress-fill';
            barBg.appendChild(barFill);

            const details = document.createElement('div');
            details.className = 'gas-progress-details';
            details.id = 'gas-progress-count';
            details.textContent = `0 of ${total}`;

            container.appendChild(header);
            container.appendChild(barBg);
            container.appendChild(details);

            document.body.appendChild(container);

            // Trigger reflow
            container.offsetHeight;
            container.classList.add('visible');
        },

        updateProgress: (current, total, textOverride = null) => {
            const container = document.getElementById('gas-progress-ui');
            if (!container) return;

            const percentage = Math.min(100, Math.round((current / total) * 100));

            const fill = document.getElementById('gas-progress-fill');
            const percentText = document.getElementById('gas-progress-percent');
            const countText = document.getElementById('gas-progress-count');
            const mainText = document.getElementById('gas-progress-text');

            if (fill) fill.style.width = `${percentage}%`;
            if (percentText) percentText.textContent = `${percentage}%`;
            if (countText) countText.textContent = `${current} of ${total}`;
            if (textOverride && mainText) mainText.textContent = textOverride;
        },

        hideProgress: () => {
            const container = document.getElementById('gas-progress-ui');
            if (container) {
                container.classList.remove('visible');
                setTimeout(() => container.remove(), 300);
            }
        },

        showUndoToast: (seconds) => {
            UI.injectStyles();
            return new Promise((resolve, reject) => {
                let container = document.querySelector('.gas-toast-container');
                if (!container) {
                    container = document.createElement('div');
                    container.className = 'gas-toast-container';
                    document.body.appendChild(container);
                }

                const toast = document.createElement('div');
                toast.className = 'gas-toast warning';
                toast.id = 'gas-undo-toast';

                let remaining = seconds;
                let interval;

                const updateText = () => {
                    // Clear current content
                    while (toast.firstChild) {
                        toast.removeChild(toast.firstChild);
                    }

                    const iconSpan = document.createElement('span');
                    iconSpan.textContent = '⏳';

                    const msgSpan = document.createElement('span');
                    msgSpan.style.flex = '1';
                    msgSpan.textContent = `Starting deletion in ${remaining}s...`;

                    const cancelBtn = document.createElement('button');
                    cancelBtn.id = 'gas-undo-cancel';
                    Object.assign(cancelBtn.style, {
                        background: 'transparent',
                        border: '1px solid white',
                        color: 'white',
                        borderRadius: '4px',
                        padding: '4px 8px',
                        cursor: 'pointer',
                        fontSize: '12px'
                    });
                    cancelBtn.textContent = 'CANCEL';

                    cancelBtn.addEventListener('click', handleCancel);

                    toast.appendChild(iconSpan);
                    toast.appendChild(msgSpan);
                    toast.appendChild(cancelBtn);
                };

                const handleCancel = () => {
                    clearInterval(interval);
                    toast.classList.remove('visible');
                    setTimeout(() => toast.remove(), 300);
                    resolve(false); // Cancelled
                };

                updateText();
                container.appendChild(toast);

                // Trigger reflow
                toast.offsetHeight;
                toast.classList.add('visible');

                interval = setInterval(() => {
                    remaining--;
                    if (remaining <= 0) {
                        clearInterval(interval);
                        toast.classList.remove('visible');
                        setTimeout(() => toast.remove(), 300);
                        resolve(true); // Timer finished
                    } else {
                        updateText();
                    }
                }, 1000);
            });
        }
    };

    // --- Configuration ---
    const aDELAY_BETWEEN_ACTIONS = 500;
    const aDELAY_AFTER_DELETION = 1500;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }


    // --- Count Management ---
    function getCounts() {
        return {
            success: parseInt(sessionStorage.getItem(aBULK_DELETE_SUCCESS_KEY) || '0', 10),
            fail: parseInt(sessionStorage.getItem(aBULK_DELETE_FAIL_KEY) || '0', 10)
        };
    }

    function incrementSuccess() {
        const current = getCounts().success;
        sessionStorage.setItem(aBULK_DELETE_SUCCESS_KEY, (current + 1).toString());
    }

    function incrementFail() {
        const current = getCounts().fail;
        sessionStorage.setItem(aBULK_DELETE_FAIL_KEY, (current + 1).toString());
    }

    function resetCounts() {
        sessionStorage.setItem(aBULK_DELETE_SUCCESS_KEY, '0');
        sessionStorage.setItem(aBULK_DELETE_FAIL_KEY, '0');
    }

    function clearCounts() {
        sessionStorage.removeItem(aBULK_DELETE_SUCCESS_KEY);
        sessionStorage.removeItem(aBULK_DELETE_FAIL_KEY);
        sessionStorage.removeItem(aBULK_DELETE_TOTAL_KEY);
    }

    async function findAndClickByText(
        selector,
        textOptions,
        maxRetries = 5,
        retryInterval = 250
    ) {
        for (let i = 0; i < maxRetries; i++) {
            const elements = document.querySelectorAll(selector);
            for (const element of elements) {
                const elementText = element.textContent.trim().toLowerCase();
                const found = textOptions.some(text =>
                    elementText.includes(text.toLowerCase())
                );
                if (found) {
                    element.click();
                    return true;
                }
            }
            await sleep(retryInterval);
        }
        console.error(
            `Could not find element with selector "${selector}" ` +
            `and text from [${textOptions.join(', ')}]`
        );
        return false;
    }

    /**
     * Processes a batch of deletions (all visible items) and then reloads.
     */
    async function processBatchDeletion(items) {
        isProcessing = true;

        if (uiElements.allBtn) uiElements.allBtn.disabled = true;
        if (uiElements.selBtn) uiElements.selBtn.disabled = true;
        if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'inline-flex';

        for (let i = 0; i < items.length; i++) {
            if (isStopRequested) {
                sessionStorage.removeItem(aBULK_DELETE_ALL_KEY);
                clearCounts();
                UI.showToast("Bulk delete process stopped by user.", 'warning');
                isProcessing = false;
                break;
            }

            const item = items[i];
            if (!document.body.contains(item)) continue;

            const counts = getCounts();
            const totalProcessed = counts.success + counts.fail + 1;
            const total = parseInt(sessionStorage.getItem(aBULK_DELETE_TOTAL_KEY) || '0');

            // const progressHTML =
            //    `<span style="font-size: 1.2em;">🔥</span> <span>Deleting... (${totalProcessed})</span>`;

            if (uiElements.allBtn) {
                // uiElements.allBtn.innerHTML = progressHTML;
                // Safe DOM update
                while (uiElements.allBtn.firstChild) uiElements.allBtn.removeChild(uiElements.allBtn.firstChild);
                const icon = document.createElement('span');
                icon.style.fontSize = '1.2em';
                icon.textContent = '🔥';
                const text = document.createElement('span');
                text.textContent = `Deleting... (${totalProcessed})`;
                uiElements.allBtn.appendChild(icon);
                uiElements.allBtn.appendChild(text);
            }

            // Update Progress Bar
            UI.updateProgress(totalProcessed, total, `Deleting ${totalProcessed} of ${total}...`);

            try {
                item.click();
                await sleep(aDELAY_BETWEEN_ACTIONS);

                const delMenu = await findAndClickByText(
                    'button[role="menuitem"]',
                    ['Delete']
                );
                if (!delMenu) {
                    document.querySelector('.cdk-overlay-backdrop')?.click();
                    incrementFail();
                    continue;
                }
                await sleep(aDELAY_BETWEEN_ACTIONS);

                const delConfirm = await findAndClickByText(
                    '.mat-mdc-dialog-actions button',
                    ['Delete']
                );
                if (!delConfirm) {
                    document.querySelector('.cdk-overlay-backdrop')?.click();
                    incrementFail();
                    continue;
                }

                incrementSuccess();
                await sleep(aDELAY_AFTER_DELETION);
            } catch (e) {
                console.error("Error deleting item:", e);
                incrementFail();
            }
        }

        if (!isStopRequested) {
            location.reload();
        } else {
            if (uiElements.allBtn) {
                uiElements.allBtn.disabled = false;
                // uiElements.allBtn.innerHTML =
                //    `<span style="font-size: 1.2em;">🔥</span> <span>Delete All</span>`;
                // Safe DOM update
                while (uiElements.allBtn.firstChild) uiElements.allBtn.removeChild(uiElements.allBtn.firstChild);
                const icon = document.createElement('span');
                icon.style.fontSize = '1.2em';
                icon.textContent = '🔥';
                const text = document.createElement('span');
                text.textContent = 'Delete All';
                uiElements.allBtn.appendChild(icon);
                uiElements.allBtn.appendChild(text);
            }
            if (uiElements.selBtn) uiElements.selBtn.disabled = false;
            if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
            isProcessing = false;
        }
    }


    /**
     * Deletes a single item and triggers a page reload.
     */
    async function deleteSingleItemAndReload(itemMenuButton, onComplete) {
        isProcessing = true;
        if (!document.body.contains(itemMenuButton)) {
            incrementFail();
            isProcessing = false;
            return;
        }

        try {
            itemMenuButton.click();
            await sleep(aDELAY_BETWEEN_ACTIONS);

            const delMenu = await findAndClickByText(
                'button[role="menuitem"]',
                ['Delete']
            );
            if (!delMenu) {
                document.querySelector('.cdk-overlay-backdrop')?.click();
                incrementFail();
                isProcessing = false;
                return;
            }
            await sleep(aDELAY_BETWEEN_ACTIONS);

            const delConfirm = await findAndClickByText(
                '.mat-mdc-dialog-actions button',
                ['Delete']
            );
            if (!delConfirm) {
                document.querySelector('.cdk-overlay-backdrop')?.click();
                incrementFail();
                isProcessing = false;
                return;
            }

            incrementSuccess();

            // Update Progress Bar
            const counts = getCounts();
            const total = parseInt(sessionStorage.getItem(aBULK_DELETE_TOTAL_KEY) || '0');
            const current = counts.success + counts.fail;
            UI.updateProgress(current, total, `Deleting ${current} of ${total}...`);

            await sleep(aDELAY_AFTER_DELETION);

            if (onComplete) onComplete();
            if (!isStopRequested) location.reload();
        } catch (e) {
            console.error("Error deleting single item:", e);
            incrementFail();

            // Update Progress Bar on failure too
            const counts = getCounts();
            const total = parseInt(sessionStorage.getItem(aBULK_DELETE_TOTAL_KEY) || '0');
            const current = counts.success + counts.fail;
            UI.updateProgress(current, total, `Error on item ${current}...`);

            if (onComplete) onComplete(); // Proceed anyway
            if (!isStopRequested) location.reload();
        }
        isProcessing = false;
    }

    /**
     * Starts the 'Bulk Delete All' process.
     */
    async function startBulkDeleteAll(isAutoStart = false) {
        const query = 'ms-prompt-options-menu button[aria-label="More options"]';
        const items = document.querySelectorAll(query);

        if (items.length === 0) {
            sessionStorage.removeItem(aBULK_DELETE_ALL_KEY);
            const counts = getCounts();
            clearCounts();

            if (counts.success > 0 || counts.fail > 0) {
                UI.showToast(`Bulk delete complete. Deleted: ${counts.success}, Failed: ${counts.fail}`, 'success', 5000);
            } else {
                UI.showToast("Bulk delete complete. Your library is now empty.", 'success', 5000);
            }
            isProcessing = false;
            return;
        }

        if (!isAutoStart) {
            const confirmMsg =
                "This will bulk-delete ALL items in your history.\n\n" +
                "It will delete items in batches of 5, reload the page, and " +
                "continue until finished.\n\nAre you sure you want to proceed?";

            const userConfirmation = await UI.showConfirm(
                "Delete All Chats?",
                confirmMsg,
                "Delete All",
                "danger"
            );
            if (!userConfirmation) return;

            await sleep(250); // Give modal time to close

            // Undo Timer
            const proceed = await UI.showUndoToast(5);
            if (!proceed) {
                UI.showToast("Deletion Cancelled.", 'info');
                return;
            }

            await sleep(250); // Give toast time to start closing if needed

            sessionStorage.setItem(aBULK_DELETE_ALL_KEY, 'true');
            resetCounts();

            // Initial Total Set
            sessionStorage.setItem(aBULK_DELETE_TOTAL_KEY, items.length.toString());
            UI.showProgress("Starting deletion...", items.length);
        } else {
            // Auto-start (after reload)
            // Update total to include new items found
            const storedTotal = parseInt(sessionStorage.getItem(aBULK_DELETE_TOTAL_KEY) || '0');
            const newTotal = storedTotal + items.length;
            sessionStorage.setItem(aBULK_DELETE_TOTAL_KEY, newTotal.toString());

            const counts = getCounts();
            UI.showProgress("Resuming deletion...", newTotal);
            UI.updateProgress(counts.success + counts.fail, newTotal);
        }

        processBatchDeletion(Array.from(items));
    }

    /**
     * Starts the 'Bulk Delete Selected' process.
     */
    async function startBulkDeleteSelected() {
        sessionStorage.removeItem(aBULK_DELETE_ALL_KEY); // Safety clear

        const selector = '.bulk-delete-checkbox:checked';
        const selectedCheckboxes = document.querySelectorAll(selector);
        if (selectedCheckboxes.length === 0) {
            UI.showToast("No chat(s) selected for deletion.", 'warning');
            return;
        }

        const confirmMsg = `Are you sure you want to delete the ${selectedCheckboxes.length} selected chat(s)?`;
        const userConfirmation = await UI.showConfirm(
            "Delete Selected Chat(s)?",
            confirmMsg,
            "Delete Selected",
            "danger"
        );
        if (!userConfirmation) return;

        // Undo Timer
        const proceed = await UI.showUndoToast(5);
        if (!proceed) {
            UI.showToast("Deletion Cancelled.", 'info');
            return;
        }


        const itemHrefs = Array.from(selectedCheckboxes).map(cb => {
            const link = cb.closest('tr').querySelector('a.name-btn');
            return link ? link.getAttribute('href') : null;
        }).filter(Boolean);

        console.log('[GAS Bulk Delete] Selected hrefs:', itemHrefs);

        const key = aBULK_DELETE_SELECTED_KEY;
        sessionStorage.setItem(key, JSON.stringify(itemHrefs));

        // Set Total for Selected
        sessionStorage.setItem(aBULK_DELETE_TOTAL_KEY, itemHrefs.length.toString());

        resetCounts();
        location.reload(); // Start the process
    }

    function addCheckboxesToRows() {
        // Only run if we are on the library page (relaxed check)
        // if (!location.href.includes('/library')) return;

        const rows = document.querySelectorAll('tbody tr[mat-row]');
        rows.forEach(row => {
            if (row.querySelector('.bulk-delete-checkbox-cell')) return;

            const checkboxCell = document.createElement('td');
            checkboxCell.className = 'mat-mdc-cell mdc-data-table__cell cdk-cell bulk-delete-checkbox-cell';
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.className = 'bulk-delete-checkbox';
            Object.assign(checkbox.style, {
                width: '18px',
                height: '18px',
                cursor: 'pointer'
            });
            checkboxCell.appendChild(checkbox);
            row.prepend(checkboxCell);

            // Range Selection Logic
            checkbox.addEventListener('click', (e) => {
                if (e.shiftKey && lastCheckedCheckbox && lastCheckedCheckbox !== checkbox) {
                    const allCheckboxes = Array.from(document.querySelectorAll('.bulk-delete-checkbox'));
                    const start = allCheckboxes.indexOf(lastCheckedCheckbox);
                    const end = allCheckboxes.indexOf(checkbox);

                    if (start !== -1 && end !== -1) {
                        const low = Math.min(start, end);
                        const high = Math.max(start, end);

                        for (let i = low; i <= high; i++) {
                            allCheckboxes[i].checked = lastCheckedCheckbox.checked;
                        }
                    }
                }
                lastCheckedCheckbox = checkbox;
                updateButtonState();
            });
        });

        const headerRow = document.querySelector('thead tr');
        if (!headerRow) return;

        const headerExists = headerRow.querySelector('.bulk-delete-header-cell');
        if (headerExists) return;

        const th = document.createElement('th');
        th.className = 'mat-mdc-header-cell mdc-data-table__header-cell cdk-header-cell bulk-delete-header-cell';

        const masterCheckbox = document.createElement('input');
        masterCheckbox.type = 'checkbox';
        masterCheckbox.id = 'master-bulk-delete-checkbox';
        Object.assign(masterCheckbox.style, {
            width: '18px',
            height: '18px',
            cursor: 'pointer'
        });

        masterCheckbox.addEventListener('change', (event) => {
            const isChecked = event.target.checked;
            const allCheckboxes = document.querySelectorAll(
                'input.bulk-delete-checkbox'
            );
            allCheckboxes.forEach(checkbox => {
                checkbox.checked = isChecked;
            });
            updateButtonState();
        });

        th.appendChild(masterCheckbox);
        headerRow.prepend(th);
    }


    function addButtons() {
        // Only run if we are on the library page (relaxed check)
        // if (!location.href.includes('/library')) return;

        // If buttons exist, update references and return
        const existingAll = document.getElementById('bulk-delete-all-button');
        if (existingAll) {
            uiElements.allBtn = existingAll;
            uiElements.selBtn = document.getElementById('bulk-delete-selected-button');
            uiElements.stopBtn = document.getElementById('stop-bulk-delete-button');
            return;
        }

        // Strategy: Look for the "Open in Drive" button or the search bar container
        // Based on browser inspection, the header structure is different.
        // We'll look for the element containing the "Open in Drive" button.
        let wrapper = null;

        // Try to find the "Open in Drive" button's parent
        const driveBtn = document.querySelector('a[href*="drive.google.com"]');
        if (driveBtn) {
            wrapper = driveBtn.parentElement;
        }

        // Fallback: Try finding the search bar and inserting before it
        if (!wrapper) {
            const searchBar = document.querySelector('ms-library-search-bar');
            if (searchBar) {
                wrapper = searchBar.parentElement;
            }
        }

        // Fallback: Try the header title
        if (!wrapper) {
            const title = document.querySelector('.lib-header-title');
            if (title) {
                // Go up to the header container
                wrapper = title.closest('.lib-header');
            }
        }

        if (!wrapper) return;

        // Common button styles
        const btnClass = 'mat-mdc-unelevated-button mdc-unelevated-button mat-mdc-button-base';
        const commonStyle = {
            margin: '0 8px',
            height: '36px',
            borderRadius: '18px',
            padding: '0 16px',
            fontSize: '14px',
            fontWeight: '500',
            textTransform: 'none',
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
            boxSizing: 'border-box',
            cursor: 'pointer',
            transition: 'background-color 0.2s, box-shadow 0.2s',
            color: 'white',
            boxShadow: 'none',
            border: 'none',
            verticalAlign: 'middle'
        };

        // --- Delete All Button ---
        const bulkDeleteAllButton = document.createElement('button');
        bulkDeleteAllButton.id = 'bulk-delete-all-button';
        bulkDeleteAllButton.setAttribute('ms-button', '');
        bulkDeleteAllButton.className = btnClass;
        Object.assign(bulkDeleteAllButton.style, {
            ...commonStyle,
            backgroundColor: '#d93025', // Google Red override
            // display: 'none' // Removed to make visible by default
        });

        // Safe DOM update for All button content
        const allIcon = document.createElement('span');
        allIcon.style.fontSize = '1.2em';
        allIcon.textContent = '🔥';
        const allText = document.createElement('span');
        allText.textContent = 'Delete All';
        bulkDeleteAllButton.appendChild(allIcon);
        bulkDeleteAllButton.appendChild(allText);

        // bulkDeleteAllButton.onclick = () => startBulkDeleteAll(false); // Handled by addEventListener below

        // --- Delete Selected Button ---
        const bulkDeleteSelectedButton = document.createElement('button');
        bulkDeleteSelectedButton.id = 'bulk-delete-selected-button';
        bulkDeleteSelectedButton.setAttribute('ms-button', '');
        bulkDeleteSelectedButton.className = btnClass;
        Object.assign(bulkDeleteSelectedButton.style, {
            ...commonStyle,
            backgroundColor: '#e37400', // Google Orange/Warning override
            // display: 'none' // Removed to make visible by default
        });

        // Safe DOM update for Selected button content
        const selIcon = document.createElement('span');
        selIcon.style.fontSize = '1.2em';
        selIcon.textContent = '🗑️';
        const selText = document.createElement('span');
        selText.textContent = 'Delete Selected';
        bulkDeleteSelectedButton.appendChild(selIcon);
        bulkDeleteSelectedButton.appendChild(selText);

        // bulkDeleteSelectedButton.onclick = () => startBulkDeleteSelected(); // Handled by addEventListener below

        // --- Stop Button ---
        const stopButton = document.createElement('button');
        stopButton.id = 'stop-bulk-delete-button';
        stopButton.setAttribute('ms-button', '');
        stopButton.className = btnClass;
        Object.assign(stopButton.style, {
            ...commonStyle,
            backgroundColor: '#f4b400', // Google Yellow
            color: '#202124', // Dark text for contrast
            display: 'none' // Hidden by default
        });

        // Safe DOM update for Stop button content
        const stopIcon = document.createElement('span');
        stopIcon.style.fontSize = '1.2em';
        stopIcon.textContent = '🛑';
        const stopText = document.createElement('span');
        stopText.textContent = 'Stop';
        stopButton.appendChild(stopIcon);
        stopButton.appendChild(stopText);

        // stopButton.onclick = ... // Handled by addEventListener below

        // Store references
        uiElements.allBtn = bulkDeleteAllButton;
        uiElements.selBtn = bulkDeleteSelectedButton;
        uiElements.stopBtn = stopButton;

        // Inject
        // If we found the drive button, append after it to keep layout clean
        if (driveBtn && wrapper === driveBtn.parentElement) {
            // Create a container to hold our buttons so they stay together
            const container = document.createElement('div');
            container.style.display = 'flex';
            container.style.alignItems = 'center';

            container.appendChild(bulkDeleteAllButton);
            container.appendChild(bulkDeleteSelectedButton);
            container.appendChild(stopButton);

            wrapper.insertBefore(container, driveBtn.nextSibling);
        } else {
            wrapper.appendChild(bulkDeleteAllButton);
            wrapper.appendChild(bulkDeleteSelectedButton);
            wrapper.appendChild(stopButton);
        }

        // --- Hover Effects ---
        // Native buttons typically have a state overlay.
        // We simulate this simply by dimming slightly on hover.
        const addHover = (btn) => {
            btn.addEventListener('mouseenter', () => btn.style.filter = 'brightness(0.95)');
            btn.addEventListener('mouseleave', () => btn.style.filter = 'brightness(1)');
        };
        addHover(bulkDeleteAllButton);
        addHover(bulkDeleteSelectedButton);
        addHover(stopButton);
        addHover(stopButton);


        // --- Event Listeners ---
        stopButton.addEventListener('click', () => {
            isStopRequested = true;
            sessionStorage.removeItem(aBULK_DELETE_ALL_KEY);
            sessionStorage.removeItem(aBULK_DELETE_SELECTED_KEY);
            clearCounts();
            stopButton.disabled = true;

            // Safe DOM update for Stop button text
            while (stopButton.firstChild) stopButton.removeChild(stopButton.firstChild);
            const stopIcon = document.createElement('span');
            stopIcon.style.fontSize = '1.2em';
            stopIcon.textContent = '🛑';
            const stopText = document.createElement('span');
            stopText.textContent = 'Stopping...';
            stopButton.appendChild(stopIcon);
            stopButton.appendChild(stopText);

            UI.showToast(
                "Bulk delete will stop. The page will not reload after the current action.",
                'info',
                5000
            );
        });

        bulkDeleteAllButton.addEventListener('click', () =>
            startBulkDeleteAll(false)
        );
        bulkDeleteSelectedButton.addEventListener('click', () =>
            startBulkDeleteSelected()
        );

        // Append to DOM
        wrapper.appendChild(bulkDeleteSelectedButton);
        wrapper.appendChild(bulkDeleteAllButton);
        wrapper.appendChild(stopButton);

        // Update Global Refs
        uiElements.allBtn = bulkDeleteAllButton;
        uiElements.selBtn = bulkDeleteSelectedButton;
        uiElements.stopBtn = stopButton;

        // If we are processing (e.g. after a re-render), restore UI state
        if (isProcessing) {
            bulkDeleteAllButton.disabled = true;
            bulkDeleteSelectedButton.disabled = true;
            stopButton.style.display = 'inline-flex';
            // Note: innerHTML for progress is updated by the running loop
        } else {
            updateButtonState(); // Set initial state
        }

        setTimeout(() => handleAutoDeletion(), 2000);
    }

    /**
     * Checks session storage on page load and continues any pending
     * deletion process.
     */
    function handleAutoDeletion() {
        console.log('[GAS Bulk Delete] handleAutoDeletion called');
        if (isProcessing) {
            console.log('[GAS Bulk Delete] Already processing, returning');
            return; // Already running
        }

        if (sessionStorage.getItem(aBULK_DELETE_ALL_KEY) === 'true') {
            console.log('[GAS Bulk Delete] Starting bulk delete all');
            startBulkDeleteAll(true);

        } else if (sessionStorage.getItem(aBULK_DELETE_SELECTED_KEY)) {
            console.log('[GAS Bulk Delete] Found selected deletion in progress');
            const key = aBULK_DELETE_SELECTED_KEY;
            let itemHrefs = JSON.parse(sessionStorage.getItem(key));
            console.log('[GAS Bulk Delete] ItemHrefs from storage:', itemHrefs);

            if (itemHrefs.length > 0) {
                // Update UI for selected deletion
                if (uiElements.allBtn) uiElements.allBtn.disabled = true;
                if (uiElements.selBtn) uiElements.selBtn.disabled = true;
                if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'inline-flex';

                // Show Progress Bar
                const total = parseInt(sessionStorage.getItem(aBULK_DELETE_TOTAL_KEY) || itemHrefs.length.toString());
                const counts = getCounts();
                const current = counts.success + counts.fail;
                UI.showProgress("Deleting selected...", total);
                UI.updateProgress(current, total);

                const nextHref = itemHrefs[0];
                console.log('[GAS Bulk Delete] Looking for href:', nextHref);
                const linkSelector = `a.name-btn[href="${nextHref}"]`;
                console.log('[GAS Bulk Delete] Using selector:', linkSelector);
                const nextLink = document.querySelector(linkSelector);
                console.log('[GAS Bulk Delete] Found link:', nextLink);

                if (nextLink) {
                    // Safe DOM update for Selected button progress
                    if (uiElements.selBtn) {
                        while (uiElements.selBtn.firstChild) uiElements.selBtn.removeChild(uiElements.selBtn.firstChild);
                        const icon = document.createElement('span');
                        icon.style.fontSize = '1.2em';
                        icon.textContent = '🗑️';
                        const text = document.createElement('span');
                        text.textContent = `Deleting ${itemHrefs.length} selected...`;
                        uiElements.selBtn.appendChild(icon);
                        uiElements.selBtn.appendChild(text);
                    }

                    const itemMenu = nextLink.closest('tr')
                        .querySelector('ms-prompt-options-menu button');
                    console.log('[GAS Bulk Delete] Found menu button:', itemMenu);

                    deleteSingleItemAndReload(itemMenu, () => {
                        itemHrefs.shift(); // Remove processed item
                        // Always save the array, even if empty, so we detect "finished" state on next load
                        sessionStorage.setItem(key, JSON.stringify(itemHrefs));
                    });
                } else {
                    console.warn(
                        `Item with href ${nextHref} not found. Checking next...`
                    );
                    itemHrefs.shift();
                    sessionStorage.setItem(key, JSON.stringify(itemHrefs));
                    incrementFail();
                    location.reload();
                }
            } else {
                // List is empty, meaning we finished
                sessionStorage.removeItem(key);
                const counts = getCounts();
                clearCounts();
                UI.hideProgress();
                UI.showToast(`Selected chat(s) have been deleted. Deleted: ${counts.success}, Failed: ${counts.fail}`, 'success', 5000);

                if (uiElements.allBtn) uiElements.allBtn.disabled = false;
                if (uiElements.selBtn) {
                    uiElements.selBtn.disabled = false;
                    updateButtonState(); // Reset text
                }
                if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
            }
        }
    }

    function updateButtonState() {
        const selectedCount = document.querySelectorAll('.bulk-delete-checkbox:checked').length;
        const btn = uiElements.selBtn;

        if (btn) {
            // Clear current content
            while (btn.firstChild) btn.removeChild(btn.firstChild);

            const icon = document.createElement('span');
            icon.style.fontSize = '1.2em';
            icon.textContent = '🗑️';

            const text = document.createElement('span');

            if (selectedCount > 0) {
                btn.disabled = false;
                text.textContent = `Delete Selected (${selectedCount})`;
            } else {
                btn.disabled = true;
                text.textContent = 'Delete Selected';
            }

            btn.appendChild(icon);
            btn.appendChild(text);
        }
    }

    function manageUI() {
        // If we are on the library page, attempt to inject UI
        // Relaxed check: just look for the table or header
        const libraryTable = document.querySelector('ms-library-table') || document.querySelector('.library-table');
        if (libraryTable || location.href.includes('/library')) {
            addButtons();
            addCheckboxesToRows();
        }
    }

    // --- Observer Setup ---
    const observer = new MutationObserver((mutations) => {
        manageUI();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // --- Fallback Interval ---
    setInterval(() => {
        manageUI();
    }, 1000);

    // Initial Run
    manageUI();

})();