TTW_Bot_GO/internal/webui/templates/commands.html

434 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
<h2>Команды чата</h2>
<div class="card">
<h3>Добавить / редактировать команду</h3>
<form id="command-form">
<input type="hidden" id="command-id" value="0">
<label>Триггер (без !):</label>
<input type="text" id="trigger" required placeholder="ping">
<label>Шаблон ответа:</label>
<textarea id="template" rows="5" required placeholder="Pong! &lt;USERNAME/&gt;"></textarea>
<!-- Панель кнопок для вставки тегов -->
<div style="margin: 8px 0; display: flex; flex-wrap: wrap; gap: 6px;">
<button type="button" class="tag-btn" data-tag="&lt;USERNAME/&gt;">👤 USERNAME</button>
<button type="button" class="tag-btn" data-tag="&lt;ARG/&gt;">📝 ARG</button>
<button type="button" class="tag-btn" data-tag="&lt;AI/&gt;">🤖 AI</button>
<button type="button" class="tag-btn" data-tag="&lt;RANDOMUSER/&gt;">🎲 RANDOMUSER</button>
<button type="button" id="random-btn">🔢 Случайное число</button>
<button type="button" id="song-btn">🎵 Вставить звук</button>
<button type="button" id="group-btn">📦 Обертка group</button>
<button type="button" id="timeout-btn">⏱ Отстранение</button>
</div>
<div>
<button type="button" id="test-btn">🧪 Тестировать</button>
</div>
<label>
<input type="checkbox" id="enabled" checked> Включена
</label>
<label>Кулдаун (секунды):</label>
<input type="number" id="cooldown" value="0" min="0">
<label>Права доступа:</label>
<select id="permission">
<option value="everyone">Все</option>
<option value="moderator">Модераторы</option>
<option value="subscriber">Подписчики</option>
<option value="vip">VIP</option>
<option value="broadcaster">Стример</option>
</select>
<button type="submit">Сохранить</button>
<button type="button" id="cancel-edit" style="background: #6c757d;">Отмена</button>
</form>
</div>
<div class="card">
<h3>Список команд</h3>
<table style="width:100%; border-collapse: collapse;">
<thead>
<tr><th>Триггер</th><th>Шаблон</th><th>Кулдаун</th><th>Права</th><th>Статус</th><th>Действия</th></tr>
</thead>
<tbody id="commands-table">
<tr><td colspan="6">Загрузка...</td></tr>
</tbody>
</table>
</div>
<!-- Модальное окно для тестирования -->
<div id="test-modal" class="modal">
<div class="modal-content">
<h3>Результат тестирования</h3>
<p><strong>Текст для чата:</strong></p>
<pre id="test-result" style="background:#f0f0f0; padding:10px; border-radius:4px; white-space:pre-wrap;"></pre>
<p><strong>Звуковые файлы:</strong> <span id="test-sounds"></span></p>
<button id="close-modal" style="margin-top:10px;">Закрыть</button>
</div>
</div>
<!-- Модальное окно для случайного числа -->
<div id="random-modal" class="modal">
<div class="modal-content">
<h3>Вставить случайное число</h3>
<label>Минимум:</label>
<input type="number" id="random-min" value="1">
<label>Максимум:</label>
<input type="number" id="random-max" value="100">
<div style="margin-top:15px;">
<button id="insert-random-btn">Вставить</button>
<button id="cancel-random-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для звука -->
<div id="song-modal" class="modal">
<div class="modal-content">
<h3>Вставить звук</h3>
<label>Выберите звуковой файл:</label>
<select id="song-select" style="width:100%;">
<option value="">-- нет звуков --</option>
</select>
<div style="margin-top:15px;">
<button id="insert-song-btn">Вставить</button>
<button id="cancel-song-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для группы -->
<div id="group-modal" class="modal">
<div class="modal-content">
<h3>Создать группу вариантов</h3>
<div id="group-variants">
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 1" style="width:80%;">
</div>
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 2" style="width:80%;">
</div>
</div>
<button type="button" id="add-variant-btn">+ Добавить вариант</button>
<button type="button" id="remove-variant-btn" style="margin-left:10px;"> Удалить последний</button>
<div style="margin-top:15px;">
<button id="insert-group-btn">Вставить</button>
<button id="cancel-group-btn">Отмена</button>
</div>
</div>
</div>
<!-- Модальное окно для отстранения (timeout) -->
<div id="timeout-modal" class="modal">
<div class="modal-content">
<h3>Отстранение (таймаут)</h3>
<label>Количество минут:</label>
<input type="number" id="timeout-minutes" value="5" min="1" max="1440">
<div style="margin-top:15px;">
<button id="insert-timeout-btn">Вставить</button>
<button id="cancel-timeout-btn">Отмена</button>
</div>
</div>
</div>
<script>
let commands = [];
let soundList = [];
async function loadSoundList() {
try {
const res = await fetch('/api/sounds/list');
if (res.ok) {
soundList = await res.json();
const select = document.getElementById('song-select');
select.innerHTML = '<option value="">-- выберите звук --</option>';
soundList.forEach(sound => {
const option = document.createElement('option');
option.value = sound;
option.textContent = sound;
select.appendChild(option);
});
}
} catch(e) { console.error('Failed to load sounds', e); }
}
function insertAtCursor(textareaId, text) {
const textarea = document.getElementById(textareaId);
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
textarea.value = value.slice(0, start) + text + value.slice(end);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
// Простые теги
document.querySelectorAll('.tag-btn').forEach(btn => {
btn.addEventListener('click', () => {
insertAtCursor('template', btn.dataset.tag);
});
});
// Random
document.getElementById('random-btn').addEventListener('click', () => {
document.getElementById('random-modal').style.display = 'flex';
});
document.getElementById('insert-random-btn').addEventListener('click', () => {
const min = document.getElementById('random-min').value;
const max = document.getElementById('random-max').value;
const tag = `<random s=${min} e=${max}/>`;
insertAtCursor('template', tag);
document.getElementById('random-modal').style.display = 'none';
});
document.getElementById('cancel-random-btn').addEventListener('click', () => {
document.getElementById('random-modal').style.display = 'none';
});
// Song
document.getElementById('song-btn').addEventListener('click', async () => {
await loadSoundList();
document.getElementById('song-modal').style.display = 'flex';
});
document.getElementById('insert-song-btn').addEventListener('click', () => {
const selected = document.getElementById('song-select').value;
if (!selected) {
alert('Выберите звуковой файл');
return;
}
const tag = `<song f="data/sounds/${selected}"/>`;
insertAtCursor('template', tag);
document.getElementById('song-modal').style.display = 'none';
});
document.getElementById('cancel-song-btn').addEventListener('click', () => {
document.getElementById('song-modal').style.display = 'none';
});
// Group
document.getElementById('group-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
container.innerHTML = `
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 1" style="width:80%;">
</div>
<div class="group-variant" style="margin-bottom:8px;">
<input type="text" class="variant-input" placeholder="Вариант 2" style="width:80%;">
</div>
`;
document.getElementById('group-modal').style.display = 'flex';
});
document.getElementById('add-variant-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
const newDiv = document.createElement('div');
newDiv.className = 'group-variant';
newDiv.style.marginBottom = '8px';
newDiv.innerHTML = `<input type="text" class="variant-input" placeholder="Новый вариант" style="width:80%;">`;
container.appendChild(newDiv);
});
document.getElementById('remove-variant-btn').addEventListener('click', () => {
const container = document.getElementById('group-variants');
if (container.children.length > 2) {
container.removeChild(container.lastChild);
} else {
alert('Должно быть хотя бы два варианта');
}
});
document.getElementById('insert-group-btn').addEventListener('click', () => {
const inputs = document.querySelectorAll('#group-variants .variant-input');
let variants = [];
inputs.forEach(inp => {
let val = inp.value.trim();
if (val !== '') variants.push(val);
});
if (variants.length < 2) {
alert('Введите хотя бы два непустых варианта');
return;
}
let groupHtml = '<group>\n';
variants.forEach(v => {
groupHtml += ` <g>${escapeHtmlForGroup(v)}</g>\n`;
});
groupHtml += '</group>';
insertAtCursor('template', groupHtml);
document.getElementById('group-modal').style.display = 'none';
});
document.getElementById('cancel-group-btn').addEventListener('click', () => {
document.getElementById('group-modal').style.display = 'none';
});
// Timeout
document.getElementById('timeout-btn').addEventListener('click', () => {
document.getElementById('timeout-modal').style.display = 'flex';
});
document.getElementById('insert-timeout-btn').addEventListener('click', () => {
const minutes = document.getElementById('timeout-minutes').value;
const tag = `<timeout minutes="${minutes}"/>`;
insertAtCursor('template', tag);
document.getElementById('timeout-modal').style.display = 'none';
});
document.getElementById('cancel-timeout-btn').addEventListener('click', () => {
document.getElementById('timeout-modal').style.display = 'none';
});
function escapeHtmlForGroup(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
// Закрытие модалок по клику вне области
window.addEventListener('click', (e) => {
const modals = ['random-modal', 'song-modal', 'group-modal', 'test-modal', 'timeout-modal'];
modals.forEach(id => {
const modal = document.getElementById(id);
if (e.target === modal) modal.style.display = 'none';
});
});
// --- Остальной код команд (без изменений) ---
async function loadCommands() {
const res = await fetch('/api/commands');
commands = await res.json();
renderTable();
}
function renderTable() {
const tbody = document.getElementById('commands-table');
if (!commands.length) {
tbody.innerHTML = '<tr><td colspan="6">Нет команд. Добавьте первую!</td></tr>';
return;
}
tbody.innerHTML = commands.map(cmd => `
<tr>
<td>!${escapeHtml(cmd.Trigger)}</td>
<td style="max-width: 300px; overflow-x: auto;">${escapeHtml(cmd.Template)}</td>
<td>${cmd.CooldownSec}</td>
<td>${cmd.Permission}</td>
<td>${cmd.Enabled ? '✅ Вкл' : '❌ Выкл'}</td>
<td>
<button onclick="editCommand(${cmd.ID})">✏️</button>
<button onclick="deleteCommand(${cmd.ID})">🗑️</button>
</td>
</tr>
`).join('');
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
function editCommand(id) {
const cmd = commands.find(c => c.ID === id);
if (!cmd) return;
document.getElementById('command-id').value = cmd.ID;
document.getElementById('trigger').value = cmd.Trigger;
document.getElementById('template').value = cmd.Template;
document.getElementById('enabled').checked = cmd.Enabled;
document.getElementById('cooldown').value = cmd.CooldownSec;
document.getElementById('permission').value = cmd.Permission;
document.querySelector('.card').scrollIntoView({ behavior: 'smooth' });
}
async function deleteCommand(id) {
if (!confirm('Удалить команду?')) return;
await fetch(`/api/commands?id=${id}`, { method: 'DELETE' });
loadCommands();
if (document.getElementById('command-id').value == id) resetForm();
}
function resetForm() {
document.getElementById('command-id').value = 0;
document.getElementById('trigger').value = '';
document.getElementById('template').value = '';
document.getElementById('enabled').checked = true;
document.getElementById('cooldown').value = 0;
document.getElementById('permission').value = 'everyone';
}
document.getElementById('cancel-edit').addEventListener('click', resetForm);
document.getElementById('command-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = parseInt(document.getElementById('command-id').value);
const trigger = document.getElementById('trigger').value.trim();
const template = document.getElementById('template').value;
const enabled = document.getElementById('enabled').checked;
const cooldown = parseInt(document.getElementById('cooldown').value);
const permission = document.getElementById('permission').value;
if (!trigger || !template) {
alert('Заполните триггер и шаблон');
return;
}
const method = id === 0 ? 'POST' : 'PUT';
const url = '/api/commands';
const body = JSON.stringify({ ID: id, Trigger: trigger, Template: template, Enabled: enabled, CooldownSec: cooldown, Permission: permission });
try {
const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body });
if (res.ok) {
loadCommands();
resetForm();
} else {
const err = await res.text();
alert('Ошибка: ' + err);
}
} catch (err) {
alert('Ошибка соединения: ' + err.message);
}
});
document.getElementById('test-btn').addEventListener('click', async () => {
const template = document.getElementById('template').value;
if (!template) {
alert('Введите шаблон для тестирования');
return;
}
const username = prompt('Введите имя пользователя для подстановки (без @):', 'TestUser');
if (!username) return;
try {
const res = await fetch('/api/commands/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ template, username })
});
if (!res.ok) {
const err = await res.text();
alert('Ошибка тестирования: ' + err);
return;
}
const data = await res.json();
document.getElementById('test-result').textContent = data.result;
const soundsSpan = document.getElementById('test-sounds');
if (data.soundFiles && data.soundFiles.length) {
soundsSpan.innerHTML = data.soundFiles.join('<br>');
} else {
soundsSpan.textContent = 'нет';
}
document.getElementById('test-modal').style.display = 'flex';
} catch (err) {
alert('Ошибка: ' + err.message);
}
});
document.getElementById('close-modal').addEventListener('click', () => {
document.getElementById('test-modal').style.display = 'none';
});
loadCommands();
loadSoundList();
</script>
{{end}}