smart-bot-factory 1.1.1__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.
- smart_bot_factory/__init__.py +3 -0
- smart_bot_factory/admin/__init__.py +18 -0
- smart_bot_factory/admin/admin_events.py +1223 -0
- smart_bot_factory/admin/admin_logic.py +553 -0
- smart_bot_factory/admin/admin_manager.py +156 -0
- smart_bot_factory/admin/admin_tester.py +157 -0
- smart_bot_factory/admin/timeout_checker.py +547 -0
- smart_bot_factory/aiogram_calendar/__init__.py +14 -0
- smart_bot_factory/aiogram_calendar/common.py +64 -0
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +259 -0
- smart_bot_factory/aiogram_calendar/schemas.py +99 -0
- smart_bot_factory/aiogram_calendar/simple_calendar.py +224 -0
- smart_bot_factory/analytics/analytics_manager.py +414 -0
- smart_bot_factory/cli.py +806 -0
- smart_bot_factory/config.py +258 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
- smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +133 -0
- smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
- smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
- smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
- smart_bot_factory/configs/growthmed-october-24/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
- smart_bot_factory/core/bot_utils.py +1108 -0
- smart_bot_factory/core/conversation_manager.py +653 -0
- smart_bot_factory/core/decorators.py +2464 -0
- smart_bot_factory/core/message_sender.py +729 -0
- smart_bot_factory/core/router.py +347 -0
- smart_bot_factory/core/router_manager.py +218 -0
- smart_bot_factory/core/states.py +27 -0
- smart_bot_factory/creation/__init__.py +7 -0
- smart_bot_factory/creation/bot_builder.py +1093 -0
- smart_bot_factory/creation/bot_testing.py +1122 -0
- smart_bot_factory/dashboard/__init__.py +3 -0
- smart_bot_factory/event/__init__.py +7 -0
- smart_bot_factory/handlers/handlers.py +2013 -0
- smart_bot_factory/integrations/langchain_openai.py +542 -0
- smart_bot_factory/integrations/openai_client.py +513 -0
- smart_bot_factory/integrations/supabase_client.py +1678 -0
- smart_bot_factory/memory/__init__.py +8 -0
- smart_bot_factory/memory/memory_manager.py +299 -0
- smart_bot_factory/memory/static_memory.py +214 -0
- smart_bot_factory/message/__init__.py +56 -0
- smart_bot_factory/rag/__init__.py +5 -0
- smart_bot_factory/rag/decorators.py +29 -0
- smart_bot_factory/rag/router.py +54 -0
- smart_bot_factory/rag/templates/__init__.py +3 -0
- smart_bot_factory/rag/templates/create_table.sql +7 -0
- smart_bot_factory/rag/templates/create_table_and_function_template.py +94 -0
- smart_bot_factory/rag/templates/match_function.sql +61 -0
- smart_bot_factory/rag/templates/match_services_template.py +82 -0
- smart_bot_factory/rag/vectorstore.py +449 -0
- smart_bot_factory/router/__init__.py +10 -0
- smart_bot_factory/setup_checker.py +512 -0
- smart_bot_factory/supabase/__init__.py +7 -0
- smart_bot_factory/supabase/client.py +631 -0
- smart_bot_factory/utils/__init__.py +11 -0
- smart_bot_factory/utils/debug_routing.py +114 -0
- smart_bot_factory/utils/prompt_loader.py +529 -0
- smart_bot_factory/utils/tool_router.py +68 -0
- smart_bot_factory/utils/user_prompt_loader.py +55 -0
- smart_bot_factory/utm_link_generator.py +123 -0
- smart_bot_factory-1.1.1.dist-info/METADATA +1135 -0
- smart_bot_factory-1.1.1.dist-info/RECORD +73 -0
- smart_bot_factory-1.1.1.dist-info/WHEEL +4 -0
- smart_bot_factory-1.1.1.dist-info/entry_points.txt +2 -0
- smart_bot_factory-1.1.1.dist-info/licenses/LICENSE +24 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Модуль для проверки корректности таймаутов диалогов админов
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from datetime import datetime, timezone, timedelta
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from ..admin.admin_manager import AdminManager
|
|
14
|
+
from ..config import Config
|
|
15
|
+
from ..core.conversation_manager import ConversationManager
|
|
16
|
+
from ..integrations.supabase_client import SupabaseClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def setup_bot_environment(bot_name: str = "growthmed-october-24") -> Optional[Path]:
|
|
22
|
+
"""Настраивает окружение для указанного бота с автоопределением BOT_ID"""
|
|
23
|
+
root_dir = Path(os.getcwd()) # Используем текущую директорию как корневую
|
|
24
|
+
config_dir = root_dir / "bots" / bot_name
|
|
25
|
+
|
|
26
|
+
logger.info(f"🔍 Ищем конфигурацию бота в: {config_dir}")
|
|
27
|
+
|
|
28
|
+
# Сохраняем оригинальный путь для возврата
|
|
29
|
+
original_cwd = os.getcwd()
|
|
30
|
+
|
|
31
|
+
if not config_dir.exists():
|
|
32
|
+
logger.error(f"❌ Папка конфигурации не найдена: {config_dir}")
|
|
33
|
+
logger.info(" Доступные боты:")
|
|
34
|
+
bots_dir = root_dir / "bots"
|
|
35
|
+
if bots_dir.exists():
|
|
36
|
+
for bot_dir in bots_dir.iterdir():
|
|
37
|
+
if bot_dir.is_dir():
|
|
38
|
+
logger.info(f" - {bot_dir.name}")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# Проверяем наличие промптов
|
|
42
|
+
prompts_dir = config_dir / "prompts"
|
|
43
|
+
if not prompts_dir.exists():
|
|
44
|
+
logger.error(f"❌ Папка с промптами не найдена: {prompts_dir}")
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
logger.info(f"✅ Найдена папка промптов: {prompts_dir}")
|
|
48
|
+
|
|
49
|
+
# Устанавливаем BOT_ID из имени бота
|
|
50
|
+
os.environ["BOT_ID"] = bot_name
|
|
51
|
+
logger.info(f"🤖 Автоматически установлен BOT_ID: {bot_name}")
|
|
52
|
+
|
|
53
|
+
# Загружаем .env из конфигурации бота
|
|
54
|
+
env_file = config_dir / ".env"
|
|
55
|
+
if env_file.exists():
|
|
56
|
+
logger.info(f"🔧 Загружаем .env из: {env_file}")
|
|
57
|
+
from dotenv import load_dotenv
|
|
58
|
+
|
|
59
|
+
load_dotenv(env_file)
|
|
60
|
+
else:
|
|
61
|
+
logger.error(f"❌ Файл .env не найден: {env_file}")
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# Сохраняем текущую директорию и меняем её
|
|
65
|
+
original_cwd = os.getcwd()
|
|
66
|
+
os.chdir(str(config_dir))
|
|
67
|
+
logger.info(f"📁 Изменена рабочая директория: {os.getcwd()}")
|
|
68
|
+
|
|
69
|
+
# Проверяем что промпты доступны относительно новой директории
|
|
70
|
+
local_prompts = Path("prompts")
|
|
71
|
+
if local_prompts.exists():
|
|
72
|
+
logger.info(
|
|
73
|
+
f"✅ Промпты доступны из рабочей директории: {local_prompts.absolute()}"
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
logger.error(
|
|
77
|
+
f"❌ Промпты не найдены в рабочей директории: {local_prompts.absolute()}"
|
|
78
|
+
)
|
|
79
|
+
os.chdir(original_cwd) # Восстанавливаем директорию
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
return config_dir
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def debug_timeout_issue(bot_name: str = "growthmed-october-24") -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Диагностирует проблему с таймаутом диалогов
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
bot_name: Имя бота для диагностики
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
bool: True если диагностика прошла успешно, False если найдены проблемы
|
|
94
|
+
"""
|
|
95
|
+
logger.info("🔍 Диагностика проблемы с таймаутом диалогов\n")
|
|
96
|
+
logger.info(f"🚀 Диагностика для бота: {bot_name}")
|
|
97
|
+
logger.info(f"🤖 Bot ID будет автоопределен как: {bot_name}\n")
|
|
98
|
+
|
|
99
|
+
# Настраиваем окружение для бота (автоматически устанавливает BOT_ID)
|
|
100
|
+
config_dir = setup_bot_environment(bot_name)
|
|
101
|
+
if not config_dir:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
# Инициализируем конфигурацию
|
|
105
|
+
config = Config()
|
|
106
|
+
logger.info("📋 Конфигурация:")
|
|
107
|
+
logger.info(f" BOT_ID: {config.BOT_ID}")
|
|
108
|
+
logger.info(
|
|
109
|
+
f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}"
|
|
110
|
+
)
|
|
111
|
+
logger.info(f" PROMT_FILES_DIR: {config.PROMT_FILES_DIR}")
|
|
112
|
+
logger.info(f" Найдено промпт-файлов: {len(config.PROMPT_FILES)}")
|
|
113
|
+
logger.info("")
|
|
114
|
+
|
|
115
|
+
# Проверяем часовые пояса
|
|
116
|
+
logger.info("🕐 Временные зоны:")
|
|
117
|
+
now_naive = datetime.now()
|
|
118
|
+
now_utc = datetime.now(timezone.utc)
|
|
119
|
+
logger.info(f" datetime.now() (локальное): {now_naive}")
|
|
120
|
+
logger.info(f" datetime.now(timezone.utc): {now_utc}")
|
|
121
|
+
logger.info(
|
|
122
|
+
f" Разница: {(now_naive.replace(tzinfo=timezone.utc) - now_utc).total_seconds() / 3600:.1f} часов"
|
|
123
|
+
)
|
|
124
|
+
logger.info("")
|
|
125
|
+
|
|
126
|
+
# Проверяем активные диалоги в БД
|
|
127
|
+
try:
|
|
128
|
+
supabase_client = SupabaseClient(config.SUPABASE_URL, config.SUPABASE_KEY)
|
|
129
|
+
await supabase_client.initialize()
|
|
130
|
+
|
|
131
|
+
response = (
|
|
132
|
+
supabase_client.client.table("admin_user_conversations")
|
|
133
|
+
.select("id", "admin_id", "user_id", "started_at", "auto_end_at")
|
|
134
|
+
.eq("status", "active")
|
|
135
|
+
.execute()
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
conversations = response.data
|
|
139
|
+
|
|
140
|
+
logger.info(f"📊 Активные диалоги в БД: {len(conversations)}")
|
|
141
|
+
|
|
142
|
+
problems_found = 0
|
|
143
|
+
|
|
144
|
+
for i, conv in enumerate(conversations, 1):
|
|
145
|
+
logger.info(f"\n{i}. Диалог ID: {conv['id']}")
|
|
146
|
+
logger.info(
|
|
147
|
+
f" Админ: {conv['admin_id']}, Пользователь: {conv['user_id']}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Парсим времена
|
|
151
|
+
started_at = conv["started_at"]
|
|
152
|
+
auto_end_at = conv["auto_end_at"]
|
|
153
|
+
|
|
154
|
+
logger.info(f" started_at (сырое): {started_at}")
|
|
155
|
+
logger.info(f" auto_end_at (сырое): {auto_end_at}")
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
# Парсим как делает код
|
|
159
|
+
if started_at.endswith("Z"):
|
|
160
|
+
start_time = datetime.fromisoformat(
|
|
161
|
+
started_at.replace("Z", "+00:00")
|
|
162
|
+
)
|
|
163
|
+
elif "+" in started_at or started_at.count(":") >= 3:
|
|
164
|
+
start_time = datetime.fromisoformat(started_at)
|
|
165
|
+
else:
|
|
166
|
+
naive_time = datetime.fromisoformat(started_at)
|
|
167
|
+
start_time = naive_time.replace(tzinfo=timezone.utc)
|
|
168
|
+
|
|
169
|
+
if auto_end_at.endswith("Z"):
|
|
170
|
+
end_time = datetime.fromisoformat(
|
|
171
|
+
auto_end_at.replace("Z", "+00:00")
|
|
172
|
+
)
|
|
173
|
+
elif "+" in auto_end_at or auto_end_at.count(":") >= 3:
|
|
174
|
+
end_time = datetime.fromisoformat(auto_end_at)
|
|
175
|
+
else:
|
|
176
|
+
naive_time = datetime.fromisoformat(auto_end_at)
|
|
177
|
+
end_time = naive_time.replace(tzinfo=timezone.utc)
|
|
178
|
+
|
|
179
|
+
logger.info(f" start_time (парсед): {start_time}")
|
|
180
|
+
logger.info(f" end_time (парсед): {end_time}")
|
|
181
|
+
|
|
182
|
+
# Вычисляем длительность диалога
|
|
183
|
+
planned_duration = end_time - start_time
|
|
184
|
+
planned_minutes = int(planned_duration.total_seconds() / 60)
|
|
185
|
+
logger.info(f" Запланированная длительность: {planned_minutes} минут")
|
|
186
|
+
|
|
187
|
+
# Проверяем соответствие конфигу
|
|
188
|
+
expected = config.ADMIN_SESSION_TIMEOUT_MINUTES
|
|
189
|
+
if planned_minutes == expected:
|
|
190
|
+
logger.info(f" ✅ Соответствует конфигу ({expected} мин)")
|
|
191
|
+
else:
|
|
192
|
+
logger.error(
|
|
193
|
+
f" ❌ НЕ соответствует конфигу! Ожидалось {expected} мин, получили {planned_minutes} мин"
|
|
194
|
+
)
|
|
195
|
+
problems_found += 1
|
|
196
|
+
|
|
197
|
+
# Вычисляем текущее время до автозавершения
|
|
198
|
+
now_utc = datetime.now(timezone.utc)
|
|
199
|
+
|
|
200
|
+
# Приводим к UTC
|
|
201
|
+
if end_time.tzinfo != timezone.utc:
|
|
202
|
+
end_time_utc = end_time.astimezone(timezone.utc)
|
|
203
|
+
else:
|
|
204
|
+
end_time_utc = end_time
|
|
205
|
+
|
|
206
|
+
remaining = end_time_utc - now_utc
|
|
207
|
+
remaining_minutes = max(0, int(remaining.total_seconds() / 60))
|
|
208
|
+
|
|
209
|
+
logger.info(f" now_utc: {now_utc}")
|
|
210
|
+
logger.info(f" end_time_utc: {end_time_utc}")
|
|
211
|
+
logger.info(f" Оставшееся время: {remaining_minutes} минут")
|
|
212
|
+
|
|
213
|
+
# Вычисляем сколько уже прошло
|
|
214
|
+
if start_time.tzinfo != timezone.utc:
|
|
215
|
+
start_time_utc = start_time.astimezone(timezone.utc)
|
|
216
|
+
else:
|
|
217
|
+
start_time_utc = start_time
|
|
218
|
+
|
|
219
|
+
elapsed = now_utc - start_time_utc
|
|
220
|
+
elapsed_minutes = max(0, int(elapsed.total_seconds() / 60))
|
|
221
|
+
logger.info(f" Прошло времени: {elapsed_minutes} минут")
|
|
222
|
+
|
|
223
|
+
# Проверяем математику
|
|
224
|
+
total_check = elapsed_minutes + remaining_minutes
|
|
225
|
+
logger.info(
|
|
226
|
+
f" Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин (должно быть ~{planned_minutes})"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if abs(total_check - planned_minutes) > 2:
|
|
230
|
+
logger.warning(
|
|
231
|
+
" ⚠️ ПРОБЛЕМА: сумма не сходится! Возможная проблема с timezone"
|
|
232
|
+
)
|
|
233
|
+
problems_found += 1
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f" ❌ Ошибка парсинга времени: {e}")
|
|
237
|
+
problems_found += 1
|
|
238
|
+
|
|
239
|
+
if not conversations:
|
|
240
|
+
logger.info(" Нет активных диалогов для анализа")
|
|
241
|
+
logger.info(" 💡 Создайте диалог командой /чат USER_ID для тестирования")
|
|
242
|
+
|
|
243
|
+
return problems_found == 0
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"❌ Ошибка подключения к БД: {e}")
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
async def test_conversation_creation(config: Config) -> bool:
|
|
251
|
+
"""
|
|
252
|
+
Тестирует создание нового диалога с правильным таймаутом
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
config: Конфигурация бота
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
bool: True если тест прошел успешно, False если найдены проблемы
|
|
259
|
+
"""
|
|
260
|
+
logger.info(f"\n{'='*50}")
|
|
261
|
+
logger.info("🧪 ТЕСТ СОЗДАНИЯ ДИАЛОГА")
|
|
262
|
+
logger.info(f"{'='*50}")
|
|
263
|
+
|
|
264
|
+
timeout_minutes = config.ADMIN_SESSION_TIMEOUT_MINUTES
|
|
265
|
+
logger.info(f"📋 Конфигурация таймаута: {timeout_minutes} минут")
|
|
266
|
+
|
|
267
|
+
# Эмулируем создание диалога
|
|
268
|
+
now_utc = datetime.now(timezone.utc)
|
|
269
|
+
auto_end_utc = now_utc + timedelta(minutes=timeout_minutes)
|
|
270
|
+
|
|
271
|
+
logger.info(f"🕐 now_utc: {now_utc}")
|
|
272
|
+
logger.info(f"⏰ auto_end_utc: {auto_end_utc}")
|
|
273
|
+
logger.info(
|
|
274
|
+
f"📏 Разница: {int((auto_end_utc - now_utc).total_seconds() / 60)} минут"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Проверяем ISO формат
|
|
278
|
+
auto_end_iso = auto_end_utc.isoformat()
|
|
279
|
+
logger.info(f"📝 ISO формат: {auto_end_iso}")
|
|
280
|
+
|
|
281
|
+
# Проверяем парсинг обратно
|
|
282
|
+
try:
|
|
283
|
+
if auto_end_iso.endswith("Z"):
|
|
284
|
+
parsed_back = datetime.fromisoformat(auto_end_iso.replace("Z", "+00:00"))
|
|
285
|
+
elif "+" in auto_end_iso:
|
|
286
|
+
parsed_back = datetime.fromisoformat(auto_end_iso)
|
|
287
|
+
else:
|
|
288
|
+
parsed_back = datetime.fromisoformat(auto_end_iso).replace(
|
|
289
|
+
tzinfo=timezone.utc
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
logger.info(f"🔄 Парсед обратно: {parsed_back}")
|
|
293
|
+
|
|
294
|
+
# Проверяем что время совпадает
|
|
295
|
+
if abs((parsed_back - auto_end_utc).total_seconds()) < 1:
|
|
296
|
+
logger.info("✅ Парсинг работает корректно")
|
|
297
|
+
return True
|
|
298
|
+
else:
|
|
299
|
+
logger.error("❌ Проблема с парсингом времени")
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.error(f"❌ Ошибка парсинга: {e}")
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
async def check_timeouts(bot_name: str = "growthmed-october-24") -> bool:
|
|
308
|
+
"""
|
|
309
|
+
Проверяет корректность таймаутов диалогов админов
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
bot_name: Имя бота для проверки
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
bool: True если все таймауты корректны, False если найдены проблемы
|
|
316
|
+
"""
|
|
317
|
+
logger.info("🔍 Проверка таймаутов диалогов админов\n")
|
|
318
|
+
logger.info(f"🚀 Проверка для бота: {bot_name}")
|
|
319
|
+
logger.info(f"🤖 Bot ID будет автоопределен как: {bot_name}\n")
|
|
320
|
+
|
|
321
|
+
# Настраиваем окружение для бота (автоматически устанавливает BOT_ID)
|
|
322
|
+
config_dir = setup_bot_environment(bot_name)
|
|
323
|
+
if not config_dir:
|
|
324
|
+
logger.error("❌ Не удалось настроить окружение бота")
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
logger.info(f"📁 Текущая рабочая директория: {os.getcwd()}")
|
|
328
|
+
logger.info("📂 Содержимое рабочей директории:")
|
|
329
|
+
for item in Path(".").iterdir():
|
|
330
|
+
if item.is_dir():
|
|
331
|
+
logger.info(f" 📁 {item.name}/")
|
|
332
|
+
else:
|
|
333
|
+
logger.info(f" 📄 {item.name}")
|
|
334
|
+
logger.info("")
|
|
335
|
+
|
|
336
|
+
# Инициализация
|
|
337
|
+
try:
|
|
338
|
+
logger.info("⚙️ Инициализация конфигурации...")
|
|
339
|
+
config = Config()
|
|
340
|
+
logger.info("✅ Конфигурация загружена")
|
|
341
|
+
|
|
342
|
+
logger.info("🔗 Подключение к Supabase...")
|
|
343
|
+
supabase_client = SupabaseClient(config.SUPABASE_URL, config.SUPABASE_KEY)
|
|
344
|
+
await supabase_client.initialize()
|
|
345
|
+
logger.info("✅ Supabase подключен")
|
|
346
|
+
|
|
347
|
+
logger.info("👑 Инициализация менеджеров...")
|
|
348
|
+
admin_manager = AdminManager(config, supabase_client)
|
|
349
|
+
conversation_manager = ConversationManager(supabase_client, admin_manager)
|
|
350
|
+
logger.info("✅ Менеджеры инициализированы\n")
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.error(f"❌ Ошибка инициализации: {e}")
|
|
354
|
+
logger.exception("Стек ошибки:")
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
logger.info("⚙️ Конфигурация:")
|
|
358
|
+
logger.info(f" BOT_ID: {config.BOT_ID}")
|
|
359
|
+
logger.info(
|
|
360
|
+
f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}"
|
|
361
|
+
)
|
|
362
|
+
logger.info(f" PROMT_FILES_DIR: {config.PROMT_FILES_DIR}")
|
|
363
|
+
logger.info(f" Найдено промпт-файлов: {len(config.PROMPT_FILES)}")
|
|
364
|
+
logger.info(f" Админов: {len(config.ADMIN_TELEGRAM_IDS)}")
|
|
365
|
+
logger.info(f" Сейчас UTC: {datetime.now(timezone.utc)}")
|
|
366
|
+
logger.info("")
|
|
367
|
+
|
|
368
|
+
# Получаем активные диалоги
|
|
369
|
+
try:
|
|
370
|
+
logger.info("📊 Получение активных диалогов...")
|
|
371
|
+
conversations = await conversation_manager.get_active_conversations()
|
|
372
|
+
logger.info(f"✅ Получено {len(conversations)} диалогов")
|
|
373
|
+
except Exception as e:
|
|
374
|
+
logger.error(f"❌ Ошибка получения диалогов: {e}")
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
if not conversations:
|
|
378
|
+
logger.info("💬 Нет активных диалогов")
|
|
379
|
+
logger.info("💡 Создайте диалог командой /чат USER_ID для тестирования")
|
|
380
|
+
|
|
381
|
+
# Показываем пример создания тестового диалога
|
|
382
|
+
logger.info("\n🧪 Пример создания тестового диалога:")
|
|
383
|
+
logger.info(f"1. Запустите бота: python {bot_name}.py")
|
|
384
|
+
logger.info("2. Как админ выполните: /чат 123456789")
|
|
385
|
+
logger.info("3. Затем проверьте: /чаты")
|
|
386
|
+
return True # Нет диалогов = нет проблем
|
|
387
|
+
|
|
388
|
+
logger.info(f"📊 Найдено {len(conversations)} активных диалогов:")
|
|
389
|
+
logger.info("")
|
|
390
|
+
|
|
391
|
+
problems_found = 0
|
|
392
|
+
|
|
393
|
+
for i, conv in enumerate(conversations, 1):
|
|
394
|
+
logger.info(f"{i}. Диалог ID: {conv['id']}")
|
|
395
|
+
logger.info(f" 👤 Пользователь: {conv['user_id']}")
|
|
396
|
+
logger.info(f" 👑 Админ: {conv['admin_id']}")
|
|
397
|
+
|
|
398
|
+
# Анализируем времена
|
|
399
|
+
started_at_str = conv["started_at"]
|
|
400
|
+
auto_end_str = conv["auto_end_at"]
|
|
401
|
+
|
|
402
|
+
logger.info(f" 🕐 started_at (сырое): {started_at_str}")
|
|
403
|
+
logger.info(f" ⏰ auto_end_at (сырое): {auto_end_str}")
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
# Парсим время начала с правильной обработкой timezone
|
|
407
|
+
if started_at_str.endswith("Z"):
|
|
408
|
+
start_time = datetime.fromisoformat(
|
|
409
|
+
started_at_str.replace("Z", "+00:00")
|
|
410
|
+
)
|
|
411
|
+
elif "+" in started_at_str or started_at_str.count(":") >= 3:
|
|
412
|
+
start_time = datetime.fromisoformat(started_at_str)
|
|
413
|
+
else:
|
|
414
|
+
naive_time = datetime.fromisoformat(started_at_str)
|
|
415
|
+
start_time = naive_time.replace(tzinfo=timezone.utc)
|
|
416
|
+
|
|
417
|
+
# Парсим время автозавершения с правильной обработкой timezone
|
|
418
|
+
if auto_end_str.endswith("Z"):
|
|
419
|
+
auto_end = datetime.fromisoformat(auto_end_str.replace("Z", "+00:00"))
|
|
420
|
+
elif "+" in auto_end_str or auto_end_str.count(":") >= 3:
|
|
421
|
+
auto_end = datetime.fromisoformat(auto_end_str)
|
|
422
|
+
else:
|
|
423
|
+
naive_time = datetime.fromisoformat(auto_end_str)
|
|
424
|
+
auto_end = naive_time.replace(tzinfo=timezone.utc)
|
|
425
|
+
|
|
426
|
+
logger.info(f" 📅 start_time (parsed): {start_time}")
|
|
427
|
+
logger.info(f" ⏰ auto_end (parsed): {auto_end}")
|
|
428
|
+
|
|
429
|
+
# Планируемая длительность
|
|
430
|
+
planned_duration = auto_end - start_time
|
|
431
|
+
planned_minutes = int(planned_duration.total_seconds() / 60)
|
|
432
|
+
logger.info(f" 📏 Планируемая длительность: {planned_minutes} минут")
|
|
433
|
+
|
|
434
|
+
# Текущее время в UTC
|
|
435
|
+
now_utc = datetime.now(timezone.utc)
|
|
436
|
+
|
|
437
|
+
# Приводим все к UTC для корректных расчетов
|
|
438
|
+
if start_time.tzinfo != timezone.utc:
|
|
439
|
+
start_time_utc = start_time.astimezone(timezone.utc)
|
|
440
|
+
else:
|
|
441
|
+
start_time_utc = start_time
|
|
442
|
+
|
|
443
|
+
if auto_end.tzinfo != timezone.utc:
|
|
444
|
+
auto_end_utc = auto_end.astimezone(timezone.utc)
|
|
445
|
+
else:
|
|
446
|
+
auto_end_utc = auto_end
|
|
447
|
+
|
|
448
|
+
# Прошло времени
|
|
449
|
+
elapsed = now_utc - start_time_utc
|
|
450
|
+
elapsed_minutes = max(0, int(elapsed.total_seconds() / 60))
|
|
451
|
+
logger.info(f" ⏱️ Прошло времени: {elapsed_minutes} минут")
|
|
452
|
+
|
|
453
|
+
# Оставшееся время
|
|
454
|
+
remaining = auto_end_utc - now_utc
|
|
455
|
+
remaining_minutes = max(0, int(remaining.total_seconds() / 60))
|
|
456
|
+
logger.info(f" ⏰ Осталось времени: {remaining_minutes} минут")
|
|
457
|
+
|
|
458
|
+
# Проверяем корректность конфигурации
|
|
459
|
+
expected_timeout = config.ADMIN_SESSION_TIMEOUT_MINUTES
|
|
460
|
+
if (
|
|
461
|
+
abs(planned_minutes - expected_timeout) <= 2
|
|
462
|
+
): # допускаем погрешность 2 минуты
|
|
463
|
+
logger.info(
|
|
464
|
+
f" ✅ Таймаут корректный (ожидался {expected_timeout} мин)"
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
logger.error(
|
|
468
|
+
f" ❌ ОШИБКА: ожидался {expected_timeout} мин, получили {planned_minutes} мин"
|
|
469
|
+
)
|
|
470
|
+
problems_found += 1
|
|
471
|
+
|
|
472
|
+
# Проверяем математику
|
|
473
|
+
total_check = elapsed_minutes + remaining_minutes
|
|
474
|
+
logger.info(
|
|
475
|
+
f" 🔢 Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if abs(total_check - planned_minutes) > 2:
|
|
479
|
+
logger.warning(
|
|
480
|
+
" ⚠️ ПРОБЛЕМА: сумма не сходится! Возможна проблема с timezone"
|
|
481
|
+
)
|
|
482
|
+
problems_found += 1
|
|
483
|
+
else:
|
|
484
|
+
logger.info(" ✅ Математика сходится")
|
|
485
|
+
|
|
486
|
+
except Exception as e:
|
|
487
|
+
logger.error(f" ❌ Ошибка парсинга: {e}")
|
|
488
|
+
problems_found += 1
|
|
489
|
+
logger.exception(" Стек ошибки:")
|
|
490
|
+
|
|
491
|
+
logger.info("")
|
|
492
|
+
|
|
493
|
+
# Тестируем функцию форматирования
|
|
494
|
+
logger.info("🧪 Тестирование format_active_conversations:")
|
|
495
|
+
try:
|
|
496
|
+
formatted_text = conversation_manager.format_active_conversations(conversations)
|
|
497
|
+
logger.info(formatted_text)
|
|
498
|
+
except Exception as e:
|
|
499
|
+
logger.error(f"❌ Ошибка форматирования: {e}")
|
|
500
|
+
problems_found += 1
|
|
501
|
+
|
|
502
|
+
# Итоговый результат
|
|
503
|
+
logger.info(f"\n{'='*50}")
|
|
504
|
+
logger.info("📊 ИТОГОВЫЙ РЕЗУЛЬТАТ:")
|
|
505
|
+
if problems_found == 0:
|
|
506
|
+
logger.info("✅ Все таймауты корректны!")
|
|
507
|
+
else:
|
|
508
|
+
logger.error(f"❌ Найдено {problems_found} проблем")
|
|
509
|
+
logger.info("💡 Запустите fix_existing_timeouts.py для исправления")
|
|
510
|
+
logger.info(f"{'='*50}")
|
|
511
|
+
|
|
512
|
+
return problems_found == 0
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def main():
|
|
516
|
+
"""Точка входа для запуска из командной строки"""
|
|
517
|
+
# Убираем лишние логи для чистого вывода
|
|
518
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
519
|
+
|
|
520
|
+
logger.info("🔍 Утилита проверки таймаутов диалогов")
|
|
521
|
+
logger.info("Использование:")
|
|
522
|
+
logger.info(" python -m smart_bot_factory.timeout_checker [bot_name]")
|
|
523
|
+
logger.info(" python -m smart_bot_factory.timeout_checker growthmed-october-24")
|
|
524
|
+
logger.info("")
|
|
525
|
+
|
|
526
|
+
if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help", "help"]:
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
# Определяем какого бота проверять
|
|
530
|
+
bot_name = "growthmed-october-24" # по умолчанию
|
|
531
|
+
if len(sys.argv) > 1:
|
|
532
|
+
bot_name = sys.argv[1]
|
|
533
|
+
|
|
534
|
+
try:
|
|
535
|
+
success = asyncio.run(check_timeouts(bot_name))
|
|
536
|
+
if not success:
|
|
537
|
+
sys.exit(1)
|
|
538
|
+
except KeyboardInterrupt:
|
|
539
|
+
logger.info("\n⏹️ Прервано пользователем")
|
|
540
|
+
except Exception as e:
|
|
541
|
+
logger.error(f"\n💥 Критическая ошибка: {e}")
|
|
542
|
+
logger.exception("Стек ошибки:")
|
|
543
|
+
sys.exit(1)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
if __name__ == "__main__":
|
|
547
|
+
main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .common import get_user_locale
|
|
2
|
+
from .dialog_calendar import DialogCalendar
|
|
3
|
+
from .schemas import (CalendarLabels, DialogCalendarCallback,
|
|
4
|
+
SimpleCalendarCallback)
|
|
5
|
+
from .simple_calendar import SimpleCalendar
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"SimpleCalendar",
|
|
9
|
+
"DialogCalendar",
|
|
10
|
+
"SimpleCalendarCallback",
|
|
11
|
+
"DialogCalendarCallback",
|
|
12
|
+
"CalendarLabels",
|
|
13
|
+
"get_user_locale",
|
|
14
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import calendar
|
|
2
|
+
import locale
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from aiogram.types import User
|
|
6
|
+
|
|
7
|
+
from .schemas import CalendarLabels
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def get_user_locale(from_user: User) -> str:
|
|
11
|
+
"Always returns ru_RU locale for consistent Russian language support"
|
|
12
|
+
return "ru_RU"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GenericCalendar:
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
locale: str = None, # оставляем для обратной совместимости
|
|
20
|
+
cancel_btn: str = None,
|
|
21
|
+
today_btn: str = None,
|
|
22
|
+
show_alerts: bool = False,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Initialize calendar with Russian language by default
|
|
25
|
+
|
|
26
|
+
Parameters:
|
|
27
|
+
locale (str): Ignored, always uses Russian
|
|
28
|
+
cancel_btn (str): label for button Cancel to cancel date input
|
|
29
|
+
today_btn (str): label for button Today to set calendar back to todays date
|
|
30
|
+
show_alerts (bool): defines how the date range error would shown (defaults to False)
|
|
31
|
+
"""
|
|
32
|
+
self._labels = CalendarLabels()
|
|
33
|
+
|
|
34
|
+
if cancel_btn:
|
|
35
|
+
self._labels.cancel_caption = cancel_btn
|
|
36
|
+
if today_btn:
|
|
37
|
+
self._labels.today_caption = today_btn
|
|
38
|
+
|
|
39
|
+
self.min_date = None
|
|
40
|
+
self.max_date = None
|
|
41
|
+
self.show_alerts = show_alerts
|
|
42
|
+
|
|
43
|
+
def set_dates_range(self, min_date: datetime, max_date: datetime):
|
|
44
|
+
"""Sets range of minimum & maximum dates"""
|
|
45
|
+
self.min_date = min_date
|
|
46
|
+
self.max_date = max_date
|
|
47
|
+
|
|
48
|
+
async def process_day_select(self, data, query):
|
|
49
|
+
"""Checks selected date is in allowed range of dates"""
|
|
50
|
+
date = datetime(int(data.year), int(data.month), int(data.day))
|
|
51
|
+
if self.min_date and self.min_date > date:
|
|
52
|
+
await query.answer(
|
|
53
|
+
f'Дата должна быть позже {self.min_date.strftime("%d/%m/%Y")}',
|
|
54
|
+
show_alert=self.show_alerts,
|
|
55
|
+
)
|
|
56
|
+
return False, None
|
|
57
|
+
elif self.max_date and self.max_date < date:
|
|
58
|
+
await query.answer(
|
|
59
|
+
f'Дата должна быть раньше {self.max_date.strftime("%d/%m/%Y")}',
|
|
60
|
+
show_alert=self.show_alerts,
|
|
61
|
+
)
|
|
62
|
+
return False, None
|
|
63
|
+
await query.message.delete_reply_markup() # removing inline keyboard
|
|
64
|
+
return True, date
|