2324 lines
84 KiB
JavaScript
2324 lines
84 KiB
JavaScript
// 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);
|
|
});
|
|
|
|
socket.on('ia_generator_status_changed', function(data) {
|
|
// Synchroniser l'état de l'interface web
|
|
const iaGeneratorToggle = document.getElementById('iaGeneratorToggle');
|
|
const iaGeneratorStatus = document.getElementById('iaGeneratorStatus');
|
|
const chatMessageToggle = document.getElementById('chatMessageToggle');
|
|
const chatMessageStatus = document.getElementById('chatMessageStatus');
|
|
|
|
if (iaGeneratorToggle && iaGeneratorStatus) {
|
|
iaGeneratorToggle.checked = data.running;
|
|
iaGeneratorStatus.textContent = data.running ? 'En cours...' : 'Arrêté';
|
|
iaGeneratorStatus.className = data.running ? 'text-success' : 'text-muted';
|
|
}
|
|
|
|
// Synchroniser aussi l'état de l'envoi de messages
|
|
if (chatMessageToggle && chatMessageStatus) {
|
|
chatMessageToggle.checked = data.running;
|
|
chatMessageStatus.textContent = data.running ? 'Activé' : 'Désactivé';
|
|
chatMessageStatus.className = data.running ? 'text-success' : 'text-muted';
|
|
}
|
|
|
|
console.log('IA Generator status changed:', 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(),
|
|
loadInteractionConfig(),
|
|
refreshInteractionLog(),
|
|
loadSettings(),
|
|
checkAutoSubtitleStatus(), // Ajouter la vérification du statut auto
|
|
checkAutoMessageStatus(), // Ajouter la vérification du statut auto messages
|
|
checkChatMessageStatus(), // Ajouter la vérification du statut chat messages
|
|
checkSystemStatus() // Ajouter la vérification du statut système
|
|
]);
|
|
} 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 = `
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-stream fa-3x mb-3"></i>
|
|
<p>Aucun flux configuré</p>
|
|
<p class="small">Cliquez sur "Ajouter Flux" pour commencer</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = fluxList.map(flux => `
|
|
<div class="flux-item fade-in" data-flux-id="${flux.id}">
|
|
<div class="flux-controls">
|
|
<div class="flux-info">
|
|
<div class="flux-name">
|
|
<span class="flux-status ${flux.active ? 'active' : 'inactive'}"></span>
|
|
${flux.name}
|
|
<span class="badge bg-${flux.status === 'active' ? 'success' : flux.status === 'error' ? 'danger' : 'warning'} ms-2">${flux.status}</span>
|
|
</div>
|
|
<div class="flux-details">
|
|
${flux.record_audio ?
|
|
'<i class="fas fa-microphone text-success me-1"></i>Audio' :
|
|
'<i class="fas fa-microphone-slash text-muted me-1"></i>Pas d\'audio'
|
|
}
|
|
<i class="fas fa-comments text-info ms-2 me-1"></i>Chat
|
|
<span class="text-muted ms-2">${new Date(flux.created_at).toLocaleString()}</span>
|
|
${flux.error ? `<span class="text-danger ms-2">Erreur: ${flux.error}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="flux-actions">
|
|
<button class="btn btn-sm btn-outline-warning" onclick="toggleFlux(${flux.id})" ${flux.status === 'error' ? 'disabled' : ''}>
|
|
<i class="fas fa-${flux.active ? 'pause' : 'play'}"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="removeFlux(${flux.id})">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).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) => `
|
|
<div class="prompt-item fade-in">
|
|
<button class="btn btn-danger btn-sm" onclick="removePrompt(${index})">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
<div class="mb-2">
|
|
<label class="form-label small text-muted">Prompt ${index + 1}</label>
|
|
</div>
|
|
<textarea class="form-control" rows="2" data-prompt-index="${index}">${prompt}</textarea>
|
|
</div>
|
|
`).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();
|
|
const userId = parseInt(document.getElementById('custom-message-user').value);
|
|
|
|
console.log('Debug - Message:', message);
|
|
console.log('Debug - User ID:', userId);
|
|
console.log('Debug - User ID type:', typeof userId);
|
|
console.log('Debug - Selector value:', document.getElementById('custom-message-user').value);
|
|
|
|
if (!message) {
|
|
showToast('Veuillez entrer un message', 'error');
|
|
return;
|
|
}
|
|
|
|
// Vérifier si l'utilisateur est valide (pas NaN et dans la plage des utilisateurs)
|
|
if (isNaN(userId) || userId < 0 || userId >= currentUsers.length) {
|
|
showToast('Veuillez sélectionner un utilisateur valide', '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',
|
|
user_id: userId
|
|
})
|
|
});
|
|
|
|
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();
|
|
const userId = parseInt(document.getElementById('custom-message-user').value);
|
|
|
|
if (message === 'Aucun message en attente') {
|
|
showToast('Aucun message à envoyer', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Vérifier si l'utilisateur est valide (pas NaN et dans la plage des utilisateurs)
|
|
if (isNaN(userId) || userId < 0 || userId >= currentUsers.length) {
|
|
showToast('Veuillez sélectionner un utilisateur valide', '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',
|
|
user_id: userId
|
|
})
|
|
});
|
|
|
|
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() {
|
|
const userId = parseInt(document.getElementById('custom-message-user').value);
|
|
|
|
// Vérifier si l'utilisateur est valide (pas NaN et dans la plage des utilisateurs)
|
|
if (isNaN(userId) || userId < 0 || userId >= currentUsers.length) {
|
|
showToast('Veuillez sélectionner un utilisateur valide', 'error');
|
|
return;
|
|
}
|
|
|
|
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',
|
|
user_id: userId
|
|
})
|
|
});
|
|
|
|
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 = `
|
|
<div class="message-content">${message}</div>
|
|
<div class="message-time">${new Date().toLocaleTimeString()}</div>
|
|
`;
|
|
|
|
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 = '<p class="text-muted text-center">Aucun sous-titre disponible</p>';
|
|
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 => `
|
|
<div class="subtitle-item fade-in">
|
|
<div class="subtitle-content">${subtitles[key]}</div>
|
|
<div class="subtitle-time text-muted small">${key}</div>
|
|
</div>
|
|
`).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');
|
|
}
|
|
}
|
|
|
|
function processSubtitles() {
|
|
// Afficher un indicateur de chargement
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Traitement en cours...';
|
|
button.disabled = true;
|
|
|
|
fetch('/api/subtitles/process', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast(data.message, 'success');
|
|
// Actualiser les sous-titres
|
|
loadSubtitles();
|
|
} else {
|
|
showToast('Erreur: ' + data.error, 'danger');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Erreur:', error);
|
|
showToast('Erreur lors du traitement des sous-titres', 'danger');
|
|
})
|
|
.finally(() => {
|
|
// Restaurer le bouton
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
});
|
|
}
|
|
|
|
async function clearSubtitlesHistory() {
|
|
// Demander confirmation avant de nettoyer
|
|
if (!confirm('Êtes-vous sûr de vouloir nettoyer l\'historique des sous-titres ? Cette action est irréversible.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/subtitles/clear', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showToast(data.message, 'success');
|
|
// Vider l'affichage de l'historique
|
|
document.getElementById('subtitles-history').innerHTML = '<div class="text-muted text-center">Aucun sous-titre dans l\'historique</div>';
|
|
// Mettre à jour le dernier sous-titre
|
|
document.getElementById('last-subtitle').innerHTML = 'Aucun texte détecté pour le moment';
|
|
document.getElementById('last-subtitle').className = 'alert alert-warning';
|
|
} else {
|
|
showToast(data.error || 'Erreur lors du nettoyage', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('Erreur lors du nettoyage de l\'historique', '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 = `
|
|
<div class="toast-header">
|
|
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : type === 'warning' ? 'exclamation-triangle' : 'info-circle'} me-2"></i>
|
|
<strong class="me-auto">TwitchBot</strong>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
<div class="toast-body">
|
|
${message}
|
|
</div>
|
|
`;
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
// === FONCTIONS UTILITAIRES ===
|
|
|
|
function showAlert(message, type = 'info') {
|
|
const alertDiv = document.createElement('div');
|
|
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
|
alertDiv.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
// Ajouter l'alerte en haut de la page
|
|
const container = document.querySelector('.container-fluid');
|
|
container.insertBefore(alertDiv, container.firstChild);
|
|
|
|
// Supprimer automatiquement après 5 secondes
|
|
setTimeout(() => {
|
|
if (alertDiv.parentNode) {
|
|
alertDiv.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// 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');
|
|
});
|
|
|
|
// === PARAMÈTRES ===
|
|
|
|
async function loadSettings() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/config/settings`);
|
|
const result = await response.json();
|
|
if (!result.success) return;
|
|
const settings = result.settings || {};
|
|
const maxCharsEl = document.getElementById('settings-message-max-chars');
|
|
if (maxCharsEl) {
|
|
maxCharsEl.value = Number.isFinite(settings.twitch_message_max_chars) ? settings.twitch_message_max_chars : 100;
|
|
}
|
|
} catch (e) {
|
|
console.error('loadSettings error:', e);
|
|
}
|
|
}
|
|
|
|
async function saveSettings() {
|
|
try {
|
|
const maxCharsEl = document.getElementById('settings-message-max-chars');
|
|
const twitch_message_max_chars = maxCharsEl ? parseInt(maxCharsEl.value || '100', 10) : 100;
|
|
|
|
const response = await fetch(`${API_BASE}/api/config/settings`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ settings: { twitch_message_max_chars } })
|
|
});
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
showToast('Paramètres sauvegardés', 'success');
|
|
} else {
|
|
showToast(result.error || 'Erreur sauvegarde paramètres', 'error');
|
|
}
|
|
} catch (e) {
|
|
console.error('saveSettings error:', e);
|
|
showToast('Erreur sauvegarde paramètres', 'error');
|
|
}
|
|
}
|
|
|
|
// === NAVBAR SOMMAIRE (tabs) ===
|
|
function activateMainTab(tabButtonId) {
|
|
try {
|
|
const el = document.getElementById(tabButtonId);
|
|
if (!el) return;
|
|
const tab = new bootstrap.Tab(el);
|
|
tab.show();
|
|
} catch (e) {
|
|
console.error('activateMainTab error:', e);
|
|
}
|
|
}
|
|
|
|
function syncNavbarSitemapActive(tabButtonId) {
|
|
try {
|
|
const links = document.querySelectorAll('.navbar-sitemap-link[data-main-tab]');
|
|
links.forEach(a => {
|
|
const target = a.getAttribute('data-main-tab');
|
|
a.classList.toggle('active', target === tabButtonId);
|
|
});
|
|
} catch (e) {
|
|
console.error('syncNavbarSitemapActive error:', e);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Sync initial
|
|
const activeMain = document.querySelector('#mainTabs .nav-link.active');
|
|
if (activeMain && activeMain.id) syncNavbarSitemapActive(activeMain.id);
|
|
|
|
// Sync on tab changes
|
|
const mainTabButtons = document.querySelectorAll('#mainTabs .nav-link[data-bs-toggle="tab"]');
|
|
mainTabButtons.forEach(btn => {
|
|
btn.addEventListener('shown.bs.tab', function(ev) {
|
|
if (ev && ev.target && ev.target.id) syncNavbarSitemapActive(ev.target.id);
|
|
});
|
|
});
|
|
});
|
|
|
|
// === INTERACTION CHAT ===
|
|
|
|
let interactionConfig = null;
|
|
let interactionRegisteredAccounts = [];
|
|
|
|
async function loadInteractionConfig() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/interaction/config`);
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
console.error('Erreur interaction config:', result.error);
|
|
return;
|
|
}
|
|
|
|
interactionConfig = result.config || {};
|
|
interactionRegisteredAccounts = result.registered_accounts || [];
|
|
renderInteractionConfig();
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement interaction config:', error);
|
|
}
|
|
}
|
|
|
|
function renderInteractionConfig() {
|
|
if (!interactionConfig) return;
|
|
|
|
const enabledEl = document.getElementById('interaction-enabled');
|
|
const modeEl = document.getElementById('interaction-mode');
|
|
const tgptEnabledEl = document.getElementById('interaction-tgpt-enabled');
|
|
const tgptPrepromptEl = document.getElementById('interaction-tgpt-preprompt');
|
|
const tgptMaxCharsEl = document.getElementById('interaction-tgpt-max-chars');
|
|
const cooldownEl = document.getElementById('interaction-cooldown');
|
|
const defaultsEl = document.getElementById('interaction-default-responses');
|
|
const accountsEl = document.getElementById('interaction-registered-accounts');
|
|
|
|
if (enabledEl) enabledEl.checked = !!interactionConfig.enabled;
|
|
if (modeEl) modeEl.value = interactionConfig.mode || 'predefined';
|
|
if (tgptEnabledEl) tgptEnabledEl.checked = !!interactionConfig.tgpt_enabled;
|
|
if (tgptPrepromptEl) tgptPrepromptEl.value = interactionConfig.tgpt_preprompt || '';
|
|
if (tgptMaxCharsEl) tgptMaxCharsEl.value = Number.isFinite(interactionConfig.tgpt_max_chars) ? interactionConfig.tgpt_max_chars : 100;
|
|
if (cooldownEl) cooldownEl.value = Number.isFinite(interactionConfig.cooldown_seconds) ? interactionConfig.cooldown_seconds : 8;
|
|
|
|
if (defaultsEl) {
|
|
const defaults = Array.isArray(interactionConfig.default_responses) ? interactionConfig.default_responses : ['salut'];
|
|
defaultsEl.value = defaults.join('\n');
|
|
}
|
|
|
|
if (accountsEl) {
|
|
if (!interactionRegisteredAccounts || interactionRegisteredAccounts.length === 0) {
|
|
accountsEl.textContent = 'Aucun compte enregistré (config/user.json)';
|
|
} else {
|
|
accountsEl.innerHTML = interactionRegisteredAccounts
|
|
.map(a => `<div>@${escapeHtml(String(a))}</div>`)
|
|
.join('');
|
|
}
|
|
}
|
|
|
|
renderInteractionRules();
|
|
}
|
|
|
|
function renderInteractionRules() {
|
|
const container = document.getElementById('interaction-rules');
|
|
if (!container) return;
|
|
|
|
const rules = Array.isArray(interactionConfig.rules) ? interactionConfig.rules : [];
|
|
if (rules.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-muted text-center py-3 border rounded bg-dark">
|
|
Aucune règle. La réponse par défaut sera utilisée.
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = rules.map((r, idx) => {
|
|
const id = r.id || `rule_${idx}`;
|
|
return `
|
|
<div class="border rounded p-2 bg-dark">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex gap-3 align-items-center">
|
|
<div class="form-check form-switch mb-0">
|
|
<input class="form-check-input" type="checkbox" id="rule-enabled-${id}" ${r.enabled !== false ? 'checked' : ''} onchange="onInteractionRuleChange('${id}')">
|
|
<label class="form-check-label small text-muted" for="rule-enabled-${id}">Actif</label>
|
|
</div>
|
|
<div class="small text-muted">#${idx + 1}</div>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-success btn-sm" onclick="saveInteractionRule('${id}')">
|
|
<i class="fas fa-save me-1"></i>Enregistrer
|
|
</button>
|
|
<button class="btn btn-outline-danger btn-sm" onclick="deleteInteractionRule('${id}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-2 mt-2">
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted">Utilisateur source (optionnel)</label>
|
|
<input class="form-control form-control-sm" type="text" id="rule-from-${id}" value="${escapeAttr(r.from_username || '')}" placeholder="cammenbert" oninput="onInteractionRuleChange('${id}')">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted">Compte mentionné (optionnel)</label>
|
|
<input class="form-control form-control-sm" type="text" id="rule-mention-${id}" value="${escapeAttr(r.mention_account || '')}" placeholder="@exoticnaturees" oninput="onInteractionRuleChange('${id}')">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted">Contient (optionnel)</label>
|
|
<input class="form-control form-control-sm" type="text" id="rule-contains-${id}" value="${escapeAttr(r.contains_text || '')}" placeholder="etoile etoile" oninput="onInteractionRuleChange('${id}')">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-2">
|
|
<label class="form-label small text-muted">Réponse (sans @user)</label>
|
|
<input class="form-control form-control-sm" type="text" id="rule-response-${id}" value="${escapeAttr(r.response_text || '')}" placeholder="filante" oninput="onInteractionRuleChange('${id}')">
|
|
</div>
|
|
|
|
<div class="mt-2">
|
|
<label class="form-label small text-muted">Préprompt TGPT (optionnel, override)</label>
|
|
<textarea class="form-control form-control-sm" id="rule-tgpt-preprompt-${id}" rows="2" placeholder="Ex: Réponds en une phrase." oninput="onInteractionRuleChange('${id}')">${escapeHtml(r.tgpt_preprompt || '')}</textarea>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function onInteractionRuleChange(ruleId) {
|
|
const rules = Array.isArray(interactionConfig.rules) ? interactionConfig.rules : [];
|
|
const rule = rules.find(x => String(x.id) === String(ruleId));
|
|
if (!rule) return;
|
|
|
|
const enabledEl = document.getElementById(`rule-enabled-${ruleId}`);
|
|
const fromEl = document.getElementById(`rule-from-${ruleId}`);
|
|
const mentionEl = document.getElementById(`rule-mention-${ruleId}`);
|
|
const containsEl = document.getElementById(`rule-contains-${ruleId}`);
|
|
const responseEl = document.getElementById(`rule-response-${ruleId}`);
|
|
const tgptPrepromptEl = document.getElementById(`rule-tgpt-preprompt-${ruleId}`);
|
|
|
|
rule.enabled = enabledEl ? enabledEl.checked : true;
|
|
rule.from_username = fromEl ? fromEl.value.trim() : '';
|
|
rule.mention_account = mentionEl ? mentionEl.value.trim() : '';
|
|
rule.contains_text = containsEl ? containsEl.value.trim() : '';
|
|
rule.response_text = responseEl ? responseEl.value.trim() : '';
|
|
rule.tgpt_preprompt = tgptPrepromptEl ? tgptPrepromptEl.value : '';
|
|
}
|
|
|
|
async function saveInteractionRule(ruleId) {
|
|
// S'assurer que les champs du DOM sont bien remontés dans l'objet
|
|
onInteractionRuleChange(ruleId);
|
|
await saveInteractionConfig();
|
|
}
|
|
|
|
function addInteractionRule() {
|
|
if (!interactionConfig) interactionConfig = {};
|
|
if (!Array.isArray(interactionConfig.rules)) interactionConfig.rules = [];
|
|
|
|
const id = `r_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
interactionConfig.rules.unshift({
|
|
id,
|
|
enabled: true,
|
|
from_username: '',
|
|
mention_account: '',
|
|
contains_text: '',
|
|
response_text: '',
|
|
tgpt_preprompt: '',
|
|
});
|
|
|
|
renderInteractionRules();
|
|
}
|
|
|
|
function deleteInteractionRule(ruleId) {
|
|
if (!interactionConfig || !Array.isArray(interactionConfig.rules)) return;
|
|
interactionConfig.rules = interactionConfig.rules.filter(r => String(r.id) !== String(ruleId));
|
|
renderInteractionRules();
|
|
}
|
|
|
|
async function saveInteractionConfig() {
|
|
try {
|
|
const enabledEl = document.getElementById('interaction-enabled');
|
|
const modeEl = document.getElementById('interaction-mode');
|
|
const tgptEnabledEl = document.getElementById('interaction-tgpt-enabled');
|
|
const tgptPrepromptEl = document.getElementById('interaction-tgpt-preprompt');
|
|
const tgptMaxCharsEl = document.getElementById('interaction-tgpt-max-chars');
|
|
const cooldownEl = document.getElementById('interaction-cooldown');
|
|
const defaultsEl = document.getElementById('interaction-default-responses');
|
|
|
|
interactionConfig = interactionConfig || {};
|
|
interactionConfig.enabled = enabledEl ? !!enabledEl.checked : true;
|
|
interactionConfig.mode = modeEl ? modeEl.value : 'predefined';
|
|
interactionConfig.tgpt_enabled = tgptEnabledEl ? !!tgptEnabledEl.checked : false;
|
|
interactionConfig.tgpt_preprompt = tgptPrepromptEl ? tgptPrepromptEl.value : '';
|
|
interactionConfig.tgpt_max_chars = tgptMaxCharsEl ? parseInt(tgptMaxCharsEl.value || '100', 10) : 100;
|
|
interactionConfig.cooldown_seconds = cooldownEl ? parseInt(cooldownEl.value || '8', 10) : 8;
|
|
|
|
const defaults = defaultsEl ? defaultsEl.value.split('\n').map(x => x.trim()).filter(Boolean) : ['salut'];
|
|
interactionConfig.default_responses = defaults.length ? defaults : ['salut'];
|
|
|
|
// Nettoyage minimal des règles
|
|
if (Array.isArray(interactionConfig.rules)) {
|
|
interactionConfig.rules = interactionConfig.rules.map(r => ({
|
|
id: String(r.id || ''),
|
|
enabled: r.enabled !== false,
|
|
from_username: (r.from_username || '').trim() || null,
|
|
mention_account: (r.mention_account || '').trim() || null,
|
|
contains_text: (r.contains_text || '').trim() || null,
|
|
response_text: (r.response_text || '').trim(),
|
|
tgpt_preprompt: (r.tgpt_preprompt || '').trim() || null,
|
|
})).filter(r => r.id);
|
|
} else {
|
|
interactionConfig.rules = [];
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/api/interaction/config`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ config: interactionConfig })
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
interactionConfig = result.config;
|
|
renderInteractionConfig();
|
|
showToast('Interaction chat sauvegardée', 'success');
|
|
} else {
|
|
showToast(result.error || 'Erreur sauvegarde interaction chat', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur save interaction:', error);
|
|
showToast('Erreur sauvegarde interaction chat', 'error');
|
|
}
|
|
}
|
|
|
|
async function refreshInteractionConfig() {
|
|
await loadInteractionConfig();
|
|
}
|
|
|
|
async function refreshInteractionLog() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/interaction/log?limit=120`);
|
|
const result = await response.json();
|
|
const container = document.getElementById('interaction-log');
|
|
if (!container) return;
|
|
|
|
if (!result.success) {
|
|
container.innerHTML = `<div class="text-danger small">Erreur: ${escapeHtml(String(result.error || 'unknown'))}</div>`;
|
|
return;
|
|
}
|
|
|
|
const logs = Array.isArray(result.logs) ? result.logs : [];
|
|
if (logs.length === 0) {
|
|
container.innerHTML = `<div class="text-muted text-center">Aucun log</div>`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = logs.slice().reverse().map(l => {
|
|
const t = escapeHtml(String(l.ts || ''));
|
|
const typ = escapeHtml(String(l.type || ''));
|
|
const from = escapeHtml(String(l.from || ''));
|
|
const content = escapeHtml(String(l.content || ''));
|
|
const response = escapeHtml(String(l.response || ''));
|
|
const ruleId = l.rule_id ? `<span class="badge bg-info ms-2">rule ${escapeHtml(String(l.rule_id))}</span>` : '';
|
|
|
|
let body = '';
|
|
if (typ === 'responded') {
|
|
body = `<div class="small"><strong>@${from}</strong> → <span class="text-muted">${content}</span></div>
|
|
<div class="small text-success">${response}</div>`;
|
|
} else if (typ === 'error') {
|
|
body = `<div class="small text-danger">${escapeHtml(String(l.error || 'error'))}</div>`;
|
|
} else {
|
|
body = `<div class="small text-muted">${escapeHtml(JSON.stringify(l))}</div>`;
|
|
}
|
|
|
|
return `
|
|
<div class="subtitle-item fade-in">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="subtitle-time text-muted small">${t}</div>
|
|
<div><span class="badge bg-secondary">${typ}</span>${ruleId}</div>
|
|
</div>
|
|
${body}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
} catch (error) {
|
|
console.error('Erreur logs interaction:', error);
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
return String(text)
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
.replaceAll("'", ''');
|
|
}
|
|
|
|
function escapeAttr(text) {
|
|
return escapeHtml(text).replaceAll('`', '`');
|
|
}
|
|
|
|
// === 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 = `
|
|
<div class="chat-welcome">
|
|
<i class="fas fa-comments text-muted"></i>
|
|
<p class="text-muted">Le chat apparaîtra ici quand un flux sera actif</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="message-text">${message}</div>
|
|
<div class="message-time">${timestamp}</div>
|
|
`;
|
|
} else {
|
|
messageElement.innerHTML = `
|
|
<div class="username">${username}</div>
|
|
<div class="message-text">${message}</div>
|
|
<div class="message-time">${timestamp}</div>
|
|
`;
|
|
}
|
|
|
|
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();
|
|
const userId = parseInt(document.getElementById('chat-user-select').value);
|
|
|
|
console.log('Debug - Chat Message:', message);
|
|
console.log('Debug - Chat User ID:', userId);
|
|
console.log('Debug - Chat User ID type:', typeof userId);
|
|
console.log('Debug - Chat Selector value:', document.getElementById('chat-user-select').value);
|
|
|
|
if (!message) return;
|
|
|
|
// Vérifier si l'utilisateur est valide (pas NaN et dans la plage des utilisateurs)
|
|
if (isNaN(userId) || userId < 0 || userId >= currentUsers.length) {
|
|
showToast('Veuillez sélectionner un utilisateur valide', 'error');
|
|
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,
|
|
user_id: userId
|
|
})
|
|
});
|
|
|
|
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 = `
|
|
<div class="chat-welcome">
|
|
<i class="fas fa-comments text-muted"></i>
|
|
<p class="text-muted">Le chat apparaîtra ici quand un flux sera actif</p>
|
|
</div>
|
|
`;
|
|
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();
|
|
updateUserSelectors(); // Mettre à jour les sélecteurs après le chargement
|
|
} 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 = `
|
|
<div class="users-empty">
|
|
<i class="fas fa-users"></i>
|
|
<h5>Aucun utilisateur configuré</h5>
|
|
<p>Ajoutez des utilisateurs pour envoyer des messages</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = currentUsers.map((user, index) => `
|
|
<div class="user-item fade-in" data-user-id="${index}">
|
|
<div class="user-info">
|
|
<div class="user-avatar">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
<div class="user-details">
|
|
<div class="user-pseudo">
|
|
<span class="user-status active"></span>
|
|
${user.tw_acc_pseudo}
|
|
<span class="user-charactere">${user.charactere}</span>
|
|
</div>
|
|
<div class="user-token">
|
|
${user.tw_acc_token.substring(0, 20)}...
|
|
</div>
|
|
</div>
|
|
<div class="user-controls">
|
|
<div class="d-flex align-items-center me-2" title="Activer/désactiver cet utilisateur">
|
|
<span class="small text-muted me-1">Actif</span>
|
|
<div class="form-check form-switch mb-0">
|
|
<input class="form-check-input" type="checkbox" id="user-enabled-${index}" ${user.enabled === false ? '' : 'checked'} onchange="toggleUserEnabled(${index})">
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center me-2" title="Bypass anti-boucle Interaction chat">
|
|
<span class="small text-muted me-1">Bypass</span>
|
|
<div class="form-check form-switch mb-0">
|
|
<input class="form-check-input" type="checkbox" id="user-bypass-${index}" ${user.interaction_bypass_antiloop ? 'checked' : ''} onchange="toggleUserInteractionBypass(${index})">
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editUser(${index})">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser(${index})">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Mettre à jour les sélecteurs d'utilisateurs
|
|
updateUserSelectors();
|
|
}
|
|
|
|
async function toggleUserEnabled(userId) {
|
|
try {
|
|
const checkbox = document.getElementById(`user-enabled-${userId}`);
|
|
if (!checkbox) return;
|
|
const enabled = checkbox.checked;
|
|
|
|
const response = await fetch(`${API_BASE}/api/config/users/${userId}/enabled`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ enabled })
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
currentUsers[userId] = result.user;
|
|
showToast(`Utilisateur ${enabled ? 'activé' : 'désactivé'}`, 'success');
|
|
} else {
|
|
checkbox.checked = !enabled;
|
|
showToast(result.error || 'Erreur lors du changement d’état', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur toggleUserEnabled:', error);
|
|
showToast('Erreur lors du changement d’état utilisateur', 'error');
|
|
}
|
|
}
|
|
|
|
async function toggleUserInteractionBypass(userId) {
|
|
try {
|
|
const checkbox = document.getElementById(`user-bypass-${userId}`);
|
|
if (!checkbox) return;
|
|
const interaction_bypass_antiloop = checkbox.checked;
|
|
|
|
const response = await fetch(`${API_BASE}/api/config/users/${userId}/interaction-bypass`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ interaction_bypass_antiloop })
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
currentUsers[userId] = result.user;
|
|
showToast(`Bypass anti-boucle: ${interaction_bypass_antiloop ? 'ON' : 'OFF'}`, 'success');
|
|
} else {
|
|
checkbox.checked = !interaction_bypass_antiloop;
|
|
showToast(result.error || 'Erreur lors du changement', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur toggleUserInteractionBypass:', error);
|
|
showToast('Erreur lors du changement bypass', 'error');
|
|
}
|
|
}
|
|
|
|
// Mettre à jour les sélecteurs d'utilisateurs dans l'interface
|
|
function updateUserSelectors() {
|
|
console.log('Debug - updateUserSelectors called');
|
|
console.log('Debug - currentUsers:', currentUsers);
|
|
|
|
const selectors = [
|
|
'custom-message-user',
|
|
'chat-user-select'
|
|
];
|
|
|
|
selectors.forEach(selectorId => {
|
|
const selector = document.getElementById(selectorId);
|
|
if (selector) {
|
|
console.log(`Debug - Updating selector: ${selectorId}`);
|
|
|
|
// Sauvegarder la sélection actuelle
|
|
const currentValue = selector.value;
|
|
console.log(`Debug - Current value: ${currentValue}`);
|
|
|
|
// Vider le sélecteur avec une valeur par défaut différente
|
|
selector.innerHTML = '<option value="-1">Sélectionner un utilisateur...</option>';
|
|
|
|
// Ajouter les utilisateurs
|
|
currentUsers.forEach((user, index) => {
|
|
const option = document.createElement('option');
|
|
option.value = index.toString(); // Convertir en chaîne
|
|
option.textContent = `${user.tw_acc_pseudo} ${user.charactere}`;
|
|
selector.appendChild(option);
|
|
console.log(`Debug - Added option: value=${index.toString()}, text=${user.tw_acc_pseudo} ${user.charactere}`);
|
|
});
|
|
|
|
// Restaurer la sélection si elle était valide
|
|
if (currentValue && currentValue !== '-1' && currentValue !== '0') {
|
|
selector.value = currentValue;
|
|
console.log(`Debug - Restored value: ${currentValue}`);
|
|
}
|
|
} else {
|
|
console.log(`Debug - Selector not found: ${selectorId}`);
|
|
}
|
|
});
|
|
|
|
console.log('Debug - Selectors updated');
|
|
}
|
|
|
|
// 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 {
|
|
const url = userId ? `${API_BASE}/api/config/users/${userId}` : `${API_BASE}/api/config/users`;
|
|
const method = userId ? 'PUT' : 'POST';
|
|
|
|
const response = await fetch(url, {
|
|
method: method,
|
|
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 modifié' : 'Utilisateur ajouté', 'success');
|
|
bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();
|
|
await loadUsers();
|
|
updateUserSelectors(); // Mettre à jour les sélecteurs
|
|
} else {
|
|
showToast(result.error || 'Erreur lors de la sauvegarde', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('Erreur lors de la sauvegarde', '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é', 'success');
|
|
await loadUsers();
|
|
updateUserSelectors(); // Mettre à jour les sélecteurs
|
|
} else {
|
|
showToast(result.error || 'Erreur lors de la suppression', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('Erreur lors de la suppression', 'error');
|
|
}
|
|
}
|
|
|
|
// Variables globales pour la génération automatique
|
|
let autoSubtitleRunning = false;
|
|
|
|
// Variables globales pour l'envoi automatique de messages
|
|
let autoMessageRunning = false;
|
|
|
|
// Écouteurs Socket.IO pour la génération automatique
|
|
socket.on('subtitle_processing_start', function(data) {
|
|
console.log('Début de traitement:', data.file);
|
|
showProcessingFile(data.file);
|
|
});
|
|
|
|
socket.on('subtitle_processing_complete', function(data) {
|
|
console.log('Traitement terminé:', data.file);
|
|
hideProcessingFile();
|
|
showAlert(`Sous-titre créé pour ${data.file}: ${data.subtitle}`, 'success');
|
|
loadSubtitles(); // Actualiser les sous-titres
|
|
});
|
|
|
|
socket.on('subtitle_processing_error', function(data) {
|
|
console.log('Erreur de traitement:', data.file, data.error);
|
|
hideProcessingFile();
|
|
showAlert(`Erreur pour ${data.file}: ${data.error}`, 'danger');
|
|
});
|
|
|
|
// Écouteurs Socket.IO pour l'envoi automatique de messages
|
|
socket.on('message_sending_start', function(data) {
|
|
console.log('Début d\'envoi de message:', data.message);
|
|
showAlert(`Envoi en cours: ${data.message}`, 'info');
|
|
});
|
|
|
|
socket.on('message_sending_complete', function(data) {
|
|
console.log('Message envoyé:', data.message);
|
|
showAlert(`Message envoyé: ${data.message}`, 'success');
|
|
loadGenerations(); // Actualiser les générations
|
|
});
|
|
|
|
socket.on('message_sending_error', function(data) {
|
|
console.log('Erreur d\'envoi:', data.message, data.error);
|
|
showAlert(`Erreur d'envoi: ${data.error}`, 'danger');
|
|
});
|
|
|
|
// Fonction pour activer/désactiver l'envoi automatique de messages
|
|
async function toggleAutoMessage() {
|
|
const toggle = document.getElementById('autoMessageToggle');
|
|
const statusElement = document.getElementById('autoMessageStatus');
|
|
|
|
if (toggle.checked) {
|
|
// Démarrer l'envoi automatique de messages
|
|
try {
|
|
const response = await fetch('/api/messages/auto/start', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
autoMessageRunning = true;
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
showAlert('Envoi automatique de messages démarré', 'success');
|
|
} else {
|
|
toggle.checked = false;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = false;
|
|
showAlert('Erreur lors du démarrage', 'danger');
|
|
}
|
|
} else {
|
|
// Arrêter l'envoi automatique de messages
|
|
try {
|
|
const response = await fetch('/api/messages/auto/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
autoMessageRunning = false;
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
showAlert('Envoi automatique de messages arrêté', 'info');
|
|
} else {
|
|
toggle.checked = true;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = true;
|
|
showAlert('Erreur lors de l\'arrêt', 'danger');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour vérifier le statut de l'envoi automatique de messages au chargement
|
|
async function checkAutoMessageStatus() {
|
|
try {
|
|
const response = await fetch('/api/messages/auto/status');
|
|
const status = await response.json();
|
|
|
|
const toggle = document.getElementById('autoMessageToggle');
|
|
const statusElement = document.getElementById('autoMessageStatus');
|
|
|
|
if (toggle && statusElement) {
|
|
// Synchroniser l'état du toggle avec le serveur
|
|
toggle.checked = status.running;
|
|
autoMessageRunning = status.running;
|
|
|
|
if (status.running) {
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
console.log('Envoi automatique de messages actif');
|
|
} else {
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
console.log('Envoi automatique de messages arrêté');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la vérification du statut:', error);
|
|
// En cas d'erreur, forcer l'arrêt
|
|
const toggle = document.getElementById('autoMessageToggle');
|
|
const statusElement = document.getElementById('autoMessageStatus');
|
|
if (toggle && statusElement) {
|
|
toggle.checked = false;
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
autoMessageRunning = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour activer/désactiver la génération automatique
|
|
async function toggleAutoSubtitle() {
|
|
const toggle = document.getElementById('autoSubtitleToggle');
|
|
const statusElement = document.getElementById('autoSubtitleStatus');
|
|
|
|
if (toggle.checked) {
|
|
// Démarrer la génération automatique
|
|
try {
|
|
const response = await fetch('/api/subtitles/auto/start', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
autoSubtitleRunning = true;
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
showAlert('Génération automatique démarrée', 'success');
|
|
} else {
|
|
toggle.checked = false;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = false;
|
|
showAlert('Erreur lors du démarrage', 'danger');
|
|
}
|
|
} else {
|
|
// Arrêter la génération automatique
|
|
try {
|
|
const response = await fetch('/api/subtitles/auto/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
autoSubtitleRunning = false;
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
hideProcessingFile();
|
|
showAlert('Génération automatique arrêtée', 'info');
|
|
} else {
|
|
toggle.checked = true;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = true;
|
|
showAlert('Erreur lors de l\'arrêt', 'danger');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour afficher le fichier en cours de traitement
|
|
function showProcessingFile(fileName) {
|
|
const processingElement = document.getElementById('processing-file');
|
|
const fileNameElement = document.getElementById('current-file-name');
|
|
|
|
if (processingElement && fileNameElement) {
|
|
fileNameElement.textContent = `Traitement de: ${fileName}`;
|
|
processingElement.classList.remove('d-none');
|
|
}
|
|
}
|
|
|
|
// Fonction pour masquer le fichier en cours de traitement
|
|
function hideProcessingFile() {
|
|
const processingElement = document.getElementById('processing-file');
|
|
|
|
if (processingElement) {
|
|
processingElement.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
// Fonction pour vérifier le statut de la génération automatique au chargement
|
|
async function checkAutoSubtitleStatus() {
|
|
try {
|
|
const response = await fetch('/api/subtitles/auto/status');
|
|
const status = await response.json();
|
|
|
|
const toggle = document.getElementById('autoSubtitleToggle');
|
|
const statusElement = document.getElementById('autoSubtitleStatus');
|
|
|
|
if (toggle && statusElement) {
|
|
toggle.checked = status.running;
|
|
autoSubtitleRunning = status.running;
|
|
|
|
if (status.running) {
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
|
|
if (status.current_file) {
|
|
showProcessingFile(status.current_file);
|
|
}
|
|
} else {
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
hideProcessingFile();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la vérification du statut:', error);
|
|
}
|
|
}
|
|
|
|
// Fonction pour forcer l'arrêt de l'envoi automatique
|
|
async function forceStopAutoMessage() {
|
|
try {
|
|
const response = await fetch('/api/messages/auto/force-stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
// Mettre à jour l'interface
|
|
const toggle = document.getElementById('autoMessageToggle');
|
|
const statusElement = document.getElementById('autoMessageStatus');
|
|
|
|
if (toggle && statusElement) {
|
|
autoMessageRunning = false;
|
|
toggle.checked = false;
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
}
|
|
|
|
showAlert('Arrêt forcé de l\'envoi automatique de messages', 'warning');
|
|
} else {
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de l\'arrêt forcé:', error);
|
|
showAlert('Erreur lors de l\'arrêt forcé', 'danger');
|
|
}
|
|
}
|
|
|
|
// Fonction pour forcer l'arrêt de la génération automatique de sous-titres
|
|
async function forceStopAutoSubtitle() {
|
|
try {
|
|
const response = await fetch('/api/subtitles/auto/force-stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
// Mettre à jour l'interface
|
|
const toggle = document.getElementById('autoSubtitleToggle');
|
|
const statusElement = document.getElementById('autoSubtitleStatus');
|
|
|
|
if (toggle && statusElement) {
|
|
autoSubtitleRunning = false;
|
|
toggle.checked = false;
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
hideProcessingFile();
|
|
}
|
|
|
|
showAlert('Arrêt forcé de la génération automatique de sous-titres', 'warning');
|
|
} else {
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de l\'arrêt forcé:', error);
|
|
showAlert('Erreur lors de l\'arrêt forcé', 'danger');
|
|
}
|
|
}
|
|
|
|
// Fonction pour activer/désactiver l'envoi de messages dans le chat
|
|
async function toggleChatMessage() {
|
|
const toggle = document.getElementById('chatMessageToggle');
|
|
const statusElement = document.getElementById('chatMessageStatus');
|
|
|
|
if (toggle.checked) {
|
|
// Activer l'envoi de messages
|
|
try {
|
|
const response = await fetch('/api/chat/messages/enable', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'Activé';
|
|
statusElement.className = 'text-success';
|
|
showAlert('Envoi de messages dans le chat activé', 'success');
|
|
} else {
|
|
toggle.checked = false;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = false;
|
|
showAlert('Erreur lors de l\'activation', 'danger');
|
|
}
|
|
} else {
|
|
// Désactiver l'envoi de messages
|
|
try {
|
|
const response = await fetch('/api/chat/messages/disable', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'Désactivé';
|
|
statusElement.className = 'text-muted';
|
|
showAlert('Envoi de messages dans le chat désactivé', 'info');
|
|
} else {
|
|
toggle.checked = true;
|
|
showAlert('Erreur: ' + result.error, 'danger');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = true;
|
|
showAlert('Erreur lors de la désactivation', 'danger');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour vérifier le statut de l'envoi de messages chat au chargement
|
|
async function checkChatMessageStatus() {
|
|
try {
|
|
const response = await fetch('/api/chat/messages/status');
|
|
const status = await response.json();
|
|
|
|
const toggle = document.getElementById('chatMessageToggle');
|
|
const statusElement = document.getElementById('chatMessageStatus');
|
|
|
|
if (toggle && statusElement) {
|
|
toggle.checked = status.enabled;
|
|
|
|
if (status.enabled) {
|
|
statusElement.textContent = 'Activé';
|
|
statusElement.className = 'text-success';
|
|
} else {
|
|
statusElement.textContent = 'Désactivé';
|
|
statusElement.className = 'text-muted';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la vérification du statut chat:', error);
|
|
// En cas d'erreur, forcer la désactivation par défaut
|
|
const toggle = document.getElementById('chatMessageToggle');
|
|
const statusElement = document.getElementById('chatMessageStatus');
|
|
if (toggle && statusElement) {
|
|
toggle.checked = false;
|
|
statusElement.textContent = 'Désactivé';
|
|
statusElement.className = 'text-muted';
|
|
}
|
|
}
|
|
}
|
|
|
|
// === GESTION DES COMPOSANTS SYSTÈME ===
|
|
|
|
// Fonction pour activer/désactiver le générateur IA
|
|
async function toggleIAGenerator() {
|
|
const toggle = document.getElementById('iaGeneratorToggle');
|
|
const statusElement = document.getElementById('iaGeneratorStatus');
|
|
|
|
if (toggle.checked) {
|
|
// Démarrer le générateur IA
|
|
try {
|
|
const response = await fetch('/api/ia-generator/start', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
showToast('Générateur IA démarré', 'success');
|
|
} else {
|
|
toggle.checked = false;
|
|
showToast('Erreur: ' + result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = false;
|
|
showToast('Erreur lors du démarrage du générateur IA', 'error');
|
|
}
|
|
} else {
|
|
// Arrêter le générateur IA
|
|
try {
|
|
const response = await fetch('/api/ia-generator/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
showToast('Générateur IA arrêté', 'info');
|
|
} else {
|
|
toggle.checked = true;
|
|
showToast('Erreur: ' + result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = true;
|
|
showToast('Erreur lors de l\'arrêt du générateur IA', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour activer/désactiver le contrôleur Twitch
|
|
async function toggleControlTwitch() {
|
|
const toggle = document.getElementById('controlTwitchToggle');
|
|
const statusElement = document.getElementById('controlTwitchStatus');
|
|
|
|
if (toggle.checked) {
|
|
// Démarrer le contrôleur Twitch
|
|
try {
|
|
const response = await fetch('/api/control-twitch/start', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'En cours...';
|
|
statusElement.className = 'text-success';
|
|
showToast('Contrôleur Twitch démarré', 'success');
|
|
} else {
|
|
toggle.checked = false;
|
|
showToast('Erreur: ' + result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = false;
|
|
showToast('Erreur lors du démarrage du contrôleur Twitch', 'error');
|
|
}
|
|
} else {
|
|
// Arrêter le contrôleur Twitch
|
|
try {
|
|
const response = await fetch('/api/control-twitch/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
statusElement.textContent = 'Arrêté';
|
|
statusElement.className = 'text-muted';
|
|
showToast('Contrôleur Twitch arrêté', 'info');
|
|
} else {
|
|
toggle.checked = true;
|
|
showToast('Erreur: ' + result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
toggle.checked = true;
|
|
showToast('Erreur lors de l\'arrêt du contrôleur Twitch', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour vérifier le statut système au chargement
|
|
async function checkSystemStatus() {
|
|
try {
|
|
const response = await fetch('/api/system-status');
|
|
const status = await response.json();
|
|
|
|
// Mettre à jour le statut du générateur IA
|
|
const iaGeneratorToggle = document.getElementById('iaGeneratorToggle');
|
|
const iaGeneratorStatus = document.getElementById('iaGeneratorStatus');
|
|
|
|
if (iaGeneratorToggle && iaGeneratorStatus) {
|
|
iaGeneratorToggle.checked = status.ia_generator.running;
|
|
iaGeneratorStatus.textContent = status.ia_generator.status;
|
|
iaGeneratorStatus.className = status.ia_generator.running ? 'text-success' : 'text-muted';
|
|
}
|
|
|
|
// Mettre à jour le statut du contrôleur Twitch
|
|
const controlTwitchToggle = document.getElementById('controlTwitchToggle');
|
|
const controlTwitchStatus = document.getElementById('controlTwitchStatus');
|
|
|
|
if (controlTwitchToggle && controlTwitchStatus) {
|
|
controlTwitchToggle.checked = status.control_twitch.running;
|
|
controlTwitchStatus.textContent = status.control_twitch.status;
|
|
controlTwitchStatus.className = status.control_twitch.running ? 'text-success' : 'text-muted';
|
|
}
|
|
|
|
console.log('Statut système chargé:', status);
|
|
} catch (error) {
|
|
console.error('Erreur lors de la vérification du statut système:', error);
|
|
}
|
|
}
|