smart-bot-factory 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of smart-bot-factory might be problematic. Click here for more details.

Files changed (35) hide show
  1. smart_bot_factory/__init__.py +0 -30
  2. smart_bot_factory/admin/admin_logic.py +11 -11
  3. smart_bot_factory/cli.py +138 -71
  4. smart_bot_factory/clients/__init__.py +33 -0
  5. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +2 -0
  6. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +95 -28
  7. smart_bot_factory/core/__init__.py +43 -22
  8. smart_bot_factory/core/bot_utils.py +251 -88
  9. smart_bot_factory/core/conversation_manager.py +542 -535
  10. smart_bot_factory/core/decorators.py +943 -230
  11. smart_bot_factory/core/globals.py +68 -0
  12. smart_bot_factory/core/message_sender.py +6 -6
  13. smart_bot_factory/core/router.py +172 -0
  14. smart_bot_factory/core/router_manager.py +165 -0
  15. smart_bot_factory/creation/__init__.py +1 -2
  16. smart_bot_factory/creation/bot_builder.py +116 -8
  17. smart_bot_factory/creation/bot_testing.py +74 -13
  18. smart_bot_factory/handlers/handlers.py +10 -2
  19. smart_bot_factory/integrations/__init__.py +1 -0
  20. smart_bot_factory/integrations/supabase_client.py +272 -2
  21. smart_bot_factory-0.1.5.dist-info/METADATA +466 -0
  22. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/RECORD +25 -30
  23. smart_bot_factory/configs/growthmed-helper/env_example.txt +0 -1
  24. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +0 -9
  25. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +0 -582
  26. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +0 -66
  27. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +0 -232
  28. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +0 -28
  29. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +0 -7
  30. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +0 -16
  31. smart_bot_factory/configs/growthmed-helper/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
  32. smart_bot_factory-0.1.4.dist-info/METADATA +0 -126
  33. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/WHEEL +0 -0
  34. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/entry_points.txt +0 -0
  35. {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,33 +1,3 @@
1
1
  """
2
2
  Smart Bot Factory - библиотека для создания умных чат-ботов
3
3
  """
4
-
5
- from .creation.bot_builder import BotBuilder
6
- from .core.decorators import event_handler, schedule_task
7
- from .core.message_sender import send_message_by_ai, send_message_by_human
8
- from .config import Config
9
- from .integrations.openai_client import OpenAIClient
10
- from .integrations.supabase_client import SupabaseClient
11
- from .core.conversation_manager import ConversationManager
12
- from .admin.admin_manager import AdminManager
13
- from .utils.prompt_loader import PromptLoader
14
- from .handlers.handlers import setup_handlers
15
- from .admin.admin_logic import setup_admin_handlers
16
- from .core.bot_utils import setup_utils_handlers, parse_ai_response, process_events
17
- from .utils.debug_routing import setup_debug_handlers
18
- from .core.states import UserStates, AdminStates
19
- from .creation.bot_testing import main as bot_testing_main
20
- from .analytics.analytics_manager import AnalyticsManager
21
- from .admin.timeout_checker import check_timeouts, setup_bot_environment
22
- from .setup_checker import check_setup
23
- from .admin.admin_tester import test_admin_system
24
-
25
- __all__ = [
26
- 'BotBuilder',
27
- 'event_handler',
28
- 'schedule_task',
29
- 'send_message_by_ai',
30
- 'send_message_by_human',
31
- 'OpenAIClient',
32
- 'SupabaseClient'
33
- ]
@@ -22,7 +22,7 @@ def setup_admin_handlers(dp):
22
22
 
23
23
  async def admin_start_handler(message: Message, state: FSMContext):
24
24
  """Обработчик /start для админов в режиме администратора"""
25
- from handlers import get_global_var
25
+ from ..handlers.handlers import get_global_var
26
26
  admin_manager = get_global_var('admin_manager')
27
27
 
28
28
  await state.set_state(AdminStates.admin_mode)
@@ -55,7 +55,7 @@ async def admin_start_handler(message: Message, state: FSMContext):
55
55
  @admin_router.message(Command("стат"))
56
56
  async def admin_stats_handler(message: Message, state: FSMContext):
57
57
  """Статистика воронки"""
58
- from handlers import get_global_var
58
+ from ..handlers.handlers import get_global_var
59
59
  admin_manager = get_global_var('admin_manager')
60
60
  analytics_manager = get_global_var('analytics_manager')
61
61
 
@@ -82,7 +82,7 @@ async def admin_stats_handler(message: Message, state: FSMContext):
82
82
  @admin_router.message(Command("история"))
83
83
  async def admin_history_handler(message: Message, state: FSMContext):
84
84
  """История пользователя"""
85
- from handlers import get_global_var
85
+ from ..handlers.handlers import get_global_var
86
86
  admin_manager = get_global_var('admin_manager')
87
87
  analytics_manager = get_global_var('analytics_manager')
88
88
 
@@ -118,7 +118,7 @@ async def admin_history_handler(message: Message, state: FSMContext):
118
118
  @admin_router.message(Command("чат"))
119
119
  async def admin_chat_handler(message: Message, state: FSMContext):
120
120
  """Начать диалог с пользователем"""
121
- from handlers import get_global_var
121
+ from ..handlers.handlers import get_global_var
122
122
 
123
123
  admin_manager = get_global_var('admin_manager')
124
124
  supabase_client = get_global_var('supabase_client')
@@ -173,7 +173,7 @@ async def admin_chat_handler(message: Message, state: FSMContext):
173
173
  @admin_router.message(Command("чаты"))
174
174
  async def admin_active_chats_command(message: Message, state: FSMContext):
175
175
  """Показать активные диалоги админов"""
176
- from handlers import get_global_var
176
+ from ..handlers.handlers import get_global_var
177
177
  admin_manager = get_global_var('admin_manager')
178
178
  conversation_manager = get_global_var('conversation_manager')
179
179
 
@@ -194,7 +194,7 @@ async def admin_active_chats_command(message: Message, state: FSMContext):
194
194
  @admin_router.message(Command("стоп"))
195
195
  async def admin_stop_handler(message: Message, state: FSMContext):
196
196
  """Завершить диалог"""
197
- from handlers import get_global_var
197
+ from ..handlers.handlers import get_global_var
198
198
  admin_manager = get_global_var('admin_manager')
199
199
  conversation_manager = get_global_var('conversation_manager')
200
200
 
@@ -233,9 +233,9 @@ async def admin_stop_handler(message: Message, state: FSMContext):
233
233
  @admin_router.message(Command("админ"))
234
234
  async def admin_toggle_handler(message: Message, state: FSMContext):
235
235
  """Переключение режима админа"""
236
- from handlers import get_global_var
236
+ from ..handlers.handlers import get_global_var
237
237
  admin_manager = get_global_var('admin_manager')
238
- from .handlers import user_start_handler
238
+ from ..handlers.handlers import user_start_handler
239
239
 
240
240
  if not admin_manager.is_admin(message.from_user.id):
241
241
  return
@@ -253,7 +253,7 @@ async def admin_toggle_handler(message: Message, state: FSMContext):
253
253
  @admin_router.message(Command("debug_chat"))
254
254
  async def debug_chat_handler(message: Message, state: FSMContext):
255
255
  """Отладка диалогов админов"""
256
- from handlers import get_global_var
256
+ from ..handlers.handlers import get_global_var
257
257
  admin_manager = get_global_var('admin_manager')
258
258
  conversation_manager = get_global_var('conversation_manager')
259
259
  supabase_client = get_global_var('supabase_client')
@@ -304,7 +304,7 @@ async def debug_chat_handler(message: Message, state: FSMContext):
304
304
  @admin_router.callback_query(F.data.startswith("admin_"))
305
305
  async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
306
306
  """Обработчик callback кнопок админов"""
307
- from handlers import get_global_var
307
+ from ..handlers.handlers import get_global_var
308
308
  admin_manager = get_global_var('admin_manager')
309
309
  analytics_manager = get_global_var('analytics_manager')
310
310
  conversation_manager = get_global_var('conversation_manager')
@@ -394,7 +394,7 @@ async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
394
394
  @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation))
395
395
  async def admin_message_handler(message: Message, state: FSMContext):
396
396
  """Обработчик сообщений админов"""
397
- from handlers import get_global_var
397
+ from ..handlers.handlers import get_global_var
398
398
  admin_manager = get_global_var('admin_manager')
399
399
  conversation_manager = get_global_var('conversation_manager')
400
400
 
smart_bot_factory/cli.py CHANGED
@@ -32,12 +32,12 @@ def list():
32
32
  """Показать список доступных ботов"""
33
33
  bots = list_bots_in_bots_folder()
34
34
  if not bots:
35
- click.echo("Нет доступных ботов")
35
+ click.echo("🤖 Нет доступных ботов")
36
36
  return
37
37
 
38
- click.echo("Доступные боты:")
39
- for bot in sorted(bots):
40
- click.echo(f" - {bot}")
38
+ click.echo("🤖 Доступные боты:")
39
+ for bot in sorted(bots):
40
+ click.echo(f" 📱 {bot}")
41
41
 
42
42
  @cli.command()
43
43
  @click.argument("bot_id")
@@ -67,7 +67,7 @@ def run(bot_id: str):
67
67
 
68
68
  # Загружаем .env файл
69
69
  load_dotenv(env_file)
70
- click.echo(f"Загружен .env файл: {env_file}")
70
+ click.echo(f"⚙️ Загружен .env файл: {env_file}")
71
71
 
72
72
  # Устанавливаем переменные окружения
73
73
  os.environ["BOT_ID"] = bot_id
@@ -76,17 +76,17 @@ def run(bot_id: str):
76
76
  prompts_dir = bot_path / "prompts"
77
77
  if prompts_dir.exists():
78
78
  os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
79
- click.echo(f"Установлен путь к промптам: {prompts_dir}")
79
+ click.echo(f"📝 Установлен путь к промптам: {prompts_dir}")
80
80
 
81
81
  # Запускаем бота из корневой директории
82
- click.echo(f"Запускаем бота {bot_id}...")
82
+ click.echo(f"🚀 Запускаем бота {bot_id}...")
83
83
  subprocess.run([sys.executable, str(bot_file)], check=True, cwd=str(PROJECT_ROOT))
84
84
 
85
85
  except subprocess.CalledProcessError as e:
86
- click.echo(f"Ошибка при запуске бота: {e}", err=True)
86
+ click.echo(f"Ошибка при запуске бота: {e}", err=True)
87
87
  sys.exit(1)
88
88
  except Exception as e:
89
- click.echo(f"Ошибка: {e}", err=True)
89
+ click.echo(f"Ошибка: {e}", err=True)
90
90
  sys.exit(1)
91
91
 
92
92
  @cli.command()
@@ -115,7 +115,7 @@ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: i
115
115
  click.echo(f"⚠️ YAML тесты не найдены для бота {bot_id}")
116
116
  return
117
117
 
118
- click.echo(f"Запускаем тесты для бота {bot_id}...")
118
+ click.echo(f"🧪 Запускаем тесты для бота {bot_id}...")
119
119
 
120
120
  # Формируем команду для запуска
121
121
  bot_testing_path = Path(__file__).parent / "creation" / "bot_testing.py"
@@ -140,10 +140,10 @@ def test(bot_id: str, file: str = None, verbose: bool = False, max_concurrent: i
140
140
  sys.exit(1)
141
141
 
142
142
  except subprocess.CalledProcessError as e:
143
- click.echo(f"Ошибка при запуске тестов: {e}", err=True)
143
+ click.echo(f"Ошибка при запуске тестов: {e}", err=True)
144
144
  sys.exit(1)
145
145
  except Exception as e:
146
- click.echo(f"Ошибка: {e}", err=True)
146
+ click.echo(f"Ошибка: {e}", err=True)
147
147
  sys.exit(1)
148
148
 
149
149
  @cli.command()
@@ -164,14 +164,14 @@ def config(bot_id: str):
164
164
  # Определяем редактор
165
165
  editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
166
166
 
167
- click.echo(f"Открываем конфигурацию бота {bot_id}...")
167
+ click.echo(f"⚙️ Открываем конфигурацию бота {bot_id}...")
168
168
  subprocess.run([editor, str(env_file)], check=True)
169
169
 
170
170
  except subprocess.CalledProcessError as e:
171
- click.echo(f"Ошибка при открытии редактора: {e}", err=True)
171
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
172
172
  sys.exit(1)
173
173
  except Exception as e:
174
- click.echo(f"Ошибка: {e}", err=True)
174
+ click.echo(f"Ошибка: {e}", err=True)
175
175
  sys.exit(1)
176
176
 
177
177
  @cli.command()
@@ -196,12 +196,12 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
196
196
  prompt_files = [f.name for f in prompts_dir.glob("*.txt")]
197
197
 
198
198
  if not prompt_files:
199
- click.echo("Промпты не найдены")
199
+ click.echo("📝 Промпты не найдены")
200
200
  return
201
201
 
202
- click.echo(f"Промпты бота {bot_id}:")
202
+ click.echo(f"📝 Промпты бота {bot_id}:")
203
203
  for prompt_file in sorted(prompt_files):
204
- click.echo(f" - {prompt_file[:-4]}")
204
+ click.echo(f" 📄 {prompt_file[:-4]}")
205
205
 
206
206
  elif edit_prompt:
207
207
  # Редактируем промпт
@@ -210,7 +210,7 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
210
210
  raise click.ClickException(f"Промпт {edit_prompt} не найден")
211
211
 
212
212
  editor = os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
213
- click.echo(f"Редактируем промпт {edit_prompt}...")
213
+ click.echo(f"✏️ Редактируем промпт {edit_prompt}...")
214
214
  subprocess.run([editor, str(prompt_file)], check=True)
215
215
 
216
216
  elif add_prompt:
@@ -233,16 +233,16 @@ def prompts(bot_id: str, list_prompts: bool = False, edit_prompt: str = None, ad
233
233
 
234
234
  else:
235
235
  # Показываем справку
236
- click.echo("Использование:")
237
- click.echo(" sbf prompts <bot_id> --list # Показать список промптов")
238
- click.echo(" sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
239
- click.echo(" sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
236
+ click.echo("📖 Использование:")
237
+ click.echo(" 📋 sbf prompts <bot_id> --list # Показать список промптов")
238
+ click.echo(" ✏️ sbf prompts <bot_id> --edit <prompt_name> # Редактировать промпт")
239
+ click.echo("sbf prompts <bot_id> --add <prompt_name> # Добавить новый промпт")
240
240
 
241
241
  except subprocess.CalledProcessError as e:
242
- click.echo(f"Ошибка при открытии редактора: {e}", err=True)
242
+ click.echo(f"Ошибка при открытии редактора: {e}", err=True)
243
243
  sys.exit(1)
244
244
  except Exception as e:
245
- click.echo(f"Ошибка: {e}", err=True)
245
+ click.echo(f"Ошибка: {e}", err=True)
246
246
  sys.exit(1)
247
247
 
248
248
  @cli.command()
@@ -267,31 +267,31 @@ def rm(bot_id: str, force: bool = False):
267
267
  raise click.ClickException(f"Файл {bot_id}.py не найден в корневой директории")
268
268
 
269
269
  # Показываем что будет удалено
270
- click.echo("Будет удалено:")
271
- click.echo(f" - Файл запускалки: {bot_file}")
272
- click.echo(f" - Папка бота: {bot_path}")
270
+ click.echo("🗑️ Будет удалено:")
271
+ click.echo(f" 📄 Файл запускалки: {bot_file}")
272
+ click.echo(f" 📁 Папка бота: {bot_path}")
273
273
 
274
274
  # Запрашиваем подтверждение если не указан --force
275
275
  if not force:
276
- if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
- click.echo("Удаление отменено")
276
+ if not click.confirm(f"Вы уверены, что хотите удалить бота {bot_id}?"):
277
+ click.echo("Удаление отменено")
278
278
  return
279
279
 
280
280
  # Удаляем файл запускалки
281
281
  if bot_file.exists():
282
282
  bot_file.unlink()
283
- click.echo(f"Файл {bot_file} удален")
283
+ click.echo(f"Файл {bot_file} удален")
284
284
 
285
285
  # Удаляем папку бота
286
286
  if bot_path.exists():
287
287
  import shutil
288
288
  shutil.rmtree(bot_path)
289
- click.echo(f"Папка {bot_path} удалена")
289
+ click.echo(f"Папка {bot_path} удалена")
290
290
 
291
- click.echo(f"Бот {bot_id} полностью удален")
291
+ click.echo(f"🗑️ Бот {bot_id} полностью удален")
292
292
 
293
293
  except Exception as e:
294
- click.echo(f"Ошибка при удалении бота: {e}", err=True)
294
+ click.echo(f"Ошибка при удалении бота: {e}", err=True)
295
295
  sys.exit(1)
296
296
 
297
297
 
@@ -318,8 +318,8 @@ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
318
318
 
319
319
  if new_bot_path.exists() or new_bot_file.exists():
320
320
  if not force:
321
- if not click.confirm(f"Бот {new_bot_id} уже существует. Перезаписать?"):
322
- click.echo("Копирование отменено")
321
+ if not click.confirm(f"⚠️ Бот {new_bot_id} уже существует. Перезаписать?"):
322
+ click.echo("Копирование отменено")
323
323
  return
324
324
  else:
325
325
  click.echo(f"⚠️ Перезаписываем существующего бота {new_bot_id}")
@@ -332,7 +332,7 @@ def copy(source_bot_id: str, new_bot_id: str, force: bool = False):
332
332
  click.echo(f"📝 Не забудьте настроить .env файл для нового бота")
333
333
 
334
334
  except Exception as e:
335
- click.echo(f"Ошибка при копировании бота: {e}", err=True)
335
+ click.echo(f"Ошибка при копировании бота: {e}", err=True)
336
336
  sys.exit(1)
337
337
 
338
338
  @cli.command()
@@ -349,10 +349,10 @@ def link():
349
349
  subprocess.run([sys.executable, str(link_script)], check=True)
350
350
 
351
351
  except subprocess.CalledProcessError as e:
352
- click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
352
+ click.echo(f"Ошибка при запуске генератора ссылок: {e}", err=True)
353
353
  sys.exit(1)
354
354
  except Exception as e:
355
- click.echo(f"Ошибка: {e}", err=True)
355
+ click.echo(f"Ошибка: {e}", err=True)
356
356
  sys.exit(1)
357
357
 
358
358
  def create_new_bot_structure(template: str, bot_id: str) -> bool:
@@ -365,7 +365,7 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
365
365
  # Создаем папку для нового бота
366
366
  bot_dir = bots_dir / bot_id
367
367
  if bot_dir.exists():
368
- click.echo(f"Бот {bot_id} уже существует")
368
+ click.echo(f"⚠️ Бот {bot_id} уже существует")
369
369
  return False
370
370
 
371
371
  bot_dir.mkdir()
@@ -384,12 +384,12 @@ def create_new_bot_structure(template: str, bot_id: str) -> bool:
384
384
  # Используем другой шаблон из папки bots
385
385
  copy_from_bot_template(template, bot_dir, bot_id)
386
386
 
387
- click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
388
- click.echo(f"Не забудьте настроить .env файл перед запуском")
387
+ click.echo(f"Бот {bot_id} создан в папке bots/{bot_id}/")
388
+ click.echo(f"📝 Не забудьте настроить .env файл перед запуском")
389
389
  return True
390
390
 
391
391
  except Exception as e:
392
- click.echo(f"Ошибка при создании бота: {e}")
392
+ click.echo(f"Ошибка при создании бота: {e}")
393
393
  return False
394
394
 
395
395
  def list_bots_in_bots_folder() -> list:
@@ -414,25 +414,25 @@ def create_bot_template(bot_id: str) -> str:
414
414
 
415
415
  import asyncio
416
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
- )
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")
424
424
 
425
425
  # =============================================================================
426
426
  # ОБРАБОТЧИКИ СОБЫТИЙ
427
427
  # =============================================================================
428
428
 
429
- @event_handler("example_event", "Пример обработчика события")
430
- async def handle_example_event(user_id: int, event_data: dict):
429
+ @router.event_handler("example_event")
430
+ async def handle_example_event(user_id: int, event_data: str):
431
431
  """Пример обработчика события"""
432
432
  # Отправляем подтверждение пользователю
433
433
  await send_message_by_human(
434
434
  user_id=user_id,
435
- message_text="✅ Событие обработано!"
435
+ message_text=f"✅ Событие обработано! Данные: {{event_data}}"
436
436
  )
437
437
 
438
438
  return {{
@@ -441,24 +441,82 @@ async def handle_example_event(user_id: int, event_data: dict):
441
441
  }}
442
442
 
443
443
  # =============================================================================
444
- # ЗАПЛАНИРОВАННЫЕ ЗАДАЧИ
444
+ # ВРЕМЕННЫЕ ЗАДАЧИ ДЛЯ ОДНОГО ПОЛЬЗОВАТЕЛЯ
445
445
  # =============================================================================
446
446
 
447
- @schedule_task("example_task", "Пример запланированной задачи")
448
- async def example_task(user_id: int, message: str):
449
- """Пример запланированной задачи"""
450
- # Отправляем сообщение
447
+ @router.schedule_task("send_reminder")
448
+ async def send_user_reminder(user_id: int, reminder_text: str):
449
+ """Отправляет напоминание пользователю"""
451
450
  await send_message_by_human(
452
451
  user_id=user_id,
453
- message_text=f"🔔 Напоминание: {{message}}"
452
+ message_text=f"🔔 Напоминание: {{reminder_text}}"
454
453
  )
455
454
 
456
455
  return {{
457
- "status": "sent",
458
- "user_id": user_id,
459
- "message": message
456
+ "status": "reminder_sent",
457
+ "message": f"Напоминание отправлено пользователю {{user_id}}"
460
458
  }}
461
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
+
462
520
  # =============================================================================
463
521
  # ОСНОВНАЯ ФУНКЦИЯ
464
522
  # =============================================================================
@@ -468,6 +526,10 @@ async def main():
468
526
  try:
469
527
  # Создаем и собираем бота
470
528
  bot_builder = BotBuilder("{bot_id}")
529
+
530
+ # Регистрируем роутер ПЕРЕД сборкой, чтобы обработчики были доступны
531
+ bot_builder.register_router(router)
532
+
471
533
  await bot_builder.build()
472
534
 
473
535
  # Запускаем бота
@@ -543,12 +605,12 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
543
605
  if source_prompts.exists():
544
606
  for prompt_file in source_prompts.glob("*.txt"):
545
607
  shutil.copy2(prompt_file, target_prompts / prompt_file.name)
546
- click.echo("Промпты скопированы из growthmed-october-24")
608
+ click.echo("📝 Промпты скопированы из growthmed-october-24")
547
609
  else:
548
610
  click.echo(f"⚠️ Папка промптов не найдена: {source_prompts}")
549
611
  # Fallback к базовым промптам
550
612
  create_basic_prompts(target_prompts)
551
- click.echo("Созданы базовые промпты")
613
+ click.echo("📝 Созданы базовые промпты")
552
614
 
553
615
  # Копируем тесты из growthmed-october-24
554
616
  source_tests = Path(__file__).parent / "configs" / "growthmed-october-24" / "tests"
@@ -558,7 +620,7 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
558
620
  for test_file in source_tests.glob("*"):
559
621
  if test_file.is_file():
560
622
  shutil.copy2(test_file, target_tests / test_file.name)
561
- click.echo("Тесты скопированы из growthmed-october-24")
623
+ click.echo("🧪 Тесты скопированы из growthmed-october-24")
562
624
 
563
625
  # Копируем welcome_files из growthmed-october-24
564
626
  source_welcome = Path(__file__).parent / "configs" / "growthmed-october-24" / "welcome_file"
@@ -568,7 +630,7 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
568
630
  for welcome_file in source_welcome.glob("*"):
569
631
  if welcome_file.is_file():
570
632
  shutil.copy2(welcome_file, target_welcome / welcome_file.name)
571
- click.echo("Welcome файлы скопированы из growthmed-october-24")
633
+ click.echo("📁 Welcome файлы скопированы из growthmed-october-24")
572
634
 
573
635
  # Копируем files из growthmed-october-24
574
636
  source_files = Path(__file__).parent / "configs" / "growthmed-october-24" / "files"
@@ -578,10 +640,10 @@ def copy_from_growthmed_template(bot_dir: Path, bot_id: str):
578
640
  for file_item in source_files.glob("*"):
579
641
  if file_item.is_file():
580
642
  shutil.copy2(file_item, target_files / file_item.name)
581
- click.echo("Файлы скопированы из growthmed-october-24")
643
+ click.echo("📎 Файлы скопированы из growthmed-october-24")
582
644
 
583
645
  except Exception as e:
584
- click.echo(f"Ошибка при копировании шаблона: {e}")
646
+ click.echo(f"Ошибка при копировании шаблона: {e}")
585
647
  # Fallback к базовым промптам
586
648
  create_basic_prompts(bot_dir / "prompts")
587
649
 
@@ -668,7 +730,7 @@ def copy_bot_template(source_bot_id: str, new_bot_id: str):
668
730
  click.echo(f" 📎 Файлы скопированы")
669
731
 
670
732
  except Exception as e:
671
- click.echo(f"Ошибка при копировании бота: {e}")
733
+ click.echo(f"Ошибка при копировании бота: {e}")
672
734
  raise
673
735
 
674
736
  def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
@@ -711,10 +773,10 @@ def copy_from_bot_template(template: str, bot_dir: Path, bot_id: str):
711
773
  if test_file.is_file():
712
774
  shutil.copy2(test_file, target_tests / test_file.name)
713
775
 
714
- click.echo(f"Шаблон скопирован из {template}")
776
+ click.echo(f"📋 Шаблон скопирован из {template}")
715
777
 
716
778
  except Exception as e:
717
- click.echo(f"Ошибка при копировании шаблона {template}: {e}")
779
+ click.echo(f"Ошибка при копировании шаблона {template}: {e}")
718
780
  raise
719
781
 
720
782
  def create_basic_prompts(prompts_dir: Path):
@@ -759,6 +821,11 @@ def create_basic_prompts(prompts_dir: Path):
759
821
  - example_task: Пример запланированной задачи. Используй для демонстрации.
760
822
  Пример: {"тип": "example_task", "инфо": "через 1 час: напомнить о чем-то"}
761
823
 
824
+ ДОСТУПНЫЕ ГЛОБАЛЬНЫЕ ОБРАБОТЧИКИ:
825
+ - global_announcement: Отправляет анонс всем пользователям. Используй для важных объявлений.
826
+ Пример: {"тип": "global_announcement", "инфо": "3600"} - анонс через 1 час
827
+ Формат: "инфо" содержит время в секундах для планирования.
828
+
762
829
  Используй эти обработчики и задачи, когда это уместно в диалоге.
763
830
  </instruction>""",
764
831
  encoding='utf-8'
@@ -0,0 +1,33 @@
1
+ """
2
+ Клиенты для удобного доступа к внешним сервисам
3
+ """
4
+
5
+ from typing import Optional
6
+ from ..integrations.supabase_client import SupabaseClient
7
+ from ..integrations.openai_client import OpenAIClient
8
+
9
+ # Клиенты (будут установлены при инициализации бота)
10
+ supabase_client: Optional[SupabaseClient] = None
11
+ openai_client: Optional[OpenAIClient] = None
12
+
13
+ def set_clients(supabase: Optional[SupabaseClient] = None, openai: Optional[OpenAIClient] = None):
14
+ """Устанавливает клиенты"""
15
+ global supabase_client, openai_client
16
+ supabase_client = supabase
17
+ openai_client = openai
18
+
19
+ def get_supabase_client() -> Optional[SupabaseClient]:
20
+ """Получает клиент Supabase"""
21
+ return supabase_client
22
+
23
+ def get_openai_client() -> Optional[OpenAIClient]:
24
+ """Получает клиент OpenAI"""
25
+ return openai_client
26
+
27
+ __all__ = [
28
+ 'supabase_client',
29
+ 'openai_client',
30
+ 'set_clients',
31
+ 'get_supabase_client',
32
+ 'get_openai_client'
33
+ ]
@@ -73,6 +73,8 @@ id этапа бери из stages
73
73
  ТРЕБОВАНИЯ К формату:
74
74
  - Всегда используй кавычки для строк
75
75
  - Если в инструкциях явно не указано, что надо выслать какое-то событие, отсылай пустой массив []
76
+ - Если надо выслать какое либо событие и в инструкции написано запустить через какое то время, то в поле инфо нужно вывести число в СЕКУНДАХ
77
+ - Также в событиях связанных со временем может быть дополнительная информация, например инфо='30|Привет пользователь!' - 30 это время в секундах после | доп информация
76
78
  - Если в инструкциях явно не указано, что надо вставить в служебную информацию какой-то файл(ы) или каталоги, отсылай пустой массив
77
79
 
78
80
  ФАЙЛЫ: