smart-bot-factory 0.1.4__py3-none-any.whl → 0.1.6__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 (38) hide show
  1. smart_bot_factory/__init__.py +0 -30
  2. smart_bot_factory/admin/admin_logic.py +11 -11
  3. smart_bot_factory/cli.py +147 -85
  4. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +2 -0
  5. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +95 -28
  6. smart_bot_factory/core/bot_utils.py +224 -88
  7. smart_bot_factory/core/conversation_manager.py +542 -535
  8. smart_bot_factory/core/decorators.py +927 -230
  9. smart_bot_factory/core/message_sender.py +2 -7
  10. smart_bot_factory/core/router.py +173 -0
  11. smart_bot_factory/core/router_manager.py +166 -0
  12. smart_bot_factory/creation/__init__.py +1 -2
  13. smart_bot_factory/creation/bot_builder.py +103 -13
  14. smart_bot_factory/creation/bot_testing.py +74 -13
  15. smart_bot_factory/event/__init__.py +12 -0
  16. smart_bot_factory/handlers/handlers.py +10 -2
  17. smart_bot_factory/integrations/supabase_client.py +272 -2
  18. smart_bot_factory/message/__init__.py +12 -0
  19. smart_bot_factory/router/__init__.py +9 -0
  20. smart_bot_factory/supabase/__init__.py +7 -0
  21. smart_bot_factory-0.1.6.dist-info/METADATA +466 -0
  22. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/RECORD +26 -31
  23. smart_bot_factory/analytics/__init__.py +0 -7
  24. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +0 -9
  25. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +0 -582
  26. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +0 -66
  27. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +0 -232
  28. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +0 -28
  29. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +0 -7
  30. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +0 -16
  31. smart_bot_factory/configs/growthmed-helper/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
  32. smart_bot_factory/core/__init__.py +0 -22
  33. smart_bot_factory/integrations/__init__.py +0 -9
  34. smart_bot_factory-0.1.4.dist-info/METADATA +0 -126
  35. /smart_bot_factory/{configs/growthmed-helper/env_example.txt → supabase/example_usage.py} +0 -0
  36. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/WHEEL +0 -0
  37. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/entry_points.txt +0 -0
  38. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,33 +1,3 @@
1
1
  """
2
2
  Smart Bot Factory - библиотека для создания умных чат-ботов
3
3
  """
4
-
5
- from .creation.bot_builder import BotBuilder
6
- from .core.decorators import event_handler, schedule_task
7
- from .core.message_sender import send_message_by_ai, send_message_by_human
8
- from .config import Config
9
- from .integrations.openai_client import OpenAIClient
10
- from .integrations.supabase_client import SupabaseClient
11
- from .core.conversation_manager import ConversationManager
12
- from .admin.admin_manager import AdminManager
13
- from .utils.prompt_loader import PromptLoader
14
- from .handlers.handlers import setup_handlers
15
- from .admin.admin_logic import setup_admin_handlers
16
- from .core.bot_utils import setup_utils_handlers, parse_ai_response, process_events
17
- from .utils.debug_routing import setup_debug_handlers
18
- from .core.states import UserStates, AdminStates
19
- from .creation.bot_testing import main as bot_testing_main
20
- from .analytics.analytics_manager import AnalyticsManager
21
- from .admin.timeout_checker import check_timeouts, setup_bot_environment
22
- from .setup_checker import check_setup
23
- from .admin.admin_tester import test_admin_system
24
-
25
- __all__ = [
26
- 'BotBuilder',
27
- 'event_handler',
28
- 'schedule_task',
29
- 'send_message_by_ai',
30
- 'send_message_by_human',
31
- 'OpenAIClient',
32
- 'SupabaseClient'
33
- ]
@@ -22,7 +22,7 @@ def setup_admin_handlers(dp):
22
22
 
23
23
  async def admin_start_handler(message: Message, state: FSMContext):
24
24
  """Обработчик /start для админов в режиме администратора"""
25
- from handlers import get_global_var
25
+ from ..handlers.handlers import get_global_var
26
26
  admin_manager = get_global_var('admin_manager')
