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.
- smart_bot_factory/__init__.py +0 -48
- smart_bot_factory/admin/admin_logic.py +11 -11
- smart_bot_factory/cli.py +299 -106
- smart_bot_factory/clients/__init__.py +33 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +2 -0
- smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +95 -28
- smart_bot_factory/core/__init__.py +43 -22
- smart_bot_factory/core/bot_utils.py +268 -95
- smart_bot_factory/core/conversation_manager.py +542 -535
- smart_bot_factory/core/decorators.py +943 -229
- smart_bot_factory/core/globals.py +68 -0
- smart_bot_factory/core/message_sender.py +6 -6
- smart_bot_factory/core/router.py +172 -0
- smart_bot_factory/core/router_manager.py +165 -0
- smart_bot_factory/creation/__init__.py +1 -2
- smart_bot_factory/creation/bot_builder.py +116 -8
- smart_bot_factory/creation/bot_testing.py +74 -13
- smart_bot_factory/handlers/handlers.py +10 -2
- smart_bot_factory/integrations/__init__.py +1 -0
- smart_bot_factory/integrations/supabase_client.py +272 -2
- smart_bot_factory/utm_link_generator.py +106 -0
- smart_bot_factory-0.1.5.dist-info/METADATA +466 -0
- {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/RECORD +26 -31
- smart_bot_factory/configs/growthmed-helper/env_example.txt +0 -1
- smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +0 -9
- smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +0 -582
- smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +0 -66
- smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +0 -232
- smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +0 -28
- smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +0 -7
- smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +0 -16
- 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
- smart_bot_factory/uv.lock +0 -2004
- smart_bot_factory-0.1.3.dist-info/METADATA +0 -126
- {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.1.3.dist-info → smart_bot_factory-0.1.5.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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"
|
|
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("
|
|
238
|
-
click.echo("
|
|
239
|
-
click.echo("
|
|
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"
|
|
272
|
-
click.echo(f"
|
|
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,
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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:
|
|
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("
|
|
432
|
-
async def
|
|
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"🔔 Напоминание: {{
|
|
452
|
+
message_text=f"🔔 Напоминание: {{reminder_text}}"
|
|
438
453
|
)
|
|
439
454
|
|
|
440
455
|
return {{
|
|
441
|
-
"status": "
|
|
442
|
-
"
|
|
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
|
-
#
|
|
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
|
-
#
|
|
561
|
-
|
|
562
|
-
|
|
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'
|