smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.8__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 +1129 -749
  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 +928 -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.8.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.8.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.8.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
@@ -1,223 +1,244 @@
1
1
  # Обновленный config.py с автоопределением bot_id
2
2
 
3
- import os
4
3
  import logging
5
- from pathlib import Path
4
+ import os
6
5
  from dataclasses import dataclass, field
7
- from dotenv import load_dotenv
6
+ from pathlib import Path
8
7
  from typing import List
9
8
 
10
9
  # Переменные из .env файла загружаются в BotBuilder
11
10
 
12
11
  logger = logging.getLogger(__name__)
13
12
 
13
+
14
14
  @dataclass
15
15
  class Config:
16
16
  """Конфигурация приложения"""
17
-
17
+
18
18
  # 🆕 Bot ID автоматически из переменной окружения (устанавливается в запускалке)
19
19
  BOT_ID: str = field(init=False)
20
-
20
+
21
21
  # Telegram Bot Token
22
- TELEGRAM_BOT_TOKEN: str = os.getenv('TELEGRAM_BOT_TOKEN', '')
23
-
22
+ TELEGRAM_BOT_TOKEN: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
23
+
24
24
  # Supabase настройки
25
- SUPABASE_URL: str = os.getenv('SUPABASE_URL', '')
26
- SUPABASE_KEY: str = os.getenv('SUPABASE_KEY', '')
27
-
25
+ SUPABASE_URL: str = os.getenv("SUPABASE_URL", "")
26
+ SUPABASE_KEY: str = os.getenv("SUPABASE_KEY", "")
27
+
28
28
  # OpenAI настройки
29
- OPENAI_API_KEY: str = os.getenv('OPENAI_API_KEY', '')
30
- OPENAI_MODEL: str = os.getenv('OPENAI_MODEL', 'gpt-5-mini')
31
- OPENAI_MAX_TOKENS: int = int(os.getenv('OPENAI_MAX_TOKENS', '1500'))
32
- OPENAI_TEMPERATURE: float = float(os.getenv('OPENAI_TEMPERATURE', '0.7'))
33
-
29
+ OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
30
+ OPENAI_MODEL: str = os.getenv("OPENAI_MODEL", "gpt-5-mini")
31
+ OPENAI_MAX_TOKENS: int = int(os.getenv("OPENAI_MAX_TOKENS", "1500"))
32
+ OPENAI_TEMPERATURE: float = float(os.getenv("OPENAI_TEMPERATURE", "0.7"))
33
+
34
34
  # Каталог с файлами промптов
35
- PROMT_FILES_DIR: str = os.getenv('PROMT_FILES_DIR', 'prompts')
36
-
35
+ PROMT_FILES_DIR: str = os.getenv("PROMT_FILES_DIR", "prompts")
36
+
37
37
  # Файл после стартого сообщения
38
- WELCOME_FILE_DIR: str = os.getenv('WELCOME_FILE_URL', 'welcome_file')
39
- WELCOME_FILE_MSG: str = os.getenv('WELCOME_FILE_MSG', 'welcome_file_msg.txt')
40
-
38
+ WELCOME_FILE_DIR: str = os.getenv("WELCOME_FILE_URL", "welcome_file")
39
+ WELCOME_FILE_MSG: str = os.getenv("WELCOME_FILE_MSG", "welcome_file_msg.txt")
40
+
41
41
  # Настройки базы данных
42
- MAX_CONTEXT_MESSAGES: int = int(os.getenv('MAX_CONTEXT_MESSAGES', '50'))
43
-
42
+ MAX_CONTEXT_MESSAGES: int = int(os.getenv("MAX_CONTEXT_MESSAGES", "50"))
43
+
44
44
  # Настройки логирования
45
- LOG_LEVEL: str = os.getenv('LOG_LEVEL', 'INFO')
45
+ LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
46
46
 
47
47
  # Настройки продаж
