smart-bot-factory 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of smart-bot-factory might be problematic. Click here for more details.
- smart_bot_factory/admin/admin_events.py +420 -245
- smart_bot_factory/admin/admin_logic.py +5 -0
- smart_bot_factory/core/bot_utils.py +34 -9
- smart_bot_factory/core/decorators.py +41 -26
- smart_bot_factory/core/message_sender.py +11 -2
- smart_bot_factory/core/router.py +59 -8
- smart_bot_factory/handlers/handlers.py +25 -6
- smart_bot_factory/integrations/supabase_client.py +32 -23
- smart_bot_factory/utils/prompt_loader.py +12 -8
- {smart_bot_factory-0.3.3.dist-info → smart_bot_factory-0.3.5.dist-info}/METADATA +1 -2
- {smart_bot_factory-0.3.3.dist-info → smart_bot_factory-0.3.5.dist-info}/RECORD +14 -14
- {smart_bot_factory-0.3.3.dist-info → smart_bot_factory-0.3.5.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.3.dist-info → smart_bot_factory-0.3.5.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.3.dist-info → smart_bot_factory-0.3.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -29,6 +29,11 @@ async def cancel_handler(message: Message, state: FSMContext):
|
|
|
29
29
|
# Получаем текущий state
|
|
30
30
|
current_state = await state.get_state()
|
|
31
31
|
|
|
32
|
+
# Очищаем временные файлы если это создание события
|
|
33
|
+
if current_state and current_state.startswith('AdminStates:create_event'):
|
|
34
|
+
from .admin_events import cleanup_temp_files
|
|
35
|
+
await cleanup_temp_files(state)
|
|
36
|
+
|
|
32
37
|
# Очищаем state
|
|
33
38
|
await state.clear()
|
|
34
39
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import re
|
|
5
4
|
from datetime import datetime
|
|
6
5
|
from aiogram import Router
|
|
7
6
|
from aiogram.filters import Command
|
|
@@ -170,8 +169,13 @@ def parse_ai_response_method2(ai_response: str) -> tuple[str, dict]:
|
|
|
170
169
|
logger.warning(f"Ошибка резервного метода: {e}")
|
|
171
170
|
return ai_response, {}
|
|
172
171
|
|
|
173
|
-
async def process_events(session_id: str, events: list, user_id: int):
|
|
174
|
-
"""
|
|
172
|
+
async def process_events(session_id: str, events: list, user_id: int) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Обрабатывает события из ответа ИИ
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
bool: True если нужно отправить сообщение от ИИ, False если не нужно
|
|
178
|
+
"""
|
|
175
179
|
|
|
176
180
|
# Проверяем кастомный процессор
|
|
177
181
|
custom_processor = get_global_var('custom_event_processor')
|
|
@@ -180,11 +184,14 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
180
184
|
# Используем кастомную функцию обработки событий
|
|
181
185
|
logger.info(f"🔄 Используется кастомная обработка событий: {custom_processor.__name__}")
|
|
182
186
|
await custom_processor(session_id, events, user_id)
|
|
183
|
-
return
|
|
187
|
+
return True # По умолчанию отправляем сообщение
|
|
184
188
|
|
|
185
189
|
# Стандартная обработка
|
|
186
190
|
supabase_client = get_global_var('supabase_client')
|
|
187
191
|
|
|
192
|
+
# Флаг для отслеживания, нужно ли отправлять сообщение от ИИ
|
|
193
|
+
should_send_ai_response = True
|
|
194
|
+
|
|
188
195
|
for event in events:
|
|
189
196
|
try:
|
|
190
197
|
event_type = event.get('тип', '')
|
|
@@ -200,7 +207,6 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
200
207
|
|
|
201
208
|
# Определяем категорию события и сохраняем в БД
|
|
202
209
|
event_id = None
|
|
203
|
-
should_execute_immediately = False
|
|
204
210
|
should_notify = False
|
|
205
211
|
|
|
206
212
|
try:
|
|
@@ -228,8 +234,14 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
228
234
|
|
|
229
235
|
event_handler_info = event_handlers.get(event_type, {})
|
|
230
236
|
once_only = event_handler_info.get('once_only', True)
|
|
237
|
+
send_ai_response_flag = event_handler_info.get('send_ai_response', True)
|
|
231
238
|
|
|
232
|
-
logger.info(f" 🔍 Обработчик '{event_type}': once_only={once_only}")
|
|
239
|
+
logger.info(f" 🔍 Обработчик '{event_type}': once_only={once_only}, send_ai_response={send_ai_response_flag}")
|
|
240
|
+
|
|
241
|
+
# Проверяем флаг send_ai_response ИЗ ДЕКОРАТОРА
|
|
242
|
+
if not send_ai_response_flag:
|
|
243
|
+
should_send_ai_response = False
|
|
244
|
+
logger.warning(f" 🔇🔇🔇 ОБРАБОТЧИК '{event_type}' ЗАПРЕТИЛ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇")
|
|
233
245
|
|
|
234
246
|
# Если once_only=True - проверяем в БД наличие выполненных событий
|
|
235
247
|
if once_only:
|
|
@@ -256,7 +268,7 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
256
268
|
logger.info(f" 🎯 Немедленно выполняем user_event: '{event_type}'")
|
|
257
269
|
|
|
258
270
|
try:
|
|
259
|
-
# Выполняем
|
|
271
|
+
# Выполняем событие
|
|
260
272
|
result = await execute_event_handler(event_type, user_id, event_info)
|
|
261
273
|
|
|
262
274
|
# Сохраняем в БД УЖЕ со статусом completed (избегаем дублирования)
|
|
@@ -275,7 +287,6 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
275
287
|
event_id = response.data[0]['id']
|
|
276
288
|
|
|
277
289
|
should_notify = event_handler_info.get('notify', False)
|
|
278
|
-
should_execute_immediately = True
|
|
279
290
|
|
|
280
291
|
logger.info(f" ✅ Событие {event_id} выполнено и сохранено как completed")
|
|
281
292
|
|
|
@@ -298,8 +309,18 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
298
309
|
# Если не user_event, пробуем как запланированную задачу
|
|
299
310
|
elif event_type in scheduled_tasks:
|
|
300
311
|
try:
|
|
312
|
+
# Достаем метаданные задачи
|
|
313
|
+
task_info = scheduled_tasks.get(event_type, {})
|
|
314
|
+
send_ai_response_flag = task_info.get('send_ai_response', True)
|
|
315
|
+
|
|
316
|
+
logger.info(f" ⏰ Планируем scheduled_task: '{event_type}', send_ai_response={send_ai_response_flag}")
|
|
317
|
+
|
|
318
|
+
# Проверяем флаг send_ai_response ИЗ ДЕКОРАТОРА
|
|
319
|
+
if not send_ai_response_flag:
|
|
320
|
+
should_send_ai_response = False
|
|
321
|
+
logger.warning(f" 🔇🔇🔇 ЗАДАЧА '{event_type}' ЗАПРЕТИЛА ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇")
|
|
322
|
+
|
|
301
323
|
# Используем новую логику - время берется из декоратора
|
|
302
|
-
logger.info(f" ⏰ Планируем scheduled_task: '{event_type}' с данными: '{event_info}'")
|
|
303
324
|
result = await execute_scheduled_task_from_event(user_id, event_type, event_info, session_id)
|
|
304
325
|
event_id = result.get('event_id', 'unknown')
|
|
305
326
|
should_notify = result.get('notify', False)
|
|
@@ -354,6 +375,10 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
354
375
|
except Exception as e:
|
|
355
376
|
logger.error(f"❌ Ошибка обработки события {event}: {e}")
|
|
356
377
|
logger.exception("Стек ошибки:")
|
|
378
|
+
|
|
379
|
+
# Возвращаем флаг, нужно ли отправлять сообщение от ИИ
|
|
380
|
+
logger.warning(f"🔊🔊🔊 ИТОГОВЫЙ ФЛАГ send_ai_response: {should_send_ai_response} 🔊🔊🔊")
|
|
381
|
+
return should_send_ai_response
|
|
357
382
|
|
|
358
383
|
async def notify_admins_about_event(user_id: int, event: dict):
|
|
359
384
|
"""Отправляем уведомление админам о событии с явным указанием ID пользователя"""
|
|
@@ -377,7 +377,7 @@ _global_handlers: Dict[str, Dict[str, Any]] = {}
|
|
|
377
377
|
# Глобальный менеджер роутеров
|
|
378
378
|
_router_manager = None
|
|
379
379
|
|
|
380
|
-
def event_handler(event_type: str, notify: bool = False, once_only: bool = True):
|
|
380
|
+
def event_handler(event_type: str, notify: bool = False, once_only: bool = True, send_ai_response: bool = True):
|
|
381
381
|
"""
|
|
382
382
|
Декоратор для регистрации обработчика события
|
|
383
383
|
|
|
@@ -385,18 +385,19 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
385
385
|
event_type: Тип события (например, 'appointment_booking', 'phone_collection')
|
|
386
386
|
notify: Уведомлять ли админов о выполнении события (по умолчанию False)
|
|
387
387
|
once_only: Обрабатывать ли событие только один раз (по умолчанию True)
|
|
388
|
+
send_ai_response: Отправлять ли сообщение от ИИ после обработки события (по умолчанию True)
|
|
388
389
|
|
|
389
390
|
Example:
|
|
390
|
-
# Обработчик
|
|
391
|
+
# Обработчик с отправкой сообщения от ИИ
|
|
391
392
|
@event_handler("appointment_booking", notify=True)
|
|
392
393
|
async def book_appointment(user_id: int, appointment_data: dict):
|
|
393
394
|
# Логика записи на прием
|
|
394
395
|
return {"status": "success", "appointment_id": "123"}
|
|
395
396
|
|
|
396
|
-
# Обработчик
|
|
397
|
-
@event_handler("phone_collection", once_only=False)
|
|
397
|
+
# Обработчик БЕЗ отправки сообщения от ИИ
|
|
398
|
+
@event_handler("phone_collection", once_only=False, send_ai_response=False)
|
|
398
399
|
async def collect_phone(user_id: int, phone_data: dict):
|
|
399
|
-
# Логика сбора телефона
|
|
400
|
+
# Логика сбора телефона - ИИ не отправит сообщение
|
|
400
401
|
return {"status": "phone_collected"}
|
|
401
402
|
"""
|
|
402
403
|
def decorator(func: Callable) -> Callable:
|
|
@@ -404,7 +405,8 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
404
405
|
'handler': func,
|
|
405
406
|
'name': func.__name__,
|
|
406
407
|
'notify': notify,
|
|
407
|
-
'once_only': once_only
|
|
408
|
+
'once_only': once_only,
|
|
409
|
+
'send_ai_response': send_ai_response
|
|
408
410
|
}
|
|
409
411
|
|
|
410
412
|
logger.info(f"📝 Зарегистрирован обработчик события '{event_type}': {func.__name__}")
|
|
@@ -416,15 +418,17 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
416
418
|
result = await func(*args, **kwargs)
|
|
417
419
|
logger.info(f"✅ Обработчик '{event_type}' выполнен успешно")
|
|
418
420
|
|
|
419
|
-
# Автоматически добавляем
|
|
421
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
420
422
|
if isinstance(result, dict):
|
|
421
423
|
result['notify'] = notify
|
|
424
|
+
result['send_ai_response'] = send_ai_response
|
|
422
425
|
else:
|
|
423
426
|
# Если результат не словарь, создаем словарь
|
|
424
427
|
result = {
|
|
425
428
|
'status': 'success',
|
|
426
429
|
'result': result,
|
|
427
|
-
'notify': notify
|
|
430
|
+
'notify': notify,
|
|
431
|
+
'send_ai_response': send_ai_response
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
return result
|
|
@@ -435,7 +439,7 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
435
439
|
return wrapper
|
|
436
440
|
return decorator
|
|
437
441
|
|
|
438
|
-
def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
442
|
+
def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
439
443
|
"""
|
|
440
444
|
Декоратор для регистрации задачи, которую можно запланировать на время
|
|
441
445
|
|
|
@@ -448,6 +452,7 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
448
452
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
449
453
|
- str: Тип события для поиска в БД (например, 'appointment_booking')
|
|
450
454
|
- Callable: Функция для получения datetime (например, async def(user_id, user_data) -> datetime)
|
|
455
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения задачи (по умолчанию True)
|
|
451
456
|
|
|
452
457
|
Example:
|
|
453
458
|
# Обычная задача с фиксированным временем
|
|
@@ -509,7 +514,8 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
509
514
|
'smart_check': smart_check,
|
|
510
515
|
'once_only': once_only,
|
|
511
516
|
'default_delay': default_delay_seconds,
|
|
512
|
-
'event_type': event_type # Новое поле для типа события
|
|
517
|
+
'event_type': event_type, # Новое поле для типа события
|
|
518
|
+
'send_ai_response': send_ai_response
|
|
513
519
|
}
|
|
514
520
|
|
|
515
521
|
if event_type:
|
|
@@ -524,15 +530,17 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
524
530
|
result = await func(*args, **kwargs)
|
|
525
531
|
logger.info(f"✅ Задача '{task_name}' выполнена успешно")
|
|
526
532
|
|
|
527
|
-
# Автоматически добавляем
|
|
533
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
528
534
|
if isinstance(result, dict):
|
|
529
535
|
result['notify'] = notify
|
|
536
|
+
result['send_ai_response'] = send_ai_response
|
|
530
537
|
else:
|
|
531
538
|
# Если результат не словарь, создаем словарь
|
|
532
539
|
result = {
|
|
533
540
|
'status': 'success',
|
|
534
541
|
'result': result,
|
|
535
|
-
'notify': notify
|
|
542
|
+
'notify': notify,
|
|
543
|
+
'send_ai_response': send_ai_response
|
|
536
544
|
}
|
|
537
545
|
|
|
538
546
|
return result
|
|
@@ -543,7 +551,7 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
543
551
|
return wrapper
|
|
544
552
|
return decorator
|
|
545
553
|
|
|
546
|
-
def global_handler(handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
554
|
+
def global_handler(handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
547
555
|
"""
|
|
548
556
|
Декоратор для регистрации глобального обработчика (для всех пользователей)
|
|
549
557
|
|
|
@@ -555,6 +563,7 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
555
563
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
556
564
|
- str: Тип события для поиска в БД
|
|
557
565
|
- Callable: Функция для получения datetime (например, async def(handler_data: str) -> datetime)
|
|
566
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения обработчика (по умолчанию True)
|
|
558
567
|
|
|
559
568
|
Example:
|
|
560
569
|
# Глобальный обработчик с задержкой
|
|
@@ -610,7 +619,8 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
610
619
|
'notify': notify,
|
|
611
620
|
'once_only': once_only,
|
|
612
621
|
'default_delay': default_delay_seconds,
|
|
613
|
-
'event_type': event_type # Добавляем event_type для глобальных обработчиков
|
|
622
|
+
'event_type': event_type, # Добавляем event_type для глобальных обработчиков
|
|
623
|
+
'send_ai_response': send_ai_response
|
|
614
624
|
}
|
|
615
625
|
|
|
616
626
|
logger.info(f"🌍 Зарегистрирован глобальный обработчик '{handler_type}': {func.__name__}")
|
|
@@ -622,15 +632,17 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
622
632
|
result = await func(*args, **kwargs)
|
|
623
633
|
logger.info(f"✅ Глобальный обработчик '{handler_type}' выполнен успешно")
|
|
624
634
|
|
|
625
|
-
# Автоматически добавляем
|
|
635
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
626
636
|
if isinstance(result, dict):
|
|
627
637
|
result['notify'] = notify
|
|
638
|
+
result['send_ai_response'] = send_ai_response
|
|
628
639
|
else:
|
|
629
640
|
# Если результат не словарь, создаем словарь
|
|
630
641
|
result = {
|
|
631
642
|
'status': 'success',
|
|
632
643
|
'result': result,
|
|
633
|
-
'notify': notify
|
|
644
|
+
'notify': notify,
|
|
645
|
+
'send_ai_response': send_ai_response
|
|
634
646
|
}
|
|
635
647
|
|
|
636
648
|
return result
|
|
@@ -1747,15 +1759,15 @@ async def process_admin_event(event: Dict):
|
|
|
1747
1759
|
for file_info in files_metadata:
|
|
1748
1760
|
try:
|
|
1749
1761
|
file_bytes = await supabase_client.download_event_file(
|
|
1750
|
-
|
|
1751
|
-
|
|
1762
|
+
event_id=event_id,
|
|
1763
|
+
storage_path=file_info['storage_path']
|
|
1752
1764
|
)
|
|
1753
1765
|
|
|
1754
1766
|
# Сохраняем в соответствующую папку
|
|
1755
1767
|
if file_info['stage'] == 'with_message':
|
|
1756
|
-
file_path = temp_with_msg / file_info['
|
|
1768
|
+
file_path = temp_with_msg / file_info['original_name']
|
|
1757
1769
|
else:
|
|
1758
|
-
file_path = temp_after_msg / file_info['
|
|
1770
|
+
file_path = temp_after_msg / file_info['original_name']
|
|
1759
1771
|
|
|
1760
1772
|
with open(file_path, 'wb') as f:
|
|
1761
1773
|
f.write(file_bytes)
|
|
@@ -1794,8 +1806,11 @@ async def process_admin_event(event: Dict):
|
|
|
1794
1806
|
media_group = []
|
|
1795
1807
|
first_file = True
|
|
1796
1808
|
|
|
1797
|
-
|
|
1798
|
-
|
|
1809
|
+
# Сортируем файлы по порядку
|
|
1810
|
+
sorted_files = sorted(files_with_msg, key=lambda x: x.get('order', 0))
|
|
1811
|
+
|
|
1812
|
+
for file_info in sorted_files:
|
|
1813
|
+
file_path = temp_with_msg / file_info['original_name']
|
|
1799
1814
|
|
|
1800
1815
|
if file_info['type'] == 'photo':
|
|
1801
1816
|
media = InputMediaPhoto(
|
|
@@ -1824,7 +1839,7 @@ async def process_admin_event(event: Dict):
|
|
|
1824
1839
|
files_after = [f for f in files_metadata if f['stage'] == 'after_message']
|
|
1825
1840
|
|
|
1826
1841
|
for file_info in files_after:
|
|
1827
|
-
file_path = temp_after_msg / file_info['
|
|
1842
|
+
file_path = temp_after_msg / file_info['original_name']
|
|
1828
1843
|
|
|
1829
1844
|
if file_info['type'] == 'document':
|
|
1830
1845
|
await bot.send_document(chat_id=telegram_id, document=FSInputFile(file_path))
|
|
@@ -1848,10 +1863,10 @@ async def process_admin_event(event: Dict):
|
|
|
1848
1863
|
shutil.rmtree(temp_after_msg, ignore_errors=True)
|
|
1849
1864
|
logger.info("🗑️ Временные папки очищены")
|
|
1850
1865
|
|
|
1851
|
-
|
|
1866
|
+
# 4.2. Удаляем файлы из Supabase Storage
|
|
1852
1867
|
try:
|
|
1853
|
-
await supabase_client.delete_event_files(
|
|
1854
|
-
logger.info(f"🗑️ Файлы события '{
|
|
1868
|
+
await supabase_client.delete_event_files(event_id)
|
|
1869
|
+
logger.info(f"🗑️ Файлы события '{event_id}' удалены из Storage")
|
|
1855
1870
|
except Exception as e:
|
|
1856
1871
|
logger.error(f"❌ Ошибка удаления из Storage: {e}")
|
|
1857
1872
|
|
|
@@ -143,10 +143,10 @@ async def send_message_by_ai(
|
|
|
143
143
|
logger.info(f"✅ Этап и качество обновлены в БД")
|
|
144
144
|
|
|
145
145
|
# Обрабатываем события
|
|
146
|
-
events = ai_metadata.get('
|
|
146
|
+
events = ai_metadata.get('събития', [])
|
|
147
147
|
if events:
|
|
148
148
|
logger.info(f"🔔 Обрабатываем {len(events)} событий")
|
|
149
|
-
await process_events(session_id, events, user_id)
|
|
149
|
+
should_send_response = await process_events(session_id, events, user_id)
|
|
150
150
|
|
|
151
151
|
# Сохраняем ответ ассистента
|
|
152
152
|
await supabase_client.add_message(
|
|
@@ -165,6 +165,15 @@ async def send_message_by_ai(
|
|
|
165
165
|
else:
|
|
166
166
|
final_response = response_text
|
|
167
167
|
|
|
168
|
+
# Проверяем, нужно ли отправлять сообщение от ИИ
|
|
169
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
170
|
+
logger.info("🔇 События запретили отправку сообщения от ИИ (message_sender), пропускаем отправку")
|
|
171
|
+
return {
|
|
172
|
+
"status": "skipped",
|
|
173
|
+
"reason": "send_ai_response=False",
|
|
174
|
+
"user_id": user_id
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
# Отправляем ответ пользователю напрямую через бота
|
|
169
178
|
await bot.send_message(
|
|
170
179
|
chat_id=user_id,
|
smart_bot_factory/core/router.py
CHANGED
|
@@ -26,7 +26,7 @@ class EventRouter:
|
|
|
26
26
|
|
|
27
27
|
logger.info(f"🔄 Создан роутер: {self.name}")
|
|
28
28
|
|
|
29
|
-
def event_handler(self, event_type: str, notify: bool = False, once_only: bool = True):
|
|
29
|
+
def event_handler(self, event_type: str, notify: bool = False, once_only: bool = True, send_ai_response: bool = True):
|
|
30
30
|
"""
|
|
31
31
|
Декоратор для регистрации обработчика события в роутере
|
|
32
32
|
|
|
@@ -34,6 +34,7 @@ class EventRouter:
|
|
|
34
34
|
event_type: Тип события
|
|
35
35
|
notify: Уведомлять ли админов
|
|
36
36
|
once_only: Выполнять ли только один раз
|
|
37
|
+
send_ai_response: Отправлять ли сообщение от ИИ после обработки события (по умолчанию True)
|
|
37
38
|
"""
|
|
38
39
|
def decorator(func: Callable) -> Callable:
|
|
39
40
|
self._event_handlers[event_type] = {
|
|
@@ -41,6 +42,7 @@ class EventRouter:
|
|
|
41
42
|
'name': func.__name__,
|
|
42
43
|
'notify': notify,
|
|
43
44
|
'once_only': once_only,
|
|
45
|
+
'send_ai_response': send_ai_response,
|
|
44
46
|
'router': self.name
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -50,14 +52,29 @@ class EventRouter:
|
|
|
50
52
|
@wraps(func)
|
|
51
53
|
async def wrapper(*args, **kwargs):
|
|
52
54
|
try:
|
|
53
|
-
|
|
55
|
+
result = await func(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
58
|
+
if isinstance(result, dict):
|
|
59
|
+
result['notify'] = notify
|
|
60
|
+
result['send_ai_response'] = send_ai_response
|
|
61
|
+
else:
|
|
62
|
+
# Если результат не словарь, создаем словарь
|
|
63
|
+
result = {
|
|
64
|
+
'status': 'success',
|
|
65
|
+
'result': result,
|
|
66
|
+
'notify': notify,
|
|
67
|
+
'send_ai_response': send_ai_response
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result
|
|
54
71
|
except Exception as e:
|
|
55
72
|
logger.error(f"Ошибка выполнения обработчика '{event_type}' в роутере {self.name}: {e}")
|
|
56
73
|
raise
|
|
57
74
|
return wrapper
|
|
58
75
|
return decorator
|
|
59
76
|
|
|
60
|
-
def schedule_task(self, task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
77
|
+
def schedule_task(self, task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
61
78
|
"""
|
|
62
79
|
Декоратор для регистрации запланированной задачи в роутере
|
|
63
80
|
|
|
@@ -70,6 +87,7 @@ class EventRouter:
|
|
|
70
87
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
71
88
|
- str: Тип события для поиска в БД (например, 'appointment_booking')
|
|
72
89
|
- Callable: Функция async def(user_id, user_data) -> datetime
|
|
90
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения задачи (по умолчанию True)
|
|
73
91
|
"""
|
|
74
92
|
def decorator(func: Callable) -> Callable:
|
|
75
93
|
# Время ОБЯЗАТЕЛЬНО должно быть указано
|
|
@@ -98,7 +116,8 @@ class EventRouter:
|
|
|
98
116
|
'once_only': once_only,
|
|
99
117
|
'router': self.name,
|
|
100
118
|
'default_delay': default_delay_seconds,
|
|
101
|
-
'event_type': event_type # Новое поле для типа события
|
|
119
|
+
'event_type': event_type, # Новое поле для типа события
|
|
120
|
+
'send_ai_response': send_ai_response
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
if event_type:
|
|
@@ -110,14 +129,29 @@ class EventRouter:
|
|
|
110
129
|
@wraps(func)
|
|
111
130
|
async def wrapper(*args, **kwargs):
|
|
112
131
|
try:
|
|
113
|
-
|
|
132
|
+
result = await func(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
135
|
+
if isinstance(result, dict):
|
|
136
|
+
result['notify'] = notify
|
|
137
|
+
result['send_ai_response'] = send_ai_response
|
|
138
|
+
else:
|
|
139
|
+
# Если результат не словарь, создаем словарь
|
|
140
|
+
result = {
|
|
141
|
+
'status': 'success',
|
|
142
|
+
'result': result,
|
|
143
|
+
'notify': notify,
|
|
144
|
+
'send_ai_response': send_ai_response
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result
|
|
114
148
|
except Exception as e:
|
|
115
149
|
logger.error(f"Ошибка выполнения задачи '{task_name}' в роутере {self.name}: {e}")
|
|
116
150
|
raise
|
|
117
151
|
return wrapper
|
|
118
152
|
return decorator
|
|
119
153
|
|
|
120
|
-
def global_handler(self, handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
154
|
+
def global_handler(self, handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
121
155
|
"""
|
|
122
156
|
Декоратор для регистрации глобального обработчика в роутере
|
|
123
157
|
|
|
@@ -126,6 +160,7 @@ class EventRouter:
|
|
|
126
160
|
notify: Уведомлять ли админов
|
|
127
161
|
once_only: Выполнять ли только один раз
|
|
128
162
|
delay: Время задержки в удобном формате (например, "1h 30m", "45m", 3600) - ОБЯЗАТЕЛЬНО
|
|
163
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения обработчика (по умолчанию True)
|
|
129
164
|
"""
|
|
130
165
|
def decorator(func: Callable) -> Callable:
|
|
131
166
|
# Время ОБЯЗАТЕЛЬНО должно быть указано
|
|
@@ -150,7 +185,8 @@ class EventRouter:
|
|
|
150
185
|
'once_only': once_only,
|
|
151
186
|
'router': self.name,
|
|
152
187
|
'default_delay': default_delay_seconds,
|
|
153
|
-
'event_type': event_type # Добавляем event_type для глобальных обработчиков
|
|
188
|
+
'event_type': event_type, # Добавляем event_type для глобальных обработчиков
|
|
189
|
+
'send_ai_response': send_ai_response
|
|
154
190
|
}
|
|
155
191
|
|
|
156
192
|
logger.info(f"🌍 Роутер {self.name}: зарегистрирован глобальный обработчик '{handler_type}': {func.__name__}")
|
|
@@ -159,7 +195,22 @@ class EventRouter:
|
|
|
159
195
|
@wraps(func)
|
|
160
196
|
async def wrapper(*args, **kwargs):
|
|
161
197
|
try:
|
|
162
|
-
|
|
198
|
+
result = await func(*args, **kwargs)
|
|
199
|
+
|
|
200
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
201
|
+
if isinstance(result, dict):
|
|
202
|
+
result['notify'] = notify
|
|
203
|
+
result['send_ai_response'] = send_ai_response
|
|
204
|
+
else:
|
|
205
|
+
# Если результат не словарь, создаем словарь
|
|
206
|
+
result = {
|
|
207
|
+
'status': 'success',
|
|
208
|
+
'result': result,
|
|
209
|
+
'notify': notify,
|
|
210
|
+
'send_ai_response': send_ai_response
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result
|
|
163
214
|
except Exception as e:
|
|
164
215
|
logger.error(f"Ошибка выполнения глобального обработчика '{handler_type}' в роутере {self.name}: {e}")
|
|
165
216
|
raise
|
|
@@ -222,12 +222,18 @@ async def voice_handler(message: Message, state: FSMContext):
|
|
|
222
222
|
# Распознаем через Whisper
|
|
223
223
|
recognized_text = await openai_client.transcribe_audio(str(file_path))
|
|
224
224
|
|
|
225
|
-
# Удаляем временный файл
|
|
225
|
+
# Удаляем временный файл и папку
|
|
226
226
|
try:
|
|
227
227
|
os.remove(file_path)
|
|
228
228
|
logger.info(f"🗑️ Временный файл удален: {file_path}")
|
|
229
|
+
|
|
230
|
+
# Проверяем, пуста ли папка
|
|
231
|
+
if not any(temp_dir.iterdir()):
|
|
232
|
+
temp_dir.rmdir()
|
|
233
|
+
logger.info(f"🗑️ Временная папка удалена: {temp_dir}")
|
|
234
|
+
|
|
229
235
|
except Exception as e:
|
|
230
|
-
logger.warning(f"⚠️ Не удалось удалить
|
|
236
|
+
logger.warning(f"⚠️ Не удалось удалить временные файлы: {e}")
|
|
231
237
|
|
|
232
238
|
if not recognized_text:
|
|
233
239
|
await processing_msg.edit_text("❌ Не удалось распознать голос. Попробуйте еще раз.")
|
|
@@ -936,8 +942,8 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
|
|
|
936
942
|
logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
|
|
937
943
|
|
|
938
944
|
# Обрабатываем события в системе
|
|
939
|
-
await process_events(session_id, events, message.from_user.id)
|
|
940
|
-
logger.
|
|
945
|
+
should_send_response = await process_events(session_id, events, message.from_user.id)
|
|
946
|
+
logger.warning(f" ✅ События обработаны, should_send_response = {should_send_response}")
|
|
941
947
|
|
|
942
948
|
# Обрабатываем файлы и каталоги
|
|
943
949
|
files_list = ai_metadata.get('файлы', [])
|
|
@@ -1001,6 +1007,14 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
|
|
|
1001
1007
|
|
|
1002
1008
|
logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
|
|
1003
1009
|
|
|
1010
|
+
# ============ ПРОВЕРКА: НУЖНО ЛИ ОТПРАВЛЯТЬ СООБЩЕНИЕ ОТ ИИ ============
|
|
1011
|
+
# Проверяем флаг из событий (если события запретили отправку)
|
|
1012
|
+
logger.warning(f"🔍 Проверка should_send_response: exists={('should_send_response' in locals())}, value={locals().get('should_send_response', 'NOT_SET')}")
|
|
1013
|
+
|
|
1014
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
1015
|
+
logger.warning("🔇🔇🔇 СОБЫТИЯ ЗАПРЕТИЛИ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ, ПРОПУСКАЕМ ОТПРАВКУ 🔇🔇🔇")
|
|
1016
|
+
return
|
|
1017
|
+
|
|
1004
1018
|
# ============ ХУК 5: ФИЛЬТРЫ ОТПРАВКИ ============
|
|
1005
1019
|
send_filters = message_hooks.get('send_filters', [])
|
|
1006
1020
|
for filter_func in send_filters:
|
|
@@ -1127,9 +1141,9 @@ async def process_voice_message(message: Message, state: FSMContext, session_id:
|
|
|
1127
1141
|
await supabase_client.update_session_stage(session_id, stage, quality)
|
|
1128
1142
|
|
|
1129
1143
|
# Обрабатываем события
|
|
1130
|
-
events = ai_metadata.get('
|
|
1144
|
+
events = ai_metadata.get('події', [])
|
|
1131
1145
|
if events:
|
|
1132
|
-
await process_events(session_id, events, message.from_user.id)
|
|
1146
|
+
should_send_response = await process_events(session_id, events, message.from_user.id)
|
|
1133
1147
|
|
|
1134
1148
|
# Сохраняем ответ ассистента
|
|
1135
1149
|
await supabase_client.add_message(
|
|
@@ -1152,6 +1166,11 @@ async def process_voice_message(message: Message, state: FSMContext, session_id:
|
|
|
1152
1166
|
files_list = ai_metadata.get('файлы', [])
|
|
1153
1167
|
directories_list = ai_metadata.get('каталоги', [])
|
|
1154
1168
|
|
|
1169
|
+
# Проверяем, нужно ли отправлять сообщение от ИИ
|
|
1170
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
1171
|
+
logger.info("🔇 События запретили отправку сообщения от ИИ (voice), пропускаем отправку")
|
|
1172
|
+
return
|
|
1173
|
+
|
|
1155
1174
|
# Отправляем ответ пользователю
|
|
1156
1175
|
await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
|
|
1157
1176
|
logger.info(f"✅ Ответ отправлен пользователю {message.from_user.id}")
|