27
27
 
28
28
  await state.set_state(AdminStates.admin_mode)
@@ -55,7 +55,7 @@ async def admin_start_handler(message: Message, state: FSMContext):
55
55
  @admin_router.message(Command("стат"))
56
56
  async def admin_stats_handler(message: Message, state: FSMContext):
57
57
  """Статистика воронки"""
58
- from handlers import get_global_var
58
+ from ..handlers.handlers import get_global_var
59
59
  admin_manager = get_global_var('admin_manager')
60
60
  analytics_manager = get_global_var('analytics_manager')
61
61
 
@@ -82,7 +82,7 @@ async def admin_stats_handler(message: Message, state: FSMContext):
82
82
  @admin_router.message(Command("история"))
83
83
  async def admin_history_handler(message: Message, state: FSMContext):
84
84
  """История пользователя"""
85
- from handlers import get_global_var
85
+ from ..handlers.handlers import get_global_var
86
86
  admin_manager = get_global_var('admin_manager')
87
87
  analytics_manager = get_global_var('analytics_manager')
88
88
 
@@ -118,7 +118,7 @@ async def admin_history_handler(message: Message, state: FSMContext):
118
118
  @admin_router.message(Command("чат"))
119
119
  async def admin_chat_handler(message: Message, state: FSMContext):
120
120
  """Начать диалог с пользователем"""
121
- from handlers import get_global_var
121
+ from ..handlers.handlers import get_global_var
122
122
 
123
123
  admin_manager = get_global_var('admin_manager')
124
124
  supabase_client = get_global_var('supabase_client')
@@ -173,7 +173,7 @@ async def admin_chat_handler(message: Message, state: FSMContext):
173
173
  @admin_router.message(Command("чаты"))
174
174
  async def admin_active_chats_command(message: Message, state: FSMContext):
175
175
  """Показать активные диалоги админов"""
176
- from handlers import get_global_var
176
+ from ..handlers.handlers import get_global_var
177
177
  admin_manager = get_global_var('admin_manager')
178
178
  conversation_manager = get_global_var('conversation_manager')
179
179
 
@@ -194,7 +194,7 @@ async def admin_active_chats_command(message: Message, state: FSMContext):
194
194
  @admin_router.message(Command("стоп"))
195
195
  async def admin_stop_handler(message: Message, state: FSMContext):
196
196
  """Завершить диалог"""
197
- from handlers import get_global_var
197
+ from ..handlers.handlers import get_global_var
198
198
  admin_manager = get_global_var('admin_manager')
199
199
  conversation_manager = get_global_var('conversation_manager')
200
200
 
@@ -233,9 +233,9 @@ async def admin_stop_handler(message: Message, state: FSMContext):
233
233
  @admin_router.message(Command("админ"))
234
234
  async def admin_toggle_handler(message: Message, state: FSMContext):
235
235
  """Переключение режима админа"""
236
- from handlers import get_global_var
236
+ from ..handlers.handlers import get_global_var
237
237
  admin_manager = get_global_var('admin_manager')
238
- from .handlers import user_start_handler
238
+ from ..handlers.handlers import user_start_handler
239
239
 
240
240
  if not admin_manager.is_admin(message.from_user.id):
241
241
  return
@@ -253,7 +253,7 @@ async def admin_toggle_handler(message: Message, state: FSMContext):
253
253
  @admin_router.message(Command("debug_chat"))
254
254
  async def debug_chat_handler(message: Message, state: FSMContext):
255
255
  """Отладка диалогов админов"""
256
- from handlers import get_global_var
256
+ from ..handlers.handlers import get_global_var
257
257
  admin_manager = get_global_var('admin_manager')
258
258
  conversation_manager = get_global_var('conversation_manager')
259
259
  supabase_client = get_global_var('supabase_client')
@@ -304,7 +304,7 @@ async def debug_chat_handler(message: Message, state: FSMContext):
304
304
  @admin_router.callback_query(F.data.startswith("admin_"))
305
305
  async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
306
306
  """Обработчик callback кнопок админов"""
