// Configuration et variables globales const API_BASE = ''; let socket; let currentPrompts = []; let currentStatus = {}; let bot_controller = { flux_list: [] }; // Variable globale pour la liste des flux // Initialisation de l'application document.addEventListener('DOMContentLoaded', function() { initializeSocketIO(); loadInitialData(); setupEventListeners(); startPeriodicUpdates(); }); // Initialisation de Socket.IO pour les mises à jour en temps réel function initializeSocketIO() { socket = io(); socket.on('connect', function() { console.log('Connecté au serveur'); updateConnectionStatus(true); }); socket.on('disconnect', function() { console.log('Déconnecté du serveur'); updateConnectionStatus(false); }); socket.on('status_update', function(data) { updateDashboard(data); }); socket.on('new_subtitle', function(data) { addNewSubtitle(data); }); socket.on('new_generation', function(data) { updateNextMessage(data); }); } // Mise à jour du statut de connexion function updateConnectionStatus(connected) { const statusElement = document.getElementById('connection-status'); const iconElement = statusElement.previousElementSibling; if (connected) { statusElement.textContent = 'Connecté'; iconElement.className = 'fas fa-circle text-success pulse'; } else { statusElement.textContent = 'Déconnecté'; iconElement.className = 'fas fa-circle text-danger'; } } // Chargement des données initiales async function loadInitialData() { try { await Promise.all([ loadFluxList(), loadPrompts(), loadUsers(), loadStatus(), loadSubtitles(), loadGenerations() ]); } catch (error) { console.error('Erreur lors du chargement des données:', error); showToast('Erreur lors du chargement des données', 'error'); } } // Configuration des écouteurs d'événements function setupEventListeners() { // Modal d'ajout de flux const addFluxModal = document.getElementById('addFluxModal'); addFluxModal.addEventListener('hidden.bs.modal', function() { document.getElementById('channel-name').value = ''; document.getElementById('record-audio').checked = true; }); } // Mises à jour périodiques function startPeriodicUpdates() { // Mise à jour des données toutes les 10 secondes setInterval(async () => { await loadStatus(); await loadSubtitles(); await loadGenerations(); }, 10000); } // === GESTION DES FLUX === // Chargement de la liste des flux async function loadFluxList() { try { const response = await fetch(`${API_BASE}/api/flux`); const fluxList = await response.json(); bot_controller.flux_list = fluxList; // Mettre à jour la variable globale renderFluxList(fluxList); } catch (error) { console.error('Erreur lors du chargement des flux:', error); } } // Rendu de la liste des flux function renderFluxList(fluxList) { const container = document.getElementById('flux-list'); if (fluxList.length === 0) { container.innerHTML = `

Aucun flux configuré

Cliquez sur "Ajouter Flux" pour commencer

