smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.9__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.

Files changed (45) hide show
  1. smart_bot_factory/admin/__init__.py +7 -7
  2. smart_bot_factory/admin/admin_events.py +483 -383
  3. smart_bot_factory/admin/admin_logic.py +234 -158
  4. smart_bot_factory/admin/admin_manager.py +68 -53
  5. smart_bot_factory/admin/admin_tester.py +46 -40
  6. smart_bot_factory/admin/timeout_checker.py +201 -153
  7. smart_bot_factory/aiogram_calendar/__init__.py +11 -3
  8. smart_bot_factory/aiogram_calendar/common.py +12 -18
  9. smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
  10. smart_bot_factory/aiogram_calendar/schemas.py +49 -28
  11. smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
  12. smart_bot_factory/analytics/analytics_manager.py +414 -392
  13. smart_bot_factory/cli.py +204 -148
  14. smart_bot_factory/config.py +123 -102
  15. smart_bot_factory/core/bot_utils.py +474 -332
  16. smart_bot_factory/core/conversation_manager.py +287 -200
  17. smart_bot_factory/core/decorators.py +1200 -755
  18. smart_bot_factory/core/message_sender.py +287 -266
  19. smart_bot_factory/core/router.py +170 -100
  20. smart_bot_factory/core/router_manager.py +121 -83
  21. smart_bot_factory/core/states.py +4 -3
  22. smart_bot_factory/creation/__init__.py +1 -1
  23. smart_bot_factory/creation/bot_builder.py +320 -242
  24. smart_bot_factory/creation/bot_testing.py +440 -365
  25. smart_bot_factory/dashboard/__init__.py +1 -3
  26. smart_bot_factory/event/__init__.py +2 -7
  27. smart_bot_factory/handlers/handlers.py +676 -472
  28. smart_bot_factory/integrations/openai_client.py +218 -168
  29. smart_bot_factory/integrations/supabase_client.py +948 -637
  30. smart_bot_factory/message/__init__.py +18 -22
  31. smart_bot_factory/router/__init__.py +2 -2
  32. smart_bot_factory/setup_checker.py +162 -126
  33. smart_bot_factory/supabase/__init__.py +1 -1
  34. smart_bot_factory/supabase/client.py +631 -515
  35. smart_bot_factory/utils/__init__.py +2 -3
  36. smart_bot_factory/utils/debug_routing.py +38 -27
  37. smart_bot_factory/utils/prompt_loader.py +153 -120
  38. smart_bot_factory/utils/user_prompt_loader.py +55 -56
  39. smart_bot_factory/utm_link_generator.py +123 -116
  40. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.9.dist-info/RECORD +59 -0
  42. smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
  43. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -2,9 +2,8 @@
2
2
  Utils модули smart_bot_factory