307
- from handlers import get_global_var
307
+ from ..handlers.handlers import get_global_var
308
308
  admin_manager = get_global_var('admin_manager')
309
309
  analytics_manager = get_global_var('analytics_manager')
310
310
  conversation_manager = get_global_var('conversation_manager')
@@ -394,7 +394,7 @@ async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
394
394
  @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation))
395
395
  async def admin_message_handler(message: Message, state: FSMContext):
396
396
  """Обработчик сообщений админов"""
397
- from handlers import get_global_var
397
+ from ..handlers.handlers import get_global_var
398
398
  admin_manager = get_global_var('admin_manager')
399
399
  conversation_manager = get_global_var('conversation_manager')
400
400
 
smart_bot_factory/cli.py CHANGED
@@ -32,12 +32,12 @@ def list():
32
32
  """Показать список доступных ботов"""
33
33
  bots = list_bots_in_bots_folder()
34
34
  if not bots:
35
- click.echo("Нет доступных ботов")
35
+ click.echo("🤖 Нет доступных ботов")
36
36
  return
37
37
 
38
- click.echo("Доступные боты:")
38
+ click.echo("🤖 Доступные боты:")
39
39
  for bot in sorted(bots):
40
- click.echo(f" - {bot}")
40
+ click.echo(f" 📱 {bot}")
41
41
 
42
42
  @cli.command()
43
43
  @click.argument("bot_id")
@@ -67,7 +67,7 @@ def run(bot_id: str):
67
67
 
68
68
  # Загружаем .env файл
69
69
  load_dotenv(env_file)
70
- click.echo(f"Загружен .env файл: {env_file}")
70
+ click.echo(f"⚙️ Загружен .env файл: {env_file}")
71
71
 
72
72
  # Устанавливаем переменные окружения
73
73
  os.environ["BOT_ID"] = bot_id
@@ -76,17 +76,17 @@ def run(bot_id: str):
76
76
  prompts_dir = bot_path / "prompts"
77
77
  if prompts_dir.exists():
78
78
  os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
79
- click.echo(f"Установлен путь к промптам: {prompts_dir}")
79
+ click.echo(f"📝 Установлен путь к промптам: {prompts_dir}")
80
80
 
81
81
  # Запускаем бота из корневой директории
82
- click.echo(f"Запускаем бота {bot_id}...")
82
+ click.echo(f"🚀 Запускаем бота {bot_id}...")
83
83
  subprocess.run([sys.executable, str(bot_file)], check=True, cwd=str(PROJECT_ROOT))
84
84
 
85
85
  except subprocess.CalledProcessError as e:
86
- click.echo(f"Ошибка при запуске бота: {e}", err=True)
86
+ click.echo(f"Ошибка при запуске бота: {e}", err=True)
87
87
  sys.exit(1)
88
88
  except Exception as e:
89
- click.echo(f"Ошибка: {e}", err=True)
89
+ click.echo(f"Ошибка: {e}", err=True)
90
90
  sys.exit(1)
91
91
 
92
92
  @cli.command()
@@ -115,7 +115,7 @@ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: i
115
115
  click.echo(f"⚠️ YAML тесты не найдены для бота {bot_id}")
116
116
  return
117
117
 
118
- click.echo(f"Запускаем тесты для бота {bot_id}...")
118
+ click.echo(f"🧪 Запускаем тесты для бота {bot_id}...")
119
119
 
120
120
  # Формируем команду для запуска
121
121
  bot_testing_path = Path(__file__).parent / "creation" / "bot_testing.py"
@@ -140,10 +140,10 @@ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: i
140
140
  sys.exit(1)
141
141
 
142
142
  except subprocess.CalledProcessError as e:
143
- click.echo(f"Ошибка при запуске тестов: {e}", err=True)
143
+ click.echo(f"Ошибка при запуске тестов: {e}", err=True)
144
144
  sys.exit(1)
145
145
  except Exception as e:
146
- click.echo(f"Ошибка: {e}", err=True)
146
+ click.echo(f"Ошибка: {e}", err=True)
147
147
  sys.exit(1)
148
148
 
