smart-bot-factory 0.1.0__py3-none-any.whl → 0.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.
Potentially problematic release.
This version of smart-bot-factory might be problematic. Click here for more details.
- smart_bot_factory-0.1.1.dist-info/METADATA +31 -0
- smart_bot_factory-0.1.1.dist-info/RECORD +4 -0
- smart_bot_factory/__init__.py +0 -30
- smart_bot_factory/cli.py +0 -624
- smart_bot_factory/core/__init__.py +0 -7
- smart_bot_factory/core/bot_builder.py +0 -337
- smart_bot_factory/events/__init__.py +0 -27
- smart_bot_factory/events/decorators.py +0 -229
- smart_bot_factory/integrations/__init__.py +0 -83
- smart_bot_factory/integrations/bot_utils_integration.py +0 -224
- smart_bot_factory/services/__init__.py +0 -12
- smart_bot_factory/services/message_sender.py +0 -235
- smart_bot_factory/services/telegram_integration.py +0 -246
- smart_bot_factory/tools/__init__.py +0 -19
- smart_bot_factory/tools/decorators.py +0 -140
- smart_bot_factory-0.1.0.dist-info/METADATA +0 -125
- smart_bot_factory-0.1.0.dist-info/RECORD +0 -18
- smart_bot_factory-0.1.0.dist-info/licenses/LICENSE +0 -21
- {smart_bot_factory-0.1.0.dist-info → smart_bot_factory-0.1.1.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.1.0.dist-info → smart_bot_factory-0.1.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Строитель ботов для Smart Bot Factory
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
import asyncio
|
|
8
|
-
import logging
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional, Dict, Any, List
|
|
11
|
-
|
|
12
|
-
from ..integrations import (
|
|
13
|
-
Config, OpenAIClient, SupabaseClient,
|
|
14
|
-
ConversationManager, AdminManager, PromptLoader
|
|
15
|
-
)
|
|
16
|
-
from ..events.decorators import get_handlers_for_prompt
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
class BotBuilder:
|
|
21
|
-
"""
|
|
22
|
-
Строитель ботов, который использует существующие файлы проекта
|
|
23
|
-
и добавляет новые возможности через декораторы
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, bot_id: str, config_dir: Optional[Path] = None):
|
|
27
|
-
"""
|
|
28
|
-
Инициализация строителя бота
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
bot_id: Идентификатор бота
|
|
32
|
-
config_dir: Путь к директории конфигурации (по умолчанию configs/bot_id)
|
|
33
|
-
"""
|
|
34
|
-
self.bot_id = bot_id
|
|
35
|
-
self.config_dir = config_dir or Path("bots") / bot_id
|
|
36
|
-
|
|
37
|
-
# Компоненты бота
|
|
38
|
-
self.config: Optional[Config] = None
|
|
39
|
-
self.openai_client: Optional[OpenAIClient] = None
|
|
40
|
-
self.supabase_client: Optional[SupabaseClient] = None
|
|
41
|
-
self.conversation_manager: Optional[ConversationManager] = None
|
|
42
|
-
self.admin_manager: Optional[AdminManager] = None
|
|
43
|
-
self.prompt_loader: Optional[PromptLoader] = None
|
|
44
|
-
|
|
45
|
-
# Флаги инициализации
|
|
46
|
-
self._initialized = False
|
|
47
|
-
|
|
48
|
-
logger.info(f"🏗️ Создан BotBuilder для бота: {bot_id}")
|
|
49
|
-
|
|
50
|
-
async def build(self) -> 'BotBuilder':
|
|
51
|
-
"""
|
|
52
|
-
Строит и инициализирует все компоненты бота
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
BotBuilder: Возвращает self для цепочки вызовов
|
|
56
|
-
"""
|
|
57
|
-
if self._initialized:
|
|
58
|
-
logger.warning(f"⚠️ Бот {self.bot_id} уже инициализирован")
|
|
59
|
-
return self
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
logger.info(f"🚀 Начинаем сборку бота {self.bot_id}")
|
|
63
|
-
|
|
64
|
-
# 1. Инициализируем конфигурацию
|
|
65
|
-
await self._init_config()
|
|
66
|
-
|
|
67
|
-
# 2. Инициализируем клиенты
|
|
68
|
-
await self._init_clients()
|
|
69
|
-
|
|
70
|
-
# 3. Инициализируем менеджеры
|
|
71
|
-
await self._init_managers()
|
|
72
|
-
|
|
73
|
-
# 4. Обновляем промпты с информацией о доступных инструментах
|
|
74
|
-
await self._update_prompts_with_tools()
|
|
75
|
-
|
|
76
|
-
self._initialized = True
|
|
77
|
-
logger.info(f"✅ Бот {self.bot_id} успешно собран и готов к работе")
|
|
78
|
-
|
|
79
|
-
return self
|
|
80
|
-
|
|
81
|
-
except Exception as e:
|
|
82
|
-
logger.error(f"❌ Ошибка при сборке бота {self.bot_id}: {e}")
|
|
83
|
-
raise
|
|
84
|
-
|
|
85
|
-
async def _init_config(self):
|
|
86
|
-
"""Инициализация конфигурации"""
|
|
87
|
-
logger.info(f"⚙️ Инициализация конфигурации для {self.bot_id}")
|
|
88
|
-
|
|
89
|
-
# Устанавливаем BOT_ID в переменные окружения
|
|
90
|
-
os.environ['BOT_ID'] = self.bot_id
|
|
91
|
-
|
|
92
|
-
# Загружаем .env файл если существует
|
|
93
|
-
env_file = self.config_dir / ".env"
|
|
94
|
-
if env_file.exists():
|
|
95
|
-
from dotenv import load_dotenv
|
|
96
|
-
load_dotenv(env_file)
|
|
97
|
-
logger.info(f"📄 Загружен .env файл: {env_file}")
|
|
98
|
-
|
|
99
|
-
# Проверяем, установлен ли уже путь к промптам в файле запускалки
|
|
100
|
-
if "PROMT_FILES_DIR" in os.environ:
|
|
101
|
-
logger.info(f"📁 Путь к промптам уже установлен: {os.environ['PROMT_FILES_DIR']}")
|
|
102
|
-
else:
|
|
103
|
-
# Устанавливаем правильный путь к промптам ПОСЛЕ загрузки .env
|
|
104
|
-
# Берем значение из .env и добавляем к пути bots/bot-id/
|
|
105
|
-
prompts_subdir = os.environ.get("PROMT_FILES_DIR", "prompts")
|
|
106
|
-
logger.info(f"🔍 PROMT_FILES_DIR из .env: {prompts_subdir}")
|
|
107
|
-
logger.info(f"🔍 config_dir: {self.config_dir}")
|
|
108
|
-
|
|
109
|
-
prompts_dir = self.config_dir / prompts_subdir
|
|
110
|
-
logger.info(f"🔍 Полный путь к промптам: {prompts_dir}")
|
|
111
|
-
logger.info(f"🔍 Существует ли папка: {prompts_dir.exists()}")
|
|
112
|
-
|
|
113
|
-
# ВАЖНО: Устанавливаем правильный путь ДО создания Config
|
|
114
|
-
if prompts_dir.exists():
|
|
115
|
-
os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
|
|
116
|
-
logger.info(f"📁 Установлен путь к промптам: {prompts_dir}")
|
|
117
|
-
else:
|
|
118
|
-
logger.error(f"❌ Папка с промптами не найдена: {prompts_dir}")
|
|
119
|
-
# Устанавливаем путь даже если папки нет, чтобы Config не упал
|
|
120
|
-
os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
|
|
121
|
-
|
|
122
|
-
# Создаем конфигурацию
|
|
123
|
-
logger.info(f"🔍 PROMT_FILES_DIR перед созданием Config: {os.environ.get('PROMT_FILES_DIR')}")
|
|
124
|
-
self.config = Config()
|
|
125
|
-
logger.info(f"✅ Конфигурация инициализирована")
|
|
126
|
-
|
|
127
|
-
async def _init_clients(self):
|
|
128
|
-
"""Инициализация клиентов"""
|
|
129
|
-
logger.info(f"🔌 Инициализация клиентов для {self.bot_id}")
|
|
130
|
-
|
|
131
|
-
# OpenAI клиент
|
|
132
|
-
self.openai_client = OpenAIClient(
|
|
133
|
-
api_key=self.config.OPENAI_API_KEY,
|
|
134
|
-
model=self.config.OPENAI_MODEL,
|
|
135
|
-
max_tokens=self.config.OPENAI_MAX_TOKENS,
|
|
136
|
-
temperature=self.config.OPENAI_TEMPERATURE
|
|
137
|
-
)
|
|
138
|
-
logger.info(f"✅ OpenAI клиент инициализирован")
|
|
139
|
-
|
|
140
|
-
# Supabase клиент
|
|
141
|
-
self.supabase_client = SupabaseClient(
|
|
142
|
-
url=self.config.SUPABASE_URL,
|
|
143
|
-
key=self.config.SUPABASE_KEY,
|
|
144
|
-
bot_id=self.bot_id
|
|
145
|
-
)
|
|
146
|
-
await self.supabase_client.initialize()
|
|
147
|
-
logger.info(f"✅ Supabase клиент инициализирован")
|
|
148
|
-
|
|
149
|
-
async def _init_managers(self):
|
|
150
|
-
"""Инициализация менеджеров"""
|
|
151
|
-
logger.info(f"👥 Инициализация менеджеров для {self.bot_id}")
|
|
152
|
-
|
|
153
|
-
# Admin Manager
|
|
154
|
-
self.admin_manager = AdminManager(self.config, self.supabase_client)
|
|
155
|
-
await self.admin_manager.sync_admins_from_config()
|
|
156
|
-
logger.info(f"✅ Admin Manager инициализирован")
|
|
157
|
-
|
|
158
|
-
# Conversation Manager
|
|
159
|
-
self.conversation_manager = ConversationManager(self.supabase_client, self.admin_manager)
|
|
160
|
-
logger.info(f"✅ Conversation Manager инициализирован")
|
|
161
|
-
|
|
162
|
-
# Prompt Loader
|
|
163
|
-
self.prompt_loader = PromptLoader(
|
|
164
|
-
prompts_dir=self.config.PROMT_FILES_DIR,
|
|
165
|
-
prompt_files=self.config.PROMPT_FILES
|
|
166
|
-
)
|
|
167
|
-
await self.prompt_loader.validate_prompts()
|
|
168
|
-
logger.info(f"✅ Prompt Loader инициализирован")
|
|
169
|
-
|
|
170
|
-
async def _update_prompts_with_tools(self):
|
|
171
|
-
"""
|
|
172
|
-
Обновляет промпты информацией о доступных обработчиках событий
|
|
173
|
-
"""
|
|
174
|
-
logger.info(f"🔧 Обновление промптов с информацией об обработчиках")
|
|
175
|
-
|
|
176
|
-
# Получаем информацию о доступных обработчиках
|
|
177
|
-
event_handlers_info = get_handlers_for_prompt()
|
|
178
|
-
|
|
179
|
-
# Если есть обработчики, добавляем их в системный промпт
|
|
180
|
-
if event_handlers_info:
|
|
181
|
-
# Сохраняем информацию о обработчиках для использования в handlers.py
|
|
182
|
-
self._tools_prompt = event_handlers_info
|
|
183
|
-
|
|
184
|
-
logger.info(f"✅ Промпты обновлены с информацией об обработчиках")
|
|
185
|
-
else:
|
|
186
|
-
self._tools_prompt = ""
|
|
187
|
-
logger.info(f"ℹ️ Нет зарегистрированных обработчиков")
|
|
188
|
-
|
|
189
|
-
def get_tools_prompt(self) -> str:
|
|
190
|
-
"""Возвращает промпт с информацией об инструментах"""
|
|
191
|
-
return getattr(self, '_tools_prompt', '')
|
|
192
|
-
|
|
193
|
-
def get_status(self) -> Dict[str, Any]:
|
|
194
|
-
"""Возвращает статус бота"""
|
|
195
|
-
return {
|
|
196
|
-
"bot_id": self.bot_id,
|
|
197
|
-
"initialized": self._initialized,
|
|
198
|
-
"config_dir": str(self.config_dir),
|
|
199
|
-
"components": {
|
|
200
|
-
"config": self.config is not None,
|
|
201
|
-
"openai_client": self.openai_client is not None,
|
|
202
|
-
"supabase_client": self.supabase_client is not None,
|
|
203
|
-
"conversation_manager": self.conversation_manager is not None,
|
|
204
|
-
"admin_manager": self.admin_manager is not None,
|
|
205
|
-
"prompt_loader": self.prompt_loader is not None
|
|
206
|
-
},
|
|
207
|
-
"tools": {
|
|
208
|
-
"event_handlers": len(get_handlers_for_prompt().split('\n')) if get_handlers_for_prompt() else 0
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async def start(self):
|
|
213
|
-
"""
|
|
214
|
-
Запускает бота (аналог main.py)
|
|
215
|
-
"""
|
|
216
|
-
if not self._initialized:
|
|
217
|
-
raise RuntimeError(f"Бот {self.bot_id} не инициализирован. Вызовите build() сначала")
|
|
218
|
-
|
|
219
|
-
logger.info(f"🚀 Запускаем бота {self.bot_id}")
|
|
220
|
-
|
|
221
|
-
try:
|
|
222
|
-
# Импортируем необходимые компоненты
|
|
223
|
-
from aiogram import Bot, Dispatcher
|
|
224
|
-
from aiogram.fsm.storage.memory import MemoryStorage
|
|
225
|
-
|
|
226
|
-
# Создаем бота и диспетчер
|
|
227
|
-
bot = Bot(token=self.config.TELEGRAM_BOT_TOKEN)
|
|
228
|
-
storage = MemoryStorage()
|
|
229
|
-
dp = Dispatcher(storage=storage)
|
|
230
|
-
|
|
231
|
-
# Инициализируем базу данных
|
|
232
|
-
await self.supabase_client.initialize()
|
|
233
|
-
|
|
234
|
-
# Синхронизируем админов из конфигурации
|
|
235
|
-
await self.admin_manager.sync_admins_from_config()
|
|
236
|
-
|
|
237
|
-
# Проверяем доступность промптов
|
|
238
|
-
prompts_status = await self.prompt_loader.validate_prompts()
|
|
239
|
-
logger.info(f"Статус промптов: {prompts_status}")
|
|
240
|
-
|
|
241
|
-
# Устанавливаем глобальные переменные ДО импорта обработчиков
|
|
242
|
-
import sys
|
|
243
|
-
import importlib
|
|
244
|
-
|
|
245
|
-
# Устанавливаем глобальные переменные в модулях handlers и admin_logic
|
|
246
|
-
try:
|
|
247
|
-
handlers_module = importlib.import_module('handlers')
|
|
248
|
-
handlers_module.config = self.config
|
|
249
|
-
handlers_module.bot = bot
|
|
250
|
-
handlers_module.dp = dp
|
|
251
|
-
handlers_module.supabase_client = self.supabase_client
|
|
252
|
-
handlers_module.openai_client = self.openai_client
|
|
253
|
-
handlers_module.prompt_loader = self.prompt_loader
|
|
254
|
-
handlers_module.admin_manager = self.admin_manager
|
|
255
|
-
handlers_module.conversation_manager = self.conversation_manager
|
|
256
|
-
logger.info("✅ Глобальные переменные установлены в handlers")
|
|
257
|
-
except Exception as e:
|
|
258
|
-
logger.warning(f"⚠️ Не удалось установить глобальные переменные в handlers: {e}")
|
|
259
|
-
|
|
260
|
-
try:
|
|
261
|
-
admin_logic_module = importlib.import_module('admin_logic')
|
|
262
|
-
admin_logic_module.config = self.config
|
|
263
|
-
admin_logic_module.bot = bot
|
|
264
|
-
admin_logic_module.dp = dp
|
|
265
|
-
admin_logic_module.supabase_client = self.supabase_client
|
|
266
|
-
admin_logic_module.openai_client = self.openai_client
|
|
267
|
-
admin_logic_module.prompt_loader = self.prompt_loader
|
|
268
|
-
admin_logic_module.admin_manager = self.admin_manager
|
|
269
|
-
admin_logic_module.conversation_manager = self.conversation_manager
|
|
270
|
-
logger.info("✅ Глобальные переменные установлены в admin_logic")
|
|
271
|
-
except Exception as e:
|
|
272
|
-
logger.warning(f"⚠️ Не удалось установить глобальные переменные в admin_logic: {e}")
|
|
273
|
-
|
|
274
|
-
# Также устанавливаем в bot_utils
|
|
275
|
-
try:
|
|
276
|
-
bot_utils_module = importlib.import_module('bot_utils')
|
|
277
|
-
bot_utils_module.config = self.config
|
|
278
|
-
bot_utils_module.bot = bot
|
|
279
|
-
bot_utils_module.dp = dp
|
|
280
|
-
bot_utils_module.supabase_client = self.supabase_client
|
|
281
|
-
bot_utils_module.openai_client = self.openai_client
|
|
282
|
-
bot_utils_module.prompt_loader = self.prompt_loader
|
|
283
|
-
bot_utils_module.admin_manager = self.admin_manager
|
|
284
|
-
bot_utils_module.conversation_manager = self.conversation_manager
|
|
285
|
-
logger.info("✅ Глобальные переменные установлены в bot_utils")
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.warning(f"⚠️ Не удалось установить глобальные переменные в bot_utils: {e}")
|
|
288
|
-
|
|
289
|
-
# Также устанавливаем в debug_routing
|
|
290
|
-
try:
|
|
291
|
-
debug_routing_module = importlib.import_module('debug_routing')
|
|
292
|
-
debug_routing_module.config = self.config
|
|
293
|
-
debug_routing_module.bot = bot
|
|
294
|
-
debug_routing_module.dp = dp
|
|
295
|
-
debug_routing_module.supabase_client = self.supabase_client
|
|
296
|
-
debug_routing_module.openai_client = self.openai_client
|
|
297
|
-
debug_routing_module.prompt_loader = self.prompt_loader
|
|
298
|
-
debug_routing_module.admin_manager = self.admin_manager
|
|
299
|
-
debug_routing_module.conversation_manager = self.conversation_manager
|
|
300
|
-
logger.info("✅ Глобальные переменные установлены в debug_routing")
|
|
301
|
-
except Exception as e:
|
|
302
|
-
logger.warning(f"⚠️ Не удалось установить глобальные переменные в debug_routing: {e}")
|
|
303
|
-
|
|
304
|
-
# Теперь импортируем и настраиваем обработчики
|
|
305
|
-
from ..integrations import setup_handlers, setup_admin_handlers, setup_utils_handlers
|
|
306
|
-
|
|
307
|
-
# Настраиваем обработчики запросов
|
|
308
|
-
setup_utils_handlers(dp) # Утилитарные команды (/status, /help)
|
|
309
|
-
setup_admin_handlers(dp) # Админские команды (/админ, /стат, /чат)
|
|
310
|
-
setup_handlers(dp) # Основные пользовательские обработчики
|
|
311
|
-
|
|
312
|
-
# Логируем информацию о запуске
|
|
313
|
-
logger.info(f"✅ Бот {self.bot_id} запущен и готов к работе!")
|
|
314
|
-
logger.info(f" 📊 Изоляция данных: bot_id = {self.config.BOT_ID}")
|
|
315
|
-
logger.info(f" 👑 Админов настроено: {len(self.config.ADMIN_TELEGRAM_IDS)}")
|
|
316
|
-
logger.info(f" 📝 Загружено промптов: {len(self.config.PROMPT_FILES)}")
|
|
317
|
-
|
|
318
|
-
# Четкое сообщение о запуске
|
|
319
|
-
print(f"\n🚀 БОТ {self.bot_id.upper()} УСПЕШНО ЗАПУЩЕН!")
|
|
320
|
-
print(f"📱 Telegram Bot ID: {self.config.BOT_ID}")
|
|
321
|
-
print(f"👑 Админов: {len(self.config.ADMIN_TELEGRAM_IDS)}")
|
|
322
|
-
print(f"📝 Промптов: {len(self.config.PROMPT_FILES)}")
|
|
323
|
-
print(f"🔄 Ожидание сообщений...")
|
|
324
|
-
print(f"⏹️ Для остановки нажмите Ctrl+C\n")
|
|
325
|
-
|
|
326
|
-
# Запуск polling (бесконечная обработка сообщений)
|
|
327
|
-
await dp.start_polling(bot)
|
|
328
|
-
|
|
329
|
-
except Exception as e:
|
|
330
|
-
logger.error(f"❌ Ошибка при запуске бота {self.bot_id}: {e}")
|
|
331
|
-
import traceback
|
|
332
|
-
logger.error(f"Стек ошибки: {traceback.format_exc()}")
|
|
333
|
-
raise
|
|
334
|
-
finally:
|
|
335
|
-
# Очистка ресурсов при завершении
|
|
336
|
-
if 'bot' in locals():
|
|
337
|
-
await bot.session.close()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Система событий для Smart Bot Factory
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .decorators import (
|
|
6
|
-
event_handler,
|
|
7
|
-
schedule_task,
|
|
8
|
-
get_event_handlers,
|
|
9
|
-
get_scheduled_tasks,
|
|
10
|
-
get_handlers_for_prompt,
|
|
11
|
-
execute_event_handler,
|
|
12
|
-
execute_scheduled_task,
|
|
13
|
-
schedule_task_for_later,
|
|
14
|
-
execute_scheduled_task_from_event
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
__all__ = [
|
|
18
|
-
'event_handler',
|
|
19
|
-
'schedule_task',
|
|
20
|
-
'get_event_handlers',
|
|
21
|
-
'get_scheduled_tasks',
|
|
22
|
-
'get_handlers_for_prompt',
|
|
23
|
-
'execute_event_handler',
|
|
24
|
-
'execute_scheduled_task',
|
|
25
|
-
'schedule_task_for_later',
|
|
26
|
-
'execute_scheduled_task_from_event'
|
|
27
|
-
]
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Декораторы для обработчиков событий и временных задач
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Callable, Any, Dict, Optional
|
|
8
|
-
from datetime import datetime, timedelta
|
|
9
|
-
from functools import wraps
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
# Глобальный реестр обработчиков событий
|
|
14
|
-
_event_handlers: Dict[str, Callable] = {}
|
|
15
|
-
_scheduled_tasks: Dict[str, Dict[str, Any]] = {}
|
|
16
|
-
|
|
17
|
-
def event_handler(event_type: str, description: str = ""):
|
|
18
|
-
"""
|
|
19
|
-
Декоратор для регистрации обработчика события
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
event_type: Тип события (например, 'appointment_booking', 'phone_collection')
|
|
23
|
-
description: Описание что делает обработчик (для добавления в промпт)
|
|
24
|
-
|
|
25
|
-
Example:
|
|
26
|
-
@event_handler("appointment_booking", "Записывает пользователя на прием")
|
|
27
|
-
async def book_appointment(user_id: int, appointment_data: dict):
|
|
28
|
-
# Логика записи на прием
|
|
29
|
-
return {"status": "success", "appointment_id": "123"}
|
|
30
|
-
"""
|
|
31
|
-
def decorator(func: Callable) -> Callable:
|
|
32
|
-
_event_handlers[event_type] = {
|
|
33
|
-
'handler': func,
|
|
34
|
-
'description': description,
|
|
35
|
-
'name': func.__name__
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
logger.info(f"📝 Зарегистрирован обработчик события '{event_type}': {func.__name__}")
|
|
39
|
-
|
|
40
|
-
@wraps(func)
|
|
41
|
-
async def wrapper(*args, **kwargs):
|
|
42
|
-
try:
|
|
43
|
-
logger.info(f"🔧 Выполняем обработчик события '{event_type}'")
|
|
44
|
-
result = await func(*args, **kwargs)
|
|
45
|
-
logger.info(f"✅ Обработчик '{event_type}' выполнен успешно")
|
|
46
|
-
return result
|
|
47
|
-
except Exception as e:
|
|
48
|
-
logger.error(f"❌ Ошибка в обработчике '{event_type}': {e}")
|
|
49
|
-
raise
|
|
50
|
-
|
|
51
|
-
return wrapper
|
|
52
|
-
return decorator
|
|
53
|
-
|
|
54
|
-
def schedule_task(task_name: str, description: str = ""):
|
|
55
|
-
"""
|
|
56
|
-
Декоратор для регистрации задачи, которую можно запланировать на время
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
task_name: Название задачи (например, 'send_reminder', 'follow_up')
|
|
60
|
-
description: Описание задачи (для добавления в промпт)
|
|
61
|
-
|
|
62
|
-
Example:
|
|
63
|
-
@schedule_task("send_reminder", "Отправляет напоминание пользователю")
|
|
64
|
-
async def send_reminder(user_id: int, message: str):
|
|
65
|
-
# Логика отправки напоминания
|
|
66
|
-
return {"status": "sent"}
|
|
67
|
-
"""
|
|
68
|
-
def decorator(func: Callable) -> Callable:
|
|
69
|
-
_scheduled_tasks[task_name] = {
|
|
70
|
-
'handler': func,
|
|
71
|
-
'description': description,
|
|
72
|
-
'name': func.__name__
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
logger.info(f"⏰ Зарегистрирована задача '{task_name}': {func.__name__}")
|
|
76
|
-
|
|
77
|
-
@wraps(func)
|
|
78
|
-
async def wrapper(*args, **kwargs):
|
|
79
|
-
try:
|
|
80
|
-
logger.info(f"⏰ Выполняем запланированную задачу '{task_name}'")
|
|
81
|
-
result = await func(*args, **kwargs)
|
|
82
|
-
logger.info(f"✅ Задача '{task_name}' выполнена успешно")
|
|
83
|
-
return result
|
|
84
|
-
except Exception as e:
|
|
85
|
-
logger.error(f"❌ Ошибка в задаче '{task_name}': {e}")
|
|
86
|
-
raise
|
|
87
|
-
|
|
88
|
-
return wrapper
|
|
89
|
-
return decorator
|
|
90
|
-
|
|
91
|
-
def get_event_handlers() -> Dict[str, Dict[str, Any]]:
|
|
92
|
-
"""Возвращает все зарегистрированные обработчики событий"""
|
|
93
|
-
return _event_handlers.copy()
|
|
94
|
-
|
|
95
|
-
def get_scheduled_tasks() -> Dict[str, Dict[str, Any]]:
|
|
96
|
-
"""Возвращает все зарегистрированные задачи"""
|
|
97
|
-
return _scheduled_tasks.copy()
|
|
98
|
-
|
|
99
|
-
def get_handlers_for_prompt() -> str:
|
|
100
|
-
"""
|
|
101
|
-
Возвращает описание всех обработчиков для добавления в промпт
|
|
102
|
-
"""
|
|
103
|
-
if not _event_handlers and not _scheduled_tasks:
|
|
104
|
-
return ""
|
|
105
|
-
|
|
106
|
-
prompt_parts = []
|
|
107
|
-
|
|
108
|
-
if _event_handlers:
|
|
109
|
-
prompt_parts.append("ДОСТУПНЫЕ ОБРАБОТЧИКИ СОБЫТИЙ:")
|
|
110
|
-
for event_type, handler_info in _event_handlers.items():
|
|
111
|
-
prompt_parts.append(f"- {event_type}: {handler_info['description']}")
|
|
112
|
-
|
|
113
|
-
if _scheduled_tasks:
|
|
114
|
-
prompt_parts.append("\nДОСТУПНЫЕ ЗАДАЧИ ДЛЯ ПЛАНИРОВАНИЯ:")
|
|
115
|
-
for task_name, task_info in _scheduled_tasks.items():
|
|
116
|
-
prompt_parts.append(f"- {task_name}: {task_info['description']}")
|
|
117
|
-
|
|
118
|
-
return "\n".join(prompt_parts)
|
|
119
|
-
|
|
120
|
-
async def execute_event_handler(event_type: str, *args, **kwargs) -> Any:
|
|
121
|
-
"""Выполняет обработчик события по типу"""
|
|
122
|
-
if event_type not in _event_handlers:
|
|
123
|
-
raise ValueError(f"Обработчик события '{event_type}' не найден")
|
|
124
|
-
|
|
125
|
-
handler_info = _event_handlers[event_type]
|
|
126
|
-
return await handler_info['handler'](*args, **kwargs)
|
|
127
|
-
|
|
128
|
-
async def execute_scheduled_task(task_name: str, *args, **kwargs) -> Any:
|
|
129
|
-
"""Выполняет запланированную задачу по имени"""
|
|
130
|
-
if task_name not in _scheduled_tasks:
|
|
131
|
-
raise ValueError(f"Задача '{task_name}' не найдена")
|
|
132
|
-
|
|
133
|
-
task_info = _scheduled_tasks[task_name]
|
|
134
|
-
return await task_info['handler'](*args, **kwargs)
|
|
135
|
-
|
|
136
|
-
async def schedule_task_for_later(task_name: str, delay_seconds: int, *args, **kwargs):
|
|
137
|
-
"""
|
|
138
|
-
Планирует выполнение задачи через указанное время
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
task_name: Название задачи
|
|
142
|
-
delay_seconds: Задержка в секундах
|
|
143
|
-
*args, **kwargs: Аргументы для задачи
|
|
144
|
-
"""
|
|
145
|
-
if task_name not in _scheduled_tasks:
|
|
146
|
-
raise ValueError(f"Задача '{task_name}' не найдена")
|
|
147
|
-
|
|
148
|
-
logger.info(f"⏰ Планируем задачу '{task_name}' через {delay_seconds} секунд")
|
|
149
|
-
|
|
150
|
-
async def delayed_task():
|
|
151
|
-
await asyncio.sleep(delay_seconds)
|
|
152
|
-
await execute_scheduled_task(task_name, *args, **kwargs)
|
|
153
|
-
|
|
154
|
-
# Запускаем задачу в фоне
|
|
155
|
-
asyncio.create_task(delayed_task())
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
"status": "scheduled",
|
|
159
|
-
"task_name": task_name,
|
|
160
|
-
"delay_seconds": delay_seconds,
|
|
161
|
-
"scheduled_at": datetime.now().isoformat()
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async def execute_scheduled_task_from_event(user_id: int, task_name: str, event_info: str):
|
|
165
|
-
"""
|
|
166
|
-
Выполняет запланированную задачу на основе события от ИИ
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
user_id: ID пользователя
|
|
170
|
-
task_name: Название задачи
|
|
171
|
-
event_info: Информация от ИИ (содержит время и сообщение)
|
|
172
|
-
"""
|
|
173
|
-
if task_name not in _scheduled_tasks:
|
|
174
|
-
raise ValueError(f"Задача '{task_name}' не найдена")
|
|
175
|
-
|
|
176
|
-
# Парсим event_info для извлечения времени и сообщения
|
|
177
|
-
# Формат: "через 2 часа: напомнить о приеме"
|
|
178
|
-
try:
|
|
179
|
-
if ":" in event_info:
|
|
180
|
-
time_part, message = event_info.split(":", 1)
|
|
181
|
-
time_part = time_part.strip()
|
|
182
|
-
message = message.strip()
|
|
183
|
-
else:
|
|
184
|
-
time_part = event_info
|
|
185
|
-
message = "Напоминание"
|
|
186
|
-
|
|
187
|
-
# Парсим время
|
|
188
|
-
delay_seconds = _parse_time_to_seconds(time_part)
|
|
189
|
-
|
|
190
|
-
# Планируем задачу
|
|
191
|
-
result = await schedule_task_for_later(task_name, delay_seconds, user_id, message)
|
|
192
|
-
|
|
193
|
-
return result
|
|
194
|
-
|
|
195
|
-
except Exception as e:
|
|
196
|
-
logger.error(f"Ошибка парсинга времени из event_info '{event_info}': {e}")
|
|
197
|
-
# Fallback - планируем через 1 час
|
|
198
|
-
return await schedule_task_for_later(task_name, 3600, user_id, event_info)
|
|
199
|
-
|
|
200
|
-
def _parse_time_to_seconds(time_str: str) -> int:
|
|
201
|
-
"""
|
|
202
|
-
Парсит строку времени в секунды
|
|
203
|
-
Поддерживает форматы:
|
|
204
|
-
- "через 2 часа"
|
|
205
|
-
- "через 30 минут"
|
|
206
|
-
- "через 1 день"
|
|
207
|
-
- "через 2 часа 30 минут"
|
|
208
|
-
"""
|
|
209
|
-
import re
|
|
210
|
-
|
|
211
|
-
time_str = time_str.lower().strip()
|
|
212
|
-
|
|
213
|
-
# Ищем часы
|
|
214
|
-
hours_match = re.search(r'(\d+)\s*час', time_str)
|
|
215
|
-
hours = int(hours_match.group(1)) if hours_match else 0
|
|
216
|
-
|
|
217
|
-
# Ищем минуты
|
|
218
|
-
minutes_match = re.search(r'(\d+)\s*минут', time_str)
|
|
219
|
-
minutes = int(minutes_match.group(1)) if minutes_match else 0
|
|
220
|
-
|
|
221
|
-
# Ищем дни
|
|
222
|
-
days_match = re.search(r'(\d+)\s*дн', time_str)
|
|
223
|
-
days = int(days_match.group(1)) if days_match else 0
|
|
224
|
-
|
|
225
|
-
# Конвертируем в секунды
|
|
226
|
-
total_seconds = (days * 24 * 3600) + (hours * 3600) + (minutes * 60)
|
|
227
|
-
|
|
228
|
-
# Минимум 1 минута
|
|
229
|
-
return max(total_seconds, 60)
|