3
3
  """
4
4
 
5
-
6
5
  from .user_prompt_loader import UserPromptLoader
7
6
 
8
- __all__ = [ # Базовый класс (для библиотеки)
9
- 'UserPromptLoader', # Для пользователей (автопоиск prompts_dir)
7
+ __all__ = [ # Базовый класс (для библиотеки)
8
+ "UserPromptLoader", # Для пользователей (автопоиск prompts_dir)
10
9
  ]
@@ -1,103 +1,114 @@
1
1
  # debug_routing.py - Утилиты для отладки маршрутизации сообщений
2
2
 
3
3
  import logging
4
+
4
5
  from aiogram import Router
5
- from aiogram.types import Message
6
6
  from aiogram.fsm.context import FSMContext
7
+ from aiogram.types import Message
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
10
11
  # Создаем роутер для отладочных обработчиков
11
12
  debug_router = Router()
12
13
 
14
+
13
15
  def setup_debug_handlers(dp):
14
16
  """Настройка отладочных обработчиков"""
15
17
  dp.include_router(debug_router)
16
18
 
19
+
17
20
  # Функция для получения глобальных переменных
18
21
  def get_global_var(var_name):
19
22
  """Получает глобальную переменную из модуля debug_routing"""
20
23
  import sys
24
+
21
25
  current_module = sys.modules[__name__]
22
26
  return getattr(current_module, var_name, None)
23
27
 
28
+
24
29
  async def debug_user_state(message: Message, state: FSMContext, context: str):
25
30
  """Отладочная функция для логирования состояния пользователя"""
26
- conversation_manager = get_global_var('conversation_manager')
27
- supabase_client = get_global_var('supabase_client')
28
-
31
+ conversation_manager = get_global_var("conversation_manager")
32
+ supabase_client = get_global_var("supabase_client")
33
+
29
34
  user_id = message.from_user.id
30
35
  current_state = await state.get_state()
31
36
  state_data = await state.get_data()
32
-
37
+
33
38
  logger.info(f"🔍 DEBUG [{context}] User {user_id}:")
34
39
  logger.info(f" 📊 FSM State: {current_state}")
35
40
  logger.info(f" 📦 State Data: {list(state_data.keys())}")
36
41
  logger.info(f" 💬 Message: '{message.text[:50]}...'")
37
-
42
+
38
43
  # Проверяем диалог с админом в БД
39
44
  conversation = await conversation_manager.is_user_in_admin_chat(user_id)
40
45
  logger.info(f" 🗃️ Admin Chat in DB: {'✅' if conversation else '❌'}")
41
-
46
+
42
47
  if conversation:
43
48
  logger.info(f" 👑 Admin ID: {conversation['admin_id']}")
44
49
  logger.info(f" 🆔 Conversation ID: {conversation['id']}")
45
-
50
+
46
51
  # Проверяем активную сессию
47
52
  session_info = await supabase_client.get_active_session(user_id)
48
53
  logger.info(f" 🎯 Active Session: {'✅' if session_info else '❌'}")
49
-
54
+
50
55
  if session_info:
51
56
  logger.info(f" 📝 Session ID: {session_info['id']}")
52
-
57
+
53
58
  logger.info(f" {'='*50}")
54
59
 
60
+
55
61
  async def debug_admin_conversation_creation(admin_id: int, user_id: int):
56
62
  """Отладка создания диалога админа с пользователем"""
57
- supabase_client = get_global_var('supabase_client')
58
-
59
- logger.info(f"🔍 DEBUG CONVERSATION CREATION:")
63
+ supabase_client = get_global_var("supabase_client")
64
+
65
+ logger.info("🔍 DEBUG CONVERSATION CREATION:")
60
66
  logger.info(f" 👑 Admin: {admin_id}")
61
67
  logger.info(f" 👤 User: {user_id}")
62
-
68
+
63
69
  # Проверяем активную сессию пользователя ДО создания диалога
64
70
  session_info = await supabase_client.get_active_session(user_id)
65
71
  logger.info(f" 🎯 User has active session: {'✅' if session_info else '❌'}")
66
-
72
+
67
73
  if session_info:
68
74
  logger.info(f" 📝 Session ID: {session_info['id']}")
69
75
  logger.info(f" 📅 Session created: {session_info['created_at']}")
70
-
76
+
71
77
  # Проверяем существующие диалоги пользователя
72
78
  try:
73
- existing = supabase_client.client.table('admin_user_conversations').select(
74
- '*'
75
- ).eq('user_id', user_id).eq('status', 'active').execute()
76
-
79
+ existing = (
80
+ supabase_client.client.table("admin_user_conversations")
81
+ .select("*")
82
+ .eq("user_id", user_id)
83
+ .eq("status", "active")
84
+ .execute()
85
+ )
86
+
77
87
  logger.info(f" 💬 Existing active conversations: {len(existing.data)}")
78
88
  for conv in existing.data:
79
89
  logger.info(f" - ID: {conv['id']}, Admin: {conv['admin_id']}")
80
90
  except Exception as e:
81
91
  logger.error(f" ❌ Error checking existing conversations: {e}")
82
92
 
93
+
83
94
  async def test_message_routing(user_id: int, test_message: str):
84
95
  """Тестирует маршрутизацию сообщения без отправки через Telegram"""
85
- conversation_manager = get_global_var('conversation_manager')
86
-
87
- logger.info(f"🧪 TESTING MESSAGE ROUTING:")
96
+ conversation_manager = get_global_var("conversation_manager")
97
+
98
+ logger.info("🧪 TESTING MESSAGE ROUTING:")
88
99
  logger.info(f" 👤 User: {user_id}")
89
100
  logger.info(f" 💬 Message: '{test_message}'")
90
-
101
+
91
102
  # Проверяем есть ли диалог с админом
92
103
  conversation = await conversation_manager.is_user_in_admin_chat(user_id)
93
104
  logger.info(f" 🗃️ Admin conversation exists: {'✅' if conversation else '❌'}")
94
-
105
+
95
106
  if conversation:
96
107
  logger.info(f" 👑 Admin: {conversation['admin_id']}")
97
108
  logger.info(f" 🆔 Conv ID: {conversation['id']}")
98
109
  logger.info(f" 📅 Started: {conversation['started_at']}")
99
-
110
+
100
111
  # Тестируем должен ли этот пользователь быть в admin_chat
101
112
  return "admin_chat"
102
113
  else:
103
- return "bot_chat"
114
+ return "bot_chat"
@@ -1,44 +1,52 @@
1
1
  # Обновленный prompt_loader.py с поддержкой финальных инструкций
2
2
 
3
3
  import logging
4
- import aiofiles
5
4
  from pathlib import Path
6
- from typing import List, Dict
5
+ from typing import Dict
6
+
7
+ import aiofiles
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
11
+
10
12
  class PromptLoader:
11
13
  """Класс для загрузки промптов из локального каталога"""
12
-
14
+
13
15
  def __init__(self, prompts_dir: str):
14
- self.prompts_dir = Path(prompts_dir)
15
- self.welcome_file = self.prompts_dir / 'welcome_message.txt'
16
- self.help_file = self.prompts_dir / 'help_message.txt'
17
- self.final_instructions_file = self.prompts_dir / 'final_instructions.txt'
18
-
19
- # Автоматически находим все .txt файлы промптов (кроме специальных)
20
- all_txt_files = list(self.prompts_dir.glob('*.txt'))
21
- special_files = {'welcome_message.txt', 'help_message.txt', 'final_instructions.txt'}
22
- self.prompt_files = [f.name for f in all_txt_files if f.name not in special_files]
23
-
24
- logger.info(f"Инициализирован загрузчик промптов: {self.prompts_dir}")
25
- logger.info(f"Найдено файлов промптов: {len(self.prompt_files)}")
26
- logger.info(f"Файлы промптов: {self.prompt_files}")
16
+ self.prompts_dir = Path(prompts_dir)
17
+ self.welcome_file = self.prompts_dir / "welcome_message.txt"
18
+ self.help_file = self.prompts_dir / "help_message.txt"
19
+ self.final_instructions_file = self.prompts_dir / "final_instructions.txt"
20
+
21
+ # Автоматически находим все .txt файлы промптов (кроме специальных)
22
+ all_txt_files = list(self.prompts_dir.glob("*.txt"))
23
+ special_files = {
24
+ "welcome_message.txt",
25
+ "help_message.txt",
26
+ "final_instructions.txt",
27
+ }
28
+ self.prompt_files = [
29
+ f.name for f in all_txt_files if f.name not in special_files
30
+ ]
31
+
32
+ logger.info(f"Инициализирован загрузчик промптов: {self.prompts_dir}")
33
+ logger.info(f"Найдено файлов промптов: {len(self.prompt_files)}")
34
+ logger.info(f"Файлы промптов: {self.prompt_files}")
27
35
 
28
36
  async def load_system_prompt(self) -> str:
29
37
  """
