smart-bot-factory 0.2.1__tar.gz → 0.2.3__tar.gz

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.

Files changed (102) hide show
  1. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/PKG-INFO +1 -1
  2. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/best-valera.py +78 -2
  3. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/pyproject.toml +1 -1
  4. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/message_sender.py +241 -0
  5. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/creation/bot_builder.py +112 -0
  6. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/handlers/handlers.py +117 -71
  7. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/message/__init__.py +2 -0
  8. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/uv.lock +1 -1
  9. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.claude/settings.local.json +0 -0
  10. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.env.example +0 -0
  11. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.github/ISSUE_TEMPLATE//342/234/250-/320/267/320/260/320/277/321/200/320/276/321/201-/321/204/321/203/320/275/320/272/321/206/320/270/320/270.md" +0 -0
  12. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.github/ISSUE_TEMPLATE//360/237/220/233-/320/261/320/260/320/263-/321/200/320/265/320/277/320/276/321/200/321/202.md" +0 -0
  13. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.github/workflows/ci.yml +0 -0
  14. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.github/workflows/publish-private.yml +0 -0
  15. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.github/workflows/publish.yml +0 -0
  16. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.gitignore +0 -0
  17. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/.python-version +0 -0
  18. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/CLIENTS_USAGE.md +0 -0
  19. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/Dockerfile +0 -0
  20. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/LICENSE +0 -0
  21. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/README.md +0 -0
  22. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/1sales_context.txt +0 -0
  23. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/2product_info.txt +0 -0
  24. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/3objection_handling.txt +0 -0
  25. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/final_instructions.txt +0 -0
  26. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/help_message.txt +0 -0
  27. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/prompts/welcome_message.txt +0 -0
  28. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/tests/quick_scenarios.yaml +0 -0
  29. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/tests/realistic_scenarios.yaml +0 -0
  30. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/tests/scenario_examples.yaml +0 -0
  31. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/welcome_files/welcome_file_msg.txt +0 -0
  32. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/best-valera/welcome_files//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  33. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/prompts/2product_info.txt +0 -0
  34. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/prompts/3objection_handling.txt +0 -0
  35. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/prompts/final_instructions.txt +0 -0
  36. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/prompts/help_message.txt +0 -0
  37. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/prompts/welcome_message.txt +0 -0
  38. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/tests/quick_scenarios.yaml +0 -0
  39. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/tests/realistic_scenarios.yaml +0 -0
  40. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/tests/scenario_examples.yaml +0 -0
  41. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/welcome_files/welcome_file_msg.txt +0 -0
  42. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/bots/valera/welcome_files//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  43. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/cats//320/224/320/276/320/263/320/276/320/262/320/276/321/200.pdf" +0 -0
  44. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/cats//320/272/320/276/320/275/320/270.jpg" +0 -0
  45. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/cats//320/272/320/276/321/202.jpg" +0 -0
  46. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/cats//320/273/320/265/321/201.jpg" +0 -0
  47. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/tests/fixes_elina.yaml +0 -0
  48. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/configs/valera/welcome_file//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  49. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/create_tag.sh +0 -0
  50. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/docker-compose.yml +0 -0
  51. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/env.example +0 -0
  52. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/requirements.txt +0 -0
  53. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/save_backup.sh +0 -0
  54. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/save_fixes.sh +0 -0
  55. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/__init__.py +0 -0
  56. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/__init__.py +0 -0
  57. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/admin_logic.py +0 -0
  58. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/admin_manager.py +0 -0
  59. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/admin_migration.sql +0 -0
  60. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/admin_tester.py +0 -0
  61. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/admin/timeout_checker.py +0 -0
  62. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/analytics/analytics_manager.py +0 -0
  63. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/cli.py +0 -0
  64. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/config.py +0 -0
  65. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +0 -0
  66. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +0 -0
  67. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +0 -0
  68. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +0 -0
  69. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +0 -0
  70. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +0 -0
  71. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +0 -0
  72. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +0 -0
  73. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +0 -0
  74. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +0 -0
  75. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +0 -0
  76. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +0 -0
  77. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +0 -0
  78. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/configs/growthmed-october-24/welcome_file//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  79. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/bot_utils.py +0 -0
  80. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/conversation_manager.py +0 -0
  81. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/decorators.py +0 -0
  82. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/router.py +0 -0
  83. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/router_manager.py +0 -0
  84. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/states.py +0 -0
  85. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/core/telegram_router.py +0 -0
  86. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/creation/__init__.py +0 -0
  87. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/creation/bot_testing.py +0 -0
  88. {smart_bot_factory-0.2.1/smart_bot_factory/supabase → smart_bot_factory-0.2.3/smart_bot_factory/database}/__init__.py +0 -0
  89. {smart_bot_factory-0.2.1/smart_bot_factory/supabase → smart_bot_factory-0.2.3/smart_bot_factory/database}/client.py +0 -0
  90. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/event/__init__.py +0 -0
  91. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/integrations/openai_client.py +0 -0
  92. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/integrations/supabase_client.py +0 -0
  93. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/router/__init__.py +0 -0
  94. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/setup_checker.py +0 -0
  95. {smart_bot_factory-0.2.1/smart_bot_factory/database → smart_bot_factory-0.2.3/smart_bot_factory/table}/database_structure.sql +0 -0
  96. {smart_bot_factory-0.2.1/smart_bot_factory/database → smart_bot_factory-0.2.3/smart_bot_factory/table}/schema.sql +0 -0
  97. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/utils/__init__.py +0 -0
  98. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/utils/debug_routing.py +0 -0
  99. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/utils/prompt_loader.py +0 -0
  100. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/smart_bot_factory/utm_link_generator.py +0 -0
  101. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/system_prompt_example.txt +0 -0
  102. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.3}/valera.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smart-bot-factory
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Библиотека для создания умных чат-ботов
5
5
  Author-email: Kopatych <kopatych@example.com>