48
- LEAD_QUALIFICATION_THRESHOLD: int = int(os.getenv('LEAD_QUALIFICATION_THRESHOLD', '7'))
49
- SESSION_TIMEOUT_HOURS: int = int(os.getenv('SESSION_TIMEOUT_HOURS', '24'))
50
-
48
+ LEAD_QUALIFICATION_THRESHOLD: int = int(
49
+ os.getenv("LEAD_QUALIFICATION_THRESHOLD", "7")
50
+ )
51
+ SESSION_TIMEOUT_HOURS: int = int(os.getenv("SESSION_TIMEOUT_HOURS", "24"))
52
+
51
53
  # Настройки форматирования сообщений
52
- MESSAGE_PARSE_MODE: str = os.getenv('MESSAGE_PARSE_MODE', 'Markdown')
54
+ MESSAGE_PARSE_MODE: str = os.getenv("MESSAGE_PARSE_MODE", "Markdown")
53
55
 
54
56
  # Список найденных файлов промптов (заполняется автоматически)
55
57
  PROMPT_FILES: List[str] = field(default_factory=list)
56
58
 
57
59
  # Администраторы (заполняется из переменной окружения)
58
60
  ADMIN_TELEGRAM_IDS: List[int] = field(default_factory=list)
59
- ADMIN_SESSION_TIMEOUT_MINUTES: int = int(os.getenv('ADMIN_SESSION_TIMEOUT_MINUTES', '30'))
60
-
61
+ ADMIN_SESSION_TIMEOUT_MINUTES: int = int(
62
+ os.getenv("ADMIN_SESSION_TIMEOUT_MINUTES", "30")
63
+ )
64
+
61
65
  # Режим отладки - показывать JSON пользователю
62
- DEBUG_MODE: bool = os.getenv('DEBUG_MODE', 'false').lower() == 'true'
66
+ DEBUG_MODE: bool = os.getenv("DEBUG_MODE", "false").lower() == "true"
63
67
 
64
68
  def __post_init__(self):
65
69
  """Проверка обязательных параметров и инициализация"""
66
-
70
+
67
71
  # 🆕 Автоматически получаем BOT_ID из переменной окружения
68
- self.BOT_ID = os.getenv('BOT_ID', '')
69
-
72
+ self.BOT_ID = os.getenv("BOT_ID", "")
73
+
70
74
  if not self.BOT_ID or not self.BOT_ID.strip():
71
75
  error_msg = "BOT_ID не установлен. Запускайте бота через запускалку (например: python growthmed-october-24.py)"
72
76
  logger.error(error_msg)
73
77
  raise ValueError(error_msg)
74
-
78
+
75
79
  # Проверяем формат BOT_ID
76
80
  import re
77
- if not re.match(r'^[a-z0-9\-]+$', self.BOT_ID):
81
+
82
+ if not re.match(r"^[a-z0-9\-]+$", self.BOT_ID):
78
83
  error_msg = f"BOT_ID должен содержать только латинские буквы, цифры и дефисы: {self.BOT_ID}"
79
84
  logger.error(error_msg)
80
85
  raise ValueError(error_msg)
81
-
86
+
82
87
  # Сканируем каталог с промптами
83
88
  self._scan_prompt_files()
84
-
89
+
85
90
  # Парсим список админов
86
91
  self._parse_admin_ids()
87
-
92
+
88
93
  # Проверяем обязательные поля
89
94
  required_fields = [
90
- 'TELEGRAM_BOT_TOKEN',
91
- 'SUPABASE_URL',
92
- 'SUPABASE_KEY',
93
- 'OPENAI_API_KEY'
95
+ "TELEGRAM_BOT_TOKEN",
96
+ "SUPABASE_URL",
97
+ "SUPABASE_KEY",
98
+ "OPENAI_API_KEY",
94
99
  ]
95
-
100
+
96
101
  missing_fields = []
