Files
site_aegisone/py_service/app/templates/pages/bot_kb.html
T

240 lines
11 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 %}
<style>
.kb-table { width:100%; border-collapse:collapse; }
.kb-table th, .kb-table td { padding:10px 14px; text-align:left; border-bottom:1px solid var(--border); font-size:13px; }
.kb-table th { font-weight:600; color:var(--text-muted); background:var(--bg-card); }
.kb-table td { vertical-align:top; }
.kb-table .active-badge { display:inline-block; padding:2px 8px; border-radius:4px; font-size:11px; font-weight:600; }
.kb-table .active-badge.yes { background:var(--success-bg, #d4edda); color:var(--success, #155724); }
.kb-table .active-badge.no { background:var(--danger-bg, #f8d7da); color:var(--danger, #721c24); }
.kb-modal { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.4);
z-index:1000; align-items:center; justify-content:center; }
.kb-modal.open { display:flex; }
.kb-modal-content { background:var(--bg-card); border-radius:12px; padding:30px; width:600px; max-width:90vw;
max-height:80vh; overflow-y:auto; }
.kb-modal-content h3 { margin-bottom:20px; }
.form-group { margin-bottom:14px; }
.form-group label { display:block; font-weight:600; margin-bottom:4px; font-size:13px; color:var(--text-muted); }
.form-group input, .form-group textarea, .form-group select {
width:100%; padding:8px 12px; border:1px solid var(--border); border-radius:6px; font-size:14px;
}
.form-group textarea { min-height:100px; resize:vertical; }
.modal-actions { display:flex; gap:12px; margin-top:16px; }
.filter-bar { display:flex; gap:12px; margin-bottom:16px; align-items:center; flex-wrap:wrap; }
.filter-bar input, .filter-bar select { padding:8px 12px; border:1px solid var(--border); border-radius:6px; font-size:13px; }
</style>
<div class="card">
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
<h2>База знаний чат-бота</h2>
<button class="btn btn-primary" onclick="openModal()">+ Добавить карточку</button>
</div>
<div class="filter-bar">
<input type="text" id="kb-search" placeholder="Поиск по вопросу..." oninput="filterKB()">
<select id="kb-category-filter" onchange="filterKB()">
<option value="">Все категории</option>
</select>
<select id="kb-active-filter" onchange="filterKB()">
<option value="">Все статусы</option>
<option value="true">Активные</option>
<option value="false">Неактивные</option>
</select>
</div>
<table class="kb-table">
<thead>
<tr>
<th>ID</th>
<th>Вопрос</th>
<th>Категория</th>
<th>Статус</th>
<th>Источник</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="kb-table-body">
<tr><td colspan="6" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
</tbody>
</table>
</div>
<div class="kb-modal" id="kb-modal">
<div class="kb-modal-content">
<h3 id="modal-title">Новая карточка</h3>
<form id="kb-form">
<div class="form-group">
<label>Категория</label>
<select name="category_id" id="kb-category-id"></select>
</div>
<div class="form-group">
<label>Вопрос *</label>
<input type="text" name="question" id="kb-question" required>
</div>
<div class="form-group">
<label>Ответ (оставьте пустым, если нет ответа)</label>
<textarea name="answer" id="kb-answer"></textarea>
</div>
<div class="form-group">
<label>Ключевые слова (через запятую)</label>
<input type="text" name="keywords" id="kb-keywords" placeholder="слово1, слово2, слово3">
</div>
<div class="form-group">
<label>URL источника</label>
<input type="url" name="source_url" id="kb-source-url" placeholder="https://aegisone.ru/...">
</div>
<div class="form-group">
<label><input type="checkbox" name="is_active" id="kb-is-active" checked> Активная карточка</label>
</div>
<div class="modal-actions">
<button type="submit" class="btn btn-primary">Сохранить</button>
<button type="button" class="btn" onclick="closeModal()">Отмена</button>
</div>
</form>
</div>
</div>
<script>
var categories = {};
var allCards = [];
fetch('/service/api/bot/settings').then(function(r){ return r.json() }).then(function(d){});
function loadKB(){
fetch('/api/bot/kb').then(function(r){ return r.json() }).then(function(data){
allCards = data;
renderKB(data);
});
}
function renderKB(cards){
var tbody = document.getElementById('kb-table-body');
if(!cards.length){
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:30px;color:var(--text-muted);">Нет карточек</td></tr>';
return;
}
var html = '';
cards.forEach(function(c){
var catName = categories[c.category_id] || 'Без категории';
var activeClass = c.is_active ? 'yes' : 'no';
var activeLabel = c.is_active ? 'Активна' : 'Неактивна';
var answerPreview = c.answer ? c.answer.substring(0, 50) + '...' : '<em style="color:var(--text-muted)">Нет ответа</em>';
html += '<tr>' +
'<td>' + c.id + '</td>' +
'<td><strong>' + escapeHtml(c.question) + '</strong><br><small style="color:var(--text-muted)">' + answerPreview + '</small></td>' +
'<td>' + catName + '</td>' +
'<td><span class="active-badge ' + activeClass + '">' + activeLabel + '</span></td>' +
'<td style="font-size:12px;">' + (c.source_url ? '<a href="' + c.source_url + '" target="_blank">ссылка</a>' : '—') + '</td>' +
'<td><button class="btn btn-sm" onclick="editCard(' + c.id + ')" title="Редактировать">✏️</button> <button class="btn btn-sm" onclick="deleteCard(' + c.id + ')" style="color:#dc3545;" title="Удалить">🗑</button></td>' +
'</tr>';
});
tbody.innerHTML = html;
}
function filterKB(){
var search = document.getElementById('kb-search').value.toLowerCase();
var catFilter = document.getElementById('kb-category-filter').value;
var activeFilter = document.getElementById('kb-active-filter').value;
var filtered = allCards.filter(function(c){
if(search && !c.question.toLowerCase().includes(search)) return false;
if(catFilter && String(c.category_id) !== catFilter) return false;
if(activeFilter !== '' && String(c.is_active) !== activeFilter) return false;
return true;
});
renderKB(filtered);
}
function openModal(){
editingId = null;
document.getElementById('kb-modal').classList.add('open');
document.getElementById('modal-title').textContent = 'Новая карточка';
document.getElementById('kb-form').reset();
document.getElementById('kb-is-active').checked = true;
}
function closeModal(){
document.getElementById('kb-modal').classList.remove('open');
}
var editingId = null;
function editCard(id){
var card = allCards.find(function(c){ return c.id === id; });
if(!card) return;
editingId = id;
document.getElementById('modal-title').textContent = 'Редактировать карточку #' + id;
document.getElementById('kb-question').value = card.question || '';
document.getElementById('kb-answer').value = card.answer || '';
document.getElementById('kb-keywords').value = (card.keywords || []).join(', ');
document.getElementById('kb-source-url').value = card.source_url || '';
document.getElementById('kb-is-active').checked = card.is_active;
document.getElementById('kb-category-id').value = card.category_id || '';
document.getElementById('kb-modal').classList.add('open');
}
function deleteCard(id){
if(!confirm('Удалить карточку?')) return;
fetch('/api/bot/kb/' + id, { method: 'DELETE' }).then(function(r){ return r.json() }).then(function(d){
if(d.ok) { showNotification('Карточка удалена', 'success'); loadKB(); }
else showNotification('Ошибка удаления', 'error');
});
}
document.getElementById('kb-form').addEventListener('submit', function(e){
e.preventDefault();
var data = {
category_id: parseInt(document.getElementById('kb-category-id').value) || null,
question: document.getElementById('kb-question').value,
answer: document.getElementById('kb-answer').value || null,
keywords: document.getElementById('kb-keywords').value.split(',').map(function(s){ return s.trim(); }).filter(Boolean),
source_url: document.getElementById('kb-source-url').value || null,
is_active: document.getElementById('kb-is-active').checked,
sort_order: 0,
};
var url = '/api/bot/kb';
var method = 'POST';
if(editingId) { url += '/' + editingId; method = 'PUT'; }
fetch(url, {
method: method, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(r){ return r.json() }).then(function(d){
if(d.ok) { closeModal(); loadKB(); showNotification(editingId ? 'Карточка обновлена' : 'Карточка создана', 'success'); }
else showNotification('Ошибка сохранения', 'error');
});
});
function escapeHtml(s){
if(!s) return '';
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// Load categories
fetch('/api/bot/kb').then(function(r){ return r.json() }).then(function(data){
// Extract unique categories
var catSet = {};
data.forEach(function(c){
if(c.category_id) catSet[c.category_id] = true;
});
var catSelect = document.getElementById('kb-category-id');
var catFilter = document.getElementById('kb-category-filter');
var catOptions = {
1: 'О компании', 2: 'Аудит безопасности', 3: 'Сервисное обслуживание (SLA)',
4: 'Реагирование на инциденты', 5: 'Технический надзор',
6: 'Документация и нормативы', 7: 'Риск-инжиниринг', 8: 'Отрасли и кейсы'
};
categories = catOptions;
Object.keys(catOptions).forEach(function(id){
var opt1 = document.createElement('option');
opt1.value = id; opt1.textContent = catOptions[id];
catSelect.appendChild(opt1);
var opt2 = document.createElement('option');
opt2.value = id; opt2.textContent = catOptions[id];
catFilter.appendChild(opt2);
});
});
loadKB();
</script>
{% endblock %}