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

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

Potentially problematic release.


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

Files changed (61) hide show
  1. smart_bot_factory/__init__.py +33 -0
  2. smart_bot_factory/admin/__init__.py +16 -0
  3. smart_bot_factory/admin/admin_logic.py +430 -0
  4. smart_bot_factory/admin/admin_manager.py +141 -0
  5. smart_bot_factory/admin/admin_migration.sql +136 -0
  6. smart_bot_factory/admin/admin_tester.py +151 -0
  7. smart_bot_factory/admin/timeout_checker.py +499 -0
  8. smart_bot_factory/analytics/__init__.py +7 -0
  9. smart_bot_factory/analytics/analytics_manager.py +355 -0
  10. smart_bot_factory/cli.py +768 -0
  11. smart_bot_factory/config.py +235 -0
  12. smart_bot_factory/configs/growthmed-helper/env_example.txt +1 -0
  13. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +9 -0
  14. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +582 -0
  15. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +66 -0
  16. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +232 -0
  17. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +28 -0
  18. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +7 -0
  19. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +16 -0
  20. smart_bot_factory/configs/growthmed-helper/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
  21. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  22. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  23. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  24. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  25. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  26. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  27. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  28. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  29. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  30. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +66 -0
  31. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  32. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  33. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  34. smart_bot_factory/configs/growthmed-october-24/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
  35. smart_bot_factory/core/__init__.py +22 -0
  36. smart_bot_factory/core/bot_utils.py +703 -0
  37. smart_bot_factory/core/conversation_manager.py +536 -0
  38. smart_bot_factory/core/decorators.py +230 -0
  39. smart_bot_factory/core/message_sender.py +249 -0
  40. smart_bot_factory/core/states.py +14 -0
  41. smart_bot_factory/creation/__init__.py +8 -0
  42. smart_bot_factory/creation/bot_builder.py +329 -0
  43. smart_bot_factory/creation/bot_testing.py +986 -0
  44. smart_bot_factory/database/database_structure.sql +57 -0
  45. smart_bot_factory/database/schema.sql +1094 -0
  46. smart_bot_factory/handlers/handlers.py +583 -0
  47. smart_bot_factory/integrations/__init__.py +9 -0
  48. smart_bot_factory/integrations/openai_client.py +435 -0
  49. smart_bot_factory/integrations/supabase_client.py +592 -0
  50. smart_bot_factory/setup_checker.py +476 -0
  51. smart_bot_factory/utils/__init__.py +9 -0
  52. smart_bot_factory/utils/debug_routing.py +103 -0
  53. smart_bot_factory/utils/prompt_loader.py +427 -0
  54. smart_bot_factory/utm_link_generator.py +106 -0
  55. smart_bot_factory-0.1.4.dist-info/METADATA +126 -0
  56. smart_bot_factory-0.1.4.dist-info/RECORD +59 -0
  57. smart_bot_factory-0.1.4.dist-info/licenses/LICENSE +24 -0
  58. smart_bot_factory-0.1.2.dist-info/METADATA +0 -31
  59. smart_bot_factory-0.1.2.dist-info/RECORD +0 -4
  60. {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.4.dist-info}/WHEEL +0 -0
  61. {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,583 @@
1
+ # Исправленный handlers.py с отладкой маршрутизации
2
+
3
+ import logging
4
+ import time
5
+ from aiogram import Router, F
6
+ from aiogram.filters import Command, StateFilter
7
+ from aiogram.fsm.context import FSMContext
8
+ from aiogram.types import Message
9
+
10
+ from ..core.bot_utils import send_message, parse_ai_response, process_events, send_welcome_file
11
+ from ..core.states import UserStates, AdminStates
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Создаем роутер для обработчиков
16
+ router = Router()
17
+
18
+ def setup_handlers(dp):
19
+ """Настройка основных обработчиков"""
20
+ # Подключаем middleware
21
+ router.message.middleware()(admin_middleware)
22
+
23
+ # Регистрируем роутер
24
+ dp.include_router(router)
25
+
26
+ # Функция для получения глобальных переменных
27
+ def get_global_var(var_name):
28
+ """Получает глобальную переменную из модуля handlers"""
29
+ import sys
30
+ current_module = sys.modules[__name__]
31
+ return getattr(current_module, var_name, None)
32
+
33
+ # Middleware для проверки админов
34
+ async def admin_middleware(handler, event: Message, data: dict):
35
+ """Middleware для обновления информации об админах"""
36
+ admin_manager = get_global_var('admin_manager')
37
+
38
+ if admin_manager and admin_manager.is_admin(event.from_user.id):
39
+ await admin_manager.update_admin_info(event.from_user)
40
+
41
+ return await handler(event, data)
42
+
43
+ @router.message(Command(commands=["start", "старт", "ст"]))
44
+ async def start_handler(message: Message, state: FSMContext):
45
+ """Обработчик команды /start - сброс сессии и начало заново"""
46
+ admin_manager = get_global_var('admin_manager')
47
+ from ..admin.admin_logic import admin_start_handler
48
+ from ..utils.debug_routing import debug_user_state
49
+
50
+ try:
51
+ await debug_user_state(message, state, "START_COMMAND")
52
+
53
+ # Проверяем, админ ли это и в каком режиме
54
+ if admin_manager.is_admin(message.from_user.id):
55
+ if admin_manager.is_in_admin_mode(message.from_user.id):
56
+ # Админ в режиме администратора - работаем как админ
57
+ await admin_start_handler(message, state)
58
+ return
59
+ # Админ в режиме пользователя - работаем как обычный пользователь
60
+
61
+ await user_start_handler(message, state)
62
+
63
+ except Exception as e:
64
+ logger.error(f"Ошибка при обработке /start: {e}")
65
+ await send_message(message, "Произошла ошибка при инициализации. Попробуйте позже.")
66
+
67
+ async def user_start_handler(message: Message, state: FSMContext):
68
+ """Обработчик /start для обычных пользователей"""
69
+ supabase_client = get_global_var('supabase_client')
70
+ prompt_loader = get_global_var('prompt_loader')
71
+ from ..core.bot_utils import parse_utm_from_start_param
72
+
73
+ try:
74
+ # 0. ПОЛУЧАЕМ UTM ДАННЫЕ
75
+ start_param = message.text.split(' ', 1)[1] if len(message.text.split()) > 1 else None
76
+
77
+ # Логируем входящий start параметр
78
+ # Пример поддерживаемого формата: @https://t.me/bot?start=utmSource-vk_utmCampaign-summer2025 не более 64 символов после strat=
79
+
80
+ logger.info(f"📥 Получен start параметр: '{start_param}'")
81
+
82
+ utm_data = {}
83
+ if start_param:
84
+ # Парсим UTM данные
85
+ utm_data = parse_utm_from_start_param(start_param)
86
+
87
+ # Подробное логирование UTM
88
+ logger.info(f"📊 UTM данные для пользователя {message.from_user.id}:")
89
+ if utm_data:
90
+ for key, value in utm_data.items():
91
+ logger.info(f" • {key}: {value}")
92
+ logger.info(f"✅ UTM данные успешно распознаны")
93
+ else:
94
+ logger.warning(f"⚠️ UTM данные не найдены в параметре: '{start_param}'")
95
+ else:
96
+ logger.info("ℹ️ Start параметр отсутствует (обычный /start)")
97
+
98
+ # 1. ЯВНО ОЧИЩАЕМ СОСТОЯНИЕ FSM
99
+ await state.clear()
100
+ logger.info(f"🔄 Состояние FSM очищено для пользователя {message.from_user.id}")
101
+
102
+ # 2. ЗАГРУЖАЕМ ПРОМПТЫ
103
+ logger.info(f"Загрузка промптов для пользователя {message.from_user.id}")
104
+ system_prompt = await prompt_loader.load_system_prompt()
105
+
106
+ # Загружаем приветственное сообщение
107
+ welcome_message = await prompt_loader.load_welcome_message()
108
+
109
+ # 3. ПОЛУЧАЕМ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ
110
+ user_data = {
111
+ 'telegram_id': message.from_user.id,
112
+ 'username': message.from_user.username,
113
+ 'first_name': message.from_user.first_name,
114
+ 'last_name': message.from_user.last_name,
115
+ 'language_code': message.from_user.language_code,
116
+ 'source': utm_data.get('utm_source'),
117
+ 'medium': utm_data.get('utm_medium'),
118
+ 'campaign': utm_data.get('utm_campaign'),
119
+ 'content': utm_data.get('utm_content'),
120
+ 'term': utm_data.get('utm_term')
121
+ }
122
+
123
+ # 4. СОЗДАЕМ НОВУЮ СЕССИЮ (автоматически закроет активные)
124
+ # Добавляем UTM данные в метаданные пользователя
125
+ if utm_data:
126
+ user_data['metadata'] = {'utm_data': utm_data}
127
+ logger.info(f"📈 UTM данные добавлены в метаданные пользователя")
128
+
129
+ session_id = await supabase_client.create_chat_session(user_data, system_prompt)
130
+ logger.info(f"✅ Создана новая сессия {session_id} для пользователя {message.from_user.id}")
131
+
132
+ # 5. УСТАНАВЛИВАЕМ НОВОЕ СОСТОЯНИЕ
133
+ await state.update_data(session_id=session_id, system_prompt=system_prompt)
134
+ await state.set_state(UserStates.waiting_for_message)
135
+
136
+ # 6. ОТПРАВЛЯЕМ ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ
137
+ await send_message(message, welcome_message)
138
+ logger.info(f"Приветственное сообщение отправлено пользователю {message.from_user.id}")
139
+
140
+ # 7. ЕСЛИ ЕСТЬ ФАЙЛ ОТПРАВЛЯЕМ ВМЕСТЕ С ПОДПИСЬЮ
141
+ logging.info(f"📎 Попытка отправки приветственного файла для сессии {session_id}")
142
+ caption = await send_welcome_file(message)
143
+
144
+ # 8. СОХРАНЯЕМ ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ В БД
145
+ if caption:
146
+ logging.info(f"📄 Добавление подписи к файлу в приветственное сообщение для сессии {session_id}")
147
+ welcome_message = f"{welcome_message}\n\nПодпись к файлу:\n\n{caption}"
148
+ else:
149
+ logging.info(f"📄 Приветственный файл отправлен без подписи для сессии {session_id}")
150
+
151
+ logging.info(f"💾 Сохранение приветственного сообщения в БД для сессии {session_id}")
152
+
153
+ await supabase_client.add_message(
154
+ session_id=session_id,
155
+ role='assistant',
156
+ content=welcome_message,
157
+ message_type='text'
158
+ )
159
+
160
+ logging.info(f"✅ Приветственное сообщение успешно сохранено в БД для сессии {session_id}")
161
+
162
+ except Exception as e:
163
+ logger.error(f"Ошибка при обработке user /start: {e}")
164
+ await send_message(message, "Произошла ошибка при инициализации. Попробуйте позже.")
165
+
166
+ @router.message(StateFilter(None))
167
+ async def message_without_state_handler(message: Message, state: FSMContext):
168
+ """Обработчик сообщений без состояния (после перезапуска бота)"""
169
+ admin_manager = get_global_var('admin_manager')
170
+ supabase_client = get_global_var('supabase_client')
171
+ conversation_manager = get_global_var('conversation_manager')
172
+ from ..admin.admin_logic import AdminStates
173
+ from ..utils.debug_routing import debug_user_state
174
+
175
+ try:
176
+ await debug_user_state(message, state, "NO_STATE")
177
+
178
+ # СНАЧАЛА проверяем диалог с админом
179
+ conversation = await conversation_manager.is_user_in_admin_chat(message.from_user.id)
180
+
181
+ if conversation:
182
+ logger.info(f"✅ Найден диалог с админом {conversation['admin_id']}, устанавливаем состояние admin_chat")
183
+
184
+ # Устанавливаем состояние admin_chat
185
+ await state.set_state(UserStates.admin_chat)
186
+ await state.update_data(admin_conversation=conversation)
187
+
188
+ # Сразу пересылаем сообщение админу
189
+ await conversation_manager.forward_message_to_admin(message, conversation)
190
+
191
+ # Сохраняем сообщение в БД
192
+ session_info = await supabase_client.get_active_session(message.from_user.id)
193
+ if session_info:
194
+ await supabase_client.add_message(
195
+ session_id=session_info['id'],
196
+ role='user',
197
+ content=message.text,
198
+ message_type='text',
199
+ metadata={'in_admin_chat': True, 'admin_id': conversation['admin_id']}
200
+ )
201
+
202
+ return
203
+
204
+ # Проверяем, админ ли это
205
+ if admin_manager.is_admin(message.from_user.id):
206
+ logger.info(f"👑 Админ в режиме администратора без состояния")
207
+ await state.set_state(AdminStates.admin_mode)
208
+ await message.answer("👑 Режим администратора\nИспользуйте /start для панели управления")
209
+ return
210
+
211
+ logger.info(f"👤 Обычный пользователь без состояния, ищем активную сессию")
212
+
213
+ # Ищем активную сессию в БД
214
+ session_info = await supabase_client.get_active_session(message.from_user.id)
215
+
216
+ if session_info:
217
+ logger.info(f"📝 Восстанавливаем сессию {session_info['id']}")
218
+ # Восстанавливаем сессию из БД
219
+ session_id = session_info['id']
220
+ system_prompt = session_info['system_prompt']
221
+
222
+ # Сохраняем в состояние
223
+ await state.update_data(session_id=session_id, system_prompt=system_prompt)
224
+ await state.set_state(UserStates.waiting_for_message)
225
+
226
+ logger.info(f"✅ Сессия восстановлена, обрабатываем сообщение")
227
+
228
+ # Теперь обрабатываем сообщение как обычно
229
+ await process_user_message(message, state, session_id, system_prompt)
230
+ else:
231
+ logger.info(f"❌ Нет активной сессии, просим написать /start")
232
+ await send_message(message, "Привет! Напишите /start для начала диалога.")
233
+
234
+ except Exception as e:
235
+ logger.error(f"❌ Ошибка при обработке сообщения без состояния: {e}")
236
+ await send_message(message, "Произошла ошибка. Попробуйте написать /start для начала диалога.")
237
+
238
+ # ✅ ИСПРАВЛЕНИЕ: Обработчик admin_chat должен быть ПЕРВЫМ и более приоритетным
239
+ @router.message(StateFilter(UserStates.admin_chat))
240
+ async def user_in_admin_chat_handler(message: Message, state: FSMContext):
241
+ """ПРИОРИТЕТНЫЙ обработчик сообщений пользователей в диалоге с админом"""
242
+ conversation_manager = get_global_var('conversation_manager')
243
+ supabase_client = get_global_var('supabase_client')
244
+ from ..utils.debug_routing import debug_user_state
245
+
246
+ await debug_user_state(message, state, "ADMIN_CHAT_HANDLER")
247
+
248
+ user_id = message.from_user.id
249
+ logger.info(f"🎯 ADMIN_CHAT HANDLER: сообщение от {user_id}: '{message.text}'")
250
+
251
+ # Проверяем, есть ли еще активный диалог
252
+ conversation = await conversation_manager.is_user_in_admin_chat(user_id)
253
+
254
+ if conversation:
255
+ logger.info(f"✅ Диалог активен, пересылаем админу {conversation['admin_id']}")
256
+
257
+ try:
258
+ # Сохраняем сообщение в БД
259
+ session_info = await supabase_client.get_active_session(user_id)
260
+ if session_info:
261
+ await supabase_client.add_message(
262
+ session_id=session_info['id'],
263
+ role='user',
264
+ content=message.text,
265
+ message_type='text',
266
+ metadata={'in_admin_chat': True, 'admin_id': conversation['admin_id']}
267
+ )
268
+ logger.info(f"💾 Сообщение сохранено в БД")
269
+
270
+ # Пересылаем админу
271
+ await conversation_manager.forward_message_to_admin(message, conversation)
272
+ logger.info(f"📤 Сообщение переслано админу")
273
+
274
+ except Exception as e:
275
+ logger.error(f"❌ Ошибка обработки admin_chat: {e}")
276
+ await message.answer("Произошла ошибка. Попробуйте позже.")
277
+ else:
278
+ logger.info(f"💬 Диалог завершен, возвращаем к обычному режиму")
279
+ # Диалог завершен, возвращаем к обычному режиму
280
+ await state.set_state(UserStates.waiting_for_message)
281
+
282
+ # Обрабатываем как обычное сообщение
283
+ data = await state.get_data()
284
+ session_id = data.get('session_id')
285
+ system_prompt = data.get('system_prompt')
286
+
287
+ if session_id:
288
+ await process_user_message(message, state, session_id, system_prompt)
289
+ else:
290
+ await send_message(message, "Сессия не найдена. Пожалуйста, напишите /start")
291
+
292
+ # Обработчик для обычных сообщений (НЕ в admin_chat)
293
+ @router.message(StateFilter(UserStates.waiting_for_message), ~F.text.startswith('/'))
294
+ async def user_message_handler(message: Message, state: FSMContext):
295
+ """Обработчик сообщений пользователей (исключая admin_chat)"""
296
+ conversation_manager = get_global_var('conversation_manager')
297
+ from ..utils.debug_routing import debug_user_state
298
+
299
+ try:
300
+ await debug_user_state(message, state, "USER_MESSAGE_HANDLER")
301
+
302
+ # ✅ ВАЖНО: Сначала проверяем диалог с админом
303
+ conversation = await conversation_manager.is_user_in_admin_chat(message.from_user.id)
304
+
305
+ if conversation:
306
+ logger.info(f"⚠️ НЕОЖИДАННО: пользователь в waiting_for_message, но есть диалог с админом!")
307
+ logger.info(f"🔄 Принудительно переключаем в admin_chat состояние")
308
+
309
+ # Принудительно переключаем состояние
310
+ await state.set_state(UserStates.admin_chat)
311
+ await state.update_data(admin_conversation=conversation)
312
+
313
+ # Обрабатываем сообщение как admin_chat
314
+ await user_in_admin_chat_handler(message, state)
315
+ return
316
+
317
+ logger.info(f"🤖 Обычный диалог с ботом")
318
+ data = await state.get_data()
319
+ session_id = data.get('session_id')
320
+ system_prompt = data.get('system_prompt')
321
+
322
+ if not session_id:
323
+ logger.warning(f"❌ Нет session_id в состоянии")
324
+ await send_message(message, "Сессия не найдена. Пожалуйста, напишите /start")
325
+ return
326
+
327
+ logger.info(f"📝 Обрабатываем сообщение с session_id: {session_id}")
328
+ await process_user_message(message, state, session_id, system_prompt)
329
+
330
+ except Exception as e:
331
+ logger.error(f"❌ Ошибка при обработке сообщения пользователя: {e}")
332
+ await send_message(message, "Произошла ошибка. Попробуйте еще раз или напишите /start для перезапуска.")
333
+
334
+ @router.message()
335
+ async def catch_all_handler(message: Message, state: FSMContext):
336
+ """Перехватчик всех необработанных сообщений"""
337
+ admin_manager = get_global_var('admin_manager')
338
+ from ..utils.debug_routing import debug_user_state
339
+
340
+ await debug_user_state(message, state, "CATCH_ALL")
341
+
342
+ current_state = await state.get_state()
343
+ logger.warning(f"⚠️ НЕОБРАБОТАННОЕ СООБЩЕНИЕ от {message.from_user.id}: '{message.text}', состояние: {current_state}")
344
+
345
+ # Проверяем, админ ли это
346
+ if admin_manager.is_admin(message.from_user.id):
347
+ logger.info(f"👑 Необработанное сообщение админа")
348
+ await message.answer("Команда не распознана. Используйте /help для справки.")
349
+ else:
350
+ logger.info(f"👤 Необработанное сообщение пользователя")
351
+ await message.answer("Не понимаю. Напишите /start для начала диалога.")
352
+
353
+ async def process_user_message(message: Message, state: FSMContext, session_id: str, system_prompt: str):
354
+ """Общая функция для обработки сообщений пользователя"""
355
+ supabase_client = get_global_var('supabase_client')
356
+ openai_client = get_global_var('openai_client')
357
+ config = get_global_var('config')
358
+ bot = get_global_var('bot')
359
+ prompt_loader = get_global_var('prompt_loader')
360
+ from datetime import datetime
361
+ import pytz # Добавляем импорт для работы с временными зонами
362
+
363
+ try:
364
+ # Сохраняем сообщение пользователя
365
+ await supabase_client.add_message(
366
+ session_id=session_id,
367
+ role='user',
368
+ content=message.text,
369
+ message_type='text'
370
+ )
371
+ logger.info(f"✅ Сообщение пользователя сохранено в БД")
372
+
373
+ # Получаем историю сообщений
374
+ chat_history = await supabase_client.get_chat_history(session_id, limit=config.MAX_CONTEXT_MESSAGES)
375
+ logger.info(f"📚 Загружена история: {len(chat_history)} сообщений")
376
+
377
+ # ДОБАВЛЯЕМ ПОЛУЧЕНИЕ ТЕКУЩЕГО ВРЕМЕНИ
378
+ moscow_tz = pytz.timezone('Europe/Moscow')
379
+ current_time = datetime.now(moscow_tz)
380
+ time_info = current_time.strftime('%H:%M, %d.%m.%Y, %A')
381
+
382
+ # Модифицируем системный промпт, добавляя время
383
+ system_prompt_with_time = f"""
384
+ {system_prompt}
385
+
386
+ ТЕКУЩЕЕ ВРЕМЯ: {time_info} (московское время)
387
+ """
388
+
389
+ # Формируем контекст для OpenAI с обновленным системным промптом
390
+ messages = [{"role": "system", "content": system_prompt_with_time}]
391
+
392
+ for msg in chat_history[-config.MAX_CONTEXT_MESSAGES:]: # Ограничиваем контекст
393
+ messages.append({
394
+ "role": msg['role'],
395
+ "content": msg['content']
396
+ })
397
+
398
+ # Добавляем финальные инструкции в конец контекста
399
+ final_instructions = await prompt_loader.load_final_instructions()
400
+ if final_instructions:
401
+ messages.append({"role": "system", "content": final_instructions})
402
+ logger.info(f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)")
403
+
404
+ logger.info(f"📝 Контекст сформирован: {len(messages)} сообщений (включая время: {time_info})")
405
+
406
+ await bot.send_chat_action(message.chat.id, "typing")
407
+
408
+ start_time = time.time()
409
+ ai_response = await openai_client.get_completion(messages)
410
+ processing_time = int((time.time() - start_time) * 1000)
411
+
412
+ logger.info(f"🤖 OpenAI ответил за {processing_time}мс, длина ответа: {len(ai_response) if ai_response else 0}")
413
+
414
+ # ИСПРАВЛЕННАЯ ЛОГИКА: инициализируем все переменные заранее
415
+ tokens_used = 0
416
+ ai_metadata = {}
417
+ response_text = ""
418
+
419
+ # Проверяем ответ
420
+ if not ai_response or not ai_response.strip():
421
+ logger.warning(f"❌ OpenAI вернул пустой/пробельный ответ!")
422
+
423
+ # Проверяем, были ли использованы токены при пустом ответе
424
+ if hasattr(openai_client, 'last_completion_tokens'):
425
+ logger.warning(f"⚠️ Токены использованы ({openai_client.last_completion_tokens}), но ответ пустой")
426
+
427
+ # Устанавливаем fallback ответ
428
+ fallback_message = "Извините, произошла техническая ошибка. Попробуйте переформулировать вопрос или напишите /start для перезапуска."
429
+ ai_response = fallback_message
430
+ response_text = fallback_message
431
+ tokens_used = 0
432
+ ai_metadata = {}
433
+
434
+ else:
435
+ logger.info(f"📤 Сырой ответ OpenAI получен, обрабатываем...")
436
+
437
+ tokens_used = openai_client.estimate_tokens(ai_response)
438
+
439
+ # Парсим JSON метаданные
440
+ response_text, ai_metadata = parse_ai_response(ai_response)
441
+
442
+ logger.info(f"🔍 После парсинга JSON:")
443
+ logger.info(f" 📝 Текст ответа: {len(response_text)} символов: '{response_text[:100]}...'")
444
+ logger.info(f" 📊 Метаданные: {ai_metadata}")
445
+
446
+ # Более надежная проверка
447
+ if not ai_metadata:
448
+ logger.info("ℹ️ JSON не найден, используем исходный ответ")
449
+ response_text = ai_response
450
+ ai_metadata = {}
451
+ elif not response_text.strip():
452
+ logger.warning("⚠️ JSON найден, но текст ответа пустой! Используем исходный ответ.")
453
+ response_text = ai_response
454
+
455
+ logger.info(f"✅ Финальный текст для отправки: {len(response_text)} символов")
456
+
457
+ # Обновляем этап сессии и качество лида
458
+ if ai_metadata:
459
+ logger.info("🔍 Анализ метаданных от ИИ:")
460
+
461
+ # Вывод информации об этапе
462
+ stage = ai_metadata.get('этап')
463
+ if stage:
464
+ logger.info(f" 📈 Этап диалога: {stage}")
465
+
466
+ # Вывод информации о качестве лида
467
+ quality = ai_metadata.get('качество')
468
+ if quality is not None:
469
+ quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
470
+ logger.info(f" {quality_emoji} Качество лида: {quality}/10")
471
+
472
+ # Обновляем в базе данных
473
+ if stage or quality is not None:
474
+ await supabase_client.update_session_stage(session_id, stage, quality)
475
+ logger.info(f" ✅ Этап и качество обновлены в БД")
476
+
477
+ # Обрабатываем события
478
+ events = ai_metadata.get('события', [])
479
+ if events:
480
+ logger.info(f"\n🔔 События в диалоге ({len(events)}):")
481
+ for idx, event in enumerate(events, 1):
482
+ event_type = event.get('тип', 'неизвестно')
483
+ event_info = event.get('инфо', 'нет информации')
484
+
485
+ # Подбираем эмодзи для разных типов событий
486
+ event_emoji = {
487
+ 'телефон': '📱',
488
+ 'email': '📧',
489
+ 'встреча': '📅',
490
+ 'заказ': '🛍️',
491
+ 'вопрос': '❓',
492
+ 'консультация': '💬',
493
+ 'жалоба': '⚠️',
494
+ 'отзыв': '💭'
495
+ }.get(event_type.lower(), '📌')
496
+
497
+ logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
498
+
499
+ # Обрабатываем события в системе
500
+ await process_events(session_id, events, message.from_user.id)
501
+ logger.info(" ✅ События обработаны")
502
+
503
+ # Обрабатываем файлы и каталоги
504
+ files_list = ai_metadata.get('файлы', [])
505
+ directories_list = ai_metadata.get('каталоги', [])
506
+
507
+ # Форматируем информацию о файлах
508
+ if files_list:
509
+ logger.info("📎 Найденные файлы:")
510
+ for idx, file in enumerate(files_list, 1):
511
+ logger.info(f" {idx}. 📄 {file}")
512
+
513
+ # Форматируем информацию о каталогах
514
+ if directories_list:
515
+ logger.info("📂 Найденные каталоги:")
516
+ for idx, directory in enumerate(directories_list, 1):
517
+ logger.info(f" {idx}. 📁 {directory}")
518
+
519
+ # Добавляем информацию в текст ответа
520
+ if files_list or directories_list:
521
+ files_info = []
522
+ if files_list:
523
+ files_str = "\n".join(f"• {file}" for file in files_list)
524
+ files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
525
+
526
+ if directories_list:
527
+ dirs_str = "\n".join(f"• {directory}" for directory in directories_list)
528
+ files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
529
+
530
+ else:
531
+ logger.info("📎 Файлы и каталоги не указаны")
532
+
533
+ # Сохраняем ответ ассистента с метаданными
534
+ try:
535
+ await supabase_client.add_message(
536
+ session_id=session_id,
537
+ role='assistant',
538
+ content=response_text,
539
+ message_type='text',
540
+ tokens_used=tokens_used,
541
+ processing_time_ms=processing_time,
542
+ ai_metadata=ai_metadata
543
+ )
544
+ logger.info(f"✅ Ответ ассистента сохранен в БД")
545
+ except Exception as e:
546
+ logger.error(f"❌ Ошибка сохранения ответа в БД: {e}")
547
+
548
+ # Определяем финальный ответ для пользователя
549
+ if config.DEBUG_MODE:
550
+ # В режиме отладки показываем полный ответ с JSON
551
+ final_response = ai_response
552
+ logger.info(f"🐛 Режим отладки: отправляем полный ответ с JSON")
553
+ else:
554
+ # В обычном режиме показываем только текст без JSON
555
+ final_response = response_text
556
+ logger.info(f"👤 Обычный режим: отправляем очищенный текст")
557
+
558
+ # Проверяем, что есть что отправлять
559
+ if not final_response or not final_response.strip():
560
+ logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА: Финальный ответ пуст!")
561
+ final_response = "Извините, произошла ошибка при формировании ответа. Попробуйте еще раз."
562
+
563
+ logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
564
+
565
+ # Отправляем ответ пользователю
566
+ try:
567
+ await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
568
+ logger.info(f"✅ Ответ успешно отправлен пользователю {message.from_user.id}")
569
+ except Exception as e:
570
+ logger.error(f"❌ ОШИБКА ОТПРАВКИ СООБЩЕНИЯ: {e}")
571
+ # Пытаемся отправить простое сообщение об ошибке
572
+ try:
573
+ await message.answer("Произошла ошибка при отправке ответа. Попробуйте еще раз.")
574
+ except Exception as e2:
575
+ logger.error(f"❌ Не удалось отправить даже сообщение об ошибке: {e2}")
576
+
577
+ except Exception as e:
578
+ logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА в process_user_message: {e}")
579
+ logger.exception("Полный стек ошибки:")
580
+ try:
581
+ await message.answer("Произошла критическая ошибка. Попробуйте написать /start для перезапуска.")
582
+ except:
583
+ logger.error(f"❌ Не удалось отправить сообщение об критической ошибке")
@@ -0,0 +1,9 @@
1
+ """
2
+ Integrations модули smart_bot_factory
3
+ """
4
+
5
+ from .openai_client import OpenAIClient
6
+
7
+ __all__ = ['OpenAIClient']
8
+
9
+