97
102
  for field_name in required_fields:
98
103
  if not getattr(self, field_name):
99
104
  missing_fields.append(field_name)
100
-
105
+
101
106
  if missing_fields:
102
107
  error_msg = f"Отсутствуют обязательные переменные окружения: {', '.join(missing_fields)}"
103
108
  logger.error(error_msg)
104
109
  raise ValueError(error_msg)
105
-
110
+
106
111
  # Настройка уровня логирования
107
112
  log_level = getattr(logging, self.LOG_LEVEL.upper(), logging.INFO)
108
-
113
+
109
114
  # Настраиваем корневой логгер
110
115
  root_logger = logging.getLogger()
111
116
  root_logger.setLevel(log_level)
112
-
117
+
113
118
  # Настраиваем форматтер
114
119
  if not root_logger.handlers:
115
120
  handler = logging.StreamHandler()
116
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
121
+ formatter = logging.Formatter(
122
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
123
+ )
117
124
  handler.setFormatter(formatter)
118
125
  root_logger.addHandler(handler)
119
-
126
+
120
127
  # Устанавливаем уровень для всех модулей
121
- logging.getLogger('openai_client').setLevel(log_level)
122
- logging.getLogger('supabase_client').setLevel(log_level)
123
- logging.getLogger('handlers').setLevel(log_level)
124
- logging.getLogger('bot_utils').setLevel(log_level)
125
- logging.getLogger('conversation_manager').setLevel(log_level)
126
- logging.getLogger('admin_manager').setLevel(log_level)
127
- logging.getLogger('prompt_loader').setLevel(log_level)
128
- logging.getLogger('admin_logic').setLevel(log_level)
129
- logging.getLogger('debug_routing').setLevel(log_level)
130
-
128
+ logging.getLogger("openai_client").setLevel(log_level)
129
+ logging.getLogger("supabase_client").setLevel(log_level)
130
+ logging.getLogger("handlers").setLevel(log_level)
131
+ logging.getLogger("bot_utils").setLevel(log_level)
132
+ logging.getLogger("conversation_manager").setLevel(log_level)
133
+ logging.getLogger("admin_manager").setLevel(log_level)
134
+ logging.getLogger("prompt_loader").setLevel(log_level)
135
+ logging.getLogger("admin_logic").setLevel(log_level)
136
+ logging.getLogger("debug_routing").setLevel(log_level)
137
+
131
138
  # Валидация значений
132
139
  if self.OPENAI_MAX_TOKENS < 100 or self.OPENAI_MAX_TOKENS > 4000:
133
- logger.warning(f"Необычное значение OPENAI_MAX_TOKENS: {self.OPENAI_MAX_TOKENS}")
134
-
140
+ logger.warning(
141
+ f"Необычное значение OPENAI_MAX_TOKENS: {self.OPENAI_MAX_TOKENS}"
142
+ )
143
+
135
144
  if self.OPENAI_TEMPERATURE < 0 or self.OPENAI_TEMPERATURE > 1:
136
- logger.warning(f"Необычное значение OPENAI_TEMPERATURE: {self.OPENAI_TEMPERATURE}")
137
-
138
- logger.info(f"✅ Конфигурация загружена успешно")
145
+ logger.warning(
146
+ f"Необычное значение OPENAI_TEMPERATURE: {self.OPENAI_TEMPERATURE}"
147
+ )
148
+
149
+ logger.info("✅ Конфигурация загружена успешно")
139
150
  logger.info(f"🤖 Bot ID (автоопределен): {self.BOT_ID}")
140
151
  logger.debug(f"Модель OpenAI: {self.OPENAI_MODEL}")
141
152
  logger.debug(f"Каталог промптов: {self.PROMT_FILES_DIR}")
142
153
  logger.debug(f"Найдено файлов: {len(self.PROMPT_FILES)}")
