Mistral AI - Delete All Chats

Adds a native-looking "Delete All Chats" button to Mistral AI interface with multi-language support

// ==UserScript==
// @name         Mistral AI - Delete All Chats
// @namespace    http://tampermonkey.net/
// @version      1.03
// @description  Adds a native-looking "Delete All Chats" button to Mistral AI interface with multi-language support
// @author       Ognisty321
// @match        https://chat.mistral.ai/*
// @license MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

      const translations = {
          en: {
              deleteAllChats: "Delete All Chats",
              confirmDeleteAll: "Are you sure you want to delete ALL chats? This action cannot be undone!",
              modalTitle: "Deleting Chats",
              modalClose: "Close",
              startingDeletion: "Starting deletion process...",
              fetchingChats: "Fetching chats...",
              foundChats: "Found {0} chats to delete.",
              noMoreChats: "No more chats to delete!",
              deletionComplete: "✅ Deletion complete! Successfully deleted {0} chats in total.",
              startingBatch: "Starting batch #{0}...",
              completedBatch: "Completed batch #{0}: Deleted {1} chats",
              deletedChat: "Deleted chat: {0} ({1}...)",
              failedChat: "Failed to delete chat {0}: {1}",
              errorFetchingChats: "Error fetching chats: {0}",
              buttonAdded: "\"Delete All Chats\" button added successfully",
              confirmButtonLog: "Attempting to add native delete button..."
          },
          fr: {
              deleteAllChats: "Supprimer tous les chats",
              confirmDeleteAll: "Êtes-vous sûr de vouloir supprimer TOUS les chats ? Cette action est irréversible !",
              modalTitle: "Suppression des chats",
              modalClose: "Fermer",
              startingDeletion: "Début du processus de suppression...",
              fetchingChats: "Récupération des chats...",
              foundChats: "{0} chats trouvés à supprimer.",
              noMoreChats: "Aucun autre chat à supprimer !",
              deletionComplete: "✅ Suppression terminée ! {0} chats supprimés au total.",
              startingBatch: "Lancement du lot #{0}...",
              completedBatch: "Lot #{0} terminé : {1} chats supprimés",
              deletedChat: "Chat supprimé : {0} ({1}...)",
              failedChat: "Échec de la suppression du chat {0} : {1}",
              errorFetchingChats: "Erreur lors de la récupération des chats : {0}",
              buttonAdded: "Bouton « Supprimer tous les chats » ajouté avec succès",
              confirmButtonLog: "Tentative d’ajout du bouton natif de suppression..."
          },
          de: {
              deleteAllChats: "Alle Chats löschen",
              confirmDeleteAll: "Sind Sie sicher, dass Sie ALLE Chats löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden!",
              modalTitle: "Chats löschen",
              modalClose: "Schließen",
              startingDeletion: "Löschvorgang wird gestartet...",
              fetchingChats: "Chats werden geladen...",
              foundChats: "{0} Chats zum Löschen gefunden.",
              noMoreChats: "Keine weiteren Chats zum Löschen!",
              deletionComplete: "✅ Löschvorgang abgeschlossen! Insgesamt wurden {0} Chats erfolgreich gelöscht.",
              startingBatch: "Batch #{0} wird gestartet...",
              completedBatch: "Batch #{0} abgeschlossen: {1} Chats gelöscht",
              deletedChat: "Chat gelöscht: {0} ({1}...)",
              failedChat: "Chat {0} konnte nicht gelöscht werden: {1}",
              errorFetchingChats: "Fehler beim Laden der Chats: {0}",
              buttonAdded: "Button „Alle Chats löschen“ erfolgreich hinzugefügt",
              confirmButtonLog: "Versuche, nativen Lösch-Button hinzuzufügen..."
          },
          es: {
              deleteAllChats: "Eliminar todos los chats",
              confirmDeleteAll: "¿Seguro que quieres eliminar TODOS los chats? ¡Esta acción no se puede deshacer!",
              modalTitle: "Eliminando chats",
              modalClose: "Cerrar",
              startingDeletion: "Iniciando el proceso de eliminación...",
              fetchingChats: "Obteniendo los chats...",
              foundChats: "{0} chats encontrados para eliminar.",
              noMoreChats: "¡No quedan más chats para eliminar!",
              deletionComplete: "✅ ¡Eliminación completada! Se eliminaron correctamente {0} chats en total.",
              startingBatch: "Iniciando lote #{0}...",
              completedBatch: "Lote #{0} completado: {1} chats eliminados",
              deletedChat: "Chat eliminado: {0} ({1}...)",
              failedChat: "No se pudo eliminar el chat {0}: {1}",
              errorFetchingChats: "Error al obtener los chats: {0}",
              buttonAdded: "Botón «Eliminar todos los chats» agregado correctamente",
              confirmButtonLog: "Intentando añadir botón nativo de eliminación..."
          },
          pl: {
              deleteAllChats: "Usuń wszystkie czaty",
              confirmDeleteAll: "Czy na pewno chcesz usunąć WSZYSTKIE czaty? Tej operacji nie można cofnąć!",
              modalTitle: "Usuwanie czatów",
              modalClose: "Zamknij",
              startingDeletion: "Rozpoczynanie procesu usuwania...",
              fetchingChats: "Pobieranie czatów...",
              foundChats: "Znaleziono {0} czatów do usunięcia.",
              noMoreChats: "Brak kolejnych czatów do usunięcia!",
              deletionComplete: "✅ Usuwanie zakończone! Łącznie usunięto {0} czatów.",
              startingBatch: "Rozpoczynanie partii nr {0}...",
              completedBatch: "Zakończono partię nr {0}: usunięto {1} czatów",
              deletedChat: "Usunięto czat: {0} ({1}...)",
              failedChat: "Nie udało się usunąć czatu {0}: {1}",
              errorFetchingChats: "Błąd podczas pobierania czatów: {0}",
              buttonAdded: "Przycisk „Usuń wszystkie czaty” został dodany pomyślnie",
              confirmButtonLog: "Próba dodania natywnego przycisku usuwania..."
          },
          it: {
              deleteAllChats: "Elimina tutte le chat",
              confirmDeleteAll: "Sei sicuro di voler eliminare TUTTE le chat? Questa azione non può essere annullata!",
              modalTitle: "Eliminazione chat",
              modalClose: "Chiudi",
              startingDeletion: "Avvio del processo di eliminazione...",
              fetchingChats: "Recupero delle chat...",
              foundChats: "Trovate {0} chat da eliminare.",
              noMoreChats: "Non ci sono più chat da eliminare!",
              deletionComplete: "✅ Eliminazione completata! {0} chat eliminate con successo.",
              startingBatch: "Avvio batch #{0}...",
              completedBatch: "Batch #{0} completato: {1} chat eliminate",
              deletedChat: "Chat eliminata: {0} ({1}...)",
              failedChat: "Impossibile eliminare la chat {0}: {1}",
              errorFetchingChats: "Errore nel recupero delle chat: {0}",
              buttonAdded: "Pulsante «Elimina tutte le chat» aggiunto con successo",
              confirmButtonLog: "Tentativo di aggiungere il pulsante nativo di eliminazione..."
          },
          pt: {
              deleteAllChats: "Excluir todas as conversas",
              confirmDeleteAll: "Tem certeza de que deseja excluir TODAS as conversas? Esta ação não pode ser desfeita!",
              modalTitle: "Excluindo conversas",
              modalClose: "Fechar",
              startingDeletion: "Iniciando o processo de exclusão...",
              fetchingChats: "Obtendo conversas...",
              foundChats: "{0} conversas encontradas para exclusão.",
              noMoreChats: "Não há mais conversas para excluir!",
              deletionComplete: "✅ Exclusão concluída! {0} conversas excluídas com sucesso.",
              startingBatch: "Iniciando lote #{0}...",
              completedBatch: "Lote #{0} concluído: {1} conversas excluídas",
              deletedChat: "Conversa excluída: {0} ({1}...)",
              failedChat: "Falha ao excluir a conversa {0}: {1}",
              errorFetchingChats: "Erro ao obter conversas: {0}",
              buttonAdded: "Botão «Excluir todas as conversas» adicionado com sucesso",
              confirmButtonLog: "Tentando adicionar botão nativo de exclusão..."
          },
          ar: {
              deleteAllChats: "حذف كل المحادثات",
              confirmDeleteAll: "هل أنت متأكد أنك تريد حذف كل المحادثات؟ لا يمكن التراجع عن هذا الإجراء!",
              modalTitle: "جارٍ حذف المحادثات",
              modalClose: "إغلاق",
              startingDeletion: "بدء عملية الحذف...",
              fetchingChats: "جارٍ جلب المحادثات...",
              foundChats: "تم العثور على {0} محادثة للحذف.",
              noMoreChats: "لا توجد محادثات أخرى للحذف!",
              deletionComplete: "✅ اكتملت عملية الحذف! تم حذف {0} محادثة بنجاح.",
              startingBatch: "بدء الدفعة رقم {0}...",
              completedBatch: "اكتملت الدفعة رقم {0}: تم حذف {1} محادثة",
              deletedChat: "تم حذف المحادثة: {0} ({1}...)",
              failedChat: "فشل حذف المحادثة {0}: {1}",
              errorFetchingChats: "حدث خطأ أثناء جلب المحادثات: {0}",
              buttonAdded: "تمت إضافة زر «حذف كل المحادثات» بنجاح",
              confirmButtonLog: "جارٍ محاولة إضافة زر الحذف الأصلي..."
          }
      };

    function formatString(template, ...args) {
        return template.replace(/\{(\d+)\}/g, (match, index) => {
            return typeof args[index] !== 'undefined' ? args[index] : match;
        });
    }

    // Function to get cookie value by name
    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
        return undefined;
    }

    // Get language from cookie first, then fall back to browser language if no cookie found
    const cookieLang = getCookie('lang');
    const userLang = (cookieLang || navigator.language || navigator.userLanguage || 'en').slice(0, 2);
    const i18n = translations[userLang] || translations.en;

    function addNativeDeleteButton() {
        console.log(i18n.confirmButtonLog);
        const sidebarMenu = document.querySelector('ul[data-sidebar="menu"]');
        if (!sidebarMenu) {
            console.log('Sidebar menu not found, retrying in 1 second...');
            setTimeout(addNativeDeleteButton, 1000);
            return;
        }
        if (document.getElementById('delete-all-chats-button')) {
            console.log('Delete button already exists');
            return;
        }
        const menuItem = document.createElement('li');
        menuItem.setAttribute('data-sidebar', 'menu-item');
        menuItem.className = 'group/menu-item relative';
        const button = document.createElement('button');
        button.id = 'delete-all-chats-button';
        button.setAttribute('data-sidebar', 'menu-button');
        button.setAttribute('data-size', 'default');
        button.setAttribute('data-active', 'false');
        button.className = 'peer/menu-button ring-default active:bg-muted active:text-default data-[active=true]:bg-muted data-[active=true]:text-default data-[state=open]:hover:bg-muted data-[state=open]:hover:text-default outline-hidden group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left transition-colors focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 hover:bg-muted hover:text-default h-8 text-sm text-red-600';
        button.type = 'button';
        button.innerHTML = `
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M3 6h18"></path>
            <path d="M8 6V4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2v2"></path>
            <path d="M19 6l-1 14c-.1 1-1 2-2 2H8c-1 0-1.9-1-2-2L5 6"></path>
            <line x1="10" y1="11" x2="10" y2="17"></line>
            <line x1="14" y1="11" x2="14" y2="17"></line>
          </svg>
          <span>${i18n.deleteAllChats}</span>
        `;
        button.addEventListener('click', () => {
            confirmAndDeleteAllChats();
        });
        menuItem.appendChild(button);
        sidebarMenu.appendChild(menuItem);
        console.log(i18n.buttonAdded);
        createStatusModal();
    }

    function createStatusModal() {
        if (document.getElementById('delete-status-modal')) {
            return;
        }
        const modal = document.createElement('div');
        modal.id = 'delete-status-modal';
        modal.style.position = 'fixed';
        modal.style.top = '0';
        modal.style.left = '0';
        modal.style.right = '0';
        modal.style.bottom = '0';
        modal.style.backgroundColor = 'rgba(0, 0, 0, 0.75)';
        modal.style.zIndex = '9999';
        modal.style.display = 'none';
        modal.style.overflow = 'auto';
        modal.style.alignItems = 'flex-start';
        modal.style.justifyContent = 'center';
        modal.style.paddingTop = '50px';
        modal.style.paddingBottom = '50px';
        const modalContent = document.createElement('div');
        modalContent.className = 'relative w-full max-w-md rounded-lg bg-gray-900 shadow-lg text-gray-100';
        modalContent.style.margin = '0 auto';
        modalContent.style.boxShadow = '0 10px 25px -5px rgba(0, 0, 0, 0.3)';
        modalContent.style.display = 'flex';
        modalContent.style.flexDirection = 'column';
        modalContent.style.maxHeight = '80vh';
        const modalHeader = document.createElement('div');
        modalHeader.className = 'flex items-center justify-between border-b border-gray-700 pb-3 px-4 pt-4';
        modalHeader.style.position = 'sticky';
        modalHeader.style.top = '0';
        modalHeader.style.backgroundColor = 'rgb(17,24,39)';
        modalHeader.style.zIndex = '1';
        modalHeader.style.borderTopLeftRadius = '0.5rem';
        modalHeader.style.borderTopRightRadius = '0.5rem';
        modalHeader.innerHTML = `
          <div class="flex items-center">
            <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px;">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M15 9l-6 6"></path>
              <path d="M9 9l6 6"></path>
            </svg>
            <h3 class="text-lg font-semibold">${i18n.modalTitle}</h3>
          </div>
          <button id="close-status-modal" class="rounded-full p-1.5 hover:bg-gray-100 transition-colors">
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M18 6 6 18"></path>
              <path d="m6 6 12 12"></path>
            </svg>
          </button>
        `;
        const statusContainer = document.createElement('div');
        statusContainer.id = 'delete-status';
        statusContainer.className = 'overflow-y-auto px-4 py-3 space-y-2.5';
        statusContainer.style.flex = '1';
        statusContainer.style.overflowY = 'auto';
        statusContainer.style.minHeight = '100px';
        statusContainer.style.maxHeight = 'calc(80vh - 120px)';
        const modalFooter = document.createElement('div');
        modalFooter.className = 'border-t border-gray-700 px-4 py-3';
        modalFooter.style.position = 'sticky';
        modalFooter.style.bottom = '0';
        modalFooter.style.backgroundColor = 'rgb(17,24,39)';
        modalFooter.style.borderBottomLeftRadius = '0.5rem';
        modalFooter.style.borderBottomRightRadius = '0.5rem';
        const closeButton = document.createElement('button');
        closeButton.id = 'close-status-button';
        closeButton.className = 'px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors text-gray-100';
        closeButton.textContent = i18n.modalClose;
        modalFooter.appendChild(closeButton);
        modalContent.appendChild(modalHeader);
        modalContent.appendChild(statusContainer);
        modalContent.appendChild(modalFooter);
        modal.appendChild(modalContent);
        document.body.appendChild(modal);
        document.getElementById('close-status-modal').addEventListener('click', hideModal);
        document.getElementById('close-status-button').addEventListener('click', hideModal);
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                hideModal();
            }
        });
        function hideModal() {
            document.getElementById('delete-status-modal').style.display = 'none';
            document.body.style.overflow = '';
        }
    }

    function addStatus(message, type = 'info') {
        const statusContainer = document.getElementById('delete-status');
        if (!statusContainer) return;
        const statusItem = document.createElement('div');
        statusItem.className = `mb-2 p-3 rounded-md text-sm border`;
        if (type === 'success') {
          statusItem.innerHTML = `<div class="flex items-center">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
              <path d="M20 6L9 17l-5-5"></path>
            </svg>
            ${message}
          </div>`;
        } else if (type === 'error') {
          statusItem.innerHTML = `<div class="flex items-center">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="8" x2="12" y2="12"></line>
              <line x1="12" y1="16" x2="12.01" y2="16"></line>
            </svg>
            ${message}
          </div>`;
        } else {
          statusItem.innerHTML = `<div class="flex items-center">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
            ${message}
          </div>`;
        }
        statusContainer.appendChild(statusItem);
        statusContainer.scrollTop = statusContainer.scrollHeight;
    }

    function showStatusModal() {
        const modal = document.getElementById('delete-status-modal');
        if (modal) {
            modal.style.display = 'flex';
            document.body.style.overflow = 'hidden';
        }
    }

    function confirmAndDeleteAllChats() {
        if (confirm(i18n.confirmDeleteAll)) {
            const statusContainer = document.getElementById('delete-status');
            if (statusContainer) {
                statusContainer.innerHTML = '';
            }
            showStatusModal();
            addStatus(i18n.startingDeletion, 'info');
            deleteAllChats();
        }
    }

    async function fetchChats() {
        try {
            addStatus(i18n.fetchingChats, 'info');
            const url = "https://chat.mistral.ai/api/trpc/chat.last";
            const params = {
                "batch": "1",
                "input": JSON.stringify({
                    "0": {
                        "json": {
                            "chatVisibility": "private",
                            "chatPermission": "write",
                            "direction": "forward",
                            "limit": 20
                        }
                    }
                })
            };
            const response = await fetch(`${url}?${new URLSearchParams(params)}`, {
                method: 'GET',
                headers: {
                    'accept': '*/*',
                    'content-type': 'application/json',
                    'trpc-accept': 'application/jsonl',
                    'x-trpc-source': 'nextjs-react'
                },
                credentials: 'include'
            });
            if (!response.ok) {
                throw new Error(`Failed to fetch chats: ${response.status}`);
            }
            const text = await response.text();
            const chatIds = [];
            const lines = text.trim().split('\n');
            for (const line of lines) {
                try {
                    const data = JSON.parse(line);
                    if (data.json && Array.isArray(data.json)) {
                        if (data.json[2]?.[0]?.[0]?.items) {
                            for (const chat of data.json[2][0][0].items) {
                                if (chat.id) {
                                    chatIds.push({
                                        id: chat.id,
                                        title: chat.title || 'No title'
                                    });
                                }
                            }
                        }
                    }
                } catch (e) {
                    console.error('Error parsing JSON line:', e);
                }
            }
            addStatus(formatString(i18n.foundChats, chatIds.length), 'info');
            return chatIds;
        } catch (error) {
            addStatus(formatString(i18n.errorFetchingChats, error.message), 'error');
            console.error('Error fetching chats:', error);
            return [];
        }
    }

    async function deleteChat(chatId, title) {
        try {
            const url = "https://chat.mistral.ai/api/trpc/chat.delete";
            const params = {"batch": "1"};
            const payload = {"0": {"json": {"id": chatId}}};
            const response = await fetch(`${url}?${new URLSearchParams(params)}`, {
                method: 'POST',
                headers: {
                    'accept': '*/*',
                    'content-type': 'application/json',
                    'trpc-accept': 'application/jsonl',
                    'x-trpc-source': 'nextjs-react'
                },
                credentials: 'include',
                body: JSON.stringify(payload)
            });
            if (!response.ok) {
                throw new Error(`Failed to delete chat: ${response.status}`);
            }
            addStatus(formatString(i18n.deletedChat, title, chatId.substring(0, 8)), 'success');
            return true;
        } catch (error) {
            addStatus(formatString(i18n.failedChat, chatId, error.message), 'error');
            console.error('Error deleting chat:', error);
            return false;
        }
    }

    async function deleteAllChats() {
        let batchNumber = 1;
        let totalDeleted = 0;
        while (true) {
            addStatus(formatString(i18n.startingBatch, batchNumber), 'info');
            const chats = await fetchChats();
            if (chats.length === 0) {
                addStatus(i18n.noMoreChats, 'success');
                break;
            }
            let batchDeleted = 0;
            for (const chat of chats) {
                await deleteChat(chat.id, chat.title);
                batchDeleted++;
                totalDeleted++;
                await new Promise(resolve => setTimeout(resolve, 500));
            }
            addStatus(formatString(i18n.completedBatch, batchNumber, batchDeleted), 'info');
            batchNumber++;
            await new Promise(resolve => setTimeout(resolve, 2000));
        }
        addStatus(formatString(i18n.deletionComplete, totalDeleted), 'success');
    }

    setTimeout(addNativeDeleteButton, 2000);
    let lastUrl = location.href;
    const observer = new MutationObserver(() => {
        if (lastUrl !== location.href) {
            lastUrl = location.href;
            setTimeout(addNativeDeleteButton, 2000);
        }
        if (!document.getElementById('delete-all-chats-button')) {
            addNativeDeleteButton();
        }
    });
    observer.observe(document, {subtree: true, childList: true});
    setTimeout(addNativeDeleteButton, 5000);
    console.log('Mistral AI - Delete All Chats script (multi-language) loaded!');
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址