smart-bot-factory 0.2.1__py3-none-any.whl → 0.2.3__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/core/message_sender.py +241 -0
- smart_bot_factory/creation/bot_builder.py +112 -0
- smart_bot_factory/handlers/handlers.py +117 -71
- smart_bot_factory/message/__init__.py +2 -0
- {smart_bot_factory-0.2.1.dist-info → smart_bot_factory-0.2.3.dist-info}/METADATA +1 -1
- {smart_bot_factory-0.2.1.dist-info → smart_bot_factory-0.2.3.dist-info}/RECORD +13 -13
- /smart_bot_factory/{supabase → database}/__init__.py +0 -0
- /smart_bot_factory/{supabase → database}/client.py +0 -0
- /smart_bot_factory/{database → table}/database_structure.sql +0 -0
- /smart_bot_factory/{database → table}/schema.sql +0 -0
- {smart_bot_factory-0.2.1.dist-info → smart_bot_factory-0.2.3.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.2.1.dist-info → smart_bot_factory-0.2.3.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.2.1.dist-info → smart_bot_factory-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
640
|
-
|
|
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
|
-
|
|
645
|
-
|
|
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
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|
]
|
|
@@ -27,29 +27,29 @@ smart_bot_factory/configs/growthmed-october-24/welcome_file/Чек лист по
|
|
|
27
27
|
smart_bot_factory/core/bot_utils.py,sha256=XmwQ31LOpE_Wudx4OO4tlnVwse3YagakwpgN2cZC5SQ,41085
|
|
28
28
|
smart_bot_factory/core/conversation_manager.py,sha256=eoHL7MCEz68DRvTVwRwZgf2PWwGv4T6J9D-I-thETi8,28289
|
|
29
29
|
smart_bot_factory/core/decorators.py,sha256=xQCtr5SOLqqmEzaxrDf_sfxWQouIG7riI6Sa_jqGlcg,68647
|
|
30
|
-
smart_bot_factory/core/message_sender.py,sha256=
|
|
30
|
+
smart_bot_factory/core/message_sender.py,sha256=J4b6n8nXVjqf-qzL6URRSvc-FVnQfShwujVSM6qv26w,32232
|
|
31
31
|
smart_bot_factory/core/router.py,sha256=o-AzLarBgYbXq7OeeXQEfa6XFqcAtLKqf5-3Do79Boo,11555
|
|
32
32
|
smart_bot_factory/core/router_manager.py,sha256=dUwesog-oHk1U2EDdS8p0e4MTSkwtx5_qXn6nrJ9l9I,9700
|
|
33
33
|
smart_bot_factory/core/states.py,sha256=AOc19_yAsDW_8md-2oiowjBhuW3bwcsmMxszCXWZZTQ,355
|
|
34
34
|
smart_bot_factory/core/telegram_router.py,sha256=LcPLOd87Y4Y4YN6TBXVAtmpSp8gAK2otgMI4YS5f5tk,2091
|
|
35
35
|
smart_bot_factory/creation/__init__.py,sha256=IgDk8GDS3pg7Pw_Et41J33ZmeZIU5dRwQdTmYKXfJfE,128
|
|
36
|
-
smart_bot_factory/creation/bot_builder.py,sha256=
|
|
36
|
+
smart_bot_factory/creation/bot_builder.py,sha256=vMvjq6deCD_TjkQfAyTlFF1FNXxzA-W0SloLObPK4_M,32620
|
|
37
37
|
smart_bot_factory/creation/bot_testing.py,sha256=JDWXyJfZmbgo-DLdAPk8Sd9FiehtHHa4sLD17lBrTOc,55669
|
|
38
|
-
smart_bot_factory/database/
|
|
39
|
-
smart_bot_factory/database/
|
|
38
|
+
smart_bot_factory/database/__init__.py,sha256=XmZP6yM9ffERM5ddAWyJnrNzEhCYtMu3AcjVCi1rOf8,179
|
|
39
|
+
smart_bot_factory/database/client.py,sha256=8_-I3kxZQlKQElI4cTUjNGYcqlyIyEkSrUZaP0nfNNU,26044
|
|
40
40
|
smart_bot_factory/event/__init__.py,sha256=hPL449RULIOB-OXv1ZbGNiHctAYaOMUqhSWGPrDHYBM,212
|
|
41
|
-
smart_bot_factory/handlers/handlers.py,sha256=
|
|
41
|
+
smart_bot_factory/handlers/handlers.py,sha256=BZ9KSFnudVg3iU_XuNI0hter7ZOl9Kc6P4TyLwPh0AM,40803
|
|
42
42
|
smart_bot_factory/integrations/openai_client.py,sha256=aMcDrKO0GEx3ZSVEOGDeDtFCDWSXs6biUfgrbRK8yTU,23180
|
|
43
43
|
smart_bot_factory/integrations/supabase_client.py,sha256=AfALLZdDYeMWHLJw6POTGiBd-sH3i03oT6tT7m9C28I,44644
|
|
44
|
-
smart_bot_factory/message/__init__.py,sha256=
|
|
44
|
+
smart_bot_factory/message/__init__.py,sha256=8XWEEl3J0l1xDeEPmXPwFbHbnQelGApCwnicUkcBbOg,391
|
|
45
45
|
smart_bot_factory/router/__init__.py,sha256=QrfO5u8H2uVzWGMtBvNKunsJAv7UrKtvt2f_D5xSFWE,270
|
|
46
|
-
smart_bot_factory/
|
|
47
|
-
smart_bot_factory/
|
|
46
|
+
smart_bot_factory/table/database_structure.sql,sha256=26gFtMC2jdQGQF7Zb_F4Br56rMd4hUDTk9FkNZYneLo,2789
|
|
47
|
+
smart_bot_factory/table/schema.sql,sha256=-6kOmA9QnSkUtmGI2iQRbTvbdiqOhEOQcuz1lJn79mU,28059
|
|
48
48
|
smart_bot_factory/utils/__init__.py,sha256=5zNjw491bj5VkOhoEAwh2hjmv8nldyDOTrG7pbGpz6A,285
|
|
49
49
|
smart_bot_factory/utils/debug_routing.py,sha256=BOoDhKBg7UXe5uHQxRk3TSfPfLPOFqt0N7lAo6kjCOo,4719
|
|
50
50
|
smart_bot_factory/utils/prompt_loader.py,sha256=JSn7CsWnToSbHYtURdeuZn7ectyDqQGrPGHN2ixIGkw,19930
|
|
51
|
-
smart_bot_factory-0.2.
|
|
52
|
-
smart_bot_factory-0.2.
|
|
53
|
-
smart_bot_factory-0.2.
|
|
54
|
-
smart_bot_factory-0.2.
|
|
55
|
-
smart_bot_factory-0.2.
|
|
51
|
+
smart_bot_factory-0.2.3.dist-info/METADATA,sha256=rt1QUs5sfMFqWAy2b30NLMkJvum_zT3EDFpepkWSb8A,28224
|
|
52
|
+
smart_bot_factory-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
53
|
+
smart_bot_factory-0.2.3.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
|
|
54
|
+
smart_bot_factory-0.2.3.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
|
|
55
|
+
smart_bot_factory-0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|