smart-bot-factory 0.1.2__py3-none-any.whl → 0.1.4__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 (61) hide show
  1. smart_bot_factory/__init__.py +33 -0
  2. smart_bot_factory/admin/__init__.py +16 -0
  3. smart_bot_factory/admin/admin_logic.py +430 -0
  4. smart_bot_factory/admin/admin_manager.py +141 -0
  5. smart_bot_factory/admin/admin_migration.sql +136 -0
  6. smart_bot_factory/admin/admin_tester.py +151 -0
  7. smart_bot_factory/admin/timeout_checker.py +499 -0
  8. smart_bot_factory/analytics/__init__.py +7 -0
  9. smart_bot_factory/analytics/analytics_manager.py +355 -0
  10. smart_bot_factory/cli.py +768 -0
  11. smart_bot_factory/config.py +235 -0
  12. smart_bot_factory/configs/growthmed-helper/env_example.txt +1 -0
  13. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +9 -0
  14. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +582 -0
  15. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +66 -0
  16. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +232 -0
  17. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +28 -0
  18. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +7 -0
  19. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +16 -0
  20. 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
  21. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  22. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  23. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  24. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  25. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  26. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  27. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  28. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  29. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  30. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +66 -0
  31. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  32. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  33. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  34. 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
  35. smart_bot_factory/core/__init__.py +22 -0
  36. smart_bot_factory/core/bot_utils.py +703 -0
  37. smart_bot_factory/core/conversation_manager.py +536 -0
  38. smart_bot_factory/core/decorators.py +230 -0
  39. smart_bot_factory/core/message_sender.py +249 -0
  40. smart_bot_factory/core/states.py +14 -0
  41. smart_bot_factory/creation/__init__.py +8 -0
  42. smart_bot_factory/creation/bot_builder.py +329 -0
  43. smart_bot_factory/creation/bot_testing.py +986 -0
  44. smart_bot_factory/database/database_structure.sql +57 -0
  45. smart_bot_factory/database/schema.sql +1094 -0
  46. smart_bot_factory/handlers/handlers.py +583 -0
  47. smart_bot_factory/integrations/__init__.py +9 -0
  48. smart_bot_factory/integrations/openai_client.py +435 -0
  49. smart_bot_factory/integrations/supabase_client.py +592 -0
  50. smart_bot_factory/setup_checker.py +476 -0
  51. smart_bot_factory/utils/__init__.py +9 -0
  52. smart_bot_factory/utils/debug_routing.py +103 -0
  53. smart_bot_factory/utils/prompt_loader.py +427 -0
  54. smart_bot_factory/utm_link_generator.py +106 -0
  55. smart_bot_factory-0.1.4.dist-info/METADATA +126 -0
  56. smart_bot_factory-0.1.4.dist-info/RECORD +59 -0
  57. smart_bot_factory-0.1.4.dist-info/licenses/LICENSE +24 -0
  58. smart_bot_factory-0.1.2.dist-info/METADATA +0 -31
  59. smart_bot_factory-0.1.2.dist-info/RECORD +0 -4
  60. {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.4.dist-info}/WHEEL +0 -0
  61. {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,768 @@
1
+ """
2
+ CLI интерфейс для Smart Bot Factory
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import click
8
+ import subprocess
9
+ import shutil
10
+ from pathlib import Path
11
+
12
+ from project_root_finder import root
13
+
14
+ PROJECT_ROOT = root
15
+
16
+ @click.group()
17
+ def cli():
18
+ """Smart Bot Factory - инструмент для создания умных чат-ботов"""
19
+ pass
20
+
21
+ @cli.command()
22
+ @click.argument("bot_id")
23
+ @click.argument("template", required=False, default="base")
24
+ def create(bot_id: str, template: str = "base"):
25
+ """Создать нового бота"""
26
+ success = create_new_bot_structure(template, bot_id)
27
+ if not success:
28
+ sys.exit(1)
29
+
30
+ @cli.command()
31
+ def list():
32
+ """Показать список доступных ботов"""
33
+ bots = list_bots_in_bots_folder()
34
+ if not bots:
35
+ click.echo("Нет доступных ботов")
36
+ return
37
+
38
+ click.echo("Доступные боты:")
39
+ for bot in sorted(bots):
40
+ click.echo(f" - {bot}")
41
+
42
+ @cli.command()
43
+ @click.argument("bot_id")
44
+ def run(bot_id: str):
45
+ """Запустить бота"""
46
+ try:
47
+ # Проверяем существование бота
48
+ bot_path = PROJECT_ROOT / Path("bots") / bot_id
49
+ if not bot_path.exists():
50
+ raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
51
+
52
+ # Проверяем наличие основного файла бота в корневой директории
53
+ bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
54
+ if not bot_file.exists():
55
+ raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
56
+
57
+ # Проверяем наличие .env файла
58
+ env_file = bot_path / ".env"
59
+ if not env_file.exists():
60
+ raise click.ClickException(f"Файл .env не найден для бота {bot_id}")
61
+
62
+ # Настраиваем окружение для бота
63
+ from dotenv import load_dotenv
64
+
65
+ # Добавляем корень проекта в sys.path
66
+ sys.path.insert(0, str(PROJECT_ROOT))
67
+
68
+ # Загружаем .env файл
69
+ load_dotenv(env_file)
70
+ click.echo(f"Загружен .env файл: {env_file}")
71
+
72
+ # Устанавливаем переменные окружения
73
+ os.environ["BOT_ID"] = bot_id
74
+
75
+ # Устанавливаем путь к промптам
76
+ prompts_dir = bot_path / "prompts"
77
+ if prompts_dir.exists():
78
+ os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
79
+ click.echo(f"Установлен путь к промптам: {prompts_dir}")
80
+
81
+ # Запускаем бота из корневой директории
82
+ click.echo(f"Запускаем бота {bot_id}...")
83
+ subprocess.run([sys.executable, str(bot_file)], check=True, cwd=str(PROJECT_ROOT))
84
+
85
+ except subprocess.CalledProcessError as e:
86
+ click.echo(f"Ошибка при запуске бота: {e}", err=True)
87
+ sys.exit(1)
88
+ except Exception as e:
89
+ click.echo(f"Ошибка: {e}", err=True)
90
+ sys.exit(1)
91
+
92
+ @cli.command()
93
+ @click.argument("bot_id")
94
+ @click.option("--file", help="Запустить тесты только из указанного файла")
95
+ @click.option("-v", "--verbose", is_flag=True, help="Подробный вывод")
96
+ @click.option("--max-concurrent", default=5, help="Максимальное количество потоков")
97
+ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: int = 5):
98
+ """Запустить тесты бота"""
99
+ try:
100
+ # Проверяем существование бота
101
+ bot_path = PROJECT_ROOT / "bots" / bot_id
102
+ if not bot_path.exists():
103
+ raise click.ClickException(f"Бот {bot_id} не найден в папке {PROJECT_ROOT}/bots/")
104
+
105
+ # Проверяем наличие тестов
106
+ tests_dir = bot_path / "tests"
107
+ if not tests_dir.exists():
108
+ click.echo(f"⚠️ Тесты не найдены для бота {bot_id}")
109
+ return
110
+
111
+ # Ищем YAML файлы с тестами
112
+ yaml_files = [str(f.name) for f in tests_dir.glob("*.yaml")]
113
+
114
+ if not yaml_files:
115
+ click.echo(f"⚠️ YAML тесты не найдены для бота {bot_id}")
116
+ return
117
+
118
+ click.echo(f"Запускаем тесты для бота {bot_id}...")
119
+
120
+ # Формируем команду для запуска
121
+ bot_testing_path = Path(__file__).parent / "creation" / "bot_testing.py"
122
+ cmd = [sys.executable, str(bot_testing_path), bot_id]
123
+
124
+ if file:
125
+ cmd.append(file)
126
+
127
+ if verbose:
128
+ cmd.append("-v")
129
+
130
+ if max_concurrent != 5:
131
+ cmd.extend(["--max-concurrent", str(max_concurrent)])
132
+
133
+ # Запускаем тесты
134
+ result = subprocess.run(cmd, check=False)
135
+
136
+ if result.returncode == 0:
137
+ click.echo("✅ Все тесты пройдены")
138
+ else:
139
+ click.echo("❌ Есть ошибки в тестах")
140
+ sys.exit(1)
141
+
142
+ except subprocess.CalledProcessError as e:
143
+ click.echo(f"Ошибка при запуске тестов: {e}", err=True)
144
+ sys.exit(1)
145
+ except Exception as e:
146
+ click.echo(f"Ошибка: {e}", err=True)
147
+ sys.exit(1)
148
+
149
+ @cli.command()
150
+ @click.argument("bot_id")
151
+ def config(bot_id: str):
152
+ """Настроить конфигурацию бота"""
153
+ try:
154
+ # Проверяем существование бота
155
+ bot_path = PROJECT_ROOT / Path("bots") / bot_id
156
+ if not bot_path.exists():
157
+ raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
158
+
159
+ # Открываем .env файл в редакторе
160
+ env_file = bot_path / ".env"
161
+ if not env_file.exists():
162
+ raise click.ClickException(f"Файл .env не найден для бота {bot_id}")
163
+
164
+ # Определяем редактор
165
+ editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
166
+
167
+ click.echo(f"Открываем конфигурацию бота {bot_id}...")
168
+ subprocess.run([editor, str(env_file)], check=True)
169
+
170
+ except subprocess.CalledProcessError as e:
171
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
172
+ sys.exit(1)
173
+ except Exception as e:
174
+ click.echo(f"Ошибка: {e}", err=True)
175
+ sys.exit(1)
176
+
177
+ @cli.command()
178
+ @click.argument("bot_id")
179
+ @click.option("--list", "list_prompts", is_flag=True, help="Показать список промптов")
180
+ @click.option("--edit", "edit_prompt", help="Редактировать промпт")
181
+ @click.option("--add", "add_prompt", help="Добавить новый промпт")
182
+ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, add_prompt: str = None):
183
+ """Управление промптами бота"""
184
+ try:
185
+ # Проверяем существование бота
186
+ bot_path = PROJECT_ROOT / Path("bots") / bot_id
187
+ if not bot_path.exists():
188
+ raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
189
+
190
+ prompts_dir = bot_path / "prompts"
191
+ if not prompts_dir.exists():
192
+ raise click.ClickException(f"Папка промптов не найдена для бота {bot_id}")
193
+
194
+ if list_prompts:
195
+ # Показываем список промптов
196
+ prompt_files = [f.name for f in prompts_dir.glob("*.txt")]
197
+
198
+ if not prompt_files:
199
+ click.echo("Промпты не найдены")
200
+ return
201
+
202
+ click.echo(f"Промпты бота {bot_id}:")
203
+ for prompt_file in sorted(prompt_files):
204
+ click.echo(f" - {prompt_file[:-4]}")
205
+
206
+ elif edit_prompt:
207
+ # Редактируем промпт
208
+ prompt_file = prompts_dir / f"{edit_prompt}.txt"
209
+ if not prompt_file.exists():
210
+ raise click.ClickException(f"Промпт {edit_prompt} не найден")
211
+
212
+ editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
213
+ click.echo(f"Редактируем промпт {edit_prompt}...")
214
+ subprocess.run([editor, str(prompt_file)], check=True)
215
+
216
+ elif add_prompt:
217
+ # Добавляем новый промпт
218
+ prompt_file = prompts_dir / f"{add_prompt}.txt"
219
+ if prompt_file.exists():
220
+ raise click.ClickException(f"Промпт {add_prompt} уже существует")
221
+
222
+ # Создаем файл с базовым содержимым
223
+ prompt_file.write_text(
224
+ f"# Промпт: {add_prompt}\n\n"
225
+ "Введите содержимое промпта здесь...",
226
+ encoding='utf-8'
227
+ )
228
+
229
+ # Открываем в редакторе
230
+ editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
231
+ click.echo(f"📝 Создаем новый промпт {add_prompt}...")
232
+ subprocess.run([editor, str(prompt_file)], check=True)
233
+
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
+ except subprocess.CalledProcessError as e:
242
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
243
+ sys.exit(1)
244
+ except Exception as e:
245
+ click.echo(f"Ошибка: {e}", err=True)
246
+ sys.exit(1)
247
+
248
+ @cli.command()
249
+ def path():
250
+ """Показать путь к проекту"""
251
+ click.echo(PROJECT_ROOT)
252
+
253
+ @cli.command()
254
+ @click.argument("bot_id")
255
+ @click.option("--force", "-f", is_flag=True, help="Удалить без подтверждения")
256
+ def rm(bot_id: str, force: bool = False):
257
+ """Удалить бота и все его файлы"""
258
+ try:
259
+ # Проверяем существование бота
260
+ bot_path = PROJECT_ROOT / Path("bots") / bot_id
261
+ if not bot_path.exists():
262
+ raise click.ClickException(f"Бот {bot_id} не найден в папке bots/")
263
+
264
+ # Проверяем наличие основного файла бота в корневой директории
265
+ bot_file = Path(f"{bot_id}.py")
266
+ if not bot_file.exists():
267
+ raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
268
+
269
+ # Показываем что будет удалено
270
+ click.echo("Будет удалено:")
271
+ click.echo(f" - Файл запускалки: {bot_file}")
272
+ click.echo(f" - Папка бота: {bot_path}")
273
+
274
+ # Запрашиваем подтверждение если не указан --force
275
+ if not force:
276
+ if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
+ click.echo("Удаление отменено")
278
+ return
279
+
280
+ # Удаляем файл запускалки
281
+ if bot_file.exists():
282
+ bot_file.unlink()
283
+ click.echo(f"Файл {bot_file} удален")
284
+
285
+ # Удаляем папку бота
286
+ if bot_path.exists():
287
+ import shutil
288
+ shutil.rmtree(bot_path)
289
+ click.echo(f"Папка {bot_path} удалена")
290
+
291
+ click.echo(f"Бот {bot_id} полностью удален")
292
+
293
+ except Exception as e:
294
+ click.echo(f"Ошибка при удалении бота: {e}", err=True)
295
+ sys.exit(1)
296
+
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
+
338
+ @cli.command()
339
+ def link():
340
+ """Создать UTM-ссылку для бота"""
341
+ try:
342
+ # Проверяем наличие скрипта генерации ссылок
343
+ link_script = Path(__file__).parent / "utm_link_generator.py"
344
+ if not link_script.exists():
345
+ raise click.ClickException("Скрипт utm_link_generator.py не найден")
346
+
347
+ # Запускаем скрипт генерации ссылок
348
+ click.echo("🔗 Запускаем генератор UTM-ссылок...")
349
+ subprocess.run([sys.executable, str(link_script)], check=True)
350
+
351
+ except subprocess.CalledProcessError as e:
352
+ click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
353
+ sys.exit(1)
354
+ except Exception as e:
355
+ click.echo(f"Ошибка: {e}", err=True)
356
+ sys.exit(1)
357
+
358
+ def create_new_bot_structure(template: str, bot_id: str) -> bool:
359
+ """Создает новую структуру бота в папке bots/"""
360
+ try:
361
+ # Создаем папку bots если её нет
362
+ bots_dir = PROJECT_ROOT / Path("bots")
363
+ bots_dir.mkdir(exist_ok=True)
364
+
365
+ # Создаем папку для нового бота
366
+ bot_dir = bots_dir / bot_id
367
+ if bot_dir.exists():
368
+ click.echo(f"Бот {bot_id} уже существует")
369
+ return False
370
+
371
+ bot_dir.mkdir()
372
+
373
+ # Создаем структуру папок
374
+ (bot_dir / "prompts").mkdir()
375
+ (bot_dir / "tests").mkdir()
376
+ (bot_dir / "reports").mkdir()
377
+ (bot_dir / "welcome_files").mkdir()
378
+ (bot_dir / "files").mkdir()
379
+
380
+ if template == "base":
381
+ # Используем growthmed-october-24 как базовый шаблон
382
+ copy_from_growthmed_template(bot_dir, bot_id)
383
+ else:
384
+ # Используем другой шаблон из папки bots
385
+ copy_from_bot_template(template, bot_dir, bot_id)
386
+
387
+ click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
388
+ click.echo(f"Не забудьте настроить .env файл перед запуском")
389
+ return True
390
+
391
+ except Exception as e:
392
+ click.echo(f"Ошибка при создании бота: {e}")
393
+ return False
394
+
395
+ def list_bots_in_bots_folder() -> list:
396
+ """Возвращает список ботов из папки bots/"""
397
+ bots_dir = PROJECT_ROOT / Path("bots")
398
+ if not bots_dir.exists():
399
+ return []
400
+
401
+ bots = []
402
+ for item in bots_dir.iterdir():
403
+ if item.is_dir() and Path(f"{item.name}.py").exists():
404
+ bots.append(item.name)
405
+
406
+ return bots
407
+
408
+ def create_bot_template(bot_id: str) -> str:
409
+ """Создает шаблон основного файла бота"""
410
+ return f'''#!/usr/bin/env python3
411
+ """
412
+ Бот {bot_id} - создан с помощью Smart Bot Factory
413
+ """
414
+
415
+ import asyncio
416
+
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
+ )
424
+
425
+ # =============================================================================
426
+ # ОБРАБОТЧИКИ СОБЫТИЙ
427
+ # =============================================================================
428
+
429
+ @event_handler("example_event", "Пример обработчика события")
430
+ async def handle_example_event(user_id: int, event_data: dict):
431
+ """Пример обработчика события"""
432
+ # Отправляем подтверждение пользователю
433
+ await send_message_by_human(
434
+ user_id=user_id,
435
+ message_text="✅ Событие обработано!"
436
+ )
437
+
438
+ return {{
439
+ "status": "success",
440
+ "message": "Событие обработано"
441
+ }}
442
+
443
+ # =============================================================================
444
+ # ЗАПЛАНИРОВАННЫЕ ЗАДАЧИ
445
+ # =============================================================================
446
+
447
+ @schedule_task("example_task", "Пример запланированной задачи")
448
+ async def example_task(user_id: int, message: str):
449
+ """Пример запланированной задачи"""
450
+ # Отправляем сообщение
451
+ await send_message_by_human(
452
+ user_id=user_id,
453
+ message_text=f"🔔 Напоминание: {{message}}"
454
+ )
455
+
456
+ return {{
457
+ "status": "sent",
458
+ "user_id": user_id,
459
+ "message": message
460
+ }}
461
+
462
+ # =============================================================================
463
+ # ОСНОВНАЯ ФУНКЦИЯ
464
+ # =============================================================================
465
+
466
+ async def main():
467
+ """Основная функция запуска бота"""
468
+ try:
469
+ # Создаем и собираем бота
470
+ bot_builder = BotBuilder("{bot_id}")
471
+ await bot_builder.build()
472
+
473
+ # Запускаем бота
474
+ await bot_builder.start()
475
+
476
+ except Exception as e:
477
+ print(f"❌ Ошибка запуска бота: {{e}}")
478
+ raise
479
+
480
+ if __name__ == "__main__":
481
+ asyncio.run(main())
482
+ '''
483
+
484
+ def create_env_template(bot_id: str) -> str:
485
+ """Создает шаблон .env файла"""
486
+ return f'''# Telegram
487
+ TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
488
+
489
+ # Supabase
490
+ SUPABASE_URL=https://your-project.supabase.co
491
+ SUPABASE_KEY=your_supabase_anon_key
492
+
493
+ # OpenAI
494
+ OPENAI_API_KEY=sk-your-openai-api-key
495
+ OPENAI_MODEL=gpt-5-mini
496
+ OPENAI_MAX_TOKENS=1500
497
+ OPENAI_TEMPERATURE=0.7
498
+
499
+ # Промпты (каталог)
500
+ PROMT_FILES_DIR=prompts
501
+
502
+ # Файл после приветствия с подписью (если он есть - грузим его в папку welcome_file, если нет - ничего не делаем)
503
+ WELCOME_FILE_URL=welcome_files/
504
+ WELCOME_FILE_MSG=welcome_file_msg.txt
505
+
506
+ # 🆕 Администраторы (через запятую)
507
+ # Укажите Telegram ID админов
508
+ ADMIN_TELEGRAM_IDS=123456789,987654321
509
+ ADMIN_SESSION_TIMEOUT_MINUTES=30
510
+
511
+ # 🆕 Режим отладки (показывать JSON пользователям)
512
+ DEBUG_MODE=false
513
+
514
+ # Дополнительные настройки
515
+ MAX_CONTEXT_MESSAGES=50
516
+ LOG_LEVEL=INFO
517
+ MESSAGE_PARSE_MODE=Markdown
518
+
519
+ # Настройки продаж
520
+ LEAD_QUALIFICATION_THRESHOLD=7
521
+ SESSION_TIMEOUT_HOURS=24
522
+
523
+ # ⚠️ ВАЖНО: BOT_ID теперь НЕ нужен в .env!
524
+ # Bot ID автоматически определяется из имени файла запускалки
525
+ # Например: python {bot_id}.py → BOT_ID = {bot_id}
526
+ '''
527
+
528
+ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
529
+ """Копирует шаблон из growthmed-october-24"""
530
+ try:
531
+ # Создаем основной файл бота в корневой директории проекта
532
+ bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
533
+ bot_file.write_text(create_bot_template(bot_id), encoding='utf-8')
534
+
535
+ # Создаем .env файл в папке бота (НЕ копируем из шаблона)
536
+ env_file = bot_dir / ".env"
537
+ env_file.write_text(create_env_template(bot_id), encoding='utf-8')
538
+
539
+ # Копируем промпты из growthmed-october-24
540
+ source_prompts = Path(__file__).parent / "configs" / "growthmed-october-24" / "prompts"
541
+ target_prompts = bot_dir / "prompts"
542
+
543
+ if source_prompts.exists():
544
+ for prompt_file in source_prompts.glob("*.txt"):
545
+ shutil.copy2(prompt_file, target_prompts / prompt_file.name)
546
+ click.echo("Промпты скопированы из growthmed-october-24")
547
+ else:
548
+ click.echo(f"⚠️ Папка промптов не найдена: {source_prompts}")
549
+ # Fallback к базовым промптам
550
+ create_basic_prompts(target_prompts)
551
+ click.echo("Созданы базовые промпты")
552
+
553
+ # Копируем тесты из growthmed-october-24
554
+ source_tests = Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
555
+ target_tests = bot_dir / "tests"
556
+
557
+ if source_tests.exists():
558
+ for test_file in source_tests.glob("*"):
559
+ if test_file.is_file():
560
+ shutil.copy2(test_file, target_tests / test_file.name)
561
+ click.echo("Тесты скопированы из growthmed-october-24")
562
+
563
+ # Копируем welcome_files из growthmed-october-24
564
+ source_welcome = Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
565
+ target_welcome = bot_dir / "welcome_files"
566
+
567
+ if source_welcome.exists():
568
+ for welcome_file in source_welcome.glob("*"):
569
+ if welcome_file.is_file():
570
+ shutil.copy2(welcome_file, target_welcome / welcome_file.name)
571
+ click.echo("Welcome файлы скопированы из growthmed-october-24")
572
+
573
+ # Копируем files из growthmed-october-24
574
+ source_files = Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
575
+ target_files = bot_dir / "files"
576
+
577
+ if source_files.exists():
578
+ for file_item in source_files.glob("*"):
579
+ if file_item.is_file():
580
+ shutil.copy2(file_item, target_files / file_item.name)
581
+ click.echo("Файлы скопированы из growthmed-october-24")
582
+
583
+ except Exception as e:
584
+ click.echo(f"Ошибка при копировании шаблона: {e}")
585
+ # Fallback к базовым промптам
586
+ create_basic_prompts(bot_dir / "prompts")
587
+
588
+ def copy_bot_template(source_bot_id: str, new_bot_id: str):
589
+ """Копирует существующего бота как шаблон для нового бота"""
590
+ try:
591
+ source_dir = PROJECT_ROOT / "bots" / source_bot_id
592
+ new_dir = PROJECT_ROOT / "bots" / new_bot_id
593
+
594
+ # Создаем папку для нового бота
595
+ new_dir.mkdir(exist_ok=True)
596
+
597
+ # Создаем структуру папок
598
+ (new_dir / "prompts").mkdir(exist_ok=True)
599
+ (new_dir / "tests").mkdir(exist_ok=True)
600
+ (new_dir / "reports").mkdir(exist_ok=True)
601
+ (new_dir / "welcome_files").mkdir(exist_ok=True)
602
+ (new_dir / "files").mkdir(exist_ok=True)
603
+
604
+ # Копируем основной файл бота в корневую директорию
605
+ source_bot_file = PROJECT_ROOT / f"{source_bot_id}.py"
606
+ new_bot_file = PROJECT_ROOT / f"{new_bot_id}.py"
607
+
608
+ if source_bot_file.exists():
609
+ shutil.copy2(source_bot_file, new_bot_file)
610
+
611
+ # Заменяем название бота в файле
612
+ content = new_bot_file.read_text(encoding='utf-8')
613
+ content = content.replace(f'BotBuilder("{source_bot_id}")', f'BotBuilder("{new_bot_id}")')
614
+ content = content.replace(f'bot_id="{source_bot_id}"', f'bot_id="{new_bot_id}"')
615
+ new_bot_file.write_text(content, encoding='utf-8')
616
+ click.echo(f" 📄 Файл запускалки скопирован: {new_bot_id}.py")
617
+
618
+ # Копируем .env файл
619
+ source_env = source_dir / ".env"
620
+ 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 файл скопирован и обновлен")
630
+
631
+ # Копируем промпты
632
+ source_prompts = source_dir / "prompts"
633
+ new_prompts = new_dir / "prompts"
634
+
635
+ if source_prompts.exists():
636
+ for prompt_file in source_prompts.glob("*.txt"):
637
+ shutil.copy2(prompt_file, new_prompts / prompt_file.name)
638
+ click.echo(f" 📝 Промпты скопированы")
639
+
640
+ # Копируем тесты
641
+ source_tests = source_dir / "tests"
642
+ new_tests = new_dir / "tests"
643
+
644
+ if source_tests.exists():
645
+ for test_file in source_tests.glob("*"):
646
+ if test_file.is_file():
647
+ shutil.copy2(test_file, new_tests / test_file.name)
648
+ click.echo(f" 🧪 Тесты скопированы")
649
+
650
+ # Копируем welcome_files
651
+ source_welcome = source_dir / "welcome_files"
652
+ new_welcome = new_dir / "welcome_files"
653
+
654
+ if source_welcome.exists():
655
+ for welcome_file in source_welcome.glob("*"):
656
+ if welcome_file.is_file():
657
+ shutil.copy2(welcome_file, new_welcome / welcome_file.name)
658
+ click.echo(f" 📁 Welcome файлы скопированы")
659
+
660
+ # Копируем files
661
+ source_files = source_dir / "files"
662
+ new_files = new_dir / "files"
663
+
664
+ if source_files.exists():
665
+ for file_item in source_files.glob("*"):
666
+ if file_item.is_file():
667
+ shutil.copy2(file_item, new_files / file_item.name)
668
+ click.echo(f" 📎 Файлы скопированы")
669
+
670
+ except Exception as e:
671
+ click.echo(f"Ошибка при копировании бота: {e}")
672
+ raise
673
+
674
+ def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
675
+ """Копирует шаблон из существующего бота (для команды create)"""
676
+ try:
677
+ template_dir = PROJECT_ROOT / Path("bots") / template
678
+ if not template_dir.exists():
679
+ raise click.ClickException(f"Шаблон {template} не найден")
680
+
681
+ # Копируем основной файл бота в корневую директорию
682
+ template_bot_file = PROJECT_ROOT / Path(f"{template}.py")
683
+ if template_bot_file.exists():
684
+ bot_file = PROJECT_ROOT / Path(f"{bot_id}.py")
685
+ shutil.copy2(template_bot_file, bot_file)
686
+
687
+ # Заменяем название бота в файле
688
+ content = bot_file.read_text(encoding='utf-8')
689
+ content = content.replace(f'BotBuilder("{template}")', f'BotBuilder("{bot_id}")')
690
+ content = content.replace(f'bot_id="{template}"', f'bot_id="{bot_id}"')
691
+ bot_file.write_text(content, encoding='utf-8')
692
+
693
+ # Создаем .env файл в папке бота (НЕ копируем из шаблона)
694
+ env_file = bot_dir / ".env"
695
+ env_file.write_text(create_env_template(bot_id), encoding='utf-8')
696
+
697
+ # Копируем промпты
698
+ template_prompts = template_dir / "prompts"
699
+ target_prompts = bot_dir / "prompts"
700
+
701
+ if template_prompts.exists():
702
+ for prompt_file in template_prompts.glob("*.txt"):
703
+ shutil.copy2(prompt_file, target_prompts / prompt_file.name)
704
+
705
+ # Копируем тесты
706
+ template_tests = template_dir / "tests"
707
+ target_tests = bot_dir / "tests"
708
+
709
+ if template_tests.exists():
710
+ for test_file in template_tests.glob("*"):
711
+ if test_file.is_file():
712
+ shutil.copy2(test_file, target_tests / test_file.name)
713
+
714
+ click.echo(f"Шаблон скопирован из {template}")
715
+
716
+ except Exception as e:
717
+ click.echo(f"Ошибка при копировании шаблона {template}: {e}")
718
+ raise
719
+
720
+ def create_basic_prompts(prompts_dir: Path):
721
+ """Создает базовые промпты"""
722
+ # Системный промпт
723
+ (prompts_dir / "system_prompt.txt").write_text(
724
+ "Ты - помощник. Твоя задача помогать пользователям с их вопросами.\n"
725
+ "Будь дружелюбным и полезным.",
726
+ encoding='utf-8'
727
+ )
728
+
729
+ # Приветственное сообщение
730
+ (prompts_dir / "welcome_message.txt").write_text(
731
+ "👋 Привет! Я ваш помощник.\n\n"
732
+ "Чем могу помочь?",
733
+ encoding='utf-8'
734
+ )
735
+
736
+ # Финальные инструкции
737
+ (prompts_dir / "final_instructions.txt").write_text(
738
+ """<instruction>
739
+ КРИТИЧЕСКИ ВАЖНО: В НАЧАЛЕ КАЖДОГО своего ответа добавляй служебную информацию в формате:
740
+
741
+ {
742
+ "этап": id,
743
+ "качество": 1-10,
744
+ "события": [
745
+ {
746
+ "тип": тип события,
747
+ "инфо": детали события
748
+ }
749
+ ],
750
+ "файлы": [],
751
+ "каталоги": []
752
+ }
753
+
754
+ ДОСТУПНЫЕ ОБРАБОТЧИКИ СОБЫТИЙ:
755
+ - example_event: Пример обработчика события. Используй для демонстрации.
756
+ Пример: {"тип": "example_event", "инфо": {"data": "пример данных"}}
757
+
758
+ ДОСТУПНЫЕ ЗАПЛАНИРОВАННЫЕ ЗАДАЧИ:
759
+ - example_task: Пример запланированной задачи. Используй для демонстрации.
760
+ Пример: {"тип": "example_task", "инфо": "через 1 час: напомнить о чем-то"}
761
+
762
+ Используй эти обработчики и задачи, когда это уместно в диалоге.
763
+ </instruction>""",
764
+ encoding='utf-8'
765
+ )
766
+
767
+ if __name__ == "__main__":
768
+ cli()