6
6
  License: MIT
@@ -1,10 +1,13 @@
1
1
  import asyncio
2
+ import logging
2
3
 
3
4
  from smart_bot_factory.router import EventRouter, TelegramRouter
4
- from smart_bot_factory.message import send_message_by_human, send_message_to_users_by_stage
5
+ from smart_bot_factory.message import send_message_by_human, send_message_to_users_by_stage, send_message
5
6
  from smart_bot_factory.supabase import SupabaseClient
6
7
  from smart_bot_factory.creation import BotBuilder
7
8
 
9
+ logger = logging.getLogger(__name__)
10
+
8
11
  # =============================================================================
9
12
  # СОЗДАНИЕ РОУТЕРОВ
10
13
  # =============================================================================
@@ -55,6 +58,71 @@ async def custom_start_handler(user_id: int, session_id: str, message: Message,
55
58
  reply_markup=keyboard
56
59
  )
57
60
 
61
+ # =============================================================================
62
+ # ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ
63
+ # =============================================================================
64
+
65
+ # ХУК 1: Валидация сообщений
66
+ @bot_builder.validate_message
67
+ async def validate_service_names(message, supabase_client):
68
+ """Проверяем корректность названий услуг"""
69
+ # Пример: блокируем неправильные названия
70
+ incorrect_names = ["массаш", "педекюр", "макияш"]
71
+
72
+ if message.text:
73
+ for incorrect in incorrect_names:
74
+ if incorrect in message.text.lower():
75
+ await message.answer(
76
+ f"❗ Возможно, вы имели в виду другую услугу?\n"
77
+ f"Пожалуйста, уточните название"
78
+ )
79
+ return False # Прерываем обработку AI
80
+
81
+ return True # Продолжаем обработку
82
+
83
+ # ХУК 2: Обогащение промпта
84
+ @bot_builder.enrich_prompt
85
+ async def add_client_info_to_prompt(system_prompt, user_id, session_id, supabase_client):
86
+ """Добавляем информацию о клиенте в промпт"""
87
+ try:
88
+ session = await supabase_client.get_active_session(user_id)
89
+
90
+ if session and session.get('metadata'):
91
+ phone = session['metadata'].get('phone')
92
+ name = session['metadata'].get('confirmed_name') or session['metadata'].get('telegram_name')
93
+
94
+ if phone:
95
+ client_info = f"\n\nИНФОРМАЦИЯ О КЛИЕНТЕ:\n- Телефон: {phone}"
96
+ if name:
97
+ client_info += f"\n- Имя: {name}"
98
+ client_info += "\n\n⚠️ ВАЖНО: При создании записи используй ЭТОТ телефон и имя!"
99
+
100
+ return system_prompt + client_info
101
+ except Exception as e:
102
+ logger.error(f"Ошибка обогащения промпта: {e}")
103
+
104
+ return system_prompt
105
+
106
+ # ХУК 3: Обогащение контекста (например, данные из внешнего API)
107
+ @bot_builder.enrich_context
108
+ async def add_external_api_data(messages, user_id, session_id):
109
+ """Добавляем данные из внешних систем"""
110
+ # Пример: можно добавить расписание из YClients, данные из CRM и т.д.
111
+ # messages.append({
112
+ # "role": "system",
113
+ # "content": "Реальное доступное время: ..."
114
+ # })
115
+ return messages
116
+
117
+ # ХУК 4: Фильтр отправки
118
+ @bot_builder.filter_send
119
+ async def allow_all_messages(user_id, response_text):
120
+ """Разрешаем все сообщения (можно добавить блокировку по условию)"""
121
+ # Пример: блокировка во время обработки
122
+ # if is_processing(user_id):
123
+ # return False
124
+ return True
125
+
58
126
  @telegram_router_1.router.message(Command("price", "цена"))
