Greasy Fork

Greasy Fork is available in English.

Mistral AI - Delete All Chats

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

当前为 2025-03-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Mistral AI - Delete All Chats
// @namespace    http://tampermonkey.net/
// @version      1.02
// @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;
        });
    }

    const userLang = (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-red-100 hover:text-red-700 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!');
})();