// ==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!');
})();