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.

@@ -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
- # Автоматически добавляем флаг notify к результату
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
- # Автоматически добавляем флаг notify к результату
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
- # Автоматически добавляем флаг notify к результату
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
- event_name=event_name,
1751
- file_name=file_info['name']
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['name']
1768
+ file_path = temp_with_msg / file_info['original_name']
1757
1769
  else:
1758
- file_path = temp_after_msg / file_info['name']
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
- for file_info in files_with_msg:
1798
- file_path = temp_with_msg / file_info['name']
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['name']
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
- # 4.2. Удаляем файлы из Supabase Storage
1866
+ # 4.2. Удаляем файлы из Supabase Storage
1852
1867
  try:
1853
- await supabase_client.delete_event_files(event_name)
1854
- logger.info(f"🗑️ Файлы события '{event_name}' удалены из Storage")
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,
@@ -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
- return await func(*args, **kwargs)
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
- return await func(*args, **kwargs)
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
- return await func(*args, **kwargs)
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"⚠️ Не удалось удалить временный файл: {e}")
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.info(" ✅ События обработаны")
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}")