`; return; } container.innerHTML = fluxList.map(flux => `
${flux.name} ${flux.status}
${flux.record_audio ? 'Audio' : 'Pas d\'audio' } Chat ${new Date(flux.created_at).toLocaleString()} ${flux.error ? `Erreur: ${flux.error}` : ''}
`).join(''); } // Fonction pour activer/désactiver un flux async function toggleFlux(fluxId) { try { const response = await fetch(`${API_BASE}/api/flux/${fluxId}/toggle`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { showToast(`Flux ${result.active ? 'activé' : 'désactivé'}`, 'success'); await loadFluxList(); } else { showToast(result.error || 'Erreur lors du changement de statut', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors du changement de statut du flux', 'error'); } } // Ajout d'un nouveau flux async function addFlux() { const channelName = document.getElementById('channel-name').value.trim(); const recordAudio = document.getElementById('record-audio').checked; if (!channelName) { showToast('Veuillez entrer un nom de canal', 'error'); return; } try { const response = await fetch(`${API_BASE}/api/flux`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_name: channelName, record_audio: recordAudio }) }); const result = await response.json(); if (result.success) { showToast(`Flux ${channelName} ajouté avec succès`, 'success'); bootstrap.Modal.getInstance(document.getElementById('addFluxModal')).hide(); await loadFluxList(); } else { showToast(result.error || 'Erreur lors de l\'ajout du flux', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de l\'ajout du flux', 'error'); } } // Suppression d'un flux async function removeFlux(fluxId) { if (!confirm('Êtes-vous sûr de vouloir supprimer ce flux ?')) { return; } try { const response = await fetch(`${API_BASE}/api/flux/${fluxId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showToast('Flux supprimé avec succès', 'success'); await loadFluxList(); } else { showToast(result.error || 'Erreur lors de la suppression', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de la suppression du flux', 'error'); } } // === GESTION DES PROMPTS === // Chargement des prompts async function loadPrompts() { try { const response = await fetch(`${API_BASE}/api/config/prompts`); currentPrompts = await response.json(); renderPrompts(); } catch (error) { console.error('Erreur lors du chargement des prompts:', error); } } // Rendu des prompts function renderPrompts() { const container = document.getElementById('prompts-list'); container.innerHTML = currentPrompts.map((prompt, index) => `
`).join(''); } // Ajout d'un prompt function addPrompt() { currentPrompts.push('Nouveau prompt : '); renderPrompts(); // Focus sur le nouveau prompt setTimeout(() => { const newPromptTextarea = document.querySelector(`textarea[data-prompt-index="${currentPrompts.length - 1}"]`); if (newPromptTextarea) { newPromptTextarea.focus(); newPromptTextarea.select(); } }, 100); } // Suppression d'un prompt function removePrompt(index) { if (confirm('Êtes-vous sûr de vouloir supprimer ce prompt ?')) { currentPrompts.splice(index, 1); renderPrompts(); } } // Sauvegarde des prompts async function savePrompts() { // Récupérer les valeurs des textareas const textareas = document.querySelectorAll('textarea[data-prompt-index]'); const updatedPrompts = Array.from(textareas).map(textarea => textarea.value.trim()); try { const response = await fetch(`${API_BASE}/api/config/prompts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompts: updatedPrompts }) }); const result = await response.json(); if (result.success) { currentPrompts = updatedPrompts; showToast('Prompts sauvegardés avec succès', 'success'); } else { showToast('Erreur lors de la sauvegarde', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de la sauvegarde des prompts', 'error'); } } // === GESTION DES MESSAGES === // Envoi d'un message personnalisé async function sendCustomMessage() { const message = document.getElementById('custom-message').value.trim(); if (!message) { showToast('Veuillez entrer un message', 'error'); return; } try { const response = await fetch(`${API_BASE}/api/send-message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, channel: 'default' }) }); const result = await response.json(); if (result.success) { showToast('Message envoyé avec succès', 'success'); document.getElementById('custom-message').value = ''; addToRecentMessages(message); } else { showToast(result.error || 'Erreur lors de l\'envoi', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de l\'envoi du message', 'error'); } } // Envoi du prochain message async function sendNextMessage() { const nextMessageElement = document.getElementById('next-message'); const message = nextMessageElement.textContent.trim(); if (message === 'Aucun message en attente') { showToast('Aucun message à envoyer', 'warning'); return; } try { const response = await fetch(`${API_BASE}/api/send-message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, channel: 'default' }) }); const result = await response.json(); if (result.success) { showToast('Message envoyé avec succès', 'success'); addToRecentMessages(message); nextMessageElement.textContent = 'Aucun message en attente'; } else { showToast(result.error || 'Erreur lors de l\'envoi', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de l\'envoi du message', 'error'); } } // Envoi de la dernière génération async function sendLastGeneration() { try { const response = await fetch(`${API_BASE}/api/generations`); const generations = await response.json(); if (Object.keys(generations).length === 0) { showToast('Aucune génération disponible', 'warning'); return; } const sortedKeys = Object.keys(generations).sort(); const lastGeneration = generations[sortedKeys[sortedKeys.length - 1]]; const sendResponse = await fetch(`${API_BASE}/api/send-message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: lastGeneration, channel: 'default' }) }); const result = await sendResponse.json(); if (result.success) { showToast('Dernière génération envoyée', 'success'); addToRecentMessages(lastGeneration); } else { showToast(result.error || 'Erreur lors de l\'envoi', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de l\'envoi de la génération', 'error'); } } // Ajout d'un message aux messages récents function addToRecentMessages(message) { const container = document.getElementById('recent-messages'); const messageElement = document.createElement('div'); messageElement.className = 'message-item fade-in'; messageElement.innerHTML = `
${message}
${new Date().toLocaleTimeString()}
`; container.insertBefore(messageElement, container.firstChild); // Limiter à 10 messages récents const messages = container.children; if (messages.length > 10) { container.removeChild(messages[messages.length - 1]); } } // === GESTION DES SOUS-TITRES === // Chargement des sous-titres async function loadSubtitles() { try { const response = await fetch(`${API_BASE}/api/subtitles`); const subtitles = await response.json(); renderSubtitles(subtitles); } catch (error) { console.error('Erreur lors du chargement des sous-titres:', error); } } // Rendu des sous-titres function renderSubtitles(subtitles) { const historyContainer = document.getElementById('subtitles-history'); const lastSubtitleElement = document.getElementById('last-subtitle'); if (Object.keys(subtitles).length === 0) { lastSubtitleElement.textContent = 'Aucun texte détecté pour le moment'; historyContainer.innerHTML = '

Aucun sous-titre disponible

'; return; } const sortedKeys = Object.keys(subtitles).sort(); const lastKey = sortedKeys[sortedKeys.length - 1]; // Mettre à jour le dernier sous-titre lastSubtitleElement.textContent = subtitles[lastKey]; // Mettre à jour l'historique historyContainer.innerHTML = sortedKeys.reverse().slice(0, 10).map(key => `
${subtitles[key]}
${key}
`).join(''); } // Génération d'une réponse à partir du sous-titre async function generateFromSubtitle() { const lastSubtitleElement = document.getElementById('last-subtitle'); const text = lastSubtitleElement.textContent.trim(); if (text === 'Aucun texte détecté pour le moment') { showToast('Aucun sous-titre disponible pour la génération', 'warning'); return; } try { const response = await fetch(`${API_BASE}/api/generate-response`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text }) }); const result = await response.json(); if (result.success) { showToast('Génération de réponse lancée', 'success'); } else { showToast(result.error || 'Erreur lors de la génération', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de la génération de réponse', 'error'); } } // === GESTION DES GÉNÉRATIONS === // Chargement des générations async function loadGenerations() { try { const response = await fetch(`${API_BASE}/api/generations`); const generations = await response.json(); if (Object.keys(generations).length > 0) { const sortedKeys = Object.keys(generations).sort(); const lastGeneration = generations[sortedKeys[sortedKeys.length - 1]]; updateNextMessage(lastGeneration); } } catch (error) { console.error('Erreur lors du chargement des générations:', error); } } // Mise à jour du prochain message function updateNextMessage(message) { const nextMessageElement = document.getElementById('next-message'); nextMessageElement.textContent = message || 'Aucun message en attente'; } // === GESTION DU STATUT === // Chargement du statut async function loadStatus() { try { const response = await fetch(`${API_BASE}/api/status`); currentStatus = await response.json(); updateDashboard(currentStatus); } catch (error) { console.error('Erreur lors du chargement du statut:', error); } } // Mise à jour du tableau de bord function updateDashboard(status) { document.getElementById('flux-count').textContent = status.flux_count || 0; document.getElementById('recording-count').textContent = status.active_recordings || 0; document.getElementById('chat-count').textContent = status.chat_connections || 0; if (status.last_subtitle) { document.getElementById('last-subtitle').textContent = status.last_subtitle; } if (status.next_message) { updateNextMessage(status.next_message); } // Mettre à jour l'état du chat updateChatState(); } // === ACTIONS RAPIDES === // Génération d'une réponse async function generateResponse() { const lastSubtitleElement = document.getElementById('last-subtitle'); const text = lastSubtitleElement.textContent.trim(); if (text === 'Aucun texte détecté pour le moment') { showToast('Aucun sous-titre disponible pour la génération', 'warning'); return; } await generateFromSubtitle(); } // Actualisation des données async function refreshData() { showToast('Actualisation en cours...', 'info'); await loadInitialData(); showToast('Données actualisées', 'success'); } // === UTILITAIRES === // Affichage des notifications toast function showToast(message, type = 'info') { // Créer le conteneur de toast s'il n'existe pas let container = document.querySelector('.toast-container'); if (!container) { container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); } const toastId = 'toast-' + Date.now(); const toastColors = { success: 'text-bg-success', error: 'text-bg-danger', warning: 'text-bg-warning', info: 'text-bg-info' }; const toastElement = document.createElement('div'); toastElement.id = toastId; toastElement.className = `toast ${toastColors[type] || 'text-bg-info'}`; toastElement.setAttribute('role', 'alert'); toastElement.innerHTML = `
TwitchBot
${message}
`; container.appendChild(toastElement); const toast = new bootstrap.Toast(toastElement, { autohide: true, delay: type === 'error' ? 5000 : 3000 }); toast.show(); // Supprimer l'élément après fermeture toastElement.addEventListener('hidden.bs.toast', () => { container.removeChild(toastElement); }); } // Gestion des erreurs globales window.addEventListener('error', function(e) { console.error('Erreur JavaScript:', e.error); showToast('Une erreur inattendue s\'est produite', 'error'); }); // Gestion des erreurs de promesses non capturées window.addEventListener('unhandledrejection', function(e) { console.error('Promesse rejetée:', e.reason); showToast('Erreur de communication avec le serveur', 'error'); }); // === GESTION DU CHAT === // Variables globales pour le chat let chatMessages = []; let chatPaused = false; let activeFlux = null; let chatInput = null; let chatMessagesContainer = null; let chatUpdateInterval = null; // Initialisation du chat function initializeChat() { chatInput = document.getElementById('chat-input'); chatMessagesContainer = document.getElementById('chat-messages'); // Gestionnaire pour l'envoi de messages chatInput.addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChatMessage(); } }); // Mise à jour de l'état du chat selon les flux actifs updateChatState(); } // Mise à jour de l'état du chat function updateChatState() { const hasActiveFlux = currentStatus && currentStatus.flux_count > 0; if (hasActiveFlux) { chatInput.disabled = false; chatInput.nextElementSibling.disabled = false; chatInput.placeholder = "Tapez un message..."; // Supprimer le message de bienvenue const welcomeElement = chatMessagesContainer.querySelector('.chat-welcome'); if (welcomeElement) { welcomeElement.remove(); } // Démarrer la récupération des messages de chat startChatUpdates(); // Ajouter un message système seulement si c'est la première fois const existingSystemMessage = chatMessagesContainer.querySelector('.chat-message.system-message'); if (!existingSystemMessage) { addSystemMessage('Chat connecté au stream'); } } else { chatInput.disabled = true; chatInput.nextElementSibling.disabled = true; chatInput.placeholder = "Aucun flux actif"; // Arrêter la récupération des messages stopChatUpdates(); // Afficher le message de bienvenue if (!chatMessagesContainer.querySelector('.chat-welcome')) { chatMessagesContainer.innerHTML = `