149
149
  @cli.command()
@@ -164,14 +164,14 @@ def config(bot_id: str):
164
164
  # Определяем редактор
165
165
  editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
166
166
 
167
- click.echo(f"Открываем конфигурацию бота {bot_id}...")
167
+ click.echo(f"⚙️ Открываем конфигурацию бота {bot_id}...")
168
168
  subprocess.run([editor, str(env_file)], check=True)
169
169
 
170
170
  except subprocess.CalledProcessError as e:
171
- click.echo(f"Ошибка при открытии редактора: {e}", err=True)
171
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
172
172
  sys.exit(1)
173
173
  except Exception as e:
174
- click.echo(f"Ошибка: {e}", err=True)
174
+ click.echo(f"Ошибка: {e}", err=True)
175
175
  sys.exit(1)
176
176
 
177
177
  @cli.command()
@@ -191,17 +191,24 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
191
191
  if not prompts_dir.exists():
192
192
  raise click.ClickException(f"Папка промптов не найдена для бота {bot_id}")
193
193
 
194
- if list_prompts:
195
- # Показываем список промптов
194
+ if list_prompts or (not edit_prompt and not add_prompt):
195
+ # Показываем список промптов (по умолчанию или с флагом --list)
196
196
  prompt_files = [f.name for f in prompts_dir.glob("*.txt")]
197
197
 
198
198
  if not prompt_files:
199
- click.echo("Промпты не найдены")
199
+ click.echo("📝 Промпты не найдены")
200
200
  return
201
201
 
202
- click.echo(f"Промпты бота {bot_id}:")
202
+ click.echo(f"📝 Промпты бота {bot_id}:")
203
203
  for prompt_file in sorted(prompt_files):
