big update
This commit is contained in:
@@ -100,6 +100,31 @@ textarea, input,
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.navbar-sitemap-bg {
|
||||
background: linear-gradient(135deg, rgba(15, 15, 20, 0.65) 0%, rgba(35, 35, 60, 0.65) 100%);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.12);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.navbar-sitemap-link {
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px !important;
|
||||
margin: 0 3px;
|
||||
transition: background-color 140ms ease, color 140ms ease, transform 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
.navbar-sitemap-link:hover {
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.navbar-sitemap-link.active {
|
||||
background: rgba(0, 0, 0, 0.46);
|
||||
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
|
||||
@@ -87,6 +87,9 @@ async function loadInitialData() {
|
||||
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
|
||||
@@ -812,6 +815,382 @@ window.addEventListener('unhandledrejection', function(e) {
|
||||
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
|
||||
@@ -1160,6 +1539,18 @@ function renderUsers() {
|
||||
</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>
|
||||
@@ -1175,6 +1566,58 @@ function renderUsers() {
|
||||
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');
|
||||
|
||||
Reference in New Issue
Block a user