Le chat apparaîtra ici quand un flux sera actif

`; } } } // Démarrer les mises à jour du chat function startChatUpdates() { if (chatUpdateInterval) { clearInterval(chatUpdateInterval); } // Récupérer les messages toutes les 3 secondes chatUpdateInterval = setInterval(loadChatMessages, 3000); } // Arrêter les mises à jour du chat function stopChatUpdates() { if (chatUpdateInterval) { clearInterval(chatUpdateInterval); chatUpdateInterval = null; } } // Charger les messages de chat depuis l'API async function loadChatMessages() { if (chatPaused) return; try { // Trouver le premier flux actif const activeFlux = bot_controller.flux_list.find(flux => flux.active); if (!activeFlux) return; const response = await fetch(`${API_BASE}/api/chat/${activeFlux.id}/messages`); const result = await response.json(); if (result.success && result.messages) { // Ajouter les nouveaux messages result.messages.forEach(msg => { const messageExists = chatMessages.some(existing => existing.timestamp === msg.timestamp && existing.username === msg.username && existing.content === msg.content ); if (!messageExists) { addChatMessage(msg.username, msg.content, false, false); chatMessages.push(msg); } }); } } catch (error) { console.error('Erreur lors du chargement des messages de chat:', error); } } // Ajout d'un message au chat function addChatMessage(username, message, isOwn = false, isSystem = false) { const messageElement = document.createElement('div'); messageElement.className = `chat-message ${isOwn ? 'own-message' : ''} ${isSystem ? 'system-message' : ''}`; const timestamp = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }); if (isSystem) { messageElement.innerHTML = `
${message}
${timestamp}
`; } else { messageElement.innerHTML = `
${username}
${message}
${timestamp}
`; } chatMessagesContainer.appendChild(messageElement); // Auto-scroll vers le bas chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight; // Limiter le nombre de messages (garder les 100 derniers) const messages = chatMessagesContainer.querySelectorAll('.chat-message'); if (messages.length > 100) { messages[0].remove(); } } // Ajout d'un message système function addSystemMessage(message) { // Vérifier si le message système existe déjà const existingMessages = chatMessagesContainer.querySelectorAll('.chat-message.system-message'); for (let msg of existingMessages) { const messageText = msg.querySelector('.message-text'); if (messageText && messageText.textContent === message) { return; // Message déjà présent, ne pas l'ajouter } } addChatMessage('', message, false, true); } // Envoi d'un message via le chat async function sendChatMessage() { const message = chatInput.value.trim(); if (!message) return; // Trouver le premier flux actif const activeFlux = bot_controller.flux_list.find(flux => flux.active); if (!activeFlux) { showToast('Aucun flux actif pour envoyer le message', 'error'); return; } try { const response = await fetch(`${API_BASE}/api/chat/${activeFlux.id}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message }) }); const result = await response.json(); if (result.success) { // Ajouter le message au chat local addChatMessage('Vous', message, true); chatInput.value = ''; showToast('Message envoyé', 'success'); } else { showToast(result.error || 'Erreur lors de l\'envoi', 'error'); } } catch (error) { console.error('Erreur lors de l\'envoi du message:', error); showToast('Erreur lors de l\'envoi du message', 'error'); } } // Effacer le chat function clearChat() { if (confirm('Voulez-vous vraiment effacer tous les messages du chat ?')) { chatMessagesContainer.innerHTML = `

Le chat apparaîtra ici quand un flux sera actif

`; chatMessages = []; showToast('Chat effacé', 'info'); } } // Pause/Reprendre le chat function toggleChat() { chatPaused = !chatPaused; const toggleIcon = document.getElementById('chat-toggle-icon'); if (chatPaused) { toggleIcon.className = 'fas fa-play'; addSystemMessage('Chat en pause'); } else { toggleIcon.className = 'fas fa-pause'; addSystemMessage('Chat repris'); } showToast(chatPaused ? 'Chat en pause' : 'Chat repris', 'info'); } // Simulation de messages de chat (pour les tests) function simulateChatMessages() { if (chatPaused) return; const usernames = ['Viewer1', 'ChatUser', 'TwitchFan', 'StreamLover', 'Gamer123']; const messages = [ 'Salut tout le monde !', 'Super stream !', 'Comment ça va ?', 'J\'adore ce jeu', 'Merci pour le stream', 'GG !', 'Belle partie', 'Continue comme ça !', 'J\'ai hâte de voir la suite', 'Bon stream !' ]; const randomUsername = usernames[Math.floor(Math.random() * usernames.length)]; const randomMessage = messages[Math.floor(Math.random() * messages.length)]; addChatMessage(randomUsername, randomMessage); } // Mise à jour du statut avec les informations du chat function updateDashboard(status) { document.getElementById('flux-count').textContent = status.flux_count || 0; document.getElementById('recording-count').textContent = status.active_recordings || 0; document.getElementById('chat-count').textContent = status.chat_connections || 0; if (status.last_subtitle) { document.getElementById('last-subtitle').textContent = status.last_subtitle; } if (status.next_message) { updateNextMessage(status.next_message); } // Mettre à jour l'état du chat updateChatState(); } // Initialisation du chat au chargement de la page document.addEventListener('DOMContentLoaded', function() { initializeChat(); }); // === GESTION DES UTILISATEURS === // Variables globales pour les utilisateurs let currentUsers = []; // Chargement des utilisateurs async function loadUsers() { try { const response = await fetch(`${API_BASE}/api/config/users`); currentUsers = await response.json(); renderUsers(); } catch (error) { console.error('Erreur lors du chargement des utilisateurs:', error); } } // Rendu de la liste des utilisateurs function renderUsers() { const container = document.getElementById('users-list'); if (currentUsers.length === 0) { container.innerHTML = `
Aucun utilisateur configuré

Ajoutez des utilisateurs pour envoyer des messages

`; return; } container.innerHTML = currentUsers.map((user, index) => `
${user.tw_acc_pseudo} ${user.charactere}
${user.tw_acc_token.substring(0, 20)}...
`).join(''); } // Ouvrir le modal pour ajouter un utilisateur function openAddUserModal() { document.getElementById('userModalTitle').textContent = 'Ajouter un Utilisateur'; document.getElementById('user-edit-id').value = ''; document.getElementById('user-pseudo').value = ''; document.getElementById('user-token').value = ''; document.getElementById('user-charactere').value = '😊'; // Ouvrir le modal const modal = new bootstrap.Modal(document.getElementById('addUserModal')); modal.show(); } // Ouvrir le modal pour modifier un utilisateur function editUser(userId) { const user = currentUsers[userId]; if (!user) return; document.getElementById('userModalTitle').textContent = 'Modifier l\'Utilisateur'; document.getElementById('user-edit-id').value = userId; document.getElementById('user-pseudo').value = user.tw_acc_pseudo; document.getElementById('user-token').value = user.tw_acc_token; document.getElementById('user-charactere').value = user.charactere; // Ouvrir le modal const modal = new bootstrap.Modal(document.getElementById('addUserModal')); modal.show(); } // Sauvegarder un utilisateur (ajout ou modification) async function saveUser() { const userId = document.getElementById('user-edit-id').value; const pseudo = document.getElementById('user-pseudo').value.trim(); const token = document.getElementById('user-token').value.trim(); const charactere = document.getElementById('user-charactere').value.trim(); if (!pseudo || !token) { showToast('Pseudo et token requis', 'error'); return; } try { let response; if (userId === '') { // Ajouter un nouvel utilisateur response = await fetch(`${API_BASE}/api/config/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tw_acc_pseudo: pseudo, tw_acc_token: token, charactere: charactere }) }); } else { // Modifier un utilisateur existant response = await fetch(`${API_BASE}/api/config/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tw_acc_pseudo: pseudo, tw_acc_token: token, charactere: charactere }) }); } const result = await response.json(); if (result.success) { showToast(userId === '' ? 'Utilisateur ajouté' : 'Utilisateur modifié', 'success'); bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide(); await loadUsers(); } else { showToast(result.error || 'Erreur lors de la sauvegarde', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de la sauvegarde de l\'utilisateur', 'error'); } } // Supprimer un utilisateur async function deleteUser(userId) { if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?')) { return; } try { const response = await fetch(`${API_BASE}/api/config/users/${userId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showToast('Utilisateur supprimé avec succès', 'success'); await loadUsers(); } else { showToast(result.error || 'Erreur lors de la suppression', 'error'); } } catch (error) { console.error('Erreur:', error); showToast('Erreur lors de la suppression de l\'utilisateur', 'error'); } }