v1.7.0: refactor max_bot to flat structure, add VCF+UserModel+NLP history context, portal pages and proxy fixes
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { font-family:'Inter','Segoe UI',sans-serif; background:#1B2838; color:#E8EDF2; font-size:13px; height:100vh; display:flex; flex-direction:column; }
|
||||
.test-container { display:flex; flex:1; gap:12px; padding:12px; overflow:hidden; }
|
||||
.test-chat { flex:1; border:1px solid #253248; border-radius:10px; display:flex; flex-direction:column; overflow:hidden; }
|
||||
.test-chat-header { background:#0A1628; padding:10px 14px; font-weight:600; border-bottom:1px solid #253248; font-size:12px; }
|
||||
.test-chat-messages { flex:1; overflow-y:auto; padding:10px 14px; }
|
||||
.test-chat-input { display:flex; border-top:1px solid #253248; }
|
||||
.test-chat-input input { flex:1; border:none; padding:10px 14px; font-size:13px; outline:none; background:#1B2838; color:#E8EDF2; }
|
||||
.test-chat-input button { padding:10px 16px; border:none; background:#00ADEF; color:#fff; cursor:pointer; font-weight:600; }
|
||||
.test-log { width:300px; border:1px solid #253248; border-radius:10px; padding:10px; overflow-y:auto; font-family:monospace; font-size:11px; line-height:1.5; background:#0A1628; }
|
||||
.test-log .log-entry { margin-bottom:4px; padding:3px 0; border-bottom:1px solid #1e3044; }
|
||||
.test-log .log-entry .time { color:#6B8299; }
|
||||
.test-log .log-entry .event { color:#00ADEF; font-weight:600; }
|
||||
.msg-bot { background:#1e3044; padding:7px 12px; border-radius:8px 8px 8px 0; margin-bottom:6px; max-width:85%; }
|
||||
.msg-user { background:#00ADEF; color:#fff; padding:7px 12px; border-radius:8px 8px 0 8px; margin-bottom:6px; max-width:85%; margin-left:auto; }
|
||||
.test-scenarios { display:flex; gap:6px; padding:8px 12px; flex-wrap:wrap; border-bottom:1px solid #253248; }
|
||||
.test-scenarios button { font-size:11px; padding:4px 10px; background:#253248; color:#E8EDF2; border:1px solid #3a4a60; border-radius:4px; cursor:pointer; }
|
||||
.test-scenarios button:hover { background:#00ADEF; color:#fff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-scenarios">
|
||||
<button onclick="runScenario('greeting')">Приветствие</button>
|
||||
<button onclick="runScenario('consent_yes')">Согласие → заявка</button>
|
||||
<button onclick="runScenario('consent_no')">Отказ согласия</button>
|
||||
<button onclick="runScenario('question')">Общий вопрос</button>
|
||||
<button onclick="clearTest()">Очистить</button>
|
||||
</div>
|
||||
<div class="test-container">
|
||||
<div class="test-chat">
|
||||
<div class="test-chat-header">Чат с ботом (симуляция)</div>
|
||||
<div class="test-chat-messages" id="test-messages"></div>
|
||||
<div class="test-chat-input">
|
||||
<input type="text" id="test-input" placeholder="Введите сообщение..." onkeypress="if(event.key==='Enter') sendTestMessage()">
|
||||
<button onclick="sendTestMessage()">Отправить</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-log" id="test-log"></div>
|
||||
</div>
|
||||
<script>
|
||||
var testUserId = 999999001;
|
||||
function log(event, detail){
|
||||
var logDiv = document.getElementById('test-log');
|
||||
var time = new Date().toLocaleTimeString();
|
||||
var entry = document.createElement('div');
|
||||
entry.className = 'log-entry';
|
||||
entry.innerHTML = '<span class="time">[' + time + ']</span> <span class="event">' + event + '</span> ' + (detail || '');
|
||||
logDiv.appendChild(entry);
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
}
|
||||
function addMessage(text, from){
|
||||
var msgs = document.getElementById('test-messages');
|
||||
var div = document.createElement('div');
|
||||
div.className = from === 'bot' ? 'msg-bot' : 'msg-user';
|
||||
div.textContent = text;
|
||||
msgs.appendChild(div);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
}
|
||||
function sendTestMessage(){
|
||||
var input = document.getElementById('test-input');
|
||||
var text = input.value.trim();
|
||||
if(!text) return;
|
||||
input.value = '';
|
||||
addMessage(text, 'user');
|
||||
log('message_created', 'Пользователь: ' + text);
|
||||
simulateWebhook('message_created', text);
|
||||
}
|
||||
function runScenario(scenario){
|
||||
clearTest();
|
||||
switch(scenario){
|
||||
case 'greeting':
|
||||
log('bot_started', 'Пользователь начал диалог');
|
||||
simulateWebhook('bot_started', null);
|
||||
break;
|
||||
case 'consent_yes':
|
||||
log('bot_started', 'Пользователь начал диалог');
|
||||
simulateWebhook('bot_started', null);
|
||||
setTimeout(function(){
|
||||
addMessage('Здравствуйте! Я хочу оставить заявку.', 'user');
|
||||
log('message_created', 'Пользователь: хочу оставить заявку');
|
||||
simulateWebhook('message_created', 'Здравствуйте! Я хочу оставить заявку.');
|
||||
}, 1000);
|
||||
break;
|
||||
case 'consent_no':
|
||||
log('bot_started', 'Пользователь начал диалог');
|
||||
simulateWebhook('bot_started', null);
|
||||
break;
|
||||
case 'question':
|
||||
log('bot_started', 'Пользователь начал диалог');
|
||||
simulateWebhook('bot_started', null);
|
||||
setTimeout(function(){
|
||||
addMessage('Какие услуги вы предоставляете?', 'user');
|
||||
log('message_created', 'Пользователь: какие услуги?');
|
||||
simulateWebhook('message_created', 'Какие услуги вы предоставляете?');
|
||||
}, 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
function simulateWebhook(type, text){
|
||||
var payload = {update_type: type};
|
||||
if(type === 'bot_started'){
|
||||
payload.user = {user_id: testUserId, first_name: 'Тест', last_name: 'Пользователь'};
|
||||
payload.chat_id = testUserId;
|
||||
} else if(type === 'message_created'){
|
||||
payload.sender = {user_id: testUserId};
|
||||
payload.chat_id = testUserId;
|
||||
payload.body = {text: text, attachments: []};
|
||||
}
|
||||
log('webhook', 'Отправка на /webhook (' + type + ')');
|
||||
fetch('/api/bot/webhook', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Max-Bot-Api-Secret': 'aegisone_bot_secret_2026',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function(r){ return r.json() }).then(function(d){
|
||||
log('response', JSON.stringify(d));
|
||||
if(d && d.ok) log('success', 'Событие обработано');
|
||||
else log('error', 'Ошибка: ' + JSON.stringify(d));
|
||||
}).catch(function(err){
|
||||
log('error', 'Сетевая ошибка: ' + err);
|
||||
});
|
||||
}
|
||||
function clearTest(){
|
||||
document.getElementById('test-messages').innerHTML = '';
|
||||
document.getElementById('test-log').innerHTML = '';
|
||||
document.getElementById('test-input').value = '';
|
||||
log('info', 'Тест очищен');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user