Google AI Studio Bulk Chat Deleter

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

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

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

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

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

您需要先安装一个扩展,例如 篡改猴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();

})();