30
38
  Загружает и объединяет все файлы промптов в один системный промпт
31
-
39
+
32
40
  Returns:
33
41
  Объединенный системный промпт с инструкциями по JSON
34
42
  """
35
43
  try:
36
44
  prompt_parts = []
37
-
45
+
38
46
  for filename in self.prompt_files:
39
47
  logger.debug(f"Загружаем промпт из {filename}")
40
48
  content = await self._load_file(filename)
41
-
49
+
42
50
  if content:
43
51
  # Добавляем заголовок секции
44
52
  section_name = self._get_section_name(filename)
@@ -47,23 +55,25 @@ class PromptLoader:
47
55
  prompt_parts.append("\n")
48
56
  else:
49
57
  logger.warning(f"Файл {filename} пуст")
50
-
58
+
51
59
  if not prompt_parts:
52
60
  error_msg = "Не удалось загрузить ни одного промпт файла"
53
61
  logger.error(error_msg)
54
62
  raise ValueError(error_msg)
55
-
63
+
56
64
  # Добавляем инструкции по JSON метаданным
57
65
  json_instructions = self._get_json_instructions()
58
66
  prompt_parts.append("\n")
59
67
  prompt_parts.append(json_instructions)
60
-
68
+
61
69
  # Объединяем все части
62
70
  full_prompt = "".join(prompt_parts).strip()
63
-
64
- logger.info(f"Системный промпт загружен успешно ({len(full_prompt)} символов)")
71
+
72
+ logger.info(
73
+ f"Системный промпт загружен успешно ({len(full_prompt)} символов)"
74
+ )
65
75
  return full_prompt
66
-
76
+
67
77
  except Exception as e:
68
78
  logger.error(f"Ошибка при загрузке системного промпта: {e}")
69
79
  raise
@@ -71,32 +81,40 @@ class PromptLoader:
71
81
  async def load_final_instructions(self) -> str:
72
82
  """
