Files
site_aegisone/py_service/app/templates/pages/bot_dialogues.html
T
2026-06-02 19:48:00 +03:00

265 lines
14 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.
{% extends "page.html" %}
{% block page_content %}
<div class="page-header">
<h1>Диалоги чат-бота</h1>
<div class="header-controls">
<input type="text" id="search-input" placeholder="Поиск по ID или имени..." oninput="filterConversations()" class="filter-input">
<button onclick="loadConversations()" class="btn btn-primary">Обновить</button>
</div>
</div>
<script>var USER_ROLE = '{{ user.role }}';</script>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap;">
<select id="status-filter" onchange="filterConversations()" class="filter-select">
<option value="">Все статусы</option>
<option value="greeting">Приветствие</option>
<option value="collecting_name">Сбор имени</option>
<option value="collecting_phone">Сбор телефона</option>
<option value="collecting_email">Сбор email</option>
<option value="ticket_creation">Создание заявки</option>
<option value="question">Вопрос</option>
<option value="done">Завершён</option>
</select>
</div>
<div id="conversations-container">
<div class="loading"><div class="spinner"></div></div>
</div>
<div class="modal-overlay" id="dialog-modal" style="display:none;">
<div class="modal" style="max-width:800px;width:90%;">
<button type="button" class="modal-close" onclick="closeDialog()"></button>
<h2 id="dialog-title">Диалог</h2>
<div style="display:flex;gap:4px;margin-bottom:12px;">
<button onclick="showTab('messages')" class="tab-btn active" id="tab-messages-btn">Сообщения</button>
<button onclick="showTab('logs')" class="tab-btn" id="tab-logs-btn">Логи обработки</button>
</div>
<div id="tab-messages" class="tab-content"></div>
<div id="tab-logs" class="tab-content" style="display:none;"></div>
</div>
</div>
<div class="modal-overlay" id="delete-modal">
<div class="modal" style="max-width:420px;">
<button type="button" class="modal-close" onclick="closeDeleteModal()"></button>
<h2 style="font-size:16px;margin-bottom:8px;">Удалить диалог?</h2>
<p style="color:var(--text-secondary);font-size:14px;margin-bottom:20px;">
Диалог с <strong id="delete-conv-name"></strong> будет удалён навсегда. Это действие нельзя отменить.
</p>
<div class="modal-actions" style="justify-content:flex-end;">
<button type="button" class="btn" onclick="closeDeleteModal()">Отмена</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Удалить</button>
</div>
</div>
</div>
<style>
.conv-card { background:var(--bg-card,#0A1628); border:1px solid var(--border,#253248); border-radius:8px; padding:12px; margin-bottom:8px; cursor:pointer; transition:border-color 0.2s; }
.conv-card:hover { border-color:var(--accent,#00ADEF); }
.conv-card .conv-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }
.conv-card .conv-user { font-weight:600; color:var(--text-primary,#E8EDF2); }
.conv-card .conv-id { font-size:11px; color:var(--text-muted,#6B8299); }
.conv-card .conv-meta { display:flex; gap:12px; font-size:12px; color:var(--text-muted,#6B8299); }
.conv-card .conv-state { display:inline-block; padding:2px 8px; border-radius:4px; font-size:11px; font-weight:600; }
.conv-state-greeting { background:#1a3a4a; color:#6bc4e8; }
.conv-state-collecting { background:#3a2a1a; color:#e8b86b; }
.conv-state-question { background:#1a3a2a; color:#6be8a0; }
.conv-state-done { background:#2a2a2a; color:#888; }
.conv-state-default { background:#2a1a3a; color:#b86be8; }
.msg-row { margin-bottom:8px; padding:8px 12px; border-radius:8px; max-width:80%; }
.msg-in { background:var(--bg-input,#1e3044); margin-right:auto; border-radius:8px 8px 8px 0; }
.msg-out { background:var(--accent,#00ADEF); margin-left:auto; border-radius:8px 8px 0 8px; color:#fff; }
.msg-meta { font-size:10px; color:var(--text-muted,#6B8299); margin-top:3px; }
.msg-out .msg-meta { color:rgba(255,255,255,0.7); }
.log-row { padding:6px 0; border-bottom:1px solid var(--border,#253248); font-size:12px; }
.log-step { font-weight:600; color:var(--accent,#00ADEF); }
.log-ok { color:#6be8a0; }
.log-error { color:#e86b6b; }
.log-time { font-size:10px; color:var(--text-muted,#6B8299); }
.tab-btn { padding:6px 14px; border:1px solid var(--border,#253248); background:transparent; color:var(--text-primary,#E8EDF2); border-radius:4px; cursor:pointer; font-size:12px; }
.tab-btn.active { background:var(--accent,#00ADEF); border-color:var(--accent,#00ADEF); color:#fff; }
.page-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; flex-wrap:wrap; gap:8px; }
.header-controls { display:flex; gap:8px; align-items:center; }
.filter-input { padding:6px 10px; border:1px solid var(--border,#253248); border-radius:4px; background:var(--bg-body,#1B2838); color:var(--text-primary,#E8EDF2); font-size:12px; width:200px; }
.filter-select { padding:6px 10px; border:1px solid var(--border,#253248); border-radius:4px; background:var(--bg-body,#1B2838); color:var(--text-primary,#E8EDF2); font-size:12px; }
.conv-delete { background:none; border:none; cursor:pointer; font-size:14px; padding:2px 6px; border-radius:4px; opacity:0.5; transition:opacity 0.2s; margin-left:auto; }
.conv-delete:hover { opacity:1; background:rgba(232,107,107,0.15); }
.conv-header { display:flex; align-items:center; gap:8px; }
#delete-modal .modal { max-width:420px; }
#delete-modal h2 { font-size:16px; margin-bottom:8px; }
#delete-modal p { color:var(--text-secondary); font-size:14px; }
.main-header { display: none !important; }
</style>
<script>
var conversations = [];
var currentConvId = null;
function loadConversations(){
var container = document.getElementById('conversations-container');
container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
fetch('/api/bot/conversations').then(function(r){ return r.json() }).then(function(data){
if(!Array.isArray(data)){
throw new Error(data && data.error ? data.error : 'Неверный формат ответа');
}
conversations = data;
renderConversations(data);
}).catch(function(err){
container.innerHTML = '<div class="error-message">Ошибка загрузки: ' + err.message + '</div>';
});
}
function renderConversations(data){
var container = document.getElementById('conversations-container');
if(!data || data.length === 0){
container.innerHTML = '<div class="empty-state">Нет диалогов</div>';
return;
}
var html = '';
var filter = document.getElementById('search-input').value.toLowerCase();
var statusFilter = document.getElementById('status-filter').value;
data.forEach(function(c){
var userName = c.user_name.toLowerCase();
var userId = String(c.user_id);
var state = c.state || '';
if(filter && !userName.includes(filter) && !userId.includes(filter)) return;
if(statusFilter && state !== statusFilter) return;
var stateClass = 'conv-state-default';
if(state === 'greeting') stateClass = 'conv-state-greeting';
else if(state.startsWith('collecting')) stateClass = 'conv-state-collecting';
else if(state === 'question') stateClass = 'conv-state-question';
else if(state === 'done') stateClass = 'conv-state-done';
var stateLabel = state || '—';
html += '<div class="conv-card" onclick="openDialog(' + c.id + ')">';
html += '<div class="conv-header"><span class="conv-user">' + escapeHtml(c.user_name) + '</span><span class="conv-id">ID: ' + c.id + '</span>';
if(USER_ROLE === 'owner'){
html += '<button class="conv-delete" onclick="event.stopPropagation();openDeleteModal(' + c.id + ', \'' + escapeHtml(c.user_name).replace(/'/g, "\\'") + '\')" title="Удалить диалог">🗑</button>';
}
html += '</div>';
html += '<div class="conv-meta">';
html += '<span class="conv-state ' + stateClass + '">' + escapeHtml(stateLabel) + '</span>';
html += '<span>' + c.message_count + ' сообщ.</span>';
html += '<span>intent: ' + escapeHtml(c.intent || '—') + '</span>';
html += '<span>' + (c.created_at || '').substring(0, 16).replace('T', ' ') + '</span>';
html += '</div></div>';
});
container.innerHTML = html || '<div class="empty-state">Нет диалогов по фильтру</div>';
}
function filterConversations(){
renderConversations(conversations);
}
function openDialog(convId){
currentConvId = convId;
document.getElementById('dialog-modal').style.display = 'flex';
var conv = conversations.find(function(c){ return c.id === convId; });
document.getElementById('dialog-title').textContent = 'Диалог с ' + (conv ? conv.user_name : '#' + convId);
showTab('messages');
loadMessages(convId);
}
function closeDialog(){
document.getElementById('dialog-modal').style.display = 'none';
}
function showTab(tab){
document.querySelectorAll('.tab-content').forEach(function(el){ el.style.display = 'none'; });
document.querySelectorAll('.tab-btn').forEach(function(el){ el.classList.remove('active'); });
document.getElementById('tab-' + tab).style.display = 'block';
document.getElementById('tab-' + tab + '-btn').classList.add('active');
if(tab === 'logs' && currentConvId) loadLogs(currentConvId);
}
function loadMessages(convId){
var container = document.getElementById('tab-messages');
container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
fetch('/api/bot/conversations/' + convId + '/messages').then(function(r){ return r.json() }).then(function(data){
if(!Array.isArray(data)){
throw new Error(data && data.error ? data.error : 'Неверный формат ответа');
}
if(data.length === 0){
container.innerHTML = '<div class="empty-state">Нет сообщений</div>';
return;
}
var html = '';
data.forEach(function(m){
var dir = m.direction === 'incoming' ? 'msg-in' : 'msg-out';
var label = m.direction === 'incoming' ? 'Пользователь' : 'Бот';
html += '<div class="msg-row ' + dir + '">';
html += '<div>' + escapeHtml(m.text || '') + '</div>';
html += '<div class="msg-meta">' + label + ' · ' + (m.created_at || '').substring(0, 19).replace('T', ' ') + '</div>';
html += '</div>';
});
container.innerHTML = html;
}).catch(function(err){
container.innerHTML = '<div class="error-message">Ошибка: ' + err + '</div>';
});
}
function loadLogs(convId){
var container = document.getElementById('tab-logs');
container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
fetch('/api/bot/conversations/' + convId + '/logs').then(function(r){ return r.json() }).then(function(data){
if(!Array.isArray(data)){
throw new Error(data && data.error ? data.error : 'Неверный формат ответа');
}
if(data.length === 0){
container.innerHTML = '<div class="empty-state">Нет логов</div>';
return;
}
var html = '';
data.forEach(function(l){
var statusClass = l.status === 'error' ? 'log-error' : 'log-ok';
html += '<div class="log-row">';
html += '<span class="log-step">' + escapeHtml(l.step) + '</span> ';
html += '<span class="' + statusClass + '">[' + l.status + ']</span> ';
html += escapeHtml(l.message || '');
if(l.detail) html += '<div style="color:var(--text-muted,#6B8299);font-size:11px;margin-top:2px;">' + escapeHtml(l.detail) + '</div>';
html += '<div class="log-time">' + (l.created_at || '').substring(0, 19).replace('T', ' ') + '</div>';
html += '</div>';
});
container.innerHTML = html;
}).catch(function(err){
container.innerHTML = '<div class="error-message">Ошибка: ' + err + '</div>';
});
}
function escapeHtml(str){
if(!str) return '';
return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
var deleteConvId = null;
function openDeleteModal(convId, convName){
deleteConvId = convId;
document.getElementById('delete-conv-name').textContent = convName || '#' + convId;
document.getElementById('delete-modal').classList.add('open');
}
function closeDeleteModal(){
document.getElementById('delete-modal').classList.remove('open');
deleteConvId = null;
}
function confirmDelete(){
if(!deleteConvId) return;
var id = deleteConvId;
closeDeleteModal();
fetch('/api/bot/conversations/' + id, { method: 'DELETE' })
.then(function(r){ return r.json() })
.then(function(d){
if(d.ok){
showNotification('Диалог #' + id + ' удалён', 'success');
conversations = conversations.filter(function(c){ return c.id !== id; });
renderConversations(conversations);
} else {
showNotification('Ошибка: ' + (d.error || 'неизвестная'), 'error');
}
}).catch(function(err){
showNotification('Ошибка соединения', 'error');
});
}
document.addEventListener('DOMContentLoaded', loadConversations);
</script>
{% endblock %}