smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.9__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 +1200 -755
- 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 +948 -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.9.dist-info}/METADATA +3 -1
- smart_bot_factory-0.3.9.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.9.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
Utils модули smart_bot_factory
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
from .user_prompt_loader import UserPromptLoader
|
|
7
6
|
|
|
8
|
-
__all__ = [
|
|
9
|
-
|
|
7
|
+
__all__ = [ # Базовый класс (для библиотеки)
|
|
8
|
+
"UserPromptLoader", # Для пользователей (автопоиск prompts_dir)
|
|
10
9
|
]
|
|
@@ -1,103 +1,114 @@
|
|
|
1
1
|
# debug_routing.py - Утилиты для отладки маршрутизации сообщений
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
|
|
4
5
|
from aiogram import Router
|
|
5
|
-
from aiogram.types import Message
|
|
6
6
|
from aiogram.fsm.context import FSMContext
|
|
7
|
+
from aiogram.types import Message
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
10
11
|
# Создаем роутер для отладочных обработчиков
|
|
11
12
|
debug_router = Router()
|
|
12
13
|
|
|
14
|
+
|
|
13
15
|
def setup_debug_handlers(dp):
|
|
14
16
|
"""Настройка отладочных обработчиков"""
|
|
15
17
|
dp.include_router(debug_router)
|
|
16
18
|
|
|
19
|
+
|
|
17
20
|
# Функция для получения глобальных переменных
|
|
18
21
|
def get_global_var(var_name):
|
|
19
22
|
"""Получает глобальную переменную из модуля debug_routing"""
|
|
20
23
|
import sys
|
|
24
|
+
|
|
21
25
|
current_module = sys.modules[__name__]
|
|
22
26
|
return getattr(current_module, var_name, None)
|
|
23
27
|
|
|
28
|
+
|
|
24
29
|
async def debug_user_state(message: Message, state: FSMContext, context: str):
|
|
25
30
|
"""Отладочная функция для логирования состояния пользователя"""
|
|
26
|
-
conversation_manager = get_global_var(
|
|
27
|
-
supabase_client = get_global_var(
|
|
28
|
-
|
|
31
|
+
conversation_manager = get_global_var("conversation_manager")
|
|
32
|
+
supabase_client = get_global_var("supabase_client")
|
|
33
|
+
|
|
29
34
|
user_id = message.from_user.id
|
|
30
35
|
current_state = await state.get_state()
|
|
31
36
|
state_data = await state.get_data()
|
|
32
|
-
|
|
37
|
+
|
|
33
38
|
logger.info(f"🔍 DEBUG [{context}] User {user_id}:")
|
|
34
39
|
logger.info(f" 📊 FSM State: {current_state}")
|
|
35
40
|
logger.info(f" 📦 State Data: {list(state_data.keys())}")
|
|
36
41
|
logger.info(f" 💬 Message: '{message.text[:50]}...'")
|
|
37
|
-
|
|
42
|
+
|
|
38
43
|
# Проверяем диалог с админом в БД
|
|
39
44
|
conversation = await conversation_manager.is_user_in_admin_chat(user_id)
|
|
40
45
|
logger.info(f" 🗃️ Admin Chat in DB: {'✅' if conversation else '❌'}")
|
|
41
|
-
|
|
46
|
+
|
|
42
47
|
if conversation:
|
|
43
48
|
logger.info(f" 👑 Admin ID: {conversation['admin_id']}")
|
|
44
49
|
logger.info(f" 🆔 Conversation ID: {conversation['id']}")
|
|
45
|
-
|
|
50
|
+
|
|
46
51
|
# Проверяем активную сессию
|
|
47
52
|
session_info = await supabase_client.get_active_session(user_id)
|
|
48
53
|
logger.info(f" 🎯 Active Session: {'✅' if session_info else '❌'}")
|
|
49
|
-
|
|
54
|
+
|
|
50
55
|
if session_info:
|
|
51
56
|
logger.info(f" 📝 Session ID: {session_info['id']}")
|
|
52
|
-
|
|
57
|
+
|
|
53
58
|
logger.info(f" {'='*50}")
|
|
54
59
|
|
|
60
|
+
|
|
55
61
|
async def debug_admin_conversation_creation(admin_id: int, user_id: int):
|
|
56
62
|
"""Отладка создания диалога админа с пользователем"""
|
|
57
|
-
supabase_client = get_global_var(
|
|
58
|
-
|
|
59
|
-
logger.info(
|
|
63
|
+
supabase_client = get_global_var("supabase_client")
|
|
64
|
+
|
|
65
|
+
logger.info("🔍 DEBUG CONVERSATION CREATION:")
|
|
60
66
|
logger.info(f" 👑 Admin: {admin_id}")
|
|
61
67
|
logger.info(f" 👤 User: {user_id}")
|
|
62
|
-
|
|
68
|
+
|
|
63
69
|
# Проверяем активную сессию пользователя ДО создания диалога
|
|
64
70
|
session_info = await supabase_client.get_active_session(user_id)
|
|
65
71
|
logger.info(f" 🎯 User has active session: {'✅' if session_info else '❌'}")
|
|
66
|
-
|
|
72
|
+
|
|
67
73
|
if session_info:
|
|
68
74
|
logger.info(f" 📝 Session ID: {session_info['id']}")
|
|
69
75
|
logger.info(f" 📅 Session created: {session_info['created_at']}")
|
|
70
|
-
|
|
76
|
+
|
|
71
77
|
# Проверяем существующие диалоги пользователя
|
|
72
78
|
try:
|
|
73
|
-
existing =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
existing = (
|
|
80
|
+
supabase_client.client.table("admin_user_conversations")
|
|
81
|
+
.select("*")
|
|
82
|
+
.eq("user_id", user_id)
|
|
83
|
+
.eq("status", "active")
|
|
84
|
+
.execute()
|
|
85
|
+
)
|
|
86
|
+
|
|
77
87
|
logger.info(f" 💬 Existing active conversations: {len(existing.data)}")
|
|
78
88
|
for conv in existing.data:
|
|
79
89
|
logger.info(f" - ID: {conv['id']}, Admin: {conv['admin_id']}")
|
|
80
90
|
except Exception as e:
|
|
81
91
|
logger.error(f" ❌ Error checking existing conversations: {e}")
|
|
82
92
|
|
|
93
|
+
|
|
83
94
|
async def test_message_routing(user_id: int, test_message: str):
|
|
84
95
|
"""Тестирует маршрутизацию сообщения без отправки через Telegram"""
|
|
85
|
-
conversation_manager = get_global_var(
|
|
86
|
-
|
|
87
|
-
logger.info(
|
|
96
|
+
conversation_manager = get_global_var("conversation_manager")
|
|
97
|
+
|
|
98
|
+
logger.info("🧪 TESTING MESSAGE ROUTING:")
|
|
88
99
|
logger.info(f" 👤 User: {user_id}")
|
|
89
100
|
logger.info(f" 💬 Message: '{test_message}'")
|
|
90
|
-
|
|
101
|
+
|
|
91
102
|
# Проверяем есть ли диалог с админом
|
|
92
103
|
conversation = await conversation_manager.is_user_in_admin_chat(user_id)
|
|
93
104
|
logger.info(f" 🗃️ Admin conversation exists: {'✅' if conversation else '❌'}")
|
|
94
|
-
|
|
105
|
+
|
|
95
106
|
if conversation:
|
|
96
107
|
logger.info(f" 👑 Admin: {conversation['admin_id']}")
|
|
97
108
|
logger.info(f" 🆔 Conv ID: {conversation['id']}")
|
|
98
109
|
logger.info(f" 📅 Started: {conversation['started_at']}")
|
|
99
|
-
|
|
110
|
+
|
|
100
111
|
# Тестируем должен ли этот пользователь быть в admin_chat
|
|
101
112
|
return "admin_chat"
|
|
102
113
|
else:
|
|
103
|
-
return "bot_chat"
|
|
114
|
+
return "bot_chat"
|
|
@@ -1,44 +1,52 @@
|
|
|
1
1
|
# Обновленный prompt_loader.py с поддержкой финальных инструкций
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import aiofiles
|
|
5
4
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
import aiofiles
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
class PromptLoader:
|
|
11
13
|
"""Класс для загрузки промптов из локального каталога"""
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
def __init__(self, prompts_dir: str):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
self.prompts_dir = Path(prompts_dir)
|
|
17
|
+
self.welcome_file = self.prompts_dir / "welcome_message.txt"
|
|
18
|
+
self.help_file = self.prompts_dir / "help_message.txt"
|
|
19
|
+
self.final_instructions_file = self.prompts_dir / "final_instructions.txt"
|
|
20
|
+
|
|
21
|
+
# Автоматически находим все .txt файлы промптов (кроме специальных)
|
|
22
|
+
all_txt_files = list(self.prompts_dir.glob("*.txt"))
|
|
23
|
+
special_files = {
|
|
24
|
+
"welcome_message.txt",
|
|
25
|
+
"help_message.txt",
|
|
26
|
+
"final_instructions.txt",
|
|
27
|
+
}
|
|
28
|
+
self.prompt_files = [
|
|
29
|
+
f.name for f in all_txt_files if f.name not in special_files
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
logger.info(f"Инициализирован загрузчик промптов: {self.prompts_dir}")
|
|
33
|
+
logger.info(f"Найдено файлов промптов: {len(self.prompt_files)}")
|
|
34
|
+
logger.info(f"Файлы промптов: {self.prompt_files}")
|
|
27
35
|
|
|
28
36
|
async def load_system_prompt(self) -> str:
|
|
29
37
|
"""
|
|
30
38
|
Загружает и объединяет все файлы промптов в один системный промпт
|
|
31
|
-
|
|
39
|
+
|
|
32
40
|
Returns:
|
|
33
41
|
Объединенный системный промпт с инструкциями по JSON
|
|
34
42
|
"""
|
|
35
43
|
try:
|
|
36
44
|
prompt_parts = []
|
|
37
|
-
|
|
45
|
+
|
|
38
46
|
for filename in self.prompt_files:
|
|
39
47
|
logger.debug(f"Загружаем промпт из {filename}")
|
|
40
48
|
content = await self._load_file(filename)
|
|
41
|
-
|
|
49
|
+
|
|
42
50
|
if content:
|
|
43
51
|
# Добавляем заголовок секции
|
|
44
52
|
section_name = self._get_section_name(filename)
|
|
@@ -47,23 +55,25 @@ class PromptLoader:
|
|
|
47
55
|
prompt_parts.append("\n")
|
|
48
56
|
else:
|
|
49
57
|
logger.warning(f"Файл {filename} пуст")
|
|
50
|
-
|
|
58
|
+
|
|
51
59
|
if not prompt_parts:
|
|
52
60
|
error_msg = "Не удалось загрузить ни одного промпт файла"
|
|
53
61
|
logger.error(error_msg)
|
|
54
62
|
raise ValueError(error_msg)
|
|
55
|
-
|
|
63
|
+
|
|
56
64
|
# Добавляем инструкции по JSON метаданным
|
|
57
65
|
json_instructions = self._get_json_instructions()
|
|
58
66
|
prompt_parts.append("\n")
|
|
59
67
|
prompt_parts.append(json_instructions)
|
|
60
|
-
|
|
68
|
+
|
|
61
69
|
# Объединяем все части
|
|
62
70
|
full_prompt = "".join(prompt_parts).strip()
|
|
63
|
-
|
|
64
|
-
logger.info(
|
|
71
|
+
|
|
72
|
+
logger.info(
|
|
73
|
+
f"Системный промпт загружен успешно ({len(full_prompt)} символов)"
|
|
74
|
+
)
|
|
65
75
|
return full_prompt
|
|
66
|
-
|
|
76
|
+
|
|
67
77
|
except Exception as e:
|
|
68
78
|
logger.error(f"Ошибка при загрузке системного промпта: {e}")
|
|
69
79
|
raise
|
|
@@ -71,32 +81,40 @@ class PromptLoader:
|
|
|
71
81
|
async def load_final_instructions(self) -> str:
|
|
72
82
|
"""
|
|
73
83
|
Загружает финальные инструкции из final_instructions.txt
|
|
74
|
-
|
|
84
|
+
|
|
75
85
|
Returns:
|
|
76
86
|
Финальные инструкции или пустая строка если файла нет
|
|
77
87
|
"""
|
|
78
88
|
try:
|
|
79
|
-
logger.debug(
|
|
80
|
-
|
|
89
|
+
logger.debug(
|
|
90
|
+
f"Загружаем финальные инструкции из {self.final_instructions_file.name}"
|
|
91
|
+
)
|
|
92
|
+
|
|
81
93
|
if not self.final_instructions_file.exists():
|
|
82
|
-
logger.debug(
|
|
94
|
+
logger.debug(
|
|
95
|
+
f"Файл {self.final_instructions_file.name} не найден - пропускаем"
|
|
96
|
+
)
|
|
83
97
|
return ""
|
|
84
|
-
|
|
85
|
-
async with aiofiles.open(
|
|
98
|
+
|
|
99
|
+
async with aiofiles.open(
|
|
100
|
+
self.final_instructions_file, "r", encoding="utf-8"
|
|
101
|
+
) as f:
|
|
86
102
|
content = await f.read()
|
|
87
|
-
|
|
103
|
+
|
|
88
104
|
if not content.strip():
|
|
89
|
-
logger.debug(
|
|
105
|
+
logger.debug(
|
|
106
|
+
f"Файл {self.final_instructions_file.name} пуст - пропускаем"
|
|
107
|
+
)
|
|
90
108
|
return ""
|
|
91
|
-
|
|
109
|
+
|
|
92
110
|
logger.info(f"Финальные инструкции загружены ({len(content)} символов)")
|
|
93
111
|
return content.strip()
|
|
94
|
-
|
|
112
|
+
|
|
95
113
|
except Exception as e:
|
|
96
114
|
logger.error(f"Ошибка при загрузке финальных инструкций: {e}")
|
|
97
115
|
# Не прерываем работу - финальные инструкции опциональны
|
|
98
116
|
return ""
|
|
99
|
-
|
|
117
|
+
|
|
100
118
|
def _get_json_instructions(self) -> str:
|
|
101
119
|
"""Возвращает инструкции по JSON метаданным для ИИ"""
|
|
102
120
|
return """
|
|
@@ -223,59 +241,65 @@ class PromptLoader:
|
|
|
223
241
|
|
|
224
242
|
ПОМНИ: Этот JSON критически важен для работы системы администрирования и аналитики!
|
|
225
243
|
"""
|
|
226
|
-
|
|
244
|
+
|
|
227
245
|
async def load_welcome_message(self) -> str:
|
|
228
246
|
"""
|
|
229
247
|
Загружает приветственное сообщение из welcome_message.txt
|
|
230
|
-
|
|
248
|
+
|
|
231
249
|
Returns:
|
|
232
250
|
Текст приветственного сообщения
|
|
233
251
|
"""
|
|
234
252
|
try:
|
|
235
|
-
logger.debug(
|
|
236
|
-
|
|
253
|
+
logger.debug(
|
|
254
|
+
f"Загружаем приветственное сообщение из {self.welcome_file.name}"
|
|
255
|
+
)
|
|
256
|
+
|
|
237
257
|
if not self.welcome_file.exists():
|
|
238
258
|
error_msg = f"Файл приветствия не найден: {self.welcome_file}"
|
|
239
259
|
logger.error(error_msg)
|
|
240
260
|
raise FileNotFoundError(error_msg)
|
|
241
|
-
|
|
242
|
-
async with aiofiles.open(self.welcome_file,
|
|
261
|
+
|
|
262
|
+
async with aiofiles.open(self.welcome_file, "r", encoding="utf-8") as f:
|
|
243
263
|
content = await f.read()
|
|
244
|
-
|
|
264
|
+
|
|
245
265
|
if not content.strip():
|
|
246
266
|
error_msg = f"Файл приветствия пуст: {self.welcome_file}"
|
|
247
267
|
logger.error(error_msg)
|
|
248
268
|
raise ValueError(error_msg)
|
|
249
|
-
|
|
269
|
+
|
|
250
270
|
logger.info(f"Приветственное сообщение загружено ({len(content)} символов)")
|
|
251
271
|
return content.strip()
|
|
252
|
-
|
|
272
|
+
|
|
253
273
|
except Exception as e:
|
|
254
274
|
logger.error(f"Ошибка при загрузке приветственного сообщения: {e}")
|
|
255
275
|
raise
|
|
256
|
-
|
|
276
|
+
|
|
257
277
|
async def load_help_message(self) -> str:
|
|
258
278
|
"""
|
|
259
279
|
Загружает справочное сообщение из help_message.txt
|
|
260
|
-
|
|
280
|
+
|
|
261
281
|
Returns:
|
|
262
282
|
Текст справочного сообщения
|
|
263
283
|
"""
|
|
264
284
|
try:
|
|
265
285
|
logger.debug(f"Загружаем справочное сообщение из {self.help_file.name}")
|
|
266
|
-
|
|
286
|
+
|
|
267
287
|
if self.help_file.exists():
|
|
268
|
-
async with aiofiles.open(self.help_file,
|
|
288
|
+
async with aiofiles.open(self.help_file, "r", encoding="utf-8") as f:
|
|
269
289
|
content = await f.read()
|
|
270
|
-
|
|
290
|
+
|
|
271
291
|
if content.strip():
|
|
272
|
-
logger.info(
|
|
292
|
+
logger.info(
|
|
293
|
+
f"Справочное сообщение загружено ({len(content)} символов)"
|
|
294
|
+
)
|
|
273
295
|
return content.strip()
|
|
274
|
-
|
|
296
|
+
|
|
275
297
|
# Fallback если файл не найден или пуст
|
|
276
|
-
logger.warning(
|
|
298
|
+
logger.warning(
|
|
299
|
+
"Файл help_message.txt не найден или пуст, используем дефолтную справку"
|
|
300
|
+
)
|
|
277
301
|
return "🤖 **Ваш помощник готов к работе!**\n\n**Команды:**\n/start - Начать диалог\n/help - Показать справку\n/status - Проверить статус"
|
|
278
|
-
|
|
302
|
+
|
|
279
303
|
except Exception as e:
|
|
280
304
|
logger.error(f"Ошибка при загрузке справочного сообщения: {e}")
|
|
281
305
|
# Возвращаем простую справку в случае ошибки
|
|
@@ -284,148 +308,157 @@ class PromptLoader:
|
|
|
284
308
|
async def _load_file(self, filename: str) -> str:
|
|
285
309
|
"""Загружает содержимое файла из каталога промптов"""
|
|
286
310
|
file_path = self.prompts_dir / filename
|
|
287
|
-
|
|
311
|
+
|
|
288
312
|
try:
|
|
289
313
|
if not file_path.exists():
|
|
290
314
|
error_msg = f"Файл промпта не найден: {file_path}"
|
|
291
315
|
logger.error(error_msg)
|
|
292
316
|
raise FileNotFoundError(error_msg)
|
|
293
|
-
|
|
294
|
-
async with aiofiles.open(file_path,
|
|
317
|
+
|
|
318
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
|
|
295
319
|
content = await f.read()
|
|
296
|
-
|
|
320
|
+
|
|
297
321
|
if not content.strip():
|
|
298
322
|
logger.warning(f"Файл {filename} пуст")
|
|
299
323
|
return ""
|
|
300
|
-
|
|
324
|
+
|
|
301
325
|
logger.debug(f"Загружен файл {filename} ({len(content)} символов)")
|
|
302
326
|
return content
|
|
303
|
-
|
|
327
|
+
|
|
304
328
|
except Exception as e:
|
|
305
329
|
logger.error(f"Ошибка чтения файла {file_path}: {e}")
|
|
306
330
|
raise
|
|
307
|
-
|
|
331
|
+
|
|
308
332
|
def _get_section_name(self, filename: str) -> str:
|
|
309
333
|
"""Получает название секции по имени файла"""
|
|
310
334
|
name_mapping = {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
335
|
+
"system_prompt.txt": "СИСТЕМНЫЙ ПРОМПТ",
|
|
336
|
+
"sales_context.txt": "КОНТЕКСТ ПРОДАЖ",
|
|
337
|
+
"product_info.txt": "ИНФОРМАЦИЯ О ПРОДУКТЕ",
|
|
338
|
+
"objection_handling.txt": "ОБРАБОТКА ВОЗРАЖЕНИЙ",
|
|
339
|
+
"1sales_context.txt": "КОНТЕКСТ ПРОДАЖ",
|
|
340
|
+
"2product_info.txt": "ИНФОРМАЦИЯ О ПРОДУКТЕ",
|
|
341
|
+
"3objection_handling.txt": "ОБРАБОТКА ВОЗРАЖЕНИЙ",
|
|
342
|
+
"final_instructions.txt": "ФИНАЛЬНЫЕ ИНСТРУКЦИИ", # 🆕
|
|
319
343
|
}
|
|
320
|
-
|
|
321
|
-
return name_mapping.get(filename, filename.replace(
|
|
322
|
-
|
|
344
|
+
|
|
345
|
+
return name_mapping.get(filename, filename.replace(".txt", "").upper())
|
|
346
|
+
|
|
323
347
|
async def reload_prompts(self) -> str:
|
|
324
348
|
"""Перезагружает промпты (для обновления без перезапуска бота)"""
|
|
325
349
|
logger.info("Перезагрузка промптов...")
|
|
326
350
|
return await self.load_system_prompt()
|
|
327
|
-
|
|
351
|
+
|
|
328
352
|
async def validate_prompts(self) -> Dict[str, bool]:
|
|
329
353
|
"""Проверяет доступность всех файлов промптов и приветственного сообщения"""
|
|
330
354
|
results = {}
|
|
331
|
-
|
|
355
|
+
|
|
332
356
|
# Проверяем файлы промптов
|
|
333
357
|
for filename in self.prompt_files:
|
|
334
358
|
file_path = self.prompts_dir / filename
|
|
335
359
|
try:
|
|
336
360
|
if file_path.exists():
|
|
337
|
-
async with aiofiles.open(file_path,
|
|
361
|
+
async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
|
|
338
362
|
content = await f.read()
|
|
339
|
-
results[filename] = bool(
|
|
363
|
+
results[filename] = bool(
|
|
364
|
+
content.strip() and len(content.strip()) > 10
|
|
365
|
+
)
|
|
340
366
|
else:
|
|
341
367
|
results[filename] = False
|
|
342
368
|
except Exception:
|
|
343
369
|
results[filename] = False
|
|
344
|
-
|
|
370
|
+
|
|
345
371
|
# Проверяем файл приветственного сообщения
|
|
346
372
|
try:
|
|
347
373
|
if self.welcome_file.exists():
|
|
348
|
-
async with aiofiles.open(self.welcome_file,
|
|
374
|
+
async with aiofiles.open(self.welcome_file, "r", encoding="utf-8") as f:
|
|
349
375
|
content = await f.read()
|
|
350
|
-
results[
|
|
376
|
+
results["welcome_message.txt"] = bool(
|
|
377
|
+
content.strip() and len(content.strip()) > 5
|
|
378
|
+
)
|
|
351
379
|
else:
|
|
352
|
-
results[
|
|
380
|
+
results["welcome_message.txt"] = False
|
|
353
381
|
except Exception:
|
|
354
|
-
results[
|
|
355
|
-
|
|
382
|
+
results["welcome_message.txt"] = False
|
|
383
|
+
|
|
356
384
|
# Проверяем файл справки (опционально)
|
|
357
385
|
try:
|
|
358
386
|
if self.help_file.exists():
|
|
359
|
-
async with aiofiles.open(self.help_file,
|
|
387
|
+
async with aiofiles.open(self.help_file, "r", encoding="utf-8") as f:
|
|
360
388
|
content = await f.read()
|
|
361
|
-
results[
|
|
389
|
+
results["help_message.txt"] = bool(
|
|
390
|
+
content.strip() and len(content.strip()) > 5
|
|
391
|
+
)
|
|
362
392
|
else:
|
|
363
|
-
results[
|
|
393
|
+
results["help_message.txt"] = False # Не критично
|
|
364
394
|
except Exception:
|
|
365
|
-
results[
|
|
366
|
-
|
|
395
|
+
results["help_message.txt"] = False
|
|
396
|
+
|
|
367
397
|
# 🆕 Проверяем финальные инструкции (опционально)
|
|
368
398
|
try:
|
|
369
399
|
if self.final_instructions_file.exists():
|
|
370
|
-
async with aiofiles.open(
|
|
400
|
+
async with aiofiles.open(
|
|
401
|
+
self.final_instructions_file, "r", encoding="utf-8"
|
|
402
|
+
) as f:
|
|
371
403
|
content = await f.read()
|
|
372
|
-
results[
|
|
404
|
+
results["final_instructions.txt"] = bool(
|
|
405
|
+
content.strip() and len(content.strip()) > 5
|
|
406
|
+
)
|
|
373
407
|
else:
|
|
374
|
-
results[
|
|
408
|
+
results["final_instructions.txt"] = (
|
|
409
|
+
False # Не критично - опциональный файл
|
|
410
|
+
)
|
|
375
411
|
except Exception:
|
|
376
|
-
results[
|
|
377
|
-
|
|
412
|
+
results["final_instructions.txt"] = False
|
|
413
|
+
|
|
378
414
|
return results
|
|
379
|
-
|
|
415
|
+
|
|
380
416
|
def get_prompt_info(self) -> Dict[str, any]:
|
|
381
417
|
"""Возвращает информацию о конфигурации промптов"""
|
|
382
418
|
return {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
419
|
+
"prompts_dir": str(self.prompts_dir),
|
|
420
|
+
"prompt_files": self.prompt_files,
|
|
421
|
+
"welcome_file": "welcome_message.txt",
|
|
422
|
+
"help_file": "help_message.txt",
|
|
423
|
+
"final_instructions_file": "final_instructions.txt", # 🆕
|
|
424
|
+
"total_files": len(self.prompt_files) + 1, # +1 для welcome message
|
|
425
|
+
"json_instructions_included": True,
|
|
390
426
|
}
|
|
391
|
-
|
|
427
|
+
|
|
392
428
|
async def test_json_parsing(self, test_response: str) -> Dict[str, any]:
|
|
393
429
|
"""Тестирует парсинг JSON из ответа ИИ (для отладки)"""
|
|
394
430
|
import json
|
|
395
431
|
import re
|
|
396
|
-
|
|
432
|
+
|
|
397
433
|
try:
|
|
398
434
|
# Используем тот же алгоритм что и в main.py
|
|
399
435
|
json_pattern = r'\{[^{}]*"этап"[^{}]*\}$'
|
|
400
436
|
match = re.search(json_pattern, test_response.strip())
|
|
401
|
-
|
|
437
|
+
|
|
402
438
|
if match:
|
|
403
439
|
json_str = match.group(0)
|
|
404
|
-
response_text = test_response[:match.start()].strip()
|
|
405
|
-
|
|
440
|
+
response_text = test_response[: match.start()].strip()
|
|
441
|
+
|
|
406
442
|
try:
|
|
407
443
|
metadata = json.loads(json_str)
|
|
408
444
|
return {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
445
|
+
"success": True,
|
|
446
|
+
"response_text": response_text,
|
|
447
|
+
"metadata": metadata,
|
|
448
|
+
"json_str": json_str,
|
|
413
449
|
}
|
|
414
450
|
except json.JSONDecodeError as e:
|
|
415
451
|
return {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
452
|
+
"success": False,
|
|
453
|
+
"error": f"JSON decode error: {e}",
|
|
454
|
+
"json_str": json_str,
|
|
419
455
|
}
|
|
420
456
|
else:
|
|
421
457
|
return {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
458
|
+
"success": False,
|
|
459
|
+
"error": "JSON pattern not found",
|
|
460
|
+
"response_text": test_response,
|
|
425
461
|
}
|
|
426
|
-
|
|
462
|
+
|
|
427
463
|
except Exception as e:
|
|
428
|
-
return {
|
|
429
|
-
'success': False,
|
|
430
|
-
'error': f"Parse error: {e}"
|
|
431
|
-
}
|
|
464
|
+
return {"success": False, "error": f"Parse error: {e}"}
|