73
83
  Загружает финальные инструкции из final_instructions.txt
74
-
84
+
75
85
  Returns:
76
86
  Финальные инструкции или пустая строка если файла нет
77
87
  """
78
88
  try:
79
- logger.debug(f"Загружаем финальные инструкции из {self.final_instructions_file.name}")
80
-
89
+ logger.debug(
90
+ f"Загружаем финальные инструкции из {self.final_instructions_file.name}"
91
+ )
92
+
81
93
  if not self.final_instructions_file.exists():
82
- logger.debug(f"Файл {self.final_instructions_file.name} не найден - пропускаем")
94
+ logger.debug(
95
+ f"Файл {self.final_instructions_file.name} не найден - пропускаем"
96
+ )
83
97
  return ""
84
-
85
- async with aiofiles.open(self.final_instructions_file, 'r', encoding='utf-8') as f:
98
+
99
+ async with aiofiles.open(
100
+ self.final_instructions_file, "r", encoding="utf-8"
101
+ ) as f:
86
102
  content = await f.read()
87
-
103
+
88
104
  if not content.strip():
89
- logger.debug(f"Файл {self.final_instructions_file.name} пуст - пропускаем")
105
+ logger.debug(
106
+ f"Файл {self.final_instructions_file.name} пуст - пропускаем"
107
+ )
90
108
  return ""
91
-
109
+
92
110
  logger.info(f"Финальные инструкции загружены ({len(content)} символов)")
93
111
  return content.strip()
94
-
112
+
95
113
  except Exception as e:
96
114
  logger.error(f"Ошибка при загрузке финальных инструкций: {e}")
97
115
  # Не прерываем работу - финальные инструкции опциональны
98
116
  return ""
99
-
117
+
100
118
  def _get_json_instructions(self) -> str:
101
119
  """Возвращает инструкции по JSON метаданным для ИИ"""
102
120
  return """
@@ -223,59 +241,65 @@ class PromptLoader:
223
241
 
224
242
  ПОМНИ: Этот JSON критически важен для работы системы администрирования и аналитики!
225
243
  """
226
-
244
+
227
245
  async def load_welcome_message(self) -> str:
228
246
  """
229
247
  Загружает приветственное сообщение из welcome_message.txt
230
-
248
+
231
249
  Returns:
232
250
  Текст приветственного сообщения
233
251
  """
234
252
  try:
235
- logger.debug(f"Загружаем приветственное сообщение из {self.welcome_file.name}")
236
-
253
+ logger.debug(
254
+ f"Загружаем приветственное сообщение из {self.welcome_file.name}"
255
+ )
256
+
237
257
  if not self.welcome_file.exists():
238
258
  error_msg = f"Файл приветствия не найден: {self.welcome_file}"
239
259
  logger.error(error_msg)
240
260
  raise FileNotFoundError(error_msg)
241
-
242
- async with aiofiles.open(self.welcome_file, 'r', encoding='utf-8') as f:
261
+
262
+ async with aiofiles.open(self.welcome_file, "r", encoding="utf-8") as f:
243
263
  content = await f.read()
244
-
264
+
245
265
  if not content.strip():
246
266
  error_msg = f"Файл приветствия пуст: {self.welcome_file}"
247
267
  logger.error(error_msg)
248
268
  raise ValueError(error_msg)
249
-
269
+
250
270
  logger.info(f"Приветственное сообщение загружено ({len(content)} символов)")
251
271
  return content.strip()
252
-
272
+
253
273
  except Exception as e:
254
274
  logger.error(f"Ошибка при загрузке приветственного сообщения: {e}")
255
275
  raise
256
-
276
+
257
277
  async def load_help_message(self) -> str:
258
278
  """
259
279
  Загружает справочное сообщение из help_message.txt
260
-
280
+
261
281
  Returns:
262
282
  Текст справочного сообщения