59
127
  async def handle_price_command(message: Message, state: FSMContext):
60
128
  """Обработчик команды /price"""
@@ -75,7 +143,15 @@ async def handle_price_question(message: Message, state: FSMContext):
75
143
  async def handle_catalog(callback: CallbackQuery, state: FSMContext):
76
144
  """Обработка кнопки Каталог"""
77
145
  await callback.answer()
78
- await callback.message.answer("📖 Вот наш каталог товаров:\n\n1. Товар 1\n2. Товар 2\n3. Товар 3")
146
+
147
+ # Пример: используем send_message с файлами
148
+ await send_message(
149
+ message=callback.message,
150
+ text="📖 Вот наш каталог товаров:\n\n1. Товар 1\n2. Товар 2\n3. Товар 3",
151
+ supabase_client=supabase_client,
152
+ files_list=[], # Можно добавить файлы: ["catalog.pdf"]
153
+ parse_mode="Markdown"
154
+ )
79
155
 
80
156
  @telegram_router_1.router.callback_query(F.data == "prices")
81
157
  async def handle_prices(callback: CallbackQuery, state: FSMContext):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smart-bot-factory"
3
- version = "0.2.1"
3
+ version = "0.2.3"
4
4
  description = "Библиотека для создания умных чат-ботов"
