lsit user

This commit is contained in:
gpatruno
2025-07-20 02:53:10 +02:00
parent 2041e72342
commit 9725cd7a7c
8 changed files with 1087 additions and 11 deletions
+313
View File
@@ -423,4 +423,317 @@ body {
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
/* Chat Container Styles */
.chat-container {
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
position: sticky;
top: 20px;
}
.chat-container .card-body {
flex: 1;
overflow: hidden;
padding: 0;
}
.chat-messages {
height: 100%;
overflow-y: auto;
padding: 1rem;
background-color: var(--primary-bg);
}
.chat-welcome {
text-align: center;
padding: 2rem 1rem;
color: var(--text-secondary);
}
.chat-welcome i {
font-size: 3rem;
margin-bottom: 1rem;
display: block;
}
.chat-message {
margin-bottom: 0.75rem;
padding: 0.5rem;
border-radius: 8px;
background-color: var(--tertiary-bg);
border-left: 3px solid var(--accent-color);
animation: fadeIn 0.3s ease-in;
}
.chat-message.own-message {
background-color: rgba(0, 123, 255, 0.2);
border-left-color: var(--accent-color);
text-align: right;
}
.chat-message.system-message {
background-color: rgba(255, 193, 7, 0.2);
border-left-color: var(--warning-color);
font-style: italic;
}
.chat-message .username {
font-weight: bold;
color: var(--accent-color);
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.chat-message .message-text {
color: var(--text-primary);
word-wrap: break-word;
line-height: 1.4;
}
.chat-message .message-time {
font-size: 0.75rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
.chat-controls {
display: flex;
gap: 0.5rem;
}
.chat-controls .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.chat-container .card-footer {
background-color: var(--tertiary-bg);
border-top: 1px solid var(--border-color);
padding: 1rem;
}
.chat-container .input-group {
margin: 0;
}
.chat-container .form-control {
border-radius: 8px 0 0 8px;
border-right: none;
}
.chat-container .btn {
border-radius: 0 8px 8px 0;
border-left: none;
}
/* Chat scrollbar styling */
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: var(--secondary-bg);
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb:hover {
background: var(--accent-color);
}
/* Chat animations */
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.chat-message {
animation: slideInRight 0.3s ease-out;
}
/* Responsive chat */
@media (max-width: 1200px) {
.chat-container {
height: 400px;
position: relative;
top: 0;
}
}
@media (max-width: 768px) {
.col-md-3 {
margin-top: 1rem;
}
.chat-container {
height: 300px;
}
}
/* Chat status indicators */
.chat-status {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0.5rem;
}
.chat-status.online {
background-color: var(--success-color);
box-shadow: 0 0 5px var(--success-color);
}
.chat-status.offline {
background-color: var(--danger-color);
}
.chat-status.connecting {
background-color: var(--warning-color);
animation: pulse 1s infinite;
}
/* User Item Styles */
.user-item {
background-color: var(--tertiary-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
transition: all 0.3s ease;
position: relative;
}
.user-item:hover {
background-color: rgba(255, 255, 255, 0.05);
transform: translateX(5px);
}
.user-info {
display: flex;
align-items: center;
gap: 1rem;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
color: white;
}
.user-details {
flex-grow: 1;
}
.user-pseudo {
font-weight: bold;
font-size: 1.1rem;
margin-bottom: 0.25rem;
color: var(--text-primary);
}
.user-token {
font-size: 0.8rem;
color: var(--text-secondary);
font-family: monospace;
word-break: break-all;
}
.user-charactere {
font-size: 1.5rem;
margin-left: 0.5rem;
}
.user-controls {
display: flex;
gap: 0.5rem;
align-items: center;
}
.user-controls .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
/* User form styles */
.user-form-group {
margin-bottom: 1rem;
}
.user-form-group label {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.user-form-group .form-control {
background-color: var(--secondary-bg);
border: 1px solid var(--border-color);
color: var(--text-primary);
}
.user-form-group .form-control:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.user-form-group .form-text {
color: var(--text-secondary);
font-size: 0.8rem;
}
/* User list empty state */
.users-empty {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
}
.users-empty i {
font-size: 4rem;
margin-bottom: 1rem;
display: block;
opacity: 0.5;
}
/* User status indicators */
.user-status {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 0.5rem;
}
.user-status.active {
background-color: var(--success-color);
box-shadow: 0 0 8px rgba(40, 167, 69, 0.5);
}
.user-status.inactive {
background-color: var(--danger-color);
}
.user-status.testing {
background-color: var(--warning-color);
animation: pulse 1s infinite;
}
+465 -3
View File
@@ -3,6 +3,7 @@ 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() {
@@ -59,6 +60,7 @@ async function loadInitialData() {
await Promise.all([
loadFluxList(),
loadPrompts(),
loadUsers(),
loadStatus(),
loadSubtitles(),
loadGenerations()
@@ -95,8 +97,9 @@ function startPeriodicUpdates() {
async function loadFluxList() {
try {
const response = await fetch(`${API_BASE}/api/flux`);
const flux = await response.json();
renderFluxList(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);
}
@@ -575,6 +578,9 @@ function updateDashboard(status) {
if (status.next_message) {
updateNextMessage(status.next_message);
}
// Mettre à jour l'état du chat
updateChatState();
}
// === ACTIONS RAPIDES ===
@@ -659,4 +665,460 @@ window.addEventListener('error', function(e) {
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 = `
<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();
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 = `
<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();
} 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">
<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('');
}
// 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');
}
}