263
283
  """
264
284
  try:
265
285
  logger.debug(f"Загружаем справочное сообщение из {self.help_file.name}")
266
-
286
+
267
287
  if self.help_file.exists():
268
- async with aiofiles.open(self.help_file, 'r', encoding='utf-8') as f:
288
+ async with aiofiles.open(self.help_file, "r", encoding="utf-8") as f:
269
289
  content = await f.read()
270
-
290
+
271
291
  if content.strip():
272
- logger.info(f"Справочное сообщение загружено ({len(content)} символов)")
292
+ logger.info(
293
+ f"Справочное сообщение загружено ({len(content)} символов)"
294
+ )
273
295
  return content.strip()
274
-
296
+
275
297
  # Fallback если файл не найден или пуст
276
- logger.warning("Файл help_message.txt не найден или пуст, используем дефолтную справку")
298
+ logger.warning(
299
+ "Файл help_message.txt не найден или пуст, используем дефолтную справку"
300
+ )
277
301
  return "🤖 **Ваш помощник готов к работе!**\n\n**Команды:**\n/start - Начать диалог\n/help - Показать справку\n/status - Проверить статус"
278
-
302
+
279
303
  except Exception as e:
280
304
  logger.error(f"Ошибка при загрузке справочного сообщения: {e}")
281
305
  # Возвращаем простую справку в случае ошибки
@@ -284,148 +308,157 @@ class PromptLoader:
284
308
  async def _load_file(self, filename: str) -> str:
285
309
  """Загружает содержимое файла из каталога промптов"""
286
310
  file_path = self.prompts_dir / filename
287
-
311
+
288
312
  try:
289
313
  if not file_path.exists():
290
314
  error_msg = f"Файл промпта не найден: {file_path}"
291
315
  logger.error(error_msg)
292
316
  raise FileNotFoundError(error_msg)
293
-
294
- async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
317
+
318
+ async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
295
319
  content = await f.read()
296
-
320
+
297
321
  if not content.strip():
298
322
  logger.warning(f"Файл {filename} пуст")
299
323
  return ""
300
-
324
+
301
325
  logger.debug(f"Загружен файл {filename} ({len(content)} символов)")
302
326
  return content
303
-
327
+
304
328
  except Exception as e:
305
329
  logger.error(f"Ошибка чтения файла {file_path}: {e}")
306
330
  raise
307
-
331
+
308
332
  def _get_section_name(self, filename: str) -> str:
309
333
  """Получает название секции по имени файла"""
310
334
  name_mapping = {
311
- 'system_prompt.txt': 'СИСТЕМНЫЙ ПРОМПТ',
312
- 'sales_context.txt': 'КОНТЕКСТ ПРОДАЖ',
313
- 'product_info.txt': 'ИНФОРМАЦИЯ О ПРОДУКТЕ',
314
- 'objection_handling.txt': 'ОБРАБОТКА ВОЗРАЖЕНИЙ',
315
- '1sales_context.txt': 'КОНТЕКСТ ПРОДАЖ',
316
- '2product_info.txt': 'ИНФОРМАЦИЯ О ПРОДУКТЕ',
317
- '3objection_handling.txt': 'ОБРАБОТКА ВОЗРАЖЕНИЙ',
318
- 'final_instructions.txt': 'ФИНАЛЬНЫЕ ИНСТРУКЦИИ' # 🆕
335
+ "system_prompt.txt": "СИСТЕМНЫЙ ПРОМПТ",
336
+ "sales_context.txt": "КОНТЕКСТ ПРОДАЖ",
337
+ "product_info.txt": "ИНФОРМАЦИЯ О ПРОДУКТЕ",
338
+ "objection_handling.txt": "ОБРАБОТКА ВОЗРАЖЕНИЙ",
339
+ "1sales_context.txt": "КОНТЕКСТ ПРОДАЖ",
340
+ "2product_info.txt": "ИНФОРМАЦИЯ О ПРОДУКТЕ",
341
+ "3objection_handling.txt": "ОБРАБОТКА ВОЗРАЖЕНИЙ",
342
+ "final_instructions.txt": "ФИНАЛЬНЫЕ ИНСТРУКЦИИ", # 🆕
319
343
  }
320
-
321
- return name_mapping.get(filename, filename.replace('.txt', '').upper())
322
-
344
+
345
+ return name_mapping.get(filename, filename.replace(".txt", "").upper())
346
+
323
347
  async def reload_prompts(self) -> str:
324
348
  """Перезагружает промпты (для обновления без перезапуска бота)"""
325
349
  logger.info("Перезагрузка промптов...")
326
350
  return await self.load_system_prompt()
327
-
351
+
328
352
  async def validate_prompts(self) -> Dict[str, bool]:
329
353
  """Проверяет доступность всех файлов промптов и приветственного сообщения"""
330
354
  results = {}
331
-
355
+
332
356
  # Проверяем файлы промптов
333
357
  for filename in self.prompt_files:
334
358
  file_path = self.prompts_dir / filename
335
359
  try:
336
360
  if file_path.exists():
337
- async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
361
+ async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
338
362
  content = await f.read()
339
- results[filename] = bool(content.strip() and len(content.strip()) > 10)
363
+ results[filename] = bool(
364
+ content.strip() and len(content.strip()) > 10
365
+ )
340
366
  else:
341
367
  results[filename] = False
342
368
  except Exception:
343
369
  results[filename] = False
344
-
370
+
345
371
  # Проверяем файл приветственного сообщения
346
372
  try:
347
373
  if self.welcome_file.exists():
348
- async with aiofiles.open(self.welcome_file, 'r', encoding='utf-8') as f:
374
+ async with aiofiles.open(self.welcome_file, "r", encoding="utf-8") as f:
349
375
  content = await f.read()
350
- results['welcome_message.txt'] = bool(content.strip() and len(content.strip()) > 5)
376
+ results["welcome_message.txt"] = bool(
377
+ content.strip() and len(content.strip()) > 5
378
+ )
351
379
  else:
352
- results['welcome_message.txt'] = False
380
+ results["welcome_message.txt"] = False
353
381
  except Exception:
354
- results['welcome_message.txt'] = False
355
-
382
+ results["welcome_message.txt"] = False
383
+
356
384
  # Проверяем файл справки (опционально)
357
385
  try:
358
386
  if self.help_file.exists():
359
- async with aiofiles.open(self.help_file, 'r', encoding='utf-8') as f:
387
+ async with aiofiles.open(self.help_file, "r", encoding="utf-8") as f:
360
388
  content = await f.read()
361
- results['help_message.txt'] = bool(content.strip() and len(content.strip()) > 5)
389
+ results["help_message.txt"] = bool(
390
+ content.strip() and len(content.strip()) > 5
391
+ )
362
392
  else:
363
- results['help_message.txt'] = False # Не критично
393
+ results["help_message.txt"] = False # Не критично
364
394
  except Exception:
365
- results['help_message.txt'] = False
366
-
395
+ results["help_message.txt"] = False
396
+
367
397
  # 🆕 Проверяем финальные инструкции (опционально)
368
398
  try:
369
399
  if self.final_instructions_file.exists():
370
- async with aiofiles.open(self.final_instructions_file, 'r', encoding='utf-8') as f:
400
+ async with aiofiles.open(
401
+ self.final_instructions_file, "r", encoding="utf-8"
402
+ ) as f:
371
403
  content = await f.read()
372
- results['final_instructions.txt'] = bool(content.strip() and len(content.strip()) > 5)
404
+ results["final_instructions.txt"] = bool(
405
+ content.strip() and len(content.strip()) > 5
406
+ )
373
407
  else:
374
- results['final_instructions.txt'] = False # Не критично - опциональный файл
408
+ results["final_instructions.txt"] = (
409
+ False # Не критично - опциональный файл
410
+ )
375
411
  except Exception:
376
- results['final_instructions.txt'] = False
377
-
412
+ results["final_instructions.txt"] = False
413
+
378
414
  return results
379
-
415
+
380
416
  def get_prompt_info(self) -> Dict[str, any]:
381
417
  """Возвращает информацию о конфигурации промптов"""
382
418
  return {
383
- 'prompts_dir': str(self.prompts_dir),
384
- 'prompt_files': self.prompt_files,
385
- 'welcome_file': 'welcome_message.txt',
386
- 'help_file': 'help_message.txt',
387
- 'final_instructions_file': 'final_instructions.txt', # 🆕
388
- 'total_files': len(self.prompt_files) + 1, # +1 для welcome message
389
- 'json_instructions_included': True
419
+ "prompts_dir": str(self.prompts_dir),
420
+ "prompt_files": self.prompt_files,
421
+ "welcome_file": "welcome_message.txt",
422
+ "help_file": "help_message.txt",
423
+ "final_instructions_file": "final_instructions.txt", # 🆕
424
+ "total_files": len(self.prompt_files) + 1, # +1 для welcome message
425
+ "json_instructions_included": True,
390
426
  }
391
-
427
+
392
428
  async def test_json_parsing(self, test_response: str) -> Dict[str, any]:
393
429
  """Тестирует парсинг JSON из ответа ИИ (для отладки)"""
394
430
  import json
395
431
  import re
396
-
432
+
397
433
  try:
398
434
  # Используем тот же алгоритм что и в main.py
399
435
  json_pattern = r'\{[^{}]*"этап"[^{}]*\}$'
400
436
  match = re.search(json_pattern, test_response.strip())
401
-
437
+
402
438
  if match:
403
439
  json_str = match.group(0)
404
- response_text = test_response[:match.start()].strip()
405
-
440
+ response_text = test_response[: match.start()].strip()
441
+
406
442
  try:
407
443
  metadata = json.loads(json_str)
408
444
  return {
409
- 'success': True,
410
- 'response_text': response_text,
411
- 'metadata': metadata,
412
- 'json_str': json_str
445
+ "success": True,
446
+ "response_text": response_text,
447
+ "metadata": metadata,
448
+ "json_str": json_str,
413
449
  }
414
450
  except json.JSONDecodeError as e:
415
451
  return {
416
- 'success': False,
417
- 'error': f"JSON decode error: {e}",
418
- 'json_str': json_str
452
+ "success": False,
453
+ "error": f"JSON decode error: {e}",
454
+ "json_str": json_str,
419
455
  }
420
456
  else:
421
457
  return {
422
- 'success': False,
423
- 'error': "JSON pattern not found",
424
- 'response_text': test_response
458
+ "success": False,
459
+ "error": "JSON pattern not found",
460
+ "response_text": test_response,
425
461
  }
426
-
462
+
427
463
  except Exception as e:
428
- return {
429
- 'success': False,
430
- 'error': f"Parse error: {e}"
431
- }
464
+ return {"success": False, "error": f"Parse error: {e}"}