143
154
  logger.info(f"👥 Админов настроено: {len(self.ADMIN_TELEGRAM_IDS)}")
144
155
  if self.DEBUG_MODE:
145
- logger.warning("🐛 Режим отладки ВКЛЮЧЕН - JSON будет показываться пользователям")
156
+ logger.warning(
157
+ "🐛 Режим отладки ВКЛЮЧЕН - JSON будет показываться пользователям"
158
+ )
146
159
 
147
160
  def get_summary(self) -> dict:
148
161
  """Возвращает сводку конфигурации (без секретов)"""
149
162
  return {
150
- 'bot_id': self.BOT_ID, # 🆕 автоопределенный
151
- 'openai_model': self.OPENAI_MODEL,
152
- 'max_tokens': self.OPENAI_MAX_TOKENS,
153
- 'temperature': self.OPENAI_TEMPERATURE,
154
- 'prompts_dir': self.PROMT_FILES_DIR,
155
- 'max_context': self.MAX_CONTEXT_MESSAGES,
156
- 'log_level': self.LOG_LEVEL,
157
- 'prompt_files_count': len(self.PROMPT_FILES),
158
- 'prompt_files': self.PROMPT_FILES,
159
- 'has_telegram_token': bool(self.TELEGRAM_BOT_TOKEN),
160
- 'has_supabase_config': bool(self.SUPABASE_URL and self.SUPABASE_KEY),
161
- 'has_openai_key': bool(self.OPENAI_API_KEY),
162
- 'admin_count': len(self.ADMIN_TELEGRAM_IDS),
163
- 'debug_mode': self.DEBUG_MODE
163
+ "bot_id": self.BOT_ID, # 🆕 автоопределенный
164
+ "openai_model": self.OPENAI_MODEL,
165
+ "max_tokens": self.OPENAI_MAX_TOKENS,
166
+ "temperature": self.OPENAI_TEMPERATURE,
167
+ "prompts_dir": self.PROMT_FILES_DIR,
168
+ "max_context": self.MAX_CONTEXT_MESSAGES,
169
+ "log_level": self.LOG_LEVEL,
170
+ "prompt_files_count": len(self.PROMPT_FILES),
171
+ "prompt_files": self.PROMPT_FILES,
172
+ "has_telegram_token": bool(self.TELEGRAM_BOT_TOKEN),
173
+ "has_supabase_config": bool(self.SUPABASE_URL and self.SUPABASE_KEY),
174
+ "has_openai_key": bool(self.OPENAI_API_KEY),
175
+ "admin_count": len(self.ADMIN_TELEGRAM_IDS),
176
+ "debug_mode": self.DEBUG_MODE,
164
177
  }
165
178
 
166
179
  def _parse_admin_ids(self):
167
180
  """Парсит список ID админов из переменной окружения"""
168
- admin_ids_str = os.getenv('ADMIN_TELEGRAM_IDS', '')
169
-
181
+ admin_ids_str = os.getenv("ADMIN_TELEGRAM_IDS", "")
182
+
170
183
  if not admin_ids_str.strip():
171
- logger.warning("⚠️ ADMIN_TELEGRAM_IDS не настроен - админские функции недоступны")
184
+ logger.warning(
185
+ "⚠️ ADMIN_TELEGRAM_IDS не настроен - админские функции недоступны"
186
+ )
172
187
  return
173
-
188
+
174
189
  try:
175
190
  # Парсим строку вида "123456,789012,345678"
176
- ids = admin_ids_str.split(',')
191
+ ids = admin_ids_str.split(",")
177
192
  admin_ids = [int(id_str.strip()) for id_str in ids if id_str.strip()]
178
-
193
+
179
194
  if not admin_ids:
180
- logger.warning("⚠️ ADMIN_TELEGRAM_IDS пуст - админские функции недоступны")
195
+ logger.warning(
196
+ "⚠️ ADMIN_TELEGRAM_IDS пуст - админские функции недоступны"
197
+ )
181
198
  else:
