smart-bot-factory 0.1.3__py3-none-any.whl → 0.1.5__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 (37) hide show
  1. smart_bot_factory/__init__.py +0 -48
  2. smart_bot_factory/admin/admin_logic.py +11 -11
  3. smart_bot_factory/cli.py +299 -106
  4. smart_bot_factory/clients/__init__.py +33 -0
  5. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +2 -0
  6. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +95 -28
  7. smart_bot_factory/core/__init__.py +43 -22
  8. smart_bot_factory/core/bot_utils.py +268 -95
  9. smart_bot_factory/core/conversation_manager.py +542 -535
  10. smart_bot_factory/core/decorators.py +943 -229
  11. smart_bot_factory/core/globals.py +68 -0
  12. smart_bot_factory/core/message_sender.py +6 -6
  13. smart_bot_factory/core/router.py +172 -0
  14. smart_bot_factory/core/router_manager.py +165 -0
  15. smart_bot_factory/creation/__init__.py +1 -2
  16. smart_bot_factory/creation/bot_builder.py +116 -8
  17. smart_bot_factory/creation/bot_testing.py +74 -13
  18. smart_bot_factory/handlers/handlers.py +10 -2
  19. smart_bot_factory/integrations/__init__.py +1 -0
  20. smart_bot_factory/integrations/supabase_client.py +272 -2
  21. smart_bot_factory/utm_link_generator.py +106 -0
  22. smart_bot_factory-0.1.5.dist-info/METADATA +466 -0
  23. {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/RECORD +26 -31
  24. smart_bot_factory/configs/growthmed-helper/env_example.txt +0 -1
  25. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +0 -9
  26. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +0 -582
  27. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +0 -66
  28. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +0 -232
  29. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +0 -28
  30. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +0 -7
  31. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +0 -16
  32. 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
  33. smart_bot_factory/uv.lock +0 -2004
  34. smart_bot_factory-0.1.3.dist-info/METADATA +0 -126
  35. {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/WHEEL +0 -0
  36. {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/entry_points.txt +0 -0
  37. {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/licenses/LICENSE +0 -0
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("Доступные боты:")
39
- for bot in sorted(bots):
40
- click.echo(f" - {bot}")
38
+ click.echo("🤖 Доступные боты:")
39
+ for bot in sorted(bots):
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()
@@ -196,12 +196,12 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
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
205
 
206
206
  elif edit_prompt:
207
207
  # Редактируем промпт
@@ -210,7 +210,7 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
210
210
  raise click.ClickException(f"Промпт {edit_prompt} не найден")
211
211
 
212
212
  editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
213
- click.echo(f"Редактируем промпт {edit_prompt}...")
213
+ click.echo(f"✏️ Редактируем промпт {edit_prompt}...")
214
214
  subprocess.run([editor, str(prompt_file)], check=True)
215
215
 
216
216
  elif add_prompt:
@@ -233,16 +233,16 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
233
233
 
234
234
  else:
235
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> # Добавить новый промпт")
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
240
 
241
241
  except subprocess.CalledProcessError as e:
242
- click.echo(f"Ошибка при открытии редактора: {e}", err=True)
242
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
243
243
  sys.exit(1)
244
244
  except Exception as e:
245
- click.echo(f"Ошибка: {e}", err=True)
245
+ click.echo(f"Ошибка: {e}", err=True)
246
246
  sys.exit(1)
247
247
 
248
248
  @cli.command()
@@ -267,52 +267,92 @@ def rm(bot_id: str, force: bool = False):
267
267
  raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
268
268
 
269
269
  # Показываем что будет удалено
270
- click.echo("Будет удалено:")
271
- click.echo(f" - Файл запускалки: {bot_file}")
272
- click.echo(f" - Папка бота: {bot_path}")
270
+ click.echo("🗑️ Будет удалено:")
271
+ click.echo(f" 📄 Файл запускалки: {bot_file}")
272
+ click.echo(f" 📁 Папка бота: {bot_path}")
273
273
 
274
274
  # Запрашиваем подтверждение если не указан --force
275
275
  if not force:
276
- if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
- click.echo("Удаление отменено")
276
+ if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
+ click.echo("Удаление отменено")
278
278
  return
279
279
 
280
280
  # Удаляем файл запускалки
281
281
  if bot_file.exists():
282
282
  bot_file.unlink()
283
- click.echo(f"Файл {bot_file} удален")
283
+ click.echo(f"Файл {bot_file} удален")
284
284
 
285
285
  # Удаляем папку бота
286
286
  if bot_path.exists():
287
287
  import shutil
288
288
  shutil.rmtree(bot_path)
289
- click.echo(f"Папка {bot_path} удалена")
289
+ click.echo(f"Папка {bot_path} удалена")
290
290
 
291
- click.echo(f"Бот {bot_id} полностью удален")
291
+ click.echo(f"🗑️ Бот {bot_id} полностью удален")
292
292
 
293
293
  except Exception as e:
294
- click.echo(f"Ошибка при удалении бота: {e}", err=True)
294
+ click.echo(f"Ошибка при удалении бота: {e}", err=True)
295
295
  sys.exit(1)
296
296
 
297
297
 
298
+ @cli.command()
299
+ @click.argument("source_bot_id")
300
+ @click.argument("new_bot_id")
301
+ @click.option("--force", "-f", is_flag=True, help="Перезаписать существующего бота без подтверждения")
302
+ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
303
+ """Скопировать существующего бота как шаблон"""
304
+ try:
305
+ # Проверяем существование исходного бота
306
+ source_bot_path = PROJECT_ROOT / "bots" / source_bot_id
307
+ if not source_bot_path.exists():
308
+ raise click.ClickException(f"Исходный бот {source_bot_id} не найден в папке bots/")
309
+
310
+ # Проверяем наличие файла запускалки исходного бота
311
+ source_bot_file = PROJECT_ROOT / f"{source_bot_id}.py"
312
+ if not source_bot_file.exists():
313
+ raise click.ClickException(f"Файл запускалки {source_bot_id}.py не найден в корневой директории")
314
+
315
+ # Проверяем, не существует ли уже новый бот
316
+ new_bot_path = PROJECT_ROOT / "bots" / new_bot_id
317
+ new_bot_file = PROJECT_ROOT / f"{new_bot_id}.py"
318
+
319
+ if new_bot_path.exists() or new_bot_file.exists():
320
+ if not force:
321
+ if not click.confirm(f"⚠️ Бот {new_bot_id} уже существует. Перезаписать?"):
322
+ click.echo("❌ Копирование отменено")
323
+ return
324
+ else:
325
+ click.echo(f"⚠️ Перезаписываем существующего бота {new_bot_id}")
326
+
327
+ # Копируем бота
328
+ click.echo(f"📋 Копируем бота {source_bot_id} → {new_bot_id}...")
329
+ copy_bot_template(source_bot_id, new_bot_id)
330
+
331
+ click.echo(f"✅ Бот {new_bot_id} успешно скопирован из {source_bot_id}")
332
+ click.echo(f"📝 Не забудьте настроить .env файл для нового бота")
333
+
334
+ except Exception as e:
335
+ click.echo(f"❌ Ошибка при копировании бота: {e}", err=True)
336
+ sys.exit(1)
337
+
298
338
  @cli.command()
299
339
  def link():
300
340
  """Создать UTM-ссылку для бота"""
301
341
  try:
302
342
  # Проверяем наличие скрипта генерации ссылок
303
- link_script = Path("utm_link_generator.py")
343
+ link_script = Path(__file__).parent / "utm_link_generator.py"
304
344
  if not link_script.exists():
305
345
  raise click.ClickException("Скрипт utm_link_generator.py не найден")
306
346
 
307
- # Запускаем ваш скрипт генерации ссылок
308
- click.echo("Запускаем генератор UTM-ссылок...")
309
- subprocess.run([sys.executable, "utm_link_generator.py"], check=True)
347
+ # Запускаем скрипт генерации ссылок
348
+ click.echo("🔗 Запускаем генератор UTM-ссылок...")
349
+ subprocess.run([sys.executable, str(link_script)], check=True)
310
350
 
311
351
  except subprocess.CalledProcessError as e:
312
- click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
352
+ click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
313
353
  sys.exit(1)
314
354
  except Exception as e:
315
- click.echo(f"Ошибка: {e}", err=True)
355
+ click.echo(f"Ошибка: {e}", err=True)
316
356
  sys.exit(1)
317
357
 
318
358
  def create_new_bot_structure(template: str, bot_id: str) -> bool:
@@ -325,7 +365,7 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
325
365
  # Создаем папку для нового бота
326
366
  bot_dir = bots_dir / bot_id
327
367
  if bot_dir.exists():
328
- click.echo(f"Бот {bot_id} уже существует")
368
+ click.echo(f"⚠️ Бот {bot_id} уже существует")
329
369
  return False
330
370
 
331
371
  bot_dir.mkdir()
@@ -335,6 +375,7 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
335
375
  (bot_dir / "tests").mkdir()
336
376
  (bot_dir / "reports").mkdir()
337
377
  (bot_dir / "welcome_files").mkdir()
378
+ (bot_dir / "files").mkdir()
338
379
 
339
380
  if template == "base":
340
381
  # Используем growthmed-october-24 как базовый шаблон
@@ -343,12 +384,12 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
343
384
  # Используем другой шаблон из папки bots
344
385
  copy_from_bot_template(template, bot_dir, bot_id)
345
386
 
346
- click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
347
- click.echo(f"Не забудьте настроить .env файл перед запуском")
387
+ click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
388
+ click.echo(f"📝 Не забудьте настроить .env файл перед запуском")
348
389
  return True
349
390
 
350
391
  except Exception as e:
351
- click.echo(f"Ошибка при создании бота: {e}")
392
+ click.echo(f"Ошибка при создании бота: {e}")
352
393
  return False
353
394
 
354
395
  def list_bots_in_bots_folder() -> list:
@@ -372,51 +413,26 @@ def create_bot_template(bot_id: str) -> str:
372
413
  """
373
414
 
374
415
  import asyncio
375
- import sys
376
- import os
377
- from pathlib import Path
378
416
 
379
- # Добавляем корень проекта в путь
380
- project_root = Path(__file__).parent
381
- sys.path.insert(0, str(project_root))
382
-
383
- # Устанавливаем переменные окружения ДО импорта библиотеки
384
- bot_id = "{bot_id}"
385
- config_dir = Path("bots") / bot_id
386
- prompts_dir = config_dir / "prompts"
387
-
388
- if prompts_dir.exists():
389
- os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
390
- print(f"📁 Установлен путь к промптам: {{prompts_dir}}")
391
-
392
- # Загружаем .env файл ДО импорта библиотеки
393
- env_file = config_dir / ".env"
394
- if env_file.exists():
395
- from dotenv import load_dotenv
396
- load_dotenv(env_file)
397
- print(f"📄 Загружен .env файл: {{env_file}}")
398
- else:
399
- print(f"⚠️ .env файл не найден: {{env_file}}")
400
-
401
- from smart_bot_factory import (
402
- BotBuilder,
403
- event_handler,
404
- schedule_task,
405
- send_message_by_human,
406
- send_message_by_ai
407
- )
417
+ from smart_bot_factory.core import Router
418
+ from smart_bot_factory.core import send_message_by_human
419
+ from smart_bot_factory.clients import supabase_client
420
+ from smart_bot_factory.creation import BotBuilder
421
+
422
+ # Создаем роутер для всех обработчиков
423
+ router = Router("{bot_id}_handlers")
408
424
 
409
425
  # =============================================================================
410
426
  # ОБРАБОТЧИКИ СОБЫТИЙ
411
427
  # =============================================================================
412
428
 
413
- @event_handler("example_event", "Пример обработчика события")
414
- async def handle_example_event(user_id: int, event_data: dict):
429
+ @router.event_handler("example_event")
430
+ async def handle_example_event(user_id: int, event_data: str):
415
431
  """Пример обработчика события"""
416
432
  # Отправляем подтверждение пользователю
417
433
  await send_message_by_human(
418
434
  user_id=user_id,
419
- message_text="✅ Событие обработано!"
435
+ message_text=f"✅ Событие обработано! Данные: {{event_data}}"
420
436
  )
421
437
 
422
438
  return {{
@@ -425,24 +441,82 @@ async def handle_example_event(user_id: int, event_data: dict):
425
441
  }}
426
442
 
427
443
  # =============================================================================
428
- # ЗАПЛАНИРОВАННЫЕ ЗАДАЧИ
444
+ # ВРЕМЕННЫЕ ЗАДАЧИ ДЛЯ ОДНОГО ПОЛЬЗОВАТЕЛЯ
429
445
  # =============================================================================
430
446
 
431
- @schedule_task("example_task", "Пример запланированной задачи")
432
- async def example_task(user_id: int, message: str):
433
- """Пример запланированной задачи"""
434
- # Отправляем сообщение
447
+ @router.schedule_task("send_reminder")
448
+ async def send_user_reminder(user_id: int, reminder_text: str):
449
+ """Отправляет напоминание пользователю"""
435
450
  await send_message_by_human(
436
451
  user_id=user_id,
437
- message_text=f"🔔 Напоминание: {{message}}"
452
+ message_text=f"🔔 Напоминание: {{reminder_text}}"
438
453
  )
439
454
 
440
455
  return {{
441
- "status": "sent",
442
- "user_id": user_id,
443
- "message": message
456
+ "status": "reminder_sent",
457
+ "message": f"Напоминание отправлено пользователю {{user_id}}"
444
458
  }}
445
459
 
460
+ # =============================================================================
461
+ # ГЛОБАЛЬНЫЕ ОБРАБОТЧИКИ (для всех пользователей)
462
+ # =============================================================================
463
+
464
+ @router.global_handler("mass_notification", notify=True)
465
+ async def send_global_announcement(announcement_text: str):
466
+ """Отправляет анонс всем пользователям бота"""
467
+ import logging
468
+ logger = logging.getLogger(__name__)
469
+
470
+ logger.info(f"🚀 Начинаем глобальную рассылку: '{{announcement_text[:50]}}...'")
471
+
472
+ # Проверяем доступность клиента
473
+ if not supabase_client:
474
+ logger.error("❌ Supabase клиент не найден для глобальной рассылки")
475
+ return {{"status": "error", "message": "Supabase клиент не найден"}}
476
+
477
+ try:
478
+ # Получаем всех пользователей из БД с учетом bot_id (изоляция данных)
479
+ users_response = supabase_client.client.table('sales_users').select(
480
+ 'telegram_id'
481
+ ).eq('bot_id', supabase_client.bot_id).execute()
482
+
483
+ if not users_response.data:
484
+ logger.warning("⚠️ Пользователи не найдены для глобальной рассылки")
485
+ return {{"status": "no_users", "message": "Пользователи не найдены"}}
486
+
487
+ total_users = len(users_response.data)
488
+ logger.info(f"👥 Найдено {{total_users}} пользователей для рассылки")
489
+
490
+ # Отправляем сообщение каждому пользователю
491
+ sent_count = 0
492
+ failed_count = 0
493
+
494
+ for user in users_response.data:
495
+ try:
496
+ await send_message_by_human(
497
+ user_id=user['telegram_id'],
498
+ message_text=f"📢 {{announcement_text}}"
499
+ )
500
+ sent_count += 1
501
+ # Небольшая задержка между отправками
502
+ await asyncio.sleep(0.1)
503
+
504
+ except Exception as e:
505
+ logger.error(f"❌ Ошибка отправки пользователю {{user['telegram_id']}}: {{e}}")
506
+ failed_count += 1
507
+
508
+ return {{
509
+ "status": "completed",
510
+ "sent_count": sent_count,
511
+ "failed_count": failed_count,
512
+ "total_users": total_users,
513
+ "message": f"Рассылка завершена: {{sent_count}} отправлено, {{failed_count}} ошибок"
514
+ }}
515
+
516
+ except Exception as e:
517
+ logger.error(f"❌ Критическая ошибка глобальной рассылки: {{e}}")
518
+ return {{"status": "error", "message": str(e)}}
519
+
446
520
  # =============================================================================
447
521
  # ОСНОВНАЯ ФУНКЦИЯ
448
522
  # =============================================================================
@@ -452,6 +526,10 @@ async def main():
452
526
  try:
453
527
  # Создаем и собираем бота
454
528
  bot_builder = BotBuilder("{bot_id}")
529
+
530
+ # Регистрируем роутер ПЕРЕД сборкой, чтобы обработчики были доступны
531
+ bot_builder.register_router(router)
532
+
455
533
  await bot_builder.build()
456
534
 
457
535
  # Запускаем бота
@@ -516,30 +594,147 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
516
594
  bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
517
595
  bot_file.write_text(create_bot_template(bot_id), encoding='utf-8')
518
596
 
519
- # Копируем .env файл в папку бота
597
+ # Создаем .env файл в папке бота (НЕ копируем из шаблона)
520
598
  env_file = bot_dir / ".env"
521
599
  env_file.write_text(create_env_template(bot_id), encoding='utf-8')
522
600
 
523
601
  # Копируем промпты из growthmed-october-24
524
- source_prompts = Path("configs/growthmed-october-24/prompts")
602
+ source_prompts = Path(__file__).parent / "configs" / "growthmed-october-24" / "prompts"
525
603
  target_prompts = bot_dir / "prompts"
526
604
 
527
605
  if source_prompts.exists():
528
606
  for prompt_file in source_prompts.glob("*.txt"):
529
607
  shutil.copy2(prompt_file, target_prompts / prompt_file.name)
530
- click.echo("Промпты скопированы из growthmed-october-24")
608
+ click.echo("📝 Промпты скопированы из growthmed-october-24")
531
609
  else:
610
+ click.echo(f"⚠️ Папка промптов не найдена: {source_prompts}")
532
611
  # Fallback к базовым промптам
533
612
  create_basic_prompts(target_prompts)
534
- click.echo("Созданы базовые промпты")
613
+ click.echo("📝 Созданы базовые промпты")
614
+
615
+ # Копируем тесты из growthmed-october-24
616
+ source_tests = Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
617
+ target_tests = bot_dir / "tests"
618
+
619
+ if source_tests.exists():
620
+ for test_file in source_tests.glob("*"):
621
+ if test_file.is_file():
622
+ shutil.copy2(test_file, target_tests / test_file.name)
623
+ click.echo("🧪 Тесты скопированы из growthmed-october-24")
624
+
625
+ # Копируем welcome_files из growthmed-october-24
626
+ source_welcome = Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
627
+ target_welcome = bot_dir / "welcome_files"
628
+
629
+ if source_welcome.exists():
630
+ for welcome_file in source_welcome.glob("*"):
631
+ if welcome_file.is_file():
632
+ shutil.copy2(welcome_file, target_welcome / welcome_file.name)
633
+ click.echo("📁 Welcome файлы скопированы из growthmed-october-24")
634
+
635
+ # Копируем files из growthmed-october-24
636
+ source_files = Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
637
+ target_files = bot_dir / "files"
638
+
639
+ if source_files.exists():
640
+ for file_item in source_files.glob("*"):
641
+ if file_item.is_file():
642
+ shutil.copy2(file_item, target_files / file_item.name)
643
+ click.echo("📎 Файлы скопированы из growthmed-october-24")
535
644
 
536
645
  except Exception as e:
537
- click.echo(f"Ошибка при копировании шаблона: {e}")
646
+ click.echo(f"Ошибка при копировании шаблона: {e}")
538
647
  # Fallback к базовым промптам
539
648
  create_basic_prompts(bot_dir / "prompts")
540
649
 
650
+ def copy_bot_template(source_bot_id: str, new_bot_id: str):
651
+ """Копирует существующего бота как шаблон для нового бота"""
652
+ try:
653
+ source_dir = PROJECT_ROOT / "bots" / source_bot_id
654
+ new_dir = PROJECT_ROOT / "bots" / new_bot_id
655
+
656
+ # Создаем папку для нового бота
657
+ new_dir.mkdir(exist_ok=True)
658
+
659
+ # Создаем структуру папок
660
+ (new_dir / "prompts").mkdir(exist_ok=True)
661
+ (new_dir / "tests").mkdir(exist_ok=True)
662
+ (new_dir / "reports").mkdir(exist_ok=True)
663
+ (new_dir / "welcome_files").mkdir(exist_ok=True)
664
+ (new_dir / "files").mkdir(exist_ok=True)
665
+
666
+ # Копируем основной файл бота в корневую директорию
667
+ source_bot_file = PROJECT_ROOT / f"{source_bot_id}.py"
668
+ new_bot_file = PROJECT_ROOT / f"{new_bot_id}.py"
669
+
670
+ if source_bot_file.exists():
671
+ shutil.copy2(source_bot_file, new_bot_file)
672
+
673
+ # Заменяем название бота в файле
674
+ content = new_bot_file.read_text(encoding='utf-8')
675
+ content = content.replace(f'BotBuilder("{source_bot_id}")', f'BotBuilder("{new_bot_id}")')
676
+ content = content.replace(f'bot_id="{source_bot_id}"', f'bot_id="{new_bot_id}"')
677
+ new_bot_file.write_text(content, encoding='utf-8')
678
+ click.echo(f" 📄 Файл запускалки скопирован: {new_bot_id}.py")
679
+
680
+ # Копируем .env файл
681
+ source_env = source_dir / ".env"
682
+ new_env = new_dir / ".env"
683
+
684
+ if source_env.exists():
685
+ shutil.copy2(source_env, new_env)
686
+
687
+ # Заменяем BOT_ID в .env
688
+ env_content = new_env.read_text(encoding='utf-8')
689
+ env_content = env_content.replace(f'BOT_ID={source_bot_id}', f'BOT_ID={new_bot_id}')
690
+ new_env.write_text(env_content, encoding='utf-8')
691
+ click.echo(f" ⚙️ .env файл скопирован и обновлен")
692
+
693
+ # Копируем промпты
694
+ source_prompts = source_dir / "prompts"
695
+ new_prompts = new_dir / "prompts"
696
+
697
+ if source_prompts.exists():
698
+ for prompt_file in source_prompts.glob("*.txt"):
699
+ shutil.copy2(prompt_file, new_prompts / prompt_file.name)
700
+ click.echo(f" 📝 Промпты скопированы")
701
+
702
+ # Копируем тесты
703
+ source_tests = source_dir / "tests"
704
+ new_tests = new_dir / "tests"
705
+
706
+ if source_tests.exists():
707
+ for test_file in source_tests.glob("*"):
708
+ if test_file.is_file():
709
+ shutil.copy2(test_file, new_tests / test_file.name)
710
+ click.echo(f" 🧪 Тесты скопированы")
711
+
712
+ # Копируем welcome_files
713
+ source_welcome = source_dir / "welcome_files"
714
+ new_welcome = new_dir / "welcome_files"
715
+
716
+ if source_welcome.exists():
717
+ for welcome_file in source_welcome.glob("*"):
718
+ if welcome_file.is_file():
719
+ shutil.copy2(welcome_file, new_welcome / welcome_file.name)
720
+ click.echo(f" 📁 Welcome файлы скопированы")
721
+
722
+ # Копируем files
723
+ source_files = source_dir / "files"
724
+ new_files = new_dir / "files"
725
+
726
+ if source_files.exists():
727
+ for file_item in source_files.glob("*"):
728
+ if file_item.is_file():
729
+ shutil.copy2(file_item, new_files / file_item.name)
730
+ click.echo(f" 📎 Файлы скопированы")
731
+
732
+ except Exception as e:
733
+ click.echo(f"❌ Ошибка при копировании бота: {e}")
734
+ raise
735
+
541
736
  def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
542
- """Копирует шаблон из существующего бота"""
737
+ """Копирует шаблон из существующего бота (для команды create)"""
543
738
  try:
544
739
  template_dir = PROJECT_ROOT / Path("bots") / template
545
740
  if not template_dir.exists():
@@ -557,16 +752,9 @@ def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
557
752
  content = content.replace(f'bot_id="{template}"', f'bot_id="{bot_id}"')
558
753
  bot_file.write_text(content, encoding='utf-8')
559
754
 
560
- # Копируем .env файл
561
- template_env = template_dir / ".env"
562
- if template_env.exists():
563
- env_file = bot_dir / ".env"
564
- shutil.copy2(template_env, env_file)
565
-
566
- # Заменяем BOT_ID в .env
567
- env_content = env_file.read_text(encoding='utf-8')
568
- env_content = env_content.replace(f'BOT_ID={template}', f'BOT_ID={bot_id}')
569
- env_file.write_text(env_content, encoding='utf-8')
755
+ # Создаем .env файл в папке бота (НЕ копируем из шаблона)
756
+ env_file = bot_dir / ".env"
757
+ env_file.write_text(create_env_template(bot_id), encoding='utf-8')
570
758
 
571
759
  # Копируем промпты
572
760
  template_prompts = template_dir / "prompts"
@@ -585,10 +773,10 @@ def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
585
773
  if test_file.is_file():
586
774
  shutil.copy2(test_file, target_tests / test_file.name)
587
775
 
588
- click.echo(f"Шаблон скопирован из {template}")
776
+ click.echo(f"📋 Шаблон скопирован из {template}")
589
777
 
590
778
  except Exception as e:
591
- click.echo(f"Ошибка при копировании шаблона {template}: {e}")
779
+ click.echo(f"Ошибка при копировании шаблона {template}: {e}")
592
780
  raise
593
781
 
594
782
  def create_basic_prompts(prompts_dir: Path):
@@ -633,6 +821,11 @@ def create_basic_prompts(prompts_dir: Path):
633
821
  - example_task: Пример запланированной задачи. Используй для демонстрации.
634
822
  Пример: {"тип": "example_task", "инфо": "через 1 час: напомнить о чем-то"}
635
823
 
824
+ ДОСТУПНЫЕ ГЛОБАЛЬНЫЕ ОБРАБОТЧИКИ:
825
+ - global_announcement: Отправляет анонс всем пользователям. Используй для важных объявлений.
826
+ Пример: {"тип": "global_announcement", "инфо": "3600"} - анонс через 1 час
827
+ Формат: "инфо" содержит время в секундах для планирования.
828
+
636
829
  Используй эти обработчики и задачи, когда это уместно в диалоге.
637
830
  </instruction>""",
638
831
  encoding='utf-8'