204
- click.echo(f" - {prompt_file[:-4]}")
204
+ click.echo(f" 📄 {prompt_file[:-4]}")
205
+
206
+ # Показываем справку только если не указан флаг --list
207
+ if not list_prompts:
208
+ click.echo()
209
+ click.echo("Использование:")
210
+ click.echo(" sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
211
+ click.echo(" sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
205
212
 
206
213
  elif edit_prompt:
207
214
  # Редактируем промпт
@@ -210,7 +217,7 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
210
217
  raise click.ClickException(f"Промпт {edit_prompt} не найден")
211
218
 
212
219
  editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
213
- click.echo(f"Редактируем промпт {edit_prompt}...")
220
+ click.echo(f"✏️ Редактируем промпт {edit_prompt}...")
214
221
  subprocess.run([editor, str(prompt_file)], check=True)
215
222
 
216
223
  elif add_prompt:
@@ -231,18 +238,12 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
231
238
  click.echo(f"📝 Создаем новый промпт {add_prompt}...")
232
239
  subprocess.run([editor, str(prompt_file)], check=True)
233
240
 
234
- else:
235
- # Показываем справку
236
- click.echo("Использование:")
237
- click.echo(" sbf prompts <bot_id> --list # Показать список промптов")
238
- click.echo(" sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
239
- click.echo(" sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
240
241
 
241
242
  except subprocess.CalledProcessError as e:
242
- click.echo(f"Ошибка при открытии редактора: {e}", err=True)
243
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
243
244
  sys.exit(1)
244
245
  except Exception as e:
245
- click.echo(f"Ошибка: {e}", err=True)
246
+ click.echo(f"Ошибка: {e}", err=True)
246
247
  sys.exit(1)
247
248
 
248
249
  @cli.command()
@@ -259,39 +260,39 @@ def rm(bot_id: str, force: bool = False):
259
260
  # Проверяем существование бота
260
261
  bot_path = PROJECT_ROOT / Path("bots") / bot_id
261
262
  if not bot_path.exists():
262
- raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
263
+ raise click.ClickException(f"🤖 Бот {bot_id} не найден в папке bots/")
263
264
 
264
265
  # Проверяем наличие основного файла бота в корневой директории
265
266
  bot_file = Path(f"{bot_id}.py")
266
267
  if not bot_file.exists():
267
- raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
268
+ raise click.ClickException(f"📄 Файл {bot_id}.py не найден в корневой директории")
268
269
 
269
270
  # Показываем что будет удалено
270
- click.echo("Будет удалено:")
271
- click.echo(f" - Файл запускалки: {bot_file}")
272
- click.echo(f" - Папка бота: {bot_path}")
271
+ click.echo("🗑️ Будет удалено:")
272
+ click.echo(f" 📄 Файл запускалки: {bot_file}")
273
+ click.echo(f" 📁 Папка бота: {bot_path}")
273
274
 
274
275
  # Запрашиваем подтверждение если не указан --force
275
276
  if not force:
276
- if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
- click.echo("Удаление отменено")
277
+ if not click.confirm(f"⚠️ Вы уверены, что хотите удалить бота {bot_id}?"):
278
+ click.echo("Удаление отменено")
278
279
  return
279
280
 
280
281
  # Удаляем файл запускалки
281
282
  if bot_file.exists():
282
283
  bot_file.unlink()
283
- click.echo(f"Файл {bot_file} удален")
284
+ click.echo(f"Файл {bot_file} удален")
284
285
 
285
286
  # Удаляем папку бота
286
287
  if bot_path.exists():
287
288
  import shutil
288
289
  shutil.rmtree(bot_path)
289
- click.echo(f"Папка {bot_path} удалена")
290
+ click.echo(f"Папка {bot_path} удалена")
290
291
 
291
- click.echo(f"Бот {bot_id} полностью удален")
292
+ click.echo(f"🎉 Бот {bot_id} полностью удален")
292
293
 
293
294
  except Exception as e:
294
- click.echo(f"Ошибка при удалении бота: {e}", err=True)
295
+ click.echo(f"Ошибка при удалении бота: {e}", err=True)
295
296
  sys.exit(1)
296
297
 
297
298
 
@@ -332,7 +333,7 @@ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
332
333
  click.echo(f"📝 Не забудьте настроить .env файл для нового бота")
333
334
 
334
335
  except Exception as e:
335
- click.echo(f"Ошибка при копировании бота: {e}", err=True)
336
+ click.echo(f"Ошибка при копировании бота: {e}", err=True)
336
337
  sys.exit(1)
337
338
 
338
339
  @cli.command()
@@ -349,10 +350,10 @@ def link():
349
350
  subprocess.run([sys.executable, str(link_script)], check=True)
350
351
 
351
352
  except subprocess.CalledProcessError as e:
352
- click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
353
+ click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
353
354
  sys.exit(1)
354
355
  except Exception as e:
355
- click.echo(f"Ошибка: {e}", err=True)
356
+ click.echo(f"Ошибка: {e}", err=True)
356
357
  sys.exit(1)
357
358
 
358
359
  def create_new_bot_structure(template: str, bot_id: str) -> bool:
@@ -365,7 +366,7 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
365
366
  # Создаем папку для нового бота
366
367
  bot_dir = bots_dir / bot_id
367
368
  if bot_dir.exists():
368
- click.echo(f"Бот {bot_id} уже существует")
369
+ click.echo(f"⚠️ Бот {bot_id} уже существует")
369
370
  return False
370
371
 
371
372
  bot_dir.mkdir()
@@ -384,12 +385,12 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
384
385
  # Используем другой шаблон из папки bots
385
386
  copy_from_bot_template(template, bot_dir, bot_id)
386
387
 
387
- click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
388
- click.echo(f"Не забудьте настроить .env файл перед запуском")
388
+ click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
389
+ click.echo(f"📝 Не забудьте настроить .env файл перед запуском")
389
390
  return True
390
391
 
391
392
  except Exception as e:
392
- click.echo(f"Ошибка при создании бота: {e}")
393
+ click.echo(f"Ошибка при создании бота: {e}")
393
394
  return False
394
395
 
395
396
  def list_bots_in_bots_folder() -> list:
@@ -414,25 +415,27 @@ def create_bot_template(bot_id: str) -> str:
414
415
 
415
416
  import asyncio
416
417
 
417
- from smart_bot_factory import (
418
- BotBuilder,
419
- event_handler,
420
- schedule_task,
421
- send_message_by_human,
422
- send_message_by_ai
423
- )
418
+ from smart_bot_factory.router import Router
419
+ from smart_bot_factory.message import send_message_by_human
420
+ from smart_bot_factory.supabase import SupabaseClient
421
+ from smart_bot_factory.creation import BotBuilder
422
+
423
+ # Создаем роутер для всех обработчиков
424
+ router = Router("{bot_id}_handlers")
425
+
426
+ supabase_client = SupabaseClient("{bot_id}")
424
427
 
425
428
  # =============================================================================
426
429
  # ОБРАБОТЧИКИ СОБЫТИЙ
427
430
  # =============================================================================
428
431
 
429
- @event_handler("example_event", "Пример обработчика события")
430
- async def handle_example_event(user_id: int, event_data: dict):
432
+ @router.event_handler("example_event")
433
+ async def handle_example_event(user_id: int, event_data: str):
431
434
  """Пример обработчика события"""
432
435
  # Отправляем подтверждение пользователю
433
436
  await send_message_by_human(
434
437
  user_id=user_id,
435
- message_text="✅ Событие обработано!"
438
+ message_text=f"✅ Событие обработано! Данные: {{event_data}}"
436
439
  )
437
440
 
438
441
  return {{
@@ -441,24 +444,82 @@ async def handle_example_event(user_id: int, event_data: dict):
441
444
  }}
442
445
 
443
446
  # =============================================================================
444
- # ЗАПЛАНИРОВАННЫЕ ЗАДАЧИ
447
+ # ВРЕМЕННЫЕ ЗАДАЧИ ДЛЯ ОДНОГО ПОЛЬЗОВАТЕЛЯ
445
448
  # =============================================================================
446
449
 
447
- @schedule_task("example_task", "Пример запланированной задачи")
448
- async def example_task(user_id: int, message: str):
449
- """Пример запланированной задачи"""
450
- # Отправляем сообщение
450
+ @router.schedule_task("send_reminder")
451
+ async def send_user_reminder(user_id: int, reminder_text: str):
452
+ """Отправляет напоминание пользователю"""
451
453
  await send_message_by_human(
452
454
  user_id=user_id,
453
- message_text=f"🔔 Напоминание: {{message}}"
455
+ message_text=f"🔔 Напоминание: {{reminder_text}}"
454
456
  )
455
457
 
456
458
  return {{
457
- "status": "sent",
458
- "user_id": user_id,
459
- "message": message
459
+ "status": "reminder_sent",
460
+ "message": f"Напоминание отправлено пользователю {{user_id}}"
460
461
  }}
461
462
 
463
+ # =============================================================================
464
+ # ГЛОБАЛЬНЫЕ ОБРАБОТЧИКИ (для всех пользователей)
465
+ # =============================================================================
466
+
467
+ @router.global_handler("mass_notification", notify=True)
468
+ async def send_global_announcement(announcement_text: str):
469
+ """Отправляет анонс всем пользователям бота"""
470
+ import logging
471
+ logger = logging.getLogger(__name__)
472
+
473
+ logger.info(f"🚀 Начинаем глобальную рассылку: '{{announcement_text[:50]}}...'")
474
+
475
+ # Проверяем доступность клиента
476
+ if not supabase_client:
477
+ logger.error("❌ Supabase клиент не найден для глобальной рассылки")
478
+ return {{"status": "error", "message": "Supabase клиент не найден"}}
479
+
480
+ try:
481
+ # Получаем всех пользователей из БД с учетом bot_id (изоляция данных)
482
+ users_response = supabase_client.client.table('sales_users').select(
483
+ 'telegram_id'
484
+ ).eq('bot_id', supabase_client.bot_id).execute()
485
+
486
+ if not users_response.data:
487
+ logger.warning("⚠️ Пользователи не найдены для глобальной рассылки")
488
+ return {{"status": "no_users", "message": "Пользователи не найдены"}}
489
+
490
+ total_users = len(users_response.data)
491
+ logger.info(f"👥 Найдено {{total_users}} пользователей для рассылки")
492
+
493
+ # Отправляем сообщение каждому пользователю
494
+ sent_count = 0
495
+ failed_count = 0
496
+
497
+ for user in users_response.data:
498
+ try:
499
+ await send_message_by_human(
500
+ user_id=user['telegram_id'],
501
+ message_text=f"📢 {{announcement_text}}"
502
+ )
503
+ sent_count += 1
504
+ # Небольшая задержка между отправками
505
+ await asyncio.sleep(0.1)
506
+
507
+ except Exception as e:
508
+ logger.error(f"❌ Ошибка отправки пользователю {{user['telegram_id']}}: {{e}}")
509
+ failed_count += 1
510
+
511
+ return {{
512
+ "status": "completed",
513
+ "sent_count": sent_count,
514
+ "failed_count": failed_count,
515
+ "total_users": total_users,
516
+ "message": f"Рассылка завершена: {{sent_count}} отправлено, {{failed_count}} ошибок"
517
+ }}
518
+
519
+ except Exception as e:
520
+ logger.error(f"❌ Критическая ошибка глобальной рассылки: {{e}}")
521
+ return {{"status": "error", "message": str(e)}}
522
+
462
523
  # =============================================================================
463
524
  # ОСНОВНАЯ ФУНКЦИЯ
464
525
  # =============================================================================
@@ -468,6 +529,10 @@ async def main():
468
529
  try:
469
530
  # Создаем и собираем бота
470
531
  bot_builder = BotBuilder("{bot_id}")
532
+
533
+ # Регистрируем роутер ПЕРЕД сборкой, чтобы обработчики были доступны
534
+ bot_builder.register_router(router)
535
+
471
536
  await bot_builder.build()
472
537
 
473
538
  # Запускаем бота
@@ -543,12 +608,12 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
543
608
  if source_prompts.exists():
544
609
  for prompt_file in source_prompts.glob("*.txt"):
545
610
  shutil.copy2(prompt_file, target_prompts / prompt_file.name)
546
- click.echo("Промпты скопированы из growthmed-october-24")
611
+ click.echo("📝 Промпты скопированы из growthmed-october-24")
547
612
  else:
548
613
  click.echo(f"⚠️ Папка промптов не найдена: {source_prompts}")
549
614
  # Fallback к базовым промптам
550
615
  create_basic_prompts(target_prompts)
551
- click.echo("Созданы базовые промпты")
616
+ click.echo("📝 Созданы базовые промпты")
552
617
 
553
618
  # Копируем тесты из growthmed-october-24
554
619
  source_tests = Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
@@ -558,7 +623,7 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
558
623
  for test_file in source_tests.glob("*"):
559
624
  if test_file.is_file():
560
625
  shutil.copy2(test_file, target_tests / test_file.name)
561
- click.echo("Тесты скопированы из growthmed-october-24")
626
+ click.echo("🧪 Тесты скопированы из growthmed-october-24")
562
627
 
563
628
  # Копируем welcome_files из growthmed-october-24
564
629
  source_welcome = Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
@@ -568,7 +633,7 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
568
633
  for welcome_file in source_welcome.glob("*"):
569
634
  if welcome_file.is_file():
570
635
  shutil.copy2(welcome_file, target_welcome / welcome_file.name)
571
- click.echo("Welcome файлы скопированы из growthmed-october-24")
636
+ click.echo("📁 Welcome файлы скопированы из growthmed-october-24")
572
637
 
573
638
  # Копируем files из growthmed-october-24
574
639
  source_files = Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
@@ -578,10 +643,10 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
578
643
  for file_item in source_files.glob("*"):
579
644
  if file_item.is_file():
580
645
  shutil.copy2(file_item, target_files / file_item.name)
581
- click.echo("Файлы скопированы из growthmed-october-24")
646
+ click.echo("📎 Файлы скопированы из growthmed-october-24")
582
647
 
583
648
  except Exception as e:
584
- click.echo(f"Ошибка при копировании шаблона: {e}")
649
+ click.echo(f"Ошибка при копировании шаблона: {e}")
585
650
  # Fallback к базовым промптам
586
651
  create_basic_prompts(bot_dir / "prompts")
587
652
 
@@ -615,18 +680,10 @@ def copy_bot_template(source_bot_id: str, new_bot_id: str):
615
680
  new_bot_file.write_text(content, encoding='utf-8')
616
681
  click.echo(f" 📄 Файл запускалки скопирован: {new_bot_id}.py")
617
682
 
618
- # Копируем .env файл
619
- source_env = source_dir / ".env"
683
+ # Создаем шаблон .env файла (НЕ копируем существующий)
620
684
  new_env = new_dir / ".env"
621
-
622
- if source_env.exists():
623
- shutil.copy2(source_env, new_env)
624
-
625
- # Заменяем BOT_ID в .env
626
- env_content = new_env.read_text(encoding='utf-8')
627
- env_content = env_content.replace(f'BOT_ID={source_bot_id}', f'BOT_ID={new_bot_id}')
628
- new_env.write_text(env_content, encoding='utf-8')
629
- click.echo(f" ⚙️ .env файл скопирован и обновлен")
685
+ new_env.write_text(create_env_template(new_bot_id), encoding='utf-8')
686
+ click.echo(f" ⚙️ Создан шаблон .env файла")
630
687
 
631
688
  # Копируем промпты
632
689
  source_prompts = source_dir / "prompts"
@@ -668,7 +725,7 @@ def copy_bot_template(source_bot_id: str, new_bot_id: str):
668
725
  click.echo(f" 📎 Файлы скопированы")
669
726
 
670
727
  except Exception as e:
671
- click.echo(f"Ошибка при копировании бота: {e}")
728
+ click.echo(f"Ошибка при копировании бота: {e}")
672
729
  raise
673
730
 
674
731
  def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
@@ -711,10 +768,10 @@ def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
711
768
  if test_file.is_file():
712
769
  shutil.copy2(test_file, target_tests / test_file.name)
713
770
 
714
- click.echo(f"Шаблон скопирован из {template}")
771
+ click.echo(f"📋 Шаблон скопирован из {template}")
715
772
 
716
773
  except Exception as e:
717
- click.echo(f"Ошибка при копировании шаблона {template}: {e}")
774
+ click.echo(f"Ошибка при копировании шаблона {template}: {e}")
718
775
  raise
719
776
 
720
777
  def create_basic_prompts(prompts_dir: Path):
@@ -759,6 +816,11 @@ def create_basic_prompts(prompts_dir: Path):
759
816
  - example_task: Пример запланированной задачи. Используй для демонстрации.
760
817
  Пример: {"тип": "example_task", "инфо": "через 1 час: напомнить о чем-то"}
761
818
 
819
+ ДОСТУПНЫЕ ГЛОБАЛЬНЫЕ ОБРАБОТЧИКИ:
820
+ - global_announcement: Отправляет анонс всем пользователям. Используй для важных объявлений.
821
+ Пример: {"тип": "global_announcement", "инфо": "3600"} - анонс через 1 час
822
+ Формат: "инфо" содержит время в секундах для планирования.
823
+
762
824
  Используй эти обработчики и задачи, когда это уместно в диалоге.
763
825
  </instruction>""",
764
826
  encoding='utf-8'
@@ -73,6 +73,8 @@ id этапа бери из stages
73
73
  ТРЕБОВАНИЯ К формату:
74
74
  - Всегда используй кавычки для строк
75
75
  - Если в инструкциях явно не указано, что надо выслать какое-то событие, отсылай пустой массив []
76
+ - Если надо выслать какое либо событие и в инструкции написано запустить через какое то время, то в поле инфо нужно вывести число в СЕКУНДАХ
77
+ - Также в событиях связанных со временем может быть дополнительная информация, например инфо='30|Привет пользователь!' - 30 это время в секундах после | доп информация
76
78
  - Если в инструкциях явно не указано, что надо вставить в служебную информацию какой-то файл(ы) или каталоги, отсылай пустой массив
77
79
 
78
80
  ФАЙЛЫ: