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