v1.8.2: AGENTS.md, consent revocation, delete bot users, dialogues fix, intent improvements, CI for max_bot
This commit is contained in:
@@ -1,5 +1,48 @@
|
||||
# Changelog — AegisOne Service Portal
|
||||
|
||||
## 1.8.2 (02.06.2026)
|
||||
### Новые функции
|
||||
- **AGENTS.md:** правила проекта для AI-агентов (корень репозитория)
|
||||
- **Отзыв согласия:** handle_revoke_consent(), паттерн «отозвать согласие» в handle_message
|
||||
- **Удаление бот-пользователей:** DELETE /api/bot/users/{user_id} (каскад: user + conversations + messages)
|
||||
- **Кнопка удаления:** bot_consent.html — колонка «Действия» (только owner)
|
||||
- **consent_revoked_date:** поле в BotUser для фиксации даты отзыва согласия
|
||||
### Исправления
|
||||
- **Диалоги:** одна переписка = одна карточка (убрано создание new conv при completed/escalated)
|
||||
- **Analyze intent:** агрессивное определение ticket по ключевым словам услуг
|
||||
- **Clarify intent:** ограничение 1 раз за диалог, маркерные слова пропускают уточнение
|
||||
- **History context:** фильтр по времени (10 мин) вместо лимита 10 сообщений
|
||||
- **Быстрые правила:** ticket-паттерны и приветствие до NLP
|
||||
|
||||
## 1.8.1 (01.06.2026)
|
||||
### Новые функции
|
||||
- **YandexGPT-only NLP:** удалены Groq/Gemini, YandexGPT единственный провайдер
|
||||
- **Unknown questions:** BotUnknownQuestion, автогенерация KB-карточки при 3+ уникальных пользователях
|
||||
- **Callback dedup:** processed_callbacks set в main.py — пропуск дублей callback_id
|
||||
- **Consent flow:** split-based word matching, refuse words до quick_intent, operator handler
|
||||
- **DELETE conversations:** каскадное удаление диалогов (BotMessage + BotProcessingLog + BotConversation)
|
||||
- **UTC+3:** moscow_now() в greeting.py и inquiry.py
|
||||
- **Bot tickets:** contact_name, contact_phone, contact_email в BotTicket
|
||||
### Исправления
|
||||
- **Plain text phones:** MAX Platform не рендерит markdown-ссылки — телефоны/emailplain text
|
||||
- **Webhook secret:** _verify_secret() возвращает True если заголовок пуст
|
||||
- **Nginx:** добавлен /webhook proxy на HTTP порт 80
|
||||
- **Payload parsing:** message_created из body.message.body.text, message_callback из body.callback.payload
|
||||
|
||||
## 1.8.0 (01.06.2026)
|
||||
### Новые функции
|
||||
- **Consent flow:**.keyboard consent_keyboard(), contact_keyboard(), return_to_consent_keyboard()
|
||||
- **Escape hatch:** locked states проверяют analyze_intent — «question» → analyze_and_respond
|
||||
- **Completed state:** completed/escalated → новый диалог + analyze_and_respond
|
||||
- **handle_greeting fix:** убран analyze_and_respond после приветствия, state=awaiting_input
|
||||
- **Auto-detect sidebar:** parse_sidebar_menu() определяет пункты меню через API
|
||||
- **role_settings:** парсинг sidebar menu из HTML
|
||||
### Исправления
|
||||
- **handle_message:**拒绝 слова проверяются ДО analyze_intent в состояниях consent
|
||||
- **operator_words:** «оператор» → contacts вместо analyze_intent
|
||||
- **handle_manual_contact:** refuse words → consent_refused вместо «введите корректный номер»
|
||||
- **clarify_intent:** temperature 0.1, пост-обработка удаление приветствий, anti-AI-signs
|
||||
|
||||
## 1.7.1 (29.05.2026)
|
||||
### Исправления
|
||||
- **bot_test.html:** исправлен ключ localStorage для темы, var(--bg-page)→var(--bg-body), стилизация скроллбара
|
||||
|
||||
@@ -1434,3 +1434,8 @@ async def proxy_delete_conversation(conv_id: int, request: Request, user: dict =
|
||||
return await _proxy("DELETE", f"/api/bot/conversations/{conv_id}")
|
||||
|
||||
|
||||
@router.delete("/api/bot/users/{user_id}")
|
||||
async def proxy_delete_bot_user(user_id: int, request: Request, user: dict = Depends(require_role("owner"))):
|
||||
return await _proxy("DELETE", f"/api/bot/users/{user_id}")
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "page.html" %}
|
||||
{% block page_content %}
|
||||
<script>var USER_ROLE = '{{ user.role }}';</script>
|
||||
<style>
|
||||
.consent-table { width:100%; border-collapse:collapse; }
|
||||
.consent-table th, .consent-table td { padding:10px 14px; text-align:left; border-bottom:1px solid var(--border); font-size:13px; }
|
||||
@@ -53,10 +54,11 @@
|
||||
<th>Username</th>
|
||||
<th>Согласие</th>
|
||||
<th>Дата</th>
|
||||
{% if user.role == 'owner' %}<th>Действия</th>{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="consent-table-body">
|
||||
<tr><td colspan="8" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
|
||||
<tr><td colspan="{% if user.role == 'owner' %}9{% else %}8{% endif %}" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -98,7 +100,8 @@ var confirmSeconds = 10;
|
||||
|
||||
function loadConsents(){
|
||||
var tbody = document.getElementById('consent-table-body');
|
||||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>';
|
||||
var colspan = USER_ROLE === 'owner' ? 9 : 8;
|
||||
tbody.innerHTML = '<tr><td colspan="' + colspan + '" style="text-align:center;padding:30px;color:var(--text-muted);">Загрузка...</td></tr>';
|
||||
fetch('/api/bot/users/consented').then(function(r){ return r.json() }).then(function(data){
|
||||
allConsents = data;
|
||||
document.getElementById('consent-count').textContent = data.length + ' пользователей';
|
||||
@@ -106,15 +109,16 @@ function loadConsents(){
|
||||
document.getElementById('send-btn').disabled = data.length === 0;
|
||||
renderConsents(data);
|
||||
}).catch(function(err){
|
||||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:30px;color:var(--text-muted);">Ошибка загрузки</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="' + colspan + '" style="text-align:center;padding:30px;color:var(--text-muted);">Ошибка загрузки</td></tr>';
|
||||
showNotification('Ошибка загрузки согласий', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function renderConsents(list){
|
||||
var tbody = document.getElementById('consent-table-body');
|
||||
var colspan = USER_ROLE === 'owner' ? 9 : 8;
|
||||
if(!list.length){
|
||||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:30px;color:var(--text-muted);">Нет данных</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="' + colspan + '" style="text-align:center;padding:30px;color:var(--text-muted);">Нет данных</td></tr>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
@@ -129,8 +133,11 @@ function renderConsents(list){
|
||||
'<td>' + escapeHtml(u.organization || '—') + '</td>' +
|
||||
'<td>' + escapeHtml(u.username || '—') + '</td>' +
|
||||
'<td class="consent-yes">Да</td>' +
|
||||
'<td style="font-size:12px;color:var(--text-muted);">' + dateStr + '</td>' +
|
||||
'</tr>';
|
||||
'<td style="font-size:12px;color:var(--text-muted);">' + dateStr + '</td>';
|
||||
if(USER_ROLE === 'owner'){
|
||||
html += '<td><button class="btn btn-danger btn-sm" onclick="confirmDeleteUser(' + u.id + ', \'' + escapeHtml(name).replace(/'/g, "\\'") + '\')" title="Удалить пользователя">🗑</button></td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
@@ -215,6 +222,25 @@ function escapeHtml(s){
|
||||
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function confirmDeleteUser(userId, userName){
|
||||
if(!confirm('Удалить пользователя ' + userName + ' (ID: ' + userId + ')?\n\nВсе его диалоги и сообщения будут удалены навсегда. Заявки останутся.')) return;
|
||||
fetch('/api/bot/users/' + userId, { method: 'DELETE' })
|
||||
.then(function(r){ return r.json() })
|
||||
.then(function(d){
|
||||
if(d.ok){
|
||||
showNotification('Пользователь ' + userName + ' удалён', 'success');
|
||||
allConsents = allConsents.filter(function(u){ return u.id !== userId; });
|
||||
renderConsents(allConsents);
|
||||
document.getElementById('consent-count').textContent = allConsents.length + ' пользователей';
|
||||
document.getElementById('recipient-count').textContent = 'Получателей: ' + allConsents.length;
|
||||
} else {
|
||||
showNotification('Ошибка: ' + (d.error || 'неизвестная'), 'error');
|
||||
}
|
||||
}).catch(function(){
|
||||
showNotification('Ошибка соединения', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
loadConsents();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1 +1 @@
|
||||
1.7.1
|
||||
1.8.2
|
||||
Reference in New Issue
Block a user