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.
- smart_bot_factory/admin/__init__.py +7 -7
- smart_bot_factory/admin/admin_events.py +483 -383
- smart_bot_factory/admin/admin_logic.py +234 -158
- smart_bot_factory/admin/admin_manager.py +68 -53
- smart_bot_factory/admin/admin_tester.py +46 -40
- smart_bot_factory/admin/timeout_checker.py +201 -153
- smart_bot_factory/aiogram_calendar/__init__.py +11 -3
- smart_bot_factory/aiogram_calendar/common.py +12 -18
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
- smart_bot_factory/aiogram_calendar/schemas.py +49 -28
- smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
- smart_bot_factory/analytics/analytics_manager.py +414 -392
- smart_bot_factory/cli.py +204 -148
- smart_bot_factory/config.py +123 -102
- smart_bot_factory/core/bot_utils.py +474 -332
- smart_bot_factory/core/conversation_manager.py +287 -200
- smart_bot_factory/core/decorators.py +1129 -749
- smart_bot_factory/core/message_sender.py +287 -266
- smart_bot_factory/core/router.py +170 -100
- smart_bot_factory/core/router_manager.py +121 -83
- smart_bot_factory/core/states.py +4 -3
- smart_bot_factory/creation/__init__.py +1 -1
- smart_bot_factory/creation/bot_builder.py +320 -242
- smart_bot_factory/creation/bot_testing.py +440 -365
- smart_bot_factory/dashboard/__init__.py +1 -3
- smart_bot_factory/event/__init__.py +2 -7
- smart_bot_factory/handlers/handlers.py +676 -472
- smart_bot_factory/integrations/openai_client.py +218 -168
- smart_bot_factory/integrations/supabase_client.py +928 -637
- smart_bot_factory/message/__init__.py +18 -22
- smart_bot_factory/router/__init__.py +2 -2
- smart_bot_factory/setup_checker.py +162 -126
- smart_bot_factory/supabase/__init__.py +1 -1
- smart_bot_factory/supabase/client.py +631 -515
- smart_bot_factory/utils/__init__.py +2 -3
- smart_bot_factory/utils/debug_routing.py +38 -27
- smart_bot_factory/utils/prompt_loader.py +153 -120
- smart_bot_factory/utils/user_prompt_loader.py +55 -56
- smart_bot_factory/utm_link_generator.py +123 -116
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
- smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
- smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
smart_bot_factory/config.py
CHANGED
|
@@ -1,223 +1,244 @@
|
|
|
1
1
|
# Обновленный config.py с автоопределением bot_id
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import logging
|
|
5
|
-
|
|
4
|
+
import os
|
|
6
5
|
from dataclasses import dataclass, field
|
|
7
|
-
from
|
|
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(
|
|
23
|
-
|
|
22
|
+
TELEGRAM_BOT_TOKEN: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
|
|
23
|
+
|
|
24
24
|
# Supabase настройки
|
|
25
|
-
SUPABASE_URL: str = os.getenv(
|
|
26
|
-
SUPABASE_KEY: str = os.getenv(
|
|
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(
|
|
30
|
-
OPENAI_MODEL: str = os.getenv(
|
|
31
|
-
OPENAI_MAX_TOKENS: int = int(os.getenv(
|
|
32
|
-
OPENAI_TEMPERATURE: float = float(os.getenv(
|
|
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(
|
|
36
|
-
|
|
35
|
+
PROMT_FILES_DIR: str = os.getenv("PROMT_FILES_DIR", "prompts")
|
|
36
|
+
|
|
37
37
|
# Файл после стартого сообщения
|
|
38
|
-
WELCOME_FILE_DIR: str = os.getenv(
|
|
39
|
-
WELCOME_FILE_MSG: str = os.getenv(
|
|
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(
|
|
43
|
-
|
|
42
|
+
MAX_CONTEXT_MESSAGES: int = int(os.getenv("MAX_CONTEXT_MESSAGES", "50"))
|
|
43
|
+
|
|
44
44
|
# Настройки логирования
|
|
45
|
-
LOG_LEVEL: str = os.getenv(
|
|
45
|
+
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
|
46
46
|
|
|
47
47
|
# Настройки продаж
|
|
48
|
-
LEAD_QUALIFICATION_THRESHOLD: int = int(
|
|
49
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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(
|
|
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(
|
|
122
|
-
logging.getLogger(
|
|
123
|
-
logging.getLogger(
|
|
124
|
-
logging.getLogger(
|
|
125
|
-
logging.getLogger(
|
|
126
|
-
logging.getLogger(
|
|
127
|
-
logging.getLogger(
|
|
128
|
-
logging.getLogger(
|
|
129
|
-
logging.getLogger(
|
|
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(
|
|
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(
|
|
137
|
-
|
|
138
|
-
|
|
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(
|
|
156
|
+
logger.warning(
|
|
157
|
+
"🐛 Режим отладки ВКЛЮЧЕН - JSON будет показываться пользователям"
|
|
158
|
+
)
|
|
146
159
|
|
|
147
160
|
def get_summary(self) -> dict:
|
|
148
161
|
"""Возвращает сводку конфигурации (без секретов)"""
|
|
149
162
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
169
|
-
|
|
181
|
+
admin_ids_str = os.getenv("ADMIN_TELEGRAM_IDS", "")
|
|
182
|
+
|
|
170
183
|
if not admin_ids_str.strip():
|
|
171
|
-
logger.warning(
|
|
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(
|
|
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(
|
|
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(
|
|
206
|
-
|
|
222
|
+
txt_files = list(prompts_dir.glob("*.txt"))
|
|
223
|
+
|
|
207
224
|
if not txt_files:
|
|
208
|
-
error_msg =
|
|
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 /
|
|
232
|
+
welcome_file = prompts_dir / "welcome_message.txt"
|
|
214
233
|
if not welcome_file.exists():
|
|
215
|
-
error_msg =
|
|
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 = {
|
|
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(
|
|
256
|
+
logger.info("👋 Файл приветствия: welcome_message.txt")
|