5
5
  authors = [
6
6
  {name = "Kopatych", email = "kopatych@example.com"}
@@ -449,3 +449,244 @@ async def get_users_by_stage_stats(
449
449
  "error": str(e),
450
450
  "bot_id": bot_id
451
451
  }
452
+
453
+ async def send_message(
454
+ message,
455
+ text: str,
456
+ supabase_client,
457
+ files_list: list = [],
458
+ directories_list: list = [],
459
+ parse_mode: str = "Markdown",
460
+ **kwargs
461
+ ):
462
+ """
463
+ Пользовательская функция для отправки сообщений с файлами и кнопками
464
+
465
+ Args:
466
+ message: Message объект от aiogram
467
+ text: Текст сообщения
468
+ supabase_client: SupabaseClient для работы с БД
469
+ files_list: Список файлов для отправки
470
+ directories_list: Список каталогов (отправятся все файлы)
471
+ parse_mode: Режим парсинга ('Markdown', 'HTML' или None)
472
+ **kwargs: Дополнительные параметры (reply_markup и т.д.)
473
+
474
+ Returns:
475
+ Message объект отправленного сообщения или None
476
+
477
+ Example:
478
+ from smart_bot_factory.message import send_message
479
+ from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
480
+
481
+ keyboard = InlineKeyboardMarkup(inline_keyboard=[
482
+ [InlineKeyboardButton(text="Кнопка", callback_data="action")]
483
+ ])
484
+
485
+ await send_message(
486
+ message=message,
487
+ text="Привет!",
488
+ supabase_client=supabase_client,
489
+ files_list=["file.pdf"],
490
+ parse_mode="Markdown",
491
+ reply_markup=keyboard
492
+ )
493
+ """
494
+ from pathlib import Path
495
+ from aiogram.types import FSInputFile
496
+ from aiogram.utils.media_group import MediaGroupBuilder
497
+
498
+ logger.info(f"📤 send_message вызвана:")
499
+ logger.info(f" 👤 Пользователь: {message.from_user.id}")
500
+ logger.info(f" 📝 Длина текста: {len(text)} символов")
501
+ logger.info(f" 🔧 Parse mode: {parse_mode}")
502
+
503
+ try:
504
+ user_id = message.from_user.id
505
+
506
+ # Устанавливаем parse_mode (None если передана строка 'None')
507
+ actual_parse_mode = None if parse_mode == 'None' else parse_mode
508
+
509
+ # Текст уже готов, используем как есть
510
+ final_text = text
511
+
512
+ # Работаем с переданными файлами и каталогами
513
+ logger.info(f" 📦 Передано файлов: {files_list}")
514
+ logger.info(f" 📂 Передано каталогов: {directories_list}")
515
+
516
+ # Получаем список уже отправленных файлов и каталогов
517
+ sent_files = await supabase_client.get_sent_files(user_id)
518
+ sent_directories = await supabase_client.get_sent_directories(user_id)
519
+
520
+ logger.info(f" 📋 Уже отправлено файлов: {sent_files}")
521
+ logger.info(f" 📋 Уже отправлено каталогов: {sent_directories}")
522
+
523
+ # Фильтруем файлы и каталоги, которые уже отправлялись
524
+ actual_files_list = [f for f in files_list if f not in sent_files]
525
+ actual_directories_list = [d for d in directories_list if str(d) not in sent_directories]
526
+
527
+ logger.info(f" 🆕 После фильтрации файлов: {actual_files_list}")
528
+ logger.info(f" 🆕 После фильтрации каталогов: {actual_directories_list}")
529
+
530
+ # Проверяем, что есть что отправлять
531
+ if not final_text or not final_text.strip():
532
+ logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА: final_text пуст после обработки!")
533
+ logger.error(f" Исходный text: '{text[:200]}...'")
534
+ final_text = "Ошибка формирования ответа. Попробуйте еще раз."
535
+
536
+ logger.info(f"📱 Подготовка сообщения: {len(final_text)} символов")
537
+ logger.info(f" 📦 Файлов для обработки: {actual_files_list}")
538
+ logger.info(f" 📂 Каталогов для обработки: {actual_directories_list}")
539
+
540
+ # Проверяем наличие файлов для отправки
541
+ if actual_files_list or actual_directories_list:
542
+ # Функция определения типа медиа по расширению
543
+ def get_media_type(file_path: str) -> str:
544
+ ext = Path(file_path).suffix.lower()
545
+ if ext in {'.jpg', '.jpeg', '.png'}:
546
+ return 'photo'
547
+ elif ext in {'.mp4', '.mov'}:
548
+ return 'video'
549
+ else:
550
+ return 'document'
551
+
552
+ # Создаем списки для разных типов файлов
553
+ video_files = []
554
+ photo_files = []
555
+ document_files = []
556
+
557
+ # Функция обработки файла
558
+ def process_file(file_path: Path, source: str = ""):
559
+ if file_path.is_file():
560
+ media_type = get_media_type(str(file_path))
561
+ if media_type == 'video':
562
+ video_files.append(file_path)
563
+ logger.info(f" 🎥 Добавлено видео{f' из {source}' if source else ''}: {file_path.name}")
564
+ elif media_type == 'photo':
565
+ photo_files.append(file_path)
566
+ logger.info(f" 📸 Добавлено фото{f' из {source}' if source else ''}: {file_path.name}")
567
+ else:
568
+ document_files.append(file_path)
569
+ logger.info(f" 📄 Добавлен документ{f' из {source}' if source else ''}: {file_path.name}")
570
+ else:
571
+ logger.warning(f" ⚠️ Файл не найден: {file_path}")
572
+
573
+ # Обрабатываем прямые файлы
574
+ for file_name in actual_files_list:
575
+ try:
576
+ process_file(Path(f"files/{file_name}"))
577
+ except Exception as e:
578
+ logger.error(f" ❌ Ошибка обработки файла {file_name}: {e}")
579
+
580
+ # Обрабатываем файлы из каталогов
581
+ for dir_name in actual_directories_list:
582
+ dir_name = Path(dir_name)
583
+ try:
584
+ if dir_name.is_dir():
585
+ for file_path in dir_name.iterdir():
586
+ try:
587
+ process_file(file_path, dir_name)
588
+ except Exception as e:
589
+ logger.error(f" ❌ Ошибка обработки файла {file_path}: {e}")
590
+ else:
591
+ logger.warning(f" ⚠️ Каталог не найден: {dir_name}")
592
+ except Exception as e:
593
+ logger.error(f" ❌ Ошибка обработки каталога {dir_name}: {e}")
594
+
595
+ # Списки для отслеживания реально отправленных файлов
596
+ sent_files_to_save = []
597
+ sent_dirs_to_save = []
598
+
599
+ # 1. Отправляем видео (если есть)
600
+ if video_files:
601
+ video_group = MediaGroupBuilder()
602
+ for file_path in video_files:
603
+ video_group.add_video(media=FSInputFile(str(file_path)))
604
+
605
+ videos = video_group.build()
606
+ if videos:
607
+ await message.answer_media_group(media=videos)
608
+ logger.info(f" ✅ Отправлено {len(videos)} видео")
609
+
610
+ # 2. Отправляем фото (если есть)
611
+ if photo_files:
612
+ photo_group = MediaGroupBuilder()
613
+ for file_path in photo_files:
614
+ photo_group.add_photo(media=FSInputFile(str(file_path)))
615
+
616
+ photos = photo_group.build()
617
+ if photos:
618
+ await message.answer_media_group(media=photos)
619
+ logger.info(f" ✅ Отправлено {len(photos)} фото")
620
+
621
+ # 3. Отправляем текст
622
+ result = await message.answer(final_text, parse_mode=actual_parse_mode, **kwargs)
623
+ logger.info(f" ✅ Отправлен текст сообщения")
624
+
625
+ # 4. Отправляем документы (если есть)
626
+ if document_files:
627
+ doc_group = MediaGroupBuilder()
628
+ for file_path in document_files:
629
+ doc_group.add_document(media=FSInputFile(str(file_path)))
630
+
631
+ docs = doc_group.build()
632
+ if docs:
633
+ await message.answer_media_group(media=docs)
634
+ logger.info(f" ✅ Отправлено {len(docs)} документов")
635
+
636
+ # 5. Собираем список реально отправленных файлов и каталогов
637
+ if video_files or photo_files or document_files:
638
+ sent_files_to_save.extend(actual_files_list)
639
+ logger.info(f" 📝 Добавляем в список для сохранения файлы: {actual_files_list}")
640
+ sent_dirs_to_save.extend([str(d) for d in actual_directories_list])
641
+ logger.info(f" 📝 Добавляем в список для сохранения каталоги: {actual_directories_list}")
642
+
643
+ # 6. Обновляем информацию в БД
644
+ if sent_files_to_save or sent_dirs_to_save:
645
+ try:
646
+ if sent_files_to_save:
647
+ logger.info(f" 💾 Сохраняем файлы в БД: {sent_files_to_save}")
648
+ await supabase_client.add_sent_files(user_id, sent_files_to_save)
649
+ if sent_dirs_to_save:
650
+ logger.info(f" 💾 Сохраняем каталоги в БД: {sent_dirs_to_save}")
651
+ await supabase_client.add_sent_directories(user_id, sent_dirs_to_save)
652
+ logger.info(f" ✅ Обновлена информация о отправленных файлах в БД")
653
+ except Exception as e:
654
+ logger.error(f" ❌ Ошибка обновления информации о файлах в БД: {e}")
655
+ else:
656
+ logger.info(f" ℹ️ Нет новых файлов для сохранения в БД")
657
+
658
+ return result
659
+ else:
660
+ # Если нет файлов, отправляем просто текст
661
+ logger.info(" ⚠️ Нет файлов для отправки, отправляем как текст")
662
+ result = await message.answer(final_text, parse_mode=actual_parse_mode, **kwargs)
663
+ return result
664
+
665
+ except Exception as e:
666
+ # Проверяем, является ли ошибка блокировкой бота
667
+ if "Forbidden: bot was blocked by the user" in str(e):
668
+ logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
669
+ return None
670
+ elif "TelegramForbiddenError" in str(type(e).__name__):
671
+ logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
672
+ return None
673
+
674
+ logger.error(f"❌ ОШИБКА в send_message: {e}")
675
+ logger.exception("Полный стек ошибки send_message:")
676
+
677
+ # Пытаемся отправить простое сообщение без форматирования
678
+ try:
679
+ fallback_text = "Произошла ошибка при отправке ответа. Попробуйте еще раз."
680
+ result = await message.answer(fallback_text)
681
+ logger.info(f"✅ Запасное сообщение отправлено")
682
+ return result
683
+ except Exception as e2:
684
+ if "Forbidden: bot was blocked by the user" in str(e2):
685
+ logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
686
+ return None
687
+ elif "TelegramForbiddenError" in str(type(e2).__name__):
688
+ logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
689
+ return None
690
+
691
+ logger.error(f"❌ Даже запасное сообщение не отправилось: {e2}")
692
+ raise
@@ -48,6 +48,12 @@ class BotBuilder:
48
48
  self._telegram_routers: List = [] # Список Telegram роутеров
49
49
  self._start_handlers: List = [] # Список обработчиков on_start
50
50
 
51
+ # Хуки для кастомизации process_user_message
52
+ self._message_validators: List = [] # Валидация ДО обработки
53
+ self._prompt_enrichers: List = [] # Обогащение системного промпта
54
+ self._context_enrichers: List = [] # Обогащение контекста для AI
55
+ self._send_filters: List = [] # Фильтры перед отправкой пользователю
56
+
51
57
  # Флаги инициализации
52
58
  self._initialized = False
53
59
 
@@ -358,6 +364,111 @@ class BotBuilder:
358
364
  """Получает список обработчиков on_start"""
359
365
  return self._start_handlers.copy()
360
366
 
367
+ # ========== ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ ==========
368
+
369
+ def validate_message(self, handler):
370
+ """
371
+ Регистрирует валидатор сообщений (вызывается ДО обработки AI)
372
+
373
+ Если валидатор возвращает False, обработка прерывается
374
+
375
+ Args:
376
+ handler: async def(message: Message, supabase_client) -> bool
377
+
378
+ Example:
379
+ @bot_builder.validate_message
380
+ async def check_service_names(message, supabase_client):
381
+ if "неправильное название" in message.text:
382
+ await message.answer("Пожалуйста, уточните название услуги")
383
+ return False # Прерываем обработку
384
+ return True # Продолжаем
385
+ """
386
+ if not callable(handler):
387
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
388
+
389
+ self._message_validators.append(handler)
390
+ logger.info(f"✅ Зарегистрирован валидатор сообщений: {handler.__name__}")
391
+ return handler
392
+
393
+ def enrich_prompt(self, handler):
394
+ """
395
+ Регистрирует обогатитель системного промпта
396
+
397
+ Args:
398
+ handler: async def(system_prompt: str, user_id: int, session_id: str, supabase_client) -> str
399
+
400
+ Example:
401
+ @bot_builder.enrich_prompt
402
+ async def add_client_info(system_prompt, user_id, session_id, supabase_client):
403
+ session = await supabase_client.get_active_session(user_id)
404
+ phone = session.get('metadata', {}).get('phone')
405
+ if phone:
406
+ return f"{system_prompt}\\n\\nТелефон клиента: {phone}"
407
+ return system_prompt
408
+ """
409
+ if not callable(handler):
410
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
411
+
412
+ self._prompt_enrichers.append(handler)
413
+ logger.info(f"✅ Зарегистрирован обогатитель промпта: {handler.__name__}")
414
+ return handler
415
+
416
+ def enrich_context(self, handler):
417
+ """
418
+ Регистрирует обогатитель контекста для AI (messages array)
419
+
420
+ Args:
421
+ handler: async def(messages: List[dict], user_id: int, session_id: str) -> List[dict]
422
+
423
+ Example:
424
+ @bot_builder.enrich_context
425
+ async def add_external_data(messages, user_id, session_id):
426
+ # Добавляем данные из внешнего API
427
+ messages.append({
428
+ "role": "system",
429
+ "content": "Дополнительная информация..."
430
+ })
431
+ return messages
432
+ """
433
+ if not callable(handler):
434
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
435
+
436
+ self._context_enrichers.append(handler)
437
+ logger.info(f"✅ Зарегистрирован обогатитель контекста: {handler.__name__}")
438
+ return handler
439
+
440
+ def filter_send(self, handler):
441
+ """
442
+ Регистрирует фильтр отправки (может блокировать отправку пользователю)
443
+
444
+ Если фильтр возвращает False, сообщение НЕ отправляется
445
+
446
+ Args:
447
+ handler: async def(user_id: int, response_text: str) -> bool
448
+
449
+ Example:
450
+ @bot_builder.filter_send
451
+ async def block_during_process(user_id, response_text):
452
+ if is_processing(user_id):
453
+ return False # Не отправляем
454
+ return True # Отправляем
455
+ """
456
+ if not callable(handler):
457
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
458
+
459
+ self._send_filters.append(handler)
460
+ logger.info(f"✅ Зарегистрирован фильтр отправки: {handler.__name__}")
461
+ return handler
462
+
463
+ def get_message_hooks(self) -> Dict[str, List]:
464
+ """Получает все хуки для обработки сообщений"""
465
+ return {
466
+ 'validators': self._message_validators.copy(),
467
+ 'prompt_enrichers': self._prompt_enrichers.copy(),
468
+ 'context_enrichers': self._context_enrichers.copy(),
469
+ 'send_filters': self._send_filters.copy()
470
+ }
471
+
361
472
  def get_router_manager(self) -> RouterManager:
362
473
  """Получает менеджер роутеров событий"""
363
474
  return self.router_manager
@@ -407,6 +518,7 @@ class BotBuilder:
407
518
  handlers_module.analytics_manager = self.analytics_manager
408
519
  handlers_module.conversation_manager = self.conversation_manager
409
520
  handlers_module.start_handlers = self._start_handlers # Передаем обработчики on_start
521
+ handlers_module.message_hooks = self.get_message_hooks() # Передаем хуки для обработки сообщений
410
522
  logger.info("✅ Глобальные переменные установлены в handlers")
411
523
  except Exception as e:
412
524
  logger.warning(f"⚠️ Не удалось установить глобальные переменные в handlers: {e}")
@@ -492,10 +492,22 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
492
492
  config = get_global_var('config')
493
493
  bot = get_global_var('bot')
494
494
  prompt_loader = get_global_var('prompt_loader')
495
+ message_hooks = get_global_var('message_hooks') or {}
495
496
  from datetime import datetime
496
497
  import pytz # Добавляем импорт для работы с временными зонами
497
498
 
498
499
  try:
500
+ # ============ ХУК 1: ВАЛИДАЦИЯ СООБЩЕНИЯ ============
501
+ validators = message_hooks.get('validators', [])
502
+ for validator in validators:
503
+ try:
504
+ should_continue = await validator(message, supabase_client)
505
+ if not should_continue:
506
+ logger.info(f"⛔ Валидатор '{validator.__name__}' прервал обработку")
507
+ return # Прерываем обработку
508
+ except Exception as e:
509
+ logger.error(f"❌ Ошибка в валидаторе '{validator.__name__}': {e}")
510
+
499
511
  # Сохраняем сообщение пользователя
500
512
  await supabase_client.add_message(
501
513
  session_id=session_id,
@@ -514,13 +526,25 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
514
526
  current_time = datetime.now(moscow_tz)
515
527
  time_info = current_time.strftime('%H:%M, %d.%m.%Y, %A')
516
528
 
517
- # Модифицируем системный промпт, добавляя время
529
+ # Базовый системный промпт с временем
518
530
  system_prompt_with_time = f"""
519
531
  {system_prompt}
520
532
 
521
533
  ТЕКУЩЕЕ ВРЕМЯ: {time_info} (московское время)
522
534
  """
523
535
 
536
+ # ============ ХУК 2: ОБОГАЩЕНИЕ ПРОМПТА ============
537
+ prompt_enrichers = message_hooks.get('prompt_enrichers', [])
538
+ for enricher in prompt_enrichers:
539
+ try:
540
+ system_prompt_with_time = await enricher(
541
+ system_prompt_with_time,
542
+ message.from_user.id
543
+ )
544
+ logger.info(f"✅ Промпт обогащен '{enricher.__name__}'")
545
+ except Exception as e:
546
+ logger.error(f"❌ Ошибка в обогатителе промпта '{enricher.__name__}': {e}")
547
+
524
548
  # Формируем контекст для OpenAI с обновленным системным промптом
525
549
  messages = [{"role": "system", "content": system_prompt_with_time}]
526
550
 
@@ -536,6 +560,17 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
536
560
  messages.append({"role": "system", "content": final_instructions})
537
561
  logger.info(f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)")
538
562
 
563
+ # ============ ХУК 3: ОБОГАЩЕНИЕ КОНТЕКСТА ============
564
+ context_enrichers = message_hooks.get('context_enrichers', [])
565
+ for enricher in context_enrichers:
566
+ try:
567
+ messages = await enricher(
568
+ messages
569
+ )
570
+ logger.info(f"✅ Контекст обогащен '{enricher.__name__}'")
571
+ except Exception as e:
572
+ logger.error(f"❌ Ошибка в обогатителе контекста '{enricher.__name__}': {e}")
573
+
539
574
  logger.info(f"📝 Контекст сформирован: {len(messages)} сообщений (включая время: {time_info})")
540
575
 
541
576
  await bot.send_chat_action(message.chat.id, "typing")
@@ -588,82 +623,82 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
588
623
  response_text = ai_response
589
624
 
590
625
  logger.info(f"✅ Финальный текст для отправки: {len(response_text)} символов")
591
-
592
- # Обновляем этап сессии и качество лида
593
- if ai_metadata:
594
- logger.info("🔍 Анализ метаданных от ИИ:")
595
-
596
- # Вывод информации об этапе
597
- stage = ai_metadata.get('этап')
598
- if stage:
599
- logger.info(f" 📈 Этап диалога: {stage}")
600
-
601
- # Вывод информации о качестве лида
602
- quality = ai_metadata.get('качество')
603
- if quality is not None:
604
- quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
605
- logger.info(f" {quality_emoji} Качество лида: {quality}/10")
606
-
607
- # Обновляем в базе данных
608
- if stage or quality is not None:
609
- await supabase_client.update_session_stage(session_id, stage, quality)
610
- logger.info(f" ✅ Этап и качество обновлены в БД")
611
-
612
- # Обрабатываем события
613
- events = ai_metadata.get('события', [])
614
- if events:
615
- logger.info(f"\n🔔 События в диалоге ({len(events)}):")
616
- for idx, event in enumerate(events, 1):
617
- event_type = event.get('тип', 'неизвестно')
618
- event_info = event.get('инфо', 'нет информации')
619
-
620
- # Подбираем эмодзи для разных типов событий
621
- event_emoji = {
622
- 'телефон': '📱',
623
- 'email': '📧',
624
- 'встреча': '📅',
625
- 'заказ': '🛍️',
626
- 'вопрос': '❓',
627
- 'консультация': '💬',
628
- 'жалоба': '⚠️',
629
- 'отзыв': '💭'
630
- }.get(event_type.lower(), '📌')
631
-
632
- logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
633
-
634
- # Обрабатываем события в системе
635
- await process_events(session_id, events, message.from_user.id)
636
- logger.info(" ✅ События обработаны")
626
+
627
+ # Обновляем этап сессии и качество лида
628
+ if ai_metadata:
629
+ logger.info("🔍 Анализ метаданных от ИИ:")
630
+
631
+ # Вывод информации об этапе
632
+ stage = ai_metadata.get('этап')
633
+ if stage:
634
+ logger.info(f" 📈 Этап диалога: {stage}")
635
+
636
+ # Вывод информации о качестве лида
637
+ quality = ai_metadata.get('качество')
638
+ if quality is not None:
639
+ quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
640
+ logger.info(f" {quality_emoji} Качество лида: {quality}/10")
637
641
 
638
- # Обрабатываем файлы и каталоги
639
- files_list = ai_metadata.get('файлы', [])
640
- directories_list = ai_metadata.get('каталоги', [])
642
+ # Обновляем в базе данных
643
+ if stage or quality is not None:
644
+ await supabase_client.update_session_stage(session_id, stage, quality)
645
+ logger.info(f" ✅ Этап и качество обновлены в БД")
641
646
 
642
- # Форматируем информацию о файлах
647
+ # Обрабатываем события
648
+ events = ai_metadata.get('события', [])
649
+ if events:
650
+ logger.info(f"\n🔔 События в диалоге ({len(events)}):")
651
+ for idx, event in enumerate(events, 1):
652
+ event_type = event.get('тип', 'неизвестно')
653
+ event_info = event.get('инфо', 'нет информации')
654
+
655
+ # Подбираем эмодзи для разных типов событий
656
+ event_emoji = {
657
+ 'телефон': '📱',
658
+ 'email': '📧',
659
+ 'встреча': '📅',
660
+ 'заказ': '🛍️',
661
+ 'вопрос': '❓',
662
+ 'консультация': '💬',
663
+ 'жалоба': '⚠️',
664
+ 'отзыв': '💭'
665
+ }.get(event_type.lower(), '📌')
666
+
667
+ logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
668
+
669
+ # Обрабатываем события в системе
670
+ await process_events(session_id, events, message.from_user.id)
671
+ logger.info(" ✅ События обработаны")
672
+
673
+ # Обрабатываем файлы и каталоги
674
+ files_list = ai_metadata.get('файлы', [])
675
+ directories_list = ai_metadata.get('каталоги', [])
676
+
677
+ # Форматируем информацию о файлах
678
+ if files_list:
679
+ logger.info("📎 Найденные файлы:")
680
+ for idx, file in enumerate(files_list, 1):
681
+ logger.info(f" {idx}. 📄 {file}")
682
+
683
+ # Форматируем информацию о каталогах
684
+ if directories_list:
685
+ logger.info("📂 Найденные каталоги:")
686
+ for idx, directory in enumerate(directories_list, 1):
687
+ logger.info(f" {idx}. 📁 {directory}")
688
+
689
+ # Добавляем информацию в текст ответа
690
+ if files_list or directories_list:
691
+ files_info = []
643
692
  if files_list:
644
- logger.info("📎 Найденные файлы:")
645
- for idx, file in enumerate(files_list, 1):
646
- logger.info(f" {idx}. 📄 {file}")
693
+ files_str = "\n".join(f" {file}" for file in files_list)
694
+ files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
647
695
 
648
- # Форматируем информацию о каталогах
649
696
  if directories_list:
650
- logger.info("📂 Найденные каталоги:")
651
- for idx, directory in enumerate(directories_list, 1):
652
- logger.info(f" {idx}. 📁 {directory}")
697
+ dirs_str = "\n".join(f" {directory}" for directory in directories_list)
698
+ files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
653
699
 
654
- # Добавляем информацию в текст ответа
655
- if files_list or directories_list:
656
- files_info = []
657
- if files_list:
658
- files_str = "\n".join(f"• {file}" for file in files_list)
659
- files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
660
-
661
- if directories_list:
662
- dirs_str = "\n".join(f"• {directory}" for directory in directories_list)
663
- files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
664
-
665
- else:
666
- logger.info("📎 Файлы и каталоги не указаны")
700
+ else:
701
+ logger.info("📎 Файлы и каталоги не указаны")
667
702
 
668
703
  # Сохраняем ответ ассистента с метаданными
669
704
  try:
@@ -697,6 +732,17 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
697
732
 
698
733
  logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
699
734
 
735
+ # ============ ХУК 4: ФИЛЬТРЫ ОТПРАВКИ ============
736
+ send_filters = message_hooks.get('send_filters', [])
737
+ for filter_func in send_filters:
738
+ try:
739
+ should_send = await filter_func(message.from_user.id, final_response)
740
+ if not should_send:
741
+ logger.info(f"⛔ Фильтр '{filter_func.__name__}' заблокировал отправку")
742
+ return # Не отправляем
743
+ except Exception as e:
744
+ logger.error(f"❌ Ошибка в фильтре отправки '{filter_func.__name__}': {e}")
745
+
700
746
  # Отправляем ответ пользователю
701
747
  try:
702
748
  await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
@@ -7,10 +7,12 @@ from ..core.message_sender import (
7
7
  send_message_by_human,
8
8
  send_message_by_ai,
9
9
  send_message_to_users_by_stage,
10
+ send_message,
10
11
  )
11
12
 
12
13
  __all__ = [
13
14
  'send_message_by_human',
14
15
  'send_message_by_ai',
15
16
  'send_message_to_users_by_stage',
17
+ 'send_message', # Чистая отправка с файлами и кнопками
16
18
  ]
@@ -1259,7 +1259,7 @@ wheels = [
1259
1259
 
1260
1260
  [[package]]
1261
1261
  name = "smart-bot-factory"
1262
- version = "0.2.0"
1262
+ version = "0.2.2"
1263
1263
  source = { editable = "." }
1264
1264
  dependencies = [
1265
1265
  { name = "aiofiles" },