182
199
  self.ADMIN_TELEGRAM_IDS.extend(admin_ids)
183
200
  logger.info(f"👥 Загружены админы: {self.ADMIN_TELEGRAM_IDS}")
184
-
201
+
185
202
  except ValueError as e:
186
203
  logger.error(f"❌ Ошибка парсинга ADMIN_TELEGRAM_IDS: {e}")
187
- logger.error(f" Формат должен быть: ADMIN_TELEGRAM_IDS=123456,789012")
188
-
204
+ logger.error(" Формат должен быть: ADMIN_TELEGRAM_IDS=123456,789012")
205
+
189
206
  def _scan_prompt_files(self):
190
207
  """Сканирует каталог с промптами и проверяет необходимые файлы"""
191
208
  prompts_dir = Path(self.PROMT_FILES_DIR).absolute()
192
-
209
+
193
210
  # Проверяем существование каталога
194
211
  if not prompts_dir.exists():
195
212
  error_msg = f"Каталог с промптами не найден: {prompts_dir.absolute()}"
196
213
  logger.error(error_msg)
197
214
  raise FileNotFoundError(error_msg)
198
-
215
+
199
216
  if not prompts_dir.is_dir():
200
217
  error_msg = f"Путь не является каталогом: {prompts_dir.absolute()}"
201
218
  logger.error(error_msg)
202
219
  raise NotADirectoryError(error_msg)
203
-
220
+
204
221
  # Ищем все .txt файлы
205
- txt_files = list(prompts_dir.glob('*.txt'))
206
-
222
+ txt_files = list(prompts_dir.glob("*.txt"))
223
+
207
224
  if not txt_files:
208
- error_msg = f"В каталоге {prompts_dir.absolute()} не найдено ни одного .txt файла"
225
+ error_msg = (
226
+ f"В каталоге {prompts_dir.absolute()} не найдено ни одного .txt файла"
227
+ )
209
228
  logger.error(error_msg)
210
229
  raise FileNotFoundError(error_msg)
211
-
230
+
212
231
  # Проверяем обязательное наличие welcome_message.txt
213
- welcome_file = prompts_dir / 'welcome_message.txt'
232
+ welcome_file = prompts_dir / "welcome_message.txt"
214
233
  if not welcome_file.exists():
215
- error_msg = f"Обязательный файл welcome_message.txt не найден в {prompts_dir}"
234
+ error_msg = (
235
+ f"Обязательный файл welcome_message.txt не найден в {prompts_dir}"
236
+ )
216
237
  logger.error(error_msg)
217
238
  raise FileNotFoundError(error_msg)
218
239
 
219
240
  # Формируем список файлов промптов (исключая welcome_message.txt и help_message.txt)
220
- excluded_files = {'welcome_message.txt', 'help_message.txt'}
241
+ excluded_files = {"welcome_message.txt", "help_message.txt"}
221
242
  for txt_file in txt_files:
222
243
  if txt_file.name not in excluded_files:
223
244
  self.PROMPT_FILES.append(txt_file.name)
@@ -226,10 +247,10 @@ class Config:
226
247
  error_msg = f"В каталоге {prompts_dir.absolute()} найден только welcome_message.txt, но нет других файлов промптов"
227
248
  logger.error(error_msg)
228
249
  raise FileNotFoundError(error_msg)
229
-
250
+
230
251
  # Сортируем для предсказуемого порядка
231
252
  self.PROMPT_FILES.sort()
232
-
253
+
233
254
  logger.info(f"📁 Найдено файлов промптов: {len(self.PROMPT_FILES)}")
234
255
  logger.info(f"📝 Файлы промптов: {', '.join(self.PROMPT_FILES)}")
235
- logger.info(f"👋 Файл приветствия: welcome_message.txt")
256
+ logger.info("👋 Файл приветствия: welcome_message.txt")