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:
2026-05-29 02:30:30 +03:00
parent 493e0b37a1
commit 72b6879f4b
234 changed files with 26768 additions and 6240 deletions
+8 -3
View File
@@ -5,15 +5,16 @@
<button class="btn btn-primary btn-sm" onclick="document.getElementById('modal-user').classList.add('open')">+ Добавить</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>Логин</th><th>ФИО</th><th>Роль</th><th>Телефон</th><th>Email</th><th>Статус</th><th>Создан</th><th class="actions"></th></tr></thead>
<table><thead><tr><th>Логин</th><th>ФИО</th><th>Роль</th><th>Телефон</th><th>Email</th><th>MAX ID</th><th>Статус</th><th>Создан</th><th class="actions"></th></tr></thead>
<tbody>{% for u in users %}<tr>
<td>{{ u.login }}</td><td>{{ u.full_name }}</td>
<td><span class="badge badge-{{ u.role }}">{{ {'owner':'Владелец','engineer':'Инженер','technician':'Техник'}[u.role] }}</span></td>
<td>{{ u.phone }}</td><td>{{ u.email }}</td>
<td>{{ u.max_user_id or '—' }}</td>
<td><span class="badge badge-{{ 'active' if u.is_active else 'inactive' }}">{{ 'Активен' if u.is_active else 'Неактивен' }}</span></td>
<td>{{ u.created_at.strftime('%d.%m.%Y') if u.created_at else '' }}</td>
<td class="actions">
<button class="btn btn-secondary btn-sm" onclick="editUser({{ u.id }},'{{ u.login }}','{{ u.full_name }}','{{ u.role }}','{{ u.phone }}','{{ u.email }}',{{ 'true' if u.is_active else 'false' }})"></button>
<button class="btn btn-secondary btn-sm" onclick="editUser({{ u.id }},'{{ u.login }}','{{ u.full_name }}','{{ u.role }}','{{ u.phone }}','{{ u.email }}','{{ u.max_user_id or '' }}',{{ 'true' if u.is_active else 'false' }})"></button>
{% if u.role != 'owner' %}
<form method="post" action="/service/users/delete" style="display:inline" onsubmit="return confirm('Удалить пользователя?')">
<input type="hidden" name="id" value="{{ u.id }}">
@@ -41,6 +42,9 @@
<div class="form-group"><label>Телефон</label><input type="text" name="phone" id="user-phone"></div>
<div class="form-group"><label>Email</label><input type="email" name="email" id="user-email"></div>
</div>
<div class="form-row">
<div class="form-group"><label>MAX ID (мессенджер)</label><input type="number" name="max_user_id" id="user-max_id" placeholder="ID пользователя в MAX"></div>
</div>
<div class="form-check"><input type="checkbox" name="is_active" id="user-is_active" value="1" checked><label for="user-is_active">Активен</label></div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Сохранить</button>
@@ -50,7 +54,7 @@
</div>
</div>
<script>
function editUser(id,login,name,role,phone,email,active){
function editUser(id,login,name,role,phone,email,maxId,active){
document.getElementById('modal-user').classList.add('open');
document.getElementById('modal-user-title').textContent = 'Редактировать сотрудника';
document.getElementById('modal-user-form').action = '/service/users/edit';
@@ -59,6 +63,7 @@ function editUser(id,login,name,role,phone,email,active){
document.getElementById('user-full_name').value = name;
document.getElementById('user-role').value = role;
document.getElementById('user-phone').value = phone; document.getElementById('user-email').value = email;
document.getElementById('user-max_id').value = maxId || '';
document.getElementById('user-is_active').checked = active;
document.getElementById('user-password').required = false;
}