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.

Files changed (45) hide show
  1. smart_bot_factory/admin/__init__.py +7 -7
  2. smart_bot_factory/admin/admin_events.py +483 -383
  3. smart_bot_factory/admin/admin_logic.py +234 -158
  4. smart_bot_factory/admin/admin_manager.py +68 -53
  5. smart_bot_factory/admin/admin_tester.py +46 -40
  6. smart_bot_factory/admin/timeout_checker.py +201 -153
  7. smart_bot_factory/aiogram_calendar/__init__.py +11 -3
  8. smart_bot_factory/aiogram_calendar/common.py +12 -18
  9. smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
  10. smart_bot_factory/aiogram_calendar/schemas.py +49 -28
  11. smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
  12. smart_bot_factory/analytics/analytics_manager.py +414 -392
  13. smart_bot_factory/cli.py +204 -148
  14. smart_bot_factory/config.py +123 -102
  15. smart_bot_factory/core/bot_utils.py +474 -332
  16. smart_bot_factory/core/conversation_manager.py +287 -200
  17. smart_bot_factory/core/decorators.py +1200 -755
  18. smart_bot_factory/core/message_sender.py +287 -266
  19. smart_bot_factory/core/router.py +170 -100
  20. smart_bot_factory/core/router_manager.py +121 -83
  21. smart_bot_factory/core/states.py +4 -3
  22. smart_bot_factory/creation/__init__.py +1 -1
  23. smart_bot_factory/creation/bot_builder.py +320 -242
  24. smart_bot_factory/creation/bot_testing.py +440 -365
  25. smart_bot_factory/dashboard/__init__.py +1 -3
  26. smart_bot_factory/event/__init__.py +2 -7
  27. smart_bot_factory/handlers/handlers.py +676 -472
  28. smart_bot_factory/integrations/openai_client.py +218 -168
  29. smart_bot_factory/integrations/supabase_client.py +948 -637
  30. smart_bot_factory/message/__init__.py +18 -22
  31. smart_bot_factory/router/__init__.py +2 -2
  32. smart_bot_factory/setup_checker.py +162 -126
  33. smart_bot_factory/supabase/__init__.py +1 -1
  34. smart_bot_factory/supabase/client.py +631 -515
  35. smart_bot_factory/utils/__init__.py +2 -3
  36. smart_bot_factory/utils/debug_routing.py +38 -27
  37. smart_bot_factory/utils/prompt_loader.py +153 -120
  38. smart_bot_factory/utils/user_prompt_loader.py +55 -56
  39. smart_bot_factory/utm_link_generator.py +123 -116
  40. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.9.dist-info/RECORD +59 -0
  42. smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
  43. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -2,54 +2,61 @@
2
2
 
3
3
  import logging
4
4
  import time
5
- from aiogram import Router, F
5
+
6
+ from aiogram import F, Router
6
7
  from aiogram.filters import Command, StateFilter
7
8
  from aiogram.fsm.context import FSMContext
8
- from aiogram.types import Message, CallbackQuery
9
+ from aiogram.types import CallbackQuery, Message
9
10
 
10
- from ..core.bot_utils import send_message, parse_ai_response, process_events, send_welcome_file
11
- from ..core.states import UserStates, AdminStates
11
+ from ..core.bot_utils import (parse_ai_response, process_events, send_message,
12
+ send_welcome_file)
13
+ from ..core.states import UserStates
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
17
  # Создаем роутер для обработчиков
16
18
  router = Router()
17
19
 
20
+
18
21
  def setup_handlers(dp):
19
22
  """Настройка основных обработчиков"""
20
23
  # Подключаем middleware
21
24
  router.message.middleware()(admin_middleware)
22
-
25
+
23
26
  # Регистрируем роутер
24
27
  dp.include_router(router)
25
28
 
29
+
26
30
  # Функция для получения глобальных переменных
27
31
  def get_global_var(var_name):
28
32
  """Получает глобальную переменную из модуля handlers"""
29
33
  import sys
34
+
30
35
  current_module = sys.modules[__name__]
31
36
  return getattr(current_module, var_name, None)
32
37
 
38
+
33
39
  # Middleware для проверки админов
34
40
  async def admin_middleware(handler, event: Message, data: dict):
35
41
  """Middleware для обновления информации об админах"""
36
- admin_manager = get_global_var('admin_manager')
37
-
42
+ admin_manager = get_global_var("admin_manager")
43
+
38
44
  if admin_manager and admin_manager.is_admin(event.from_user.id):
39
45
  await admin_manager.update_admin_info(event.from_user)
40
-
46
+
41
47
  return await handler(event, data)
42
48
 
49
+
43
50
  @router.message(Command(commands=["start", "старт", "ст"]))
44
51
  async def start_handler(message: Message, state: FSMContext):
45
52
  """Обработчик команды /start - сброс сессии и начало заново"""
46
- admin_manager = get_global_var('admin_manager')
53
+ admin_manager = get_global_var("admin_manager")
47
54
  from ..admin.admin_logic import admin_start_handler
48
55
  from ..utils.debug_routing import debug_user_state
49
-
56
+
50
57
  try:
51
58
  await debug_user_state(message, state, "START_COMMAND")
52
-
59
+
53
60
  # Проверяем, админ ли это и в каком режиме
54
61
  if admin_manager.is_admin(message.from_user.id):
55
62
  if admin_manager.is_in_admin_mode(message.from_user.id):
@@ -57,107 +64,125 @@ async def start_handler(message: Message, state: FSMContext):
57
64
  await admin_start_handler(message, state)
58
65
  return
59
66
  # Админ в режиме пользователя - работаем как обычный пользователь
60
-
67
+
61
68
  await user_start_handler(message, state)
62
-
69
+
63
70
  except Exception as e:
64
71
  logger.error(f"Ошибка при обработке /start: {e}")
65
- await send_message(message, "Произошла ошибка при инициализации. Попробуйте позже.")
72
+ await send_message(
73
+ message, "Произошла ошибка при инициализации. Попробуйте позже."
74
+ )
75
+
66
76
 
67
77
  @router.message(Command(commands=["timeup", "вперед"]))
68
78
  async def timeup_handler(message: Message, state: FSMContext):
69
79
  """Обработчик команды /timeup (или /вперед) - тестирование запланированных событий"""
70
- from ..core.decorators import process_scheduled_event, update_event_result
71
80
  from datetime import datetime
72
-
73
- supabase_client = get_global_var('supabase_client')
74
-
81
+
82
+ from ..core.decorators import process_scheduled_event, update_event_result
83
+
84
+ supabase_client = get_global_var("supabase_client")
85
+
75
86
  try:
76
87
  await message.answer("🔄 Запускаю тестирование запланированных событий...")
77
-
88
+
78
89
  # Получаем события для этого пользователя И глобальные события (user_id = null)
79
90
  # 1. События пользователя
80
- user_events_query = supabase_client.client.table('scheduled_events').select(
81
- '*'
82
- ).eq('user_id', message.from_user.id).in_('status', ['pending', 'immediate'])
83
-
91
+ user_events_query = (
92
+ supabase_client.client.table("scheduled_events")
93
+ .select("*")
94
+ .eq("user_id", message.from_user.id)
95
+ .in_("status", ["pending", "immediate"])
96
+ )
97
+
84
98
  # 🆕 Фильтруем по bot_id если указан
85
99
  if supabase_client.bot_id:
86
- user_events_query = user_events_query.eq('bot_id', supabase_client.bot_id)
87
-
100
+ user_events_query = user_events_query.eq("bot_id", supabase_client.bot_id)
101
+
88
102
  user_events = user_events_query.execute()
89
-
103
+
90
104
  # 2. Глобальные события (без user_id)
91
- global_events_query = supabase_client.client.table('scheduled_events').select(
92
- '*'
93
- ).is_('user_id', 'null').in_('status', ['pending', 'immediate'])
94
-
105
+ global_events_query = (
106
+ supabase_client.client.table("scheduled_events")
107
+ .select("*")
108
+ .is_("user_id", "null")
109
+ .in_("status", ["pending", "immediate"])
110
+ )
111
+
95
112
  # 🆕 Фильтруем по bot_id если указан
96
113
  if supabase_client.bot_id:
97
- global_events_query = global_events_query.eq('bot_id', supabase_client.bot_id)
98
-
114
+ global_events_query = global_events_query.eq(
115
+ "bot_id", supabase_client.bot_id
116
+ )
117
+
99
118
  global_events = global_events_query.execute()
100
-
119
+
101
120
  # Объединяем события
102
121
  all_events = (user_events.data or []) + (global_events.data or [])
103
-
122
+
104
123
  if not all_events:
105
124
  await message.answer("📭 Нет запланированных событий для тестирования")
106
125
  return
107
-
126
+
108
127
  total_events = len(all_events)
109
128
  user_count = len(user_events.data or [])
110
129
  global_count = len(global_events.data or [])
111
-
130
+
112
131
  status_msg = f"📋 Найдено {total_events} событий:"
113
132
  if user_count > 0:
114
133
  status_msg += f"\n 👤 Ваших: {user_count}"
115
134
  if global_count > 0:
116
135
  status_msg += f"\n 🌍 Глобальных: {global_count}"
117
136
  status_msg += "\n\nВыполняю их немедленно..."
118
-
137
+
119
138
  await message.answer(status_msg)
120
-
139
+
121
140
  # Выполняем каждое событие
122
141
  success_count = 0
123
142
  failed_count = 0
124
143
  results = []
125
-
144
+
126
145
  for event in all_events:
127
- event_id = event['id']
128
- event_type = event['event_type']
129
- event_category = event['event_category']
130
- is_global = event.get('user_id') is None
131
-
146
+ event_id = event["id"]
147
+ event_type = event["event_type"]
148
+ event_category = event["event_category"]
149
+ is_global = event.get("user_id") is None
150
+
132
151
  try:
133
152
  event_label = f"🌍 {event_type}" if is_global else f"👤 {event_type}"
134
- logger.info(f"🧪 Тестируем событие {event_id}: {event_category}/{event_type} ({'глобальное' if is_global else f'пользователя {message.from_user.id}'})")
135
-
153
+ logger.info(
154
+ f"🧪 Тестируем событие {event_id}: {event_category}/{event_type} ({'глобальное' if is_global else f'пользователя {message.from_user.id}'})"
155
+ )
156
+
136
157
  # Выполняем событие
137
158
  await process_scheduled_event(event)
138
-
159
+
139
160
  # Помечаем как выполненное
140
- await update_event_result(event_id, 'completed', {
141
- "executed": True,
142
- "test_mode": True,
143
- "tested_by_user": message.from_user.id,
144
- "tested_at": datetime.now().isoformat()
145
- })
146
-
161
+ await update_event_result(
162
+ event_id,
163
+ "completed",
164
+ {
165
+ "executed": True,
166
+ "test_mode": True,
167
+ "tested_by_user": message.from_user.id,
168
+ "tested_at": datetime.now().isoformat(),
169
+ },
170
+ )
171
+
147
172
  success_count += 1
148
173
  results.append(f"✅ {event_label}")
149
174
  logger.info(f"✅ Событие {event_id} успешно выполнено")
150
-
175
+
151
176
  except Exception as e:
152
177
  failed_count += 1
153
178
  error_msg = str(e)
154
179
  event_label = f"🌍 {event_type}" if is_global else f"👤 {event_type}"
155
180
  results.append(f"❌ {event_label}: {error_msg[:50]}")
156
181
  logger.error(f"❌ Ошибка выполнения события {event_id}: {error_msg}")
157
-
182
+
158
183
  # Помечаем как failed
159
- await update_event_result(event_id, 'failed', None, error_msg)
160
-
184
+ await update_event_result(event_id, "failed", None, error_msg)
185
+
161
186
  # Отправляем итоговую статистику
162
187
  result_text = [
163
188
  "📊 **Результаты тестирования:**",
@@ -167,45 +192,46 @@ async def timeup_handler(message: Message, state: FSMContext):
167
192
  f"📋 Всего: {total_events}",
168
193
  "",
169
194
  "**События:**",
170
- f"👤 - ваши события",
171
- f"🌍 - глобальные события",
172
- ""
195
+ "👤 - ваши события",
196
+ "🌍 - глобальные события",
197
+ "",
173
198
  ]
174
-
199
+
175
200
  # Добавляем результаты (максимум 10 событий)
176
201
  for result in results[:10]:
177
202
  result_text.append(result)
178
-
203
+
179
204
  if len(results) > 10:
180
205
  result_text.append(f"... и еще {len(results) - 10} событий")
181
-
206
+
182
207
  await message.answer("\n".join(result_text))
183
-
208
+
184
209
  except Exception as e:
185
210
  logger.error(f"❌ Критическая ошибка в timeup_handler: {e}")
186
211
  await message.answer(f"❌ Ошибка тестирования: {str(e)}")
187
-
212
+
213
+
188
214
  @router.message(F.voice | F.audio)
189
215
  async def voice_handler(message: Message, state: FSMContext):
190
216
  """Обработчик голосовых и аудио сообщений"""
191
- openai_client = get_global_var('openai_client')
192
- bot = get_global_var('bot')
193
- admin_manager = get_global_var('admin_manager')
194
-
217
+ openai_client = get_global_var("openai_client")
218
+ bot = get_global_var("bot")
219
+ admin_manager = get_global_var("admin_manager")
220
+
195
221
  import os
196
- from pathlib import Path
197
222
  from datetime import datetime
198
-
223
+ from pathlib import Path
224
+
199
225
  processing_msg = None
200
-
226
+
201
227
  try:
202
228
  # Проверяем что это не админ в режиме администратора
203
229
  if admin_manager.is_admin(message.from_user.id):
204
230
  if admin_manager.is_in_admin_mode(message.from_user.id):
205
231
  return # Админы работают с текстом
206
-
232
+
207
233
  logger.info(f"🎤 Получено голосовое сообщение от {message.from_user.id}")
208
-
234
+
209
235
  # Получаем файл
210
236
  if message.voice:
211
237
  file_id = message.voice.file_id
@@ -213,648 +239,767 @@ async def voice_handler(message: Message, state: FSMContext):
213
239
  else:
214
240
  file_id = message.audio.file_id
215
241
  duration = message.audio.duration
216
-
242
+
217
243
  # Показываем что обрабатываем
218
244
  processing_msg = await message.answer("🎤 Распознаю голос...")
219
-
245
+
220
246
  try:
221
247
  # Скачиваем файл
222
248
  file = await bot.get_file(file_id)
223
-
249
+
224
250
  # Путь для сохранения
225
251
  temp_dir = Path("temp_audio")
226
252
  temp_dir.mkdir(exist_ok=True)
227
-
228
- file_path = temp_dir / f"{message.from_user.id}_{int(datetime.now().timestamp())}.ogg"
229
-
253
+
254
+ file_path = (
255
+ temp_dir
256
+ / f"{message.from_user.id}_{int(datetime.now().timestamp())}.ogg"
257
+ )
258
+
230
259
  # Скачиваем
231
260
  await bot.download_file(file.file_path, file_path)
232
261
  logger.info(f"📥 Файл скачан: {file_path} ({duration} сек)")
233
-
262
+
234
263
  # Распознаем через Whisper
235
264
  recognized_text = await openai_client.transcribe_audio(str(file_path))
236
-
265
+
237
266
  # Удаляем временный файл и папку
238
267
  try:
239
268
  os.remove(file_path)
240
269
  logger.info(f"🗑️ Временный файл удален: {file_path}")
241
-
270
+
242
271
  # Проверяем, пуста ли папка
243
272
  if not any(temp_dir.iterdir()):
244
273
  temp_dir.rmdir()
245
274
  logger.info(f"🗑️ Временная папка удалена: {temp_dir}")
246
-
275
+
247
276
  except Exception as e:
248
277
  logger.warning(f"⚠️ Не удалось удалить временные файлы: {e}")
249
-
278
+
250
279
  if not recognized_text:
251
- await processing_msg.edit_text("❌ Не удалось распознать голос. Попробуйте еще раз.")
280
+ await processing_msg.edit_text(
281
+ "❌ Не удалось распознать голос. Попробуйте еще раз."
282
+ )
252
283
  return
253
-
284
+
254
285
  logger.info(f"✅ Текст распознан успешно: '{recognized_text[:100]}...'")
255
-
286
+
256
287
  # Получаем данные сессии
257
288
  current_state = await state.get_state()
258
289
  data = await state.get_data()
259
-
290
+
260
291
  logger.info(f"🔍 Текущее состояние: {current_state}")
261
292
  logger.info(f"🔍 Данные в state: {data}")
262
-
263
- session_id = data.get('session_id')
264
- system_prompt = data.get('system_prompt')
265
-
293
+
294
+ session_id = data.get("session_id")
295
+ system_prompt = data.get("system_prompt")
296
+
266
297
  logger.info(f"📝 session_id из state: {session_id}")
267
-
298
+
268
299
  # Если session_id нет в state, пытаемся получить из БД
269
300
  if not session_id:
270
- logger.warning(f"⚠️ session_id не найден в state, ищем активную сессию в БД...")
271
- supabase_client = get_global_var('supabase_client')
272
-
273
- session_info = await supabase_client.get_active_session(message.from_user.id)
301
+ logger.warning(
302
+ "⚠️ session_id не найден в state, ищем активную сессию в БД..."
303
+ )
304
+ supabase_client = get_global_var("supabase_client")
305
+
306
+ session_info = await supabase_client.get_active_session(
307
+ message.from_user.id
308
+ )
274
309
  if session_info:
275
- session_id = session_info['id']
276
- system_prompt = session_info['system_prompt']
277
-
310
+ session_id = session_info["id"]
311
+ system_prompt = session_info["system_prompt"]
312
+
278
313
  # Сохраняем в state для следующих сообщений
279
- await state.update_data(session_id=session_id, system_prompt=system_prompt)
314
+ await state.update_data(
315
+ session_id=session_id, system_prompt=system_prompt
316
+ )
280
317
  await state.set_state(UserStates.waiting_for_message)
281
-
318
+
282
319
  logger.info(f"✅ Сессия восстановлена из БД: {session_id}")
283
320
  else:
284
- logger.error(f"❌ Активная сессия не найдена в БД")
285
-
321
+ logger.error("❌ Активная сессия не найдена в БД")
322
+
286
323
  if session_id:
287
324
  # Сохраняем распознанный текст в state
288
325
  await state.update_data(voice_recognized_text=recognized_text)
289
326
  await state.set_state(UserStates.voice_confirmation)
290
-
327
+
291
328
  # Показываем распознанный текст с кнопками выбора
292
- from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
293
-
294
- keyboard = InlineKeyboardMarkup(inline_keyboard=[
295
- [InlineKeyboardButton(text="✅ Отправить", callback_data="voice_send")],
296
- [InlineKeyboardButton(text="✏️ Изменить текст", callback_data="voice_edit")],
297
- [InlineKeyboardButton(text="🎤 Надиктовать заново", callback_data="voice_retry")]
298
- ])
299
-
329
+ from aiogram.types import (InlineKeyboardButton,
330
+ InlineKeyboardMarkup)
331
+
332
+ keyboard = InlineKeyboardMarkup(
333
+ inline_keyboard=[
334
+ [
335
+ InlineKeyboardButton(
336
+ text="✅ Отправить", callback_data="voice_send"
337
+ )
338
+ ],
339
+ [
340
+ InlineKeyboardButton(
341
+ text="✏️ Изменить текст", callback_data="voice_edit"
342
+ )
343
+ ],
344
+ [
345
+ InlineKeyboardButton(
346
+ text="🎤 Надиктовать заново",
347
+ callback_data="voice_retry",
348
+ )
349
+ ],
350
+ ]
351
+ )
352
+
300
353
  # Удаляем сообщение "Обрабатываю"
301
354
  try:
302
355
  await processing_msg.delete()
303
- except:
356
+ except Exception:
304
357
  pass
305
-
358
+
306
359
  # Показываем результат с кнопками
307
360
  await message.answer(
308
361
  f"✅ Распознано:\n\n<i>{recognized_text}</i>\n\n"
309
362
  f"Выберите действие:",
310
363
  reply_markup=keyboard,
311
- parse_mode='HTML'
364
+ parse_mode="HTML",
312
365
  )
313
-
314
- logger.info(f"✅ Показаны кнопки подтверждения голосового сообщения")
366
+
367
+ logger.info("✅ Показаны кнопки подтверждения голосового сообщения")
315
368
  else:
316
- logger.warning(f"❌ Нет session_id в состоянии")
369
+ logger.warning("❌ Нет session_id в состоянии")
317
370
  await processing_msg.edit_text(
318
371
  f"✅ Распознано:\n\n{recognized_text}\n\n"
319
372
  f"Сессия не найдена. Напишите /start"
320
373
  )
321
-
374
+
322
375
  except Exception as e:
323
376
  logger.error(f"❌ Ошибка в процессе обработки голоса: {e}")
324
377
  logger.exception("Полный стек ошибки:")
325
378
  if processing_msg:
326
- await processing_msg.edit_text("❌ Ошибка обработки. Попробуйте написать текстом.")
379
+ await processing_msg.edit_text(
380
+ "❌ Ошибка обработки. Попробуйте написать текстом."
381
+ )
327
382
  else:
328
- await message.answer("❌ Ошибка обработки. Попробуйте написать текстом.")
329
-
383
+ await message.answer(
384
+ "❌ Ошибка обработки. Попробуйте написать текстом."
385
+ )
386
+
330
387
  except Exception as e:
331
388
  logger.error(f"❌ КРИТИЧЕСКАЯ ошибка обработки голоса: {e}")
332
389
  logger.exception("Полный стек критической ошибки:")
333
390
  try:
334
391
  if processing_msg:
335
- await processing_msg.edit_text("❌ Ошибка распознавания. Попробуйте написать текстом.")
392
+ await processing_msg.edit_text(
393
+ "❌ Ошибка распознавания. Попробуйте написать текстом."
394
+ )
336
395
  else:
337
- await message.answer("❌ Ошибка распознавания. Попробуйте написать текстом.")
338
- except:
396
+ await message.answer(
397
+ "❌ Ошибка распознавания. Попробуйте написать текстом."
398
+ )
399
+ except Exception:
339
400
  pass
340
401
 
402
+
341
403
  async def user_start_handler(message: Message, state: FSMContext):
342
404
  """Обработчик /start для обычных пользователей"""
343
- supabase_client = get_global_var('supabase_client')
344
- prompt_loader = get_global_var('prompt_loader')
405
+ supabase_client = get_global_var("supabase_client")
406
+ prompt_loader = get_global_var("prompt_loader")
345
407
  from ..core.bot_utils import parse_utm_from_start_param
346
-
408
+
347
409
  try:
348
410
  # 0. ПОЛУЧАЕМ UTM ДАННЫЕ
349
- start_param = message.text.split(' ', 1)[1] if len(message.text.split()) > 1 else None
350
-
411
+ start_param = (
412
+ message.text.split(" ", 1)[1] if len(message.text.split()) > 1 else None
413
+ )
414
+
351
415
  # Логируем входящий start параметр
352
416
  # Пример поддерживаемого формата: @https://t.me/bot?start=utmSource-vk_utmCampaign-summer2025 не более 64 символов после strat=
353
-
417
+
354
418
  logger.info(f"📥 Получен start параметр: '{start_param}'")
355
-
419
+
356
420
  utm_data = {}
357
421
  if start_param:
358
422
  # Парсим UTM данные
359
423
  utm_data = parse_utm_from_start_param(start_param)
360
-
424
+
361
425
  # Подробное логирование UTM
362
426
  logger.info(f"📊 UTM данные для пользователя {message.from_user.id}:")
363
427
  if utm_data:
364
428
  for key, value in utm_data.items():
365
429
  logger.info(f" • {key}: {value}")
366
- logger.info(f"✅ UTM данные успешно распознаны")
430
+ logger.info("✅ UTM данные успешно распознаны")
367
431
  else:
368
432
  logger.warning(f"⚠️ UTM данные не найдены в параметре: '{start_param}'")
369
433
  else:
370
434
  logger.info("ℹ️ Start параметр отсутствует (обычный /start)")
371
-
435
+
372
436
  # 1. ЯВНО ОЧИЩАЕМ СОСТОЯНИЕ FSM
373
437
  await state.clear()
374
438
  logger.info(f"🔄 Состояние FSM очищено для пользователя {message.from_user.id}")
375
-
439
+
376
440
  # 2. ЗАГРУЖАЕМ ПРОМПТЫ
377
441
  logger.info(f"Загрузка промптов для пользователя {message.from_user.id}")
378
442
  system_prompt = await prompt_loader.load_system_prompt()
379
-
443
+
380
444
  # Загружаем приветственное сообщение
381
445
  welcome_message = await prompt_loader.load_welcome_message()
382
-
446
+
383
447
  # 3. ПОЛУЧАЕМ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ
384
448
  user_data = {
385
- 'telegram_id': message.from_user.id,
386
- 'username': message.from_user.username,
387
- 'first_name': message.from_user.first_name,
388
- 'last_name': message.from_user.last_name,
389
- 'language_code': message.from_user.language_code,
390
- 'source': utm_data.get('utm_source'),
391
- 'medium': utm_data.get('utm_medium'),
392
- 'campaign': utm_data.get('utm_campaign'),
393
- 'content': utm_data.get('utm_content'),
394
- 'term': utm_data.get('utm_term'),
395
- 'segment': utm_data.get('segment')
449
+ "telegram_id": message.from_user.id,
450
+ "username": message.from_user.username,
451
+ "first_name": message.from_user.first_name,
452
+ "last_name": message.from_user.last_name,
453
+ "language_code": message.from_user.language_code,
454
+ "source": utm_data.get("utm_source"),
455
+ "medium": utm_data.get("utm_medium"),
456
+ "campaign": utm_data.get("utm_campaign"),
457
+ "content": utm_data.get("utm_content"),
458
+ "term": utm_data.get("utm_term"),
459
+ "segment": utm_data.get("segment"),
396
460
  }
397
-
461
+
398
462
  # 4. СОЗДАЕМ НОВУЮ СЕССИЮ (автоматически закроет активные)
399
463
  # Добавляем UTM данные в метаданные пользователя
400
464
  if utm_data:
401
- user_data['metadata'] = {'utm_data': utm_data}
402
- logger.info(f"📈 UTM данные добавлены в метаданные пользователя")
403
-
465
+ user_data["metadata"] = {"utm_data": utm_data}
466
+ logger.info("📈 UTM данные добавлены в метаданные пользователя")
467
+
404
468
  session_id = await supabase_client.create_chat_session(user_data, system_prompt)
405
- logger.info(f"✅ Создана новая сессия {session_id} для пользователя {message.from_user.id}")
406
-
469
+ logger.info(
470
+ f"✅ Создана новая сессия {session_id} для пользователя {message.from_user.id}"
471
+ )
472
+
407
473
  # 5. УСТАНАВЛИВАЕМ НОВОЕ СОСТОЯНИЕ
408
474
  await state.update_data(session_id=session_id, system_prompt=system_prompt)
409
475
  await state.set_state(UserStates.waiting_for_message)
410
-
476
+
411
477
  # 6. ОТПРАВЛЯЕМ ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ
412
478
  try:
413
479
  await send_message(message, welcome_message)
414
- logger.info(f"Приветственное сообщение отправлено пользователю {message.from_user.id}")
480
+ logger.info(
481
+ f"Приветственное сообщение отправлено пользователю {message.from_user.id}"
482
+ )
415
483
  except Exception as e:
416
484
  if "Forbidden: bot was blocked by the user" in str(e):
417
- logger.warning(f"🚫 Бот заблокирован пользователем {message.from_user.id}")
485
+ logger.warning(
486
+ f"🚫 Бот заблокирован пользователем {message.from_user.id}"
487
+ )
418
488
  return
419
489
  else:
420
490
  logger.error(f"❌ Ошибка отправки приветственного сообщения: {e}")
421
491
  raise
422
-
492
+
423
493
  # 7. ЕСЛИ ЕСТЬ ФАЙЛ ОТПРАВЛЯЕМ ВМЕСТЕ С ПОДПИСЬЮ
424
- logging.info(f"📎 Попытка отправки приветственного файла для сессии {session_id}")
494
+ logging.info(
495
+ f"📎 Попытка отправки приветственного файла для сессии {session_id}"
496
+ )
425
497
  caption = await send_welcome_file(message)
426
-
498
+
427
499
  # 8. СОХРАНЯЕМ ПРИВЕТСТВЕННОЕ СООБЩЕНИЕ В БД
428
500
  if caption:
429
- logging.info(f"📄 Добавление подписи к файлу в приветственное сообщение для сессии {session_id}")
501
+ logging.info(
502
+ f"📄 Добавление подписи к файлу в приветственное сообщение для сессии {session_id}"
503
+ )
430
504
  welcome_message = f"{welcome_message}\n\nПодпись к файлу:\n\n{caption}"
431
505
  else:
432
- logging.info(f"📄 Приветственный файл отправлен без подписи для сессии {session_id}")
433
-
434
- logging.info(f"💾 Сохранение приветственного сообщения в БД для сессии {session_id}")
435
-
506
+ logging.info(
507
+ f"📄 Приветственный файл отправлен без подписи для сессии {session_id}"
508
+ )
509
+
510
+ logging.info(
511
+ f"💾 Сохранение приветственного сообщения в БД для сессии {session_id}"
512
+ )
513
+
436
514
  await supabase_client.add_message(
437
515
  session_id=session_id,
438
- role='assistant',
516
+ role="assistant",
439
517
  content=welcome_message,
440
- message_type='text'
518
+ message_type="text",
519
+ )
520
+
521
+ logging.info(
522
+ f"✅ Приветственное сообщение успешно сохранено в БД для сессии {session_id}"
441
523
  )
442
-
443
- logging.info(f"✅ Приветственное сообщение успешно сохранено в БД для сессии {session_id}")
444
-
524
+
445
525
  # ВЫЗЫВАЕМ ПОЛЬЗОВАТЕЛЬСКИЕ ОБРАБОТЧИКИ on_start
446
- start_handlers = get_global_var('start_handlers')
526
+ start_handlers = get_global_var("start_handlers")
447
527
  if start_handlers:
448
- logger.info(f"🔔 Вызов {len(start_handlers)} пользовательских обработчиков on_start")
528
+ logger.info(
529
+ f"🔔 Вызов {len(start_handlers)} пользовательских обработчиков on_start"
530
+ )
449
531
  for handler in start_handlers:
450
532
  try:
451
533
  await handler(
452
534
  user_id=message.from_user.id,
453
535
  session_id=session_id,
454
536
  message=message,
455
- state=state
537
+ state=state,
538
+ )
539
+ logger.info(
540
+ f"✅ Обработчик on_start '{handler.__name__}' выполнен успешно"
456
541
  )
457
- logger.info(f"✅ Обработчик on_start '{handler.__name__}' выполнен успешно")
458
542
  except Exception as handler_error:
459
- logger.error(f"❌ Ошибка в обработчике on_start '{handler.__name__}': {handler_error}")
543
+ logger.error(
544
+ f"❌ Ошибка в обработчике on_start '{handler.__name__}': {handler_error}"
545
+ )
460
546
  # Продолжаем выполнение остальных обработчиков
461
-
547
+
462
548
  except Exception as e:
463
549
  logger.error(f"Ошибка при обработке user /start: {e}")
464
- await send_message(message, "Произошла ошибка при инициализации. Попробуйте позже.")
550
+ await send_message(
551
+ message, "Произошла ошибка при инициализации. Попробуйте позже."
552
+ )
553
+
465
554
 
466
555
  @router.message(StateFilter(None))
467
556
  async def message_without_state_handler(message: Message, state: FSMContext):
468
557
  """Обработчик сообщений без состояния (после перезапуска бота)"""
469
- admin_manager = get_global_var('admin_manager')
470
- supabase_client = get_global_var('supabase_client')
471
- conversation_manager = get_global_var('conversation_manager')
472
- from ..admin.admin_logic import AdminStates
558
+ admin_manager = get_global_var("admin_manager")
559
+ supabase_client = get_global_var("supabase_client")
560
+ conversation_manager = get_global_var("conversation_manager")
561
+ from ..admin.admin_logic import AdminStates as AdminLogicStates
473
562
  from ..utils.debug_routing import debug_user_state
474
-
563
+
475
564
  try:
476
565
  await debug_user_state(message, state, "NO_STATE")
477
-
566
+
478
567
  # СНАЧАЛА проверяем диалог с админом
479
- conversation = await conversation_manager.is_user_in_admin_chat(message.from_user.id)
480
-
568
+ conversation = await conversation_manager.is_user_in_admin_chat(
569
+ message.from_user.id
570
+ )
571
+
481
572
  if conversation:
482
- logger.info(f"✅ Найден диалог с админом {conversation['admin_id']}, устанавливаем состояние admin_chat")
483
-
573
+ logger.info(
574
+ f"✅ Найден диалог с админом {conversation['admin_id']}, устанавливаем состояние admin_chat"
575
+ )
576
+
484
577
  # Устанавливаем состояние admin_chat
485
578
  await state.set_state(UserStates.admin_chat)
486
579
  await state.update_data(admin_conversation=conversation)
487
-
580
+
488
581
  # Сразу пересылаем сообщение админу
489
582
  await conversation_manager.forward_message_to_admin(message, conversation)
490
-
583
+
491
584
  # Сохраняем сообщение в БД
492
- session_info = await supabase_client.get_active_session(message.from_user.id)
585
+ session_info = await supabase_client.get_active_session(
586
+ message.from_user.id
587
+ )
493
588
  if session_info:
494
589
  await supabase_client.add_message(
495
- session_id=session_info['id'],
496
- role='user',
590
+ session_id=session_info["id"],
591
+ role="user",
497
592
  content=message.text,
498
- message_type='text',
499
- metadata={'in_admin_chat': True, 'admin_id': conversation['admin_id']}
593
+ message_type="text",
594
+ metadata={
595
+ "in_admin_chat": True,
596
+ "admin_id": conversation["admin_id"],
597
+ },
500
598
  )
501
-
599
+
502
600
  return
503
-
601
+
504
602
  # Проверяем, админ ли это
505
603
  if admin_manager.is_admin(message.from_user.id):
506
- logger.info(f"👑 Админ в режиме администратора без состояния")
507
- await state.set_state(AdminStates.admin_mode)
508
- await message.answer("👑 Режим администратора\nИспользуйте /start для панели управления")
604
+ logger.info("👑 Админ в режиме администратора без состояния")
605
+ await state.set_state(AdminLogicStates.admin_mode)
606
+ await message.answer(
607
+ "👑 Режим администратора\nИспользуйте /start для панели управления"
608
+ )
509
609
  return
510
-
511
- logger.info(f"👤 Обычный пользователь без состояния, ищем активную сессию")
512
-
610
+
611
+ logger.info("👤 Обычный пользователь без состояния, ищем активную сессию")
612
+
513
613
  # Ищем активную сессию в БД
514
614
  session_info = await supabase_client.get_active_session(message.from_user.id)
515
-
615
+
516
616
  if session_info:
517
617
  logger.info(f"📝 Восстанавливаем сессию {session_info['id']}")
518
618
  # Восстанавливаем сессию из БД
519
- session_id = session_info['id']
520
- system_prompt = session_info['system_prompt']
521
-
619
+ session_id = session_info["id"]
620
+ system_prompt = session_info["system_prompt"]
621
+
522
622
  # Сохраняем в состояние
523
623
  await state.update_data(session_id=session_id, system_prompt=system_prompt)
524
624
  await state.set_state(UserStates.waiting_for_message)
525
-
526
- logger.info(f"✅ Сессия восстановлена, обрабатываем сообщение")
527
-
625
+
626
+ logger.info("✅ Сессия восстановлена, обрабатываем сообщение")
627
+
528
628
  # Теперь обрабатываем сообщение как обычно
529
629
  await process_user_message(message, state, session_id, system_prompt)
530
630
  else:
531
- logger.info(f"❌ Нет активной сессии, просим написать /start")
631
+ logger.info("❌ Нет активной сессии, просим написать /start")
532
632
  await send_message(message, "Привет! Напишите /start для начала диалога.")
533
-
633
+
534
634
  except Exception as e:
535
635
  logger.error(f"❌ Ошибка при обработке сообщения без состояния: {e}")
536
- await send_message(message, "Произошла ошибка. Попробуйте написать /start для начала диалога.")
636
+ await send_message(
637
+ message, "Произошла ошибка. Попробуйте написать /start для начала диалога."
638
+ )
639
+
537
640
 
538
641
  # ✅ ИСПРАВЛЕНИЕ: Обработчик admin_chat должен быть ПЕРВЫМ и более приоритетным
539
642
  @router.message(StateFilter(UserStates.admin_chat))
540
643
  async def user_in_admin_chat_handler(message: Message, state: FSMContext):
541
644
  """ПРИОРИТЕТНЫЙ обработчик сообщений пользователей в диалоге с админом"""
542
- conversation_manager = get_global_var('conversation_manager')
543
- supabase_client = get_global_var('supabase_client')
645
+ conversation_manager = get_global_var("conversation_manager")
646
+ supabase_client = get_global_var("supabase_client")
544
647
  from ..utils.debug_routing import debug_user_state
545
-
648
+
546
649
  await debug_user_state(message, state, "ADMIN_CHAT_HANDLER")
547
-
650
+
548
651
  user_id = message.from_user.id
549
652
  logger.info(f"🎯 ADMIN_CHAT HANDLER: сообщение от {user_id}: '{message.text}'")
550
-
653
+
551
654
  # Проверяем, есть ли еще активный диалог
552
655
  conversation = await conversation_manager.is_user_in_admin_chat(user_id)
553
-
656
+
554
657
  if conversation:
555
658
  logger.info(f"✅ Диалог активен, пересылаем админу {conversation['admin_id']}")
556
-
659
+
557
660
  try:
558
661
  # Сохраняем сообщение в БД
559
662
  session_info = await supabase_client.get_active_session(user_id)
560
663
  if session_info:
561
664
  await supabase_client.add_message(
562
- session_id=session_info['id'],
563
- role='user',
665
+ session_id=session_info["id"],
666
+ role="user",
564
667
  content=message.text,
565
- message_type='text',
566
- metadata={'in_admin_chat': True, 'admin_id': conversation['admin_id']}
668
+ message_type="text",
669
+ metadata={
670
+ "in_admin_chat": True,
671
+ "admin_id": conversation["admin_id"],
672
+ },
567
673
  )
568
- logger.info(f"💾 Сообщение сохранено в БД")
569
-
674
+ logger.info("💾 Сообщение сохранено в БД")
675
+
570
676
  # Пересылаем админу
571
677
  await conversation_manager.forward_message_to_admin(message, conversation)
572
- logger.info(f"📤 Сообщение переслано админу")
573
-
678
+ logger.info("📤 Сообщение переслано админу")
679
+
574
680
  except Exception as e:
575
681
  logger.error(f"❌ Ошибка обработки admin_chat: {e}")
576
682
  await message.answer("Произошла ошибка. Попробуйте позже.")
577
683
  else:
578
- logger.info(f"💬 Диалог завершен, возвращаем к обычному режиму")
684
+ logger.info("💬 Диалог завершен, возвращаем к обычному режиму")
579
685
  # Диалог завершен, возвращаем к обычному режиму
580
686
  await state.set_state(UserStates.waiting_for_message)
581
-
687
+
582
688
  # Обрабатываем как обычное сообщение
583
689
  data = await state.get_data()
584
- session_id = data.get('session_id')
585
- system_prompt = data.get('system_prompt')
586
-
690
+ session_id = data.get("session_id")
691
+ system_prompt = data.get("system_prompt")
692
+
587
693
  if session_id:
588
694
  await process_user_message(message, state, session_id, system_prompt)
589
695
  else:
590
- await send_message(message, "Сессия не найдена. Пожалуйста, напишите /start")
696
+ await send_message(
697
+ message, "Сессия не найдена. Пожалуйста, напишите /start"
698
+ )
699
+
591
700
 
592
701
  # Обработчик для обычных сообщений (НЕ в admin_chat)
593
- @router.message(StateFilter(UserStates.waiting_for_message), ~F.text.startswith('/'))
702
+ @router.message(StateFilter(UserStates.waiting_for_message), ~F.text.startswith("/"))
594
703
  async def user_message_handler(message: Message, state: FSMContext):
595
704
  """Обработчик сообщений пользователей (исключая admin_chat)"""
596
- conversation_manager = get_global_var('conversation_manager')
705
+ conversation_manager = get_global_var("conversation_manager")
597
706
  from ..utils.debug_routing import debug_user_state
598
-
707
+
599
708
  try:
600
709
  await debug_user_state(message, state, "USER_MESSAGE_HANDLER")
601
-
710
+
602
711
  # ✅ ВАЖНО: Сначала проверяем диалог с админом
603
- conversation = await conversation_manager.is_user_in_admin_chat(message.from_user.id)
604
-
712
+ conversation = await conversation_manager.is_user_in_admin_chat(
713
+ message.from_user.id
714
+ )
715
+
605
716
  if conversation:
606
- logger.info(f"⚠️ НЕОЖИДАННО: пользователь в waiting_for_message, но есть диалог с админом!")
607
- logger.info(f"🔄 Принудительно переключаем в admin_chat состояние")
608
-
717
+ logger.info(
718
+ "⚠️ НЕОЖИДАННО: пользователь в waiting_for_message, но есть диалог с админом!"
719
+ )
720
+ logger.info("🔄 Принудительно переключаем в admin_chat состояние")
721
+
609
722
  # Принудительно переключаем состояние
610
723
  await state.set_state(UserStates.admin_chat)
611
724
  await state.update_data(admin_conversation=conversation)
612
-
725
+
613
726
  # Обрабатываем сообщение как admin_chat
614
727
  await user_in_admin_chat_handler(message, state)
615
728
  return
616
-
617
- logger.info(f"🤖 Обычный диалог с ботом")
729
+
730
+ logger.info("🤖 Обычный диалог с ботом")
618
731
  data = await state.get_data()
619
- session_id = data.get('session_id')
620
- system_prompt = data.get('system_prompt')
621
-
732
+ session_id = data.get("session_id")
733
+ system_prompt = data.get("system_prompt")
734
+
622
735
  if not session_id:
623
- logger.warning(f"❌ Нет session_id в состоянии")
624
- await send_message(message, "Сессия не найдена. Пожалуйста, напишите /start")
736
+ logger.warning("❌ Нет session_id в состоянии")
737
+ await send_message(
738
+ message, "Сессия не найдена. Пожалуйста, напишите /start"
739
+ )
625
740
  return
626
-
741
+
627
742
  logger.info(f"📝 Обрабатываем сообщение с session_id: {session_id}")
628
743
  await process_user_message(message, state, session_id, system_prompt)
629
-
744
+
630
745
  except Exception as e:
631
746
  logger.error(f"❌ Ошибка при обработке сообщения пользователя: {e}")
632
- await send_message(message, "Произошла ошибка. Попробуйте еще раз или напишите /start для перезапуска.")
633
-
747
+ await send_message(
748
+ message,
749
+ "Произошла ошибка. Попробуйте еще раз или напишите /start для перезапуска.",
750
+ )
751
+
752
+
634
753
  @router.callback_query(F.data == "voice_send")
635
754
  async def voice_send_handler(callback: CallbackQuery, state: FSMContext):
636
755
  """Обработчик кнопки 'Отправить' для голосового сообщения"""
637
756
  try:
638
757
  data = await state.get_data()
639
- recognized_text = data.get('voice_recognized_text')
640
- session_id = data.get('session_id')
641
- system_prompt = data.get('system_prompt')
642
-
758
+ recognized_text = data.get("voice_recognized_text")
759
+ session_id = data.get("session_id")
760
+ system_prompt = data.get("system_prompt")
761
+
643
762
  if not recognized_text or not session_id:
644
763
  await callback.answer("❌ Ошибка: текст не найден", show_alert=True)
645
764
  return
646
-
765
+
647
766
  # Удаляем сообщение с кнопками
648
767
  await callback.message.delete()
649
-
768
+
650
769
  # Обрабатываем текст сразу без промежуточного сообщения
651
- await process_voice_message(callback.message, state, session_id, system_prompt, recognized_text)
652
-
770
+ await process_voice_message(
771
+ callback.message, state, session_id, system_prompt, recognized_text
772
+ )
773
+
653
774
  # Возвращаем в обычное состояние
654
775
  await state.set_state(UserStates.waiting_for_message)
655
776
  await state.update_data(voice_recognized_text=None)
656
-
777
+
657
778
  await callback.answer()
658
-
779
+
659
780
  except Exception as e:
660
781
  logger.error(f"❌ Ошибка отправки голосового: {e}")
661
782
  await callback.answer("❌ Ошибка обработки", show_alert=True)
662
783
 
784
+
663
785
  @router.callback_query(F.data == "voice_edit")
664
786
  async def voice_edit_handler(callback: CallbackQuery, state: FSMContext):
665
787
  """Обработчик кнопки 'Изменить текст' для голосового сообщения"""
666
788
  try:
667
789
  data = await state.get_data()
668
- recognized_text = data.get('voice_recognized_text')
669
-
790
+ recognized_text = data.get("voice_recognized_text")
791
+
670
792
  if not recognized_text:
671
793
  await callback.answer("❌ Ошибка: текст не найден", show_alert=True)
672
794
  return
673
-
795
+
674
796
  # Переводим в режим редактирования
675
797
  await state.set_state(UserStates.voice_editing)
676
-
798
+
677
799
  # Показываем текст для редактирования (обычный формат)
678
800
  await callback.message.edit_text(
679
801
  f"✏️ Отредактируйте распознанный текст:\n\n"
680
802
  f"{recognized_text}\n\n"
681
803
  f"Напишите исправленный текст:"
682
804
  )
683
-
805
+
684
806
  await callback.answer()
685
-
807
+
686
808
  except Exception as e:
687
809
  logger.error(f"❌ Ошибка редактирования: {e}")
688
810
  await callback.answer("❌ Ошибка", show_alert=True)
689
811
 
812
+
690
813
  @router.callback_query(F.data == "voice_retry")
691
814
  async def voice_retry_handler(callback: CallbackQuery, state: FSMContext):
692
815
  """Обработчик кнопки 'Надиктовать заново' для голосового сообщения"""
693
816
  try:
694
817
  # Удаляем сообщение с кнопками
695
818
  await callback.message.delete()
696
-
819
+
697
820
  # Возвращаем в обычное состояние
698
821
  await state.set_state(UserStates.waiting_for_message)
699
822
  await state.update_data(voice_recognized_text=None)
700
-
823
+
701
824
  # Просим отправить заново
702
825
  await callback.message.answer("🎤 Отправьте голосовое сообщение заново")
703
-
826
+
704
827
  await callback.answer()
705
-
828
+
706
829
  except Exception as e:
707
830
  logger.error(f"❌ Ошибка повтора: {e}")
708
831
  await callback.answer("❌ Ошибка", show_alert=True)
709
832
 
833
+
710
834
  @router.message(StateFilter(UserStates.voice_editing))
711
835
  async def voice_edit_text_handler(message: Message, state: FSMContext):
712
836
  """Обработчик получения отредактированного текста"""
713
837
  try:
714
838
  edited_text = message.text.strip()
715
-
839
+
716
840
  if not edited_text:
717
841
  await message.answer("⚠️ Текст не может быть пустым. Напишите текст:")
718
842
  return
719
-
843
+
720
844
  # Получаем данные сессии
721
845
  data = await state.get_data()
722
- session_id = data.get('session_id')
723
- system_prompt = data.get('system_prompt')
724
-
846
+ session_id = data.get("session_id")
847
+ system_prompt = data.get("system_prompt")
848
+
725
849
  if not session_id:
726
850
  await message.answer("❌ Сессия не найдена. Напишите /start")
727
851
  return
728
-
852
+
729
853
  # Обрабатываем отредактированный текст сразу
730
- await process_voice_message(message, state, session_id, system_prompt, edited_text)
731
-
854
+ await process_voice_message(
855
+ message, state, session_id, system_prompt, edited_text
856
+ )
857
+
732
858
  # Возвращаем в обычное состояние
733
859
  await state.set_state(UserStates.waiting_for_message)
734
860
  await state.update_data(voice_recognized_text=None)
735
-
861
+
736
862
  except Exception as e:
737
863
  logger.error(f"❌ Ошибка обработки отредактированного текста: {e}")
738
- await message.answer("❌ Ошибка обработки. Попробуйте еще раз или напишите /start")
864
+ await message.answer(
865
+ "❌ Ошибка обработки. Попробуйте еще раз или напишите /start"
866
+ )
867
+
739
868
 
740
869
  @router.message()
741
870
  async def catch_all_handler(message: Message, state: FSMContext):
742
871
  """Перехватчик всех необработанных сообщений"""
743
- admin_manager = get_global_var('admin_manager')
872
+ admin_manager = get_global_var("admin_manager")
744
873
  from ..utils.debug_routing import debug_user_state
745
-
874
+
746
875
  await debug_user_state(message, state, "CATCH_ALL")
747
-
876
+
748
877
  current_state = await state.get_state()
749
- logger.warning(f"⚠️ НЕОБРАБОТАННОЕ СООБЩЕНИЕ от {message.from_user.id}: '{message.text}', состояние: {current_state}")
750
-
878
+ logger.warning(
879
+ f"⚠️ НЕОБРАБОТАННОЕ СООБЩЕНИЕ от {message.from_user.id}: '{message.text}', состояние: {current_state}"
880
+ )
881
+
751
882
  # Проверяем, админ ли это
752
883
  if admin_manager.is_admin(message.from_user.id):
753
- logger.info(f"👑 Необработанное сообщение админа")
884
+ logger.info("👑 Необработанное сообщение админа")
754
885
  await message.answer("Команда не распознана. Используйте /help для справки.")
755
886
  else:
756
- logger.info(f"👤 Необработанное сообщение пользователя")
887
+ logger.info("👤 Необработанное сообщение пользователя")
757
888
  await message.answer("Не понимаю. Напишите /start для начала диалога.")
758
889
 
759
- async def process_user_message(message: Message, state: FSMContext, session_id: str, system_prompt: str):
890
+
891
+ async def process_user_message(
892
+ message: Message, state: FSMContext, session_id: str, system_prompt: str
893
+ ):
760
894
  """Общая функция для обработки сообщений пользователя"""
761
- supabase_client = get_global_var('supabase_client')
762
- openai_client = get_global_var('openai_client')
763
- config = get_global_var('config')
764
- bot = get_global_var('bot')
765
- prompt_loader = get_global_var('prompt_loader')
766
- message_hooks = get_global_var('message_hooks') or {}
895
+ supabase_client = get_global_var("supabase_client")
896
+ openai_client = get_global_var("openai_client")
897
+ config = get_global_var("config")
898
+ bot = get_global_var("bot")
899
+ prompt_loader = get_global_var("prompt_loader")
900
+ message_hooks = get_global_var("message_hooks") or {}
767
901
  from datetime import datetime
902
+
768
903
  import pytz # Добавляем импорт для работы с временными зонами
769
-
904
+
770
905
  try:
771
906
  # ============ ХУК 1: ВАЛИДАЦИЯ СООБЩЕНИЯ ============
772
- validators = message_hooks.get('validators', [])
907
+ validators = message_hooks.get("validators", [])
773
908
  for validator in validators:
774
909
  try:
775
910
  user_message = message.text
776
911
  message_obj = message
777
-
912
+
778
913
  should_continue = await validator(user_message, message_obj)
779
914
  if not should_continue:
780
- logger.info(f"⛔ Валидатор '{validator.__name__}' прервал обработку")
915
+ logger.info(
916
+ f"⛔ Валидатор '{validator.__name__}' прервал обработку"
917
+ )
781
918
  return # Прерываем обработку
782
919
  except Exception as e:
783
920
  logger.error(f"❌ Ошибка в валидаторе '{validator.__name__}': {e}")
784
-
921
+
785
922
  # Сохраняем сообщение пользователя
786
923
  await supabase_client.add_message(
787
924
  session_id=session_id,
788
- role='user',
925
+ role="user",
789
926
  content=message.text,
790
- message_type='text'
927
+ message_type="text",
791
928
  )
792
- logger.info(f"✅ Сообщение пользователя сохранено в БД")
793
-
929
+ logger.info("✅ Сообщение пользователя сохранено в БД")
930
+
794
931
  # Получаем историю сообщений
795
- chat_history = await supabase_client.get_chat_history(session_id, limit=config.MAX_CONTEXT_MESSAGES)
932
+ chat_history = await supabase_client.get_chat_history(
933
+ session_id, limit=config.MAX_CONTEXT_MESSAGES
934
+ )
796
935
  logger.info(f"📚 Загружена история: {len(chat_history)} сообщений")
797
-
936
+
798
937
  # ДОБАВЛЯЕМ ПОЛУЧЕНИЕ ТЕКУЩЕГО ВРЕМЕНИ
799
- moscow_tz = pytz.timezone('Europe/Moscow')
938
+ moscow_tz = pytz.timezone("Europe/Moscow")
800
939
  current_time = datetime.now(moscow_tz)
801
- time_info = current_time.strftime('%H:%M, %d.%m.%Y, %A')
802
-
940
+ time_info = current_time.strftime("%H:%M, %d.%m.%Y, %A")
941
+
803
942
  # Базовый системный промпт с временем
804
943
  system_prompt_with_time = f"""
805
944
  {system_prompt}
806
945
 
807
946
  ТЕКУЩЕЕ ВРЕМЯ: {time_info} (московское время)
808
947
  """
809
-
948
+
810
949
  # ============ ХУК 2: ОБОГАЩЕНИЕ ПРОМПТА ============
811
- prompt_enrichers = message_hooks.get('prompt_enrichers', [])
950
+ prompt_enrichers = message_hooks.get("prompt_enrichers", [])
812
951
  for enricher in prompt_enrichers:
813
952
  try:
814
953
  system_prompt_with_time = await enricher(
815
- system_prompt_with_time,
816
- message.from_user.id
954
+ system_prompt_with_time, message.from_user.id
817
955
  )
818
956
  logger.info(f"✅ Промпт обогащен '{enricher.__name__}'")
819
957
  except Exception as e:
820
- logger.error(f"❌ Ошибка в обогатителе промпта '{enricher.__name__}': {e}")
821
-
958
+ logger.error(
959
+ f"❌ Ошибка в обогатителе промпта '{enricher.__name__}': {e}"
960
+ )
961
+
822
962
  # Формируем контекст для OpenAI с обновленным системным промптом
823
963
  messages = [{"role": "system", "content": system_prompt_with_time}]
824
-
825
- for msg in chat_history[-config.MAX_CONTEXT_MESSAGES:]: # Ограничиваем контекст
826
- messages.append({
827
- "role": msg['role'],
828
- "content": msg['content']
829
- })
830
-
964
+
965
+ for msg in chat_history[
966
+ -config.MAX_CONTEXT_MESSAGES :
967
+ ]: # Ограничиваем контекст
968
+ messages.append({"role": msg["role"], "content": msg["content"]})
969
+
831
970
  # Добавляем финальные инструкции в конец контекста
832
971
  final_instructions = await prompt_loader.load_final_instructions()
833
972
  if final_instructions:
834
973
  messages.append({"role": "system", "content": final_instructions})
835
- logger.info(f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)")
836
-
974
+ logger.info(
975
+ f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)"
976
+ )
977
+
837
978
  # ============ ХУК 3: ОБОГАЩЕНИЕ КОНТЕКСТА ============
838
- context_enrichers = message_hooks.get('context_enrichers', [])
979
+ context_enrichers = message_hooks.get("context_enrichers", [])
839
980
  for enricher in context_enrichers:
840
981
  try:
841
- messages = await enricher(
842
- messages
843
- )
982
+ messages = await enricher(messages)
844
983
  logger.info(f"✅ Контекст обогащен '{enricher.__name__}'")
845
984
  except Exception as e:
846
- logger.error(f"❌ Ошибка в обогатителе контекста '{enricher.__name__}': {e}")
847
-
848
- logger.info(f"📝 Контекст сформирован: {len(messages)} сообщений (включая время: {time_info})")
849
-
985
+ logger.error(
986
+ f"❌ Ошибка в обогатителе контекста '{enricher.__name__}': {e}"
987
+ )
988
+
989
+ logger.info(
990
+ f"📝 Контекст сформирован: {len(messages)} сообщений (включая время: {time_info})"
991
+ )
992
+
850
993
  await bot.send_chat_action(message.chat.id, "typing")
851
-
994
+
852
995
  start_time = time.time()
853
996
  ai_response = await openai_client.get_completion(messages)
854
997
  processing_time = int((time.time() - start_time) * 1000)
855
998
 
856
- logger.info(f"🤖 OpenAI ответил за {processing_time}мс, длина ответа: {len(ai_response) if ai_response else 0}")
857
-
999
+ logger.info(
1000
+ f"🤖 OpenAI ответил за {processing_time}мс, длина ответа: {len(ai_response) if ai_response else 0}"
1001
+ )
1002
+
858
1003
  # ИСПРАВЛЕННАЯ ЛОГИКА: инициализируем все переменные заранее
859
1004
  tokens_used = 0
860
1005
  ai_metadata = {}
@@ -862,29 +1007,33 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
862
1007
 
863
1008
  # Проверяем ответ
864
1009
  if not ai_response or not ai_response.strip():
865
- logger.warning(f"❌ OpenAI вернул пустой/пробельный ответ!")
866
-
1010
+ logger.warning("❌ OpenAI вернул пустой/пробельный ответ!")
1011
+
867
1012
  # Проверяем, были ли использованы токены при пустом ответе
868
- if hasattr(openai_client, 'last_completion_tokens'):
869
- logger.warning(f"⚠️ Токены использованы ({openai_client.last_completion_tokens}), но ответ пустой")
870
-
1013
+ if hasattr(openai_client, "last_completion_tokens"):
1014
+ logger.warning(
1015
+ f"⚠️ Токены использованы ({openai_client.last_completion_tokens}), но ответ пустой"
1016
+ )
1017
+
871
1018
  # Устанавливаем fallback ответ
872
1019
  fallback_message = "Извините, произошла техническая ошибка. Попробуйте переформулировать вопрос или напишите /start для перезапуска."
873
1020
  ai_response = fallback_message
874
1021
  response_text = fallback_message
875
1022
  tokens_used = 0
876
1023
  ai_metadata = {}
877
-
1024
+
878
1025
  else:
879
- logger.info(f"📤 Сырой ответ OpenAI получен, обрабатываем...")
880
-
1026
+ logger.info("📤 Сырой ответ OpenAI получен, обрабатываем...")
1027
+
881
1028
  tokens_used = openai_client.estimate_tokens(ai_response)
882
-
1029
+
883
1030
  # Парсим JSON метаданные
884
1031
  response_text, ai_metadata = parse_ai_response(ai_response)
885
1032
 
886
- logger.info(f"🔍 После парсинга JSON:")
887
- logger.info(f" 📝 Текст ответа: {len(response_text)} символов: '{response_text[:100]}...'")
1033
+ logger.info("🔍 После парсинга JSON:")
1034
+ logger.info(
1035
+ f" 📝 Текст ответа: {len(response_text)} символов: '{response_text[:100]}...'"
1036
+ )
888
1037
  logger.info(f" 📊 Метаданные: {ai_metadata}")
889
1038
 
890
1039
  # Более надежная проверка
@@ -893,97 +1042,105 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
893
1042
  response_text = ai_response
894
1043
  ai_metadata = {}
895
1044
  elif not response_text.strip():
896
- logger.warning("⚠️ JSON найден, но текст ответа пустой! Используем исходный ответ.")
1045
+ logger.warning(
1046
+ "⚠️ JSON найден, но текст ответа пустой! Используем исходный ответ."
1047
+ )
897
1048
  response_text = ai_response
898
1049
 
899
- logger.info(f"✅ Финальный текст для отправки: {len(response_text)} символов")
900
-
1050
+ logger.info(
1051
+ f"✅ Финальный текст для отправки: {len(response_text)} символов"
1052
+ )
1053
+
901
1054
  # ============ ХУК 4: ОБРАБОТКА ОТВЕТА ============
902
- response_processors = message_hooks.get('response_processors', [])
1055
+ response_processors = message_hooks.get("response_processors", [])
903
1056
  for processor in response_processors:
904
1057
  try:
905
1058
  response_text, ai_metadata = await processor(
906
- response_text,
907
- ai_metadata,
908
- message.from_user.id
1059
+ response_text, ai_metadata, message.from_user.id
909
1060
  )
910
1061
  logger.info(f"✅ Ответ обработан '{processor.__name__}'")
911
1062
  except Exception as e:
912
- logger.error(f"❌ Ошибка в обработчике ответа '{processor.__name__}': {e}")
913
-
1063
+ logger.error(
1064
+ f"❌ Ошибка в обработчике ответа '{processor.__name__}': {e}"
1065
+ )
1066
+
914
1067
  # Обновляем этап сессии и качество лида
915
1068
  if ai_metadata:
916
1069
  logger.info("🔍 Анализ метаданных от ИИ:")
917
-
1070
+
918
1071
  # Вывод информации об этапе
919
- stage = ai_metadata.get('этап')
1072
+ stage = ai_metadata.get("этап")
920
1073
  if stage:
921
1074
  logger.info(f" 📈 Этап диалога: {stage}")
922
-
1075
+
923
1076
  # Вывод информации о качестве лида
924
- quality = ai_metadata.get('качество')
1077
+ quality = ai_metadata.get("качество")
925
1078
  if quality is not None:
926
1079
  quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
927
1080
  logger.info(f" {quality_emoji} Качество лида: {quality}/10")
928
-
1081
+
929
1082
  # Обновляем в базе данных
930
1083
  if stage or quality is not None:
931
1084
  await supabase_client.update_session_stage(session_id, stage, quality)
932
- logger.info(f" ✅ Этап и качество обновлены в БД")
933
-
1085
+ logger.info(" ✅ Этап и качество обновлены в БД")
1086
+
934
1087
  # Обрабатываем события
935
- events = ai_metadata.get('события', [])
1088
+ events = ai_metadata.get("события", [])
936
1089
  if events:
937
1090
  logger.info(f"\n🔔 События в диалоге ({len(events)}):")
938
1091
  for idx, event in enumerate(events, 1):
939
- event_type = event.get('тип', 'неизвестно')
940
- event_info = event.get('инфо', 'нет информации')
1092
+ event_type = event.get("тип", "неизвестно")
1093
+ event_info = event.get("инфо", "нет информации")
941
1094
 
942
1095
  # Подбираем эмодзи для разных типов событий
943
1096
  event_emoji = {
944
- 'телефон': '📱',
945
- 'email': '📧',
946
- 'встреча': '📅',
947
- 'заказ': '🛍️',
948
- 'вопрос': '',
949
- 'консультация': '💬',
950
- 'жалоба': '⚠️',
951
- 'отзыв': '💭'
952
- }.get(event_type.lower(), '📌')
1097
+ "телефон": "📱",
1098
+ "email": "📧",
1099
+ "встреча": "📅",
1100
+ "заказ": "🛍️",
1101
+ "вопрос": "",
1102
+ "консультация": "💬",
1103
+ "жалоба": "⚠️",
1104
+ "отзыв": "💭",
1105
+ }.get(event_type.lower(), "📌")
953
1106
 
954
1107
  logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
955
1108
 
956
1109
  # Обрабатываем события в системе
957
- should_send_response = await process_events(session_id, events, message.from_user.id)
958
- logger.warning(f" ✅ События обработаны, should_send_response = {should_send_response}")
959
-
1110
+ should_send_response = await process_events(
1111
+ session_id, events, message.from_user.id
1112
+ )
1113
+ logger.warning(
1114
+ f" ✅ События обработаны, should_send_response = {should_send_response}"
1115
+ )
1116
+
960
1117
  # Обрабатываем файлы и каталоги
961
- files_list = ai_metadata.get('файлы', [])
962
- directories_list = ai_metadata.get('каталоги', [])
963
-
1118
+ files_list = ai_metadata.get("файлы", [])
1119
+ directories_list = ai_metadata.get("каталоги", [])
1120
+
964
1121
  # Форматируем информацию о файлах
965
1122
  if files_list:
966
1123
  logger.info("📎 Найденные файлы:")
967
1124
  for idx, file in enumerate(files_list, 1):
968
1125
  logger.info(f" {idx}. 📄 {file}")
969
-
1126
+
970
1127
  # Форматируем информацию о каталогах
971
1128
  if directories_list:
972
1129
  logger.info("📂 Найденные каталоги:")
973
1130
  for idx, directory in enumerate(directories_list, 1):
974
1131
  logger.info(f" {idx}. 📁 {directory}")
975
-
1132
+
976
1133
  # Добавляем информацию в текст ответа
977
1134
  if files_list or directories_list:
978
1135
  files_info = []
979
1136
  if files_list:
980
1137
  files_str = "\n".join(f"• {file}" for file in files_list)
981
1138
  files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
982
-
1139
+
983
1140
  if directories_list:
984
1141
  dirs_str = "\n".join(f"• {directory}" for directory in directories_list)
985
1142
  files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
986
-
1143
+
987
1144
  else:
988
1145
  logger.info("📎 Файлы и каталоги не указаны")
989
1146
 
@@ -991,14 +1148,14 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
991
1148
  try:
992
1149
  await supabase_client.add_message(
993
1150
  session_id=session_id,
994
- role='assistant',
1151
+ role="assistant",
995
1152
  content=response_text,
996
- message_type='text',
1153
+ message_type="text",
997
1154
  tokens_used=tokens_used,
998
1155
  processing_time_ms=processing_time,
999
- ai_metadata=ai_metadata
1156
+ ai_metadata=ai_metadata,
1000
1157
  )
1001
- logger.info(f"✅ Ответ ассистента сохранен в БД")
1158
+ logger.info("✅ Ответ ассистента сохранен в БД")
1002
1159
  except Exception as e:
1003
1160
  logger.error(f"❌ Ошибка сохранения ответа в БД: {e}")
1004
1161
 
@@ -1006,129 +1163,161 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
1006
1163
  if config.DEBUG_MODE:
1007
1164
  # В режиме отладки показываем полный ответ с JSON
1008
1165
  final_response = ai_response
1009
- logger.info(f"🐛 Режим отладки: отправляем полный ответ с JSON")
1166
+ logger.info("🐛 Режим отладки: отправляем полный ответ с JSON")
1010
1167
  else:
1011
1168
  # В обычном режиме показываем только текст без JSON
1012
1169
  final_response = response_text
1013
- logger.info(f"👤 Обычный режим: отправляем очищенный текст")
1170
+ logger.info("👤 Обычный режим: отправляем очищенный текст")
1014
1171
 
1015
1172
  # Проверяем, что есть что отправлять
1016
1173
  if not final_response or not final_response.strip():
1017
- logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА: Финальный ответ пуст!")
1174
+ logger.error("❌ КРИТИЧЕСКАЯ ОШИБКА: Финальный ответ пуст!")
1018
1175
  final_response = "Извините, произошла ошибка при формировании ответа. Попробуйте еще раз."
1019
1176
 
1020
1177
  logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
1021
-
1178
+
1022
1179
  # ============ ПРОВЕРКА: НУЖНО ЛИ ОТПРАВЛЯТЬ СООБЩЕНИЕ ОТ ИИ ============
1023
1180
  # Проверяем флаг из событий (если события запретили отправку)
1024
- logger.warning(f"🔍 Проверка should_send_response: exists={('should_send_response' in locals())}, value={locals().get('should_send_response', 'NOT_SET')}")
1025
-
1026
- if 'should_send_response' in locals() and not should_send_response:
1027
- logger.warning("🔇🔇🔇 СОБЫТИЯ ЗАПРЕТИЛИ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ, ПРОПУСКАЕМ ОТПРАВКУ 🔇🔇🔇")
1181
+ logger.warning(
1182
+ f"🔍 Проверка should_send_response: exists={('should_send_response' in locals())}, value={locals().get('should_send_response', 'NOT_SET')}"
1183
+ )
1184
+
1185
+ if "should_send_response" in locals() and not should_send_response:
1186
+ logger.warning(
1187
+ "🔇🔇🔇 СОБЫТИЯ ЗАПРЕТИЛИ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ, ПРОПУСКАЕМ ОТПРАВКУ 🔇🔇🔇"
1188
+ )
1028
1189
  return
1029
-
1190
+
1030
1191
  # ============ ХУК 5: ФИЛЬТРЫ ОТПРАВКИ ============
1031
- send_filters = message_hooks.get('send_filters', [])
1192
+ send_filters = message_hooks.get("send_filters", [])
1032
1193
  for filter_func in send_filters:
1033
1194
  try:
1034
1195
  should_send = await filter_func(message.from_user.id)
1035
1196
  if should_send:
1036
1197
  # True = блокируем (для совместимости с should_block_ai_response)
1037
- logger.info(f"⛔ Фильтр '{filter_func.__name__}' заблокировал отправку (вернул True)")
1198
+ logger.info(
1199
+ f"⛔ Фильтр '{filter_func.__name__}' заблокировал отправку (вернул True)"
1200
+ )
1038
1201
  return # Не отправляем
1039
1202
  except Exception as e:
1040
- logger.error(f"❌ Ошибка в фильтре отправки '{filter_func.__name__}': {e}")
1041
-
1203
+ logger.error(
1204
+ f"❌ Ошибка в фильтре отправки '{filter_func.__name__}': {e}"
1205
+ )
1206
+
1042
1207
  # Отправляем ответ пользователю
1043
1208
  try:
1044
- await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
1045
- logger.info(f"✅ Ответ успешно отправлен пользователю {message.from_user.id}")
1209
+ await send_message(
1210
+ message,
1211
+ final_response,
1212
+ files_list=files_list,
1213
+ directories_list=directories_list,
1214
+ )
1215
+ logger.info(
1216
+ f"✅ Ответ успешно отправлен пользователю {message.from_user.id}"
1217
+ )
1046
1218
  except Exception as e:
1047
1219
  logger.error(f"❌ ОШИБКА ОТПРАВКИ СООБЩЕНИЯ: {e}")
1048
1220
  # Пытаемся отправить простое сообщение об ошибке
1049
1221
  try:
1050
- await message.answer("Произошла ошибка при отправке ответа. Попробуйте еще раз.")
1222
+ await message.answer(
1223
+ "Произошла ошибка при отправке ответа. Попробуйте еще раз."
1224
+ )
1051
1225
  except Exception as e2:
1052
1226
  logger.error(f"❌ Не удалось отправить даже сообщение об ошибке: {e2}")
1053
-
1227
+
1054
1228
  except Exception as e:
1055
1229
  logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА в process_user_message: {e}")
1056
1230
  logger.exception("Полный стек ошибки:")
1057
1231
  try:
1058
- await message.answer("Произошла критическая ошибка. Попробуйте написать /start для перезапуска.")
1059
- except:
1060
- logger.error(f"❌ Не удалось отправить сообщение об критической ошибке")
1061
-
1062
- async def process_voice_message(message: Message, state: FSMContext, session_id: str, system_prompt: str, recognized_text: str):
1232
+ await message.answer(
1233
+ "Произошла критическая ошибка. Попробуйте написать /start для перезапуска."
1234
+ )
1235
+ except Exception:
1236
+ logger.error("❌ Не удалось отправить сообщение об критической ошибке", exc_info=True)
1237
+
1238
+
1239
+ async def process_voice_message(
1240
+ message: Message,
1241
+ state: FSMContext,
1242
+ session_id: str,
1243
+ system_prompt: str,
1244
+ recognized_text: str,
1245
+ ):
1063
1246
  """Обработка распознанного голосового сообщения"""
1064
- supabase_client = get_global_var('supabase_client')
1065
- openai_client = get_global_var('openai_client')
1066
- config = get_global_var('config')
1067
- bot = get_global_var('bot')
1068
- prompt_loader = get_global_var('prompt_loader')
1069
-
1247
+ supabase_client = get_global_var("supabase_client")
1248
+ openai_client = get_global_var("openai_client")
1249
+ config = get_global_var("config")
1250
+ bot = get_global_var("bot")
1251
+ prompt_loader = get_global_var("prompt_loader")
1252
+
1070
1253
  from datetime import datetime
1254
+
1071
1255
  import pytz
1072
-
1256
+
1073
1257
  try:
1074
1258
  # Сохраняем распознанное сообщение как текст пользователя
1075
1259
  await supabase_client.add_message(
1076
1260
  session_id=session_id,
1077
- role='user',
1261
+ role="user",
1078
1262
  content=recognized_text,
1079
- message_type='text', # БД разрешает только 'text'
1080
- metadata={'original_type': 'voice', 'duration': message.voice.duration if message.voice else 0}
1263
+ message_type="text", # БД разрешает только 'text'
1264
+ metadata={
1265
+ "original_type": "voice",
1266
+ "duration": message.voice.duration if message.voice else 0,
1267
+ },
1081
1268
  )
1082
- logger.info(f"✅ Распознанное сообщение сохранено в БД")
1083
-
1269
+ logger.info("✅ Распознанное сообщение сохранено в БД")
1270
+
1084
1271
  # Получаем историю сообщений
1085
- chat_history = await supabase_client.get_chat_history(session_id, limit=config.MAX_CONTEXT_MESSAGES)
1272
+ chat_history = await supabase_client.get_chat_history(
1273
+ session_id, limit=config.MAX_CONTEXT_MESSAGES
1274
+ )
1086
1275
  logger.info(f"📚 Загружена история: {len(chat_history)} сообщений")
1087
-
1276
+
1088
1277
  # Добавляем текущее время
1089
- moscow_tz = pytz.timezone('Europe/Moscow')
1278
+ moscow_tz = pytz.timezone("Europe/Moscow")
1090
1279
  current_time = datetime.now(moscow_tz)
1091
- time_info = current_time.strftime('%H:%M, %d.%m.%Y, %A')
1092
-
1280
+ time_info = current_time.strftime("%H:%M, %d.%m.%Y, %A")
1281
+
1093
1282
  system_prompt_with_time = f"""
1094
1283
  {system_prompt}
1095
1284
 
1096
1285
  ТЕКУЩЕЕ ВРЕМЯ: {time_info} (московское время)
1097
1286
  """
1098
-
1287
+
1099
1288
  # Формируем контекст для OpenAI
1100
1289
  messages = [{"role": "system", "content": system_prompt_with_time}]
1101
-
1102
- for msg in chat_history[-config.MAX_CONTEXT_MESSAGES:]:
1103
- messages.append({
1104
- "role": msg['role'],
1105
- "content": msg['content']
1106
- })
1107
-
1290
+
1291
+ for msg in chat_history[-config.MAX_CONTEXT_MESSAGES :]:
1292
+ messages.append({"role": msg["role"], "content": msg["content"]})
1293
+
1108
1294
  # Добавляем финальные инструкции
1109
1295
  final_instructions = await prompt_loader.load_final_instructions()
1110
1296
  if final_instructions:
1111
1297
  messages.append({"role": "system", "content": final_instructions})
1112
- logger.info(f"🎯 Добавлены финальные инструкции")
1113
-
1298
+ logger.info("🎯 Добавлены финальные инструкции")
1299
+
1114
1300
  logger.info(f"📝 Контекст сформирован: {len(messages)} сообщений")
1115
-
1301
+
1116
1302
  await bot.send_chat_action(message.chat.id, "typing")
1117
-
1303
+
1118
1304
  import time
1305
+
1119
1306
  start_time = time.time()
1120
1307
  ai_response = await openai_client.get_completion(messages)
1121
1308
  processing_time = int((time.time() - start_time) * 1000)
1122
1309
 
1123
1310
  logger.info(f"🤖 OpenAI ответил за {processing_time}мс")
1124
-
1311
+
1125
1312
  tokens_used = 0
1126
1313
  ai_metadata = {}
1127
1314
  response_text = ""
1128
1315
 
1129
1316
  if not ai_response or not ai_response.strip():
1130
- logger.warning(f"❌ OpenAI вернул пустой ответ!")
1131
- fallback_message = "Извините, произошла техническая ошибка. Попробуйте еще раз."
1317
+ logger.warning("❌ OpenAI вернул пустой ответ!")
1318
+ fallback_message = (
1319
+ "Извините, произошла техническая ошибка. Попробуйте еще раз."
1320
+ )
1132
1321
  ai_response = fallback_message
1133
1322
  response_text = fallback_message
1134
1323
  tokens_used = 0
@@ -1141,56 +1330,71 @@ async def process_voice_message(message: Message, state: FSMContext, session_id:
1141
1330
  response_text = ai_response
1142
1331
  ai_metadata = {}
1143
1332
  elif not response_text.strip():
1144
- logger.warning("⚠️ JSON найден, но текст пустой! Используем исходный ответ.")
1333
+ logger.warning(
1334
+ "⚠️ JSON найден, но текст пустой! Используем исходный ответ."
1335
+ )
1145
1336
  response_text = ai_response
1146
1337
 
1147
1338
  # Обновляем этап и качество
1148
1339
  if ai_metadata:
1149
- stage = ai_metadata.get('этап')
1150
- quality = ai_metadata.get('качество')
1151
-
1340
+ stage = ai_metadata.get("этап")
1341
+ quality = ai_metadata.get("качество")
1342
+
1152
1343
  if stage or quality is not None:
1153
- await supabase_client.update_session_stage(session_id, stage, quality)
1154
-
1344
+ await supabase_client.update_session_stage(
1345
+ session_id, stage, quality
1346
+ )
1347
+
1155
1348
  # Обрабатываем события
1156
- events = ai_metadata.get('події', [])
1349
+ events = ai_metadata.get("події", [])
1157
1350
  if events:
1158
- should_send_response = await process_events(session_id, events, message.from_user.id)
1351
+ should_send_response = await process_events(
1352
+ session_id, events, message.from_user.id
1353
+ )
1159
1354
 
1160
1355
  # Сохраняем ответ ассистента
1161
1356
  await supabase_client.add_message(
1162
1357
  session_id=session_id,
1163
- role='assistant',
1358
+ role="assistant",
1164
1359
  content=response_text,
1165
- message_type='text',
1360
+ message_type="text",
1166
1361
  tokens_used=tokens_used,
1167
1362
  processing_time_ms=processing_time,
1168
- ai_metadata=ai_metadata
1363
+ ai_metadata=ai_metadata,
1169
1364
  )
1170
1365
 
1171
1366
  # Определяем финальный ответ
1172
1367
  final_response = ai_response if config.DEBUG_MODE else response_text
1173
-
1368
+
1174
1369
  if not final_response or not final_response.strip():
1175
1370
  final_response = "Извините, произошла ошибка при формировании ответа. Попробуйте еще раз."
1176
1371
 
1177
1372
  # Обрабатываем файлы
1178
- files_list = ai_metadata.get('файлы', [])
1179
- directories_list = ai_metadata.get('каталоги', [])
1180
-
1373
+ files_list = ai_metadata.get("файлы", [])
1374
+ directories_list = ai_metadata.get("каталоги", [])
1375
+
1181
1376
  # Проверяем, нужно ли отправлять сообщение от ИИ
1182
- if 'should_send_response' in locals() and not should_send_response:
1183
- logger.info("🔇 События запретили отправку сообщения от ИИ (voice), пропускаем отправку")
1377
+ if "should_send_response" in locals() and not should_send_response:
1378
+ logger.info(
1379
+ "🔇 События запретили отправку сообщения от ИИ (voice), пропускаем отправку"
1380
+ )
1184
1381
  return
1185
-
1382
+
1186
1383
  # Отправляем ответ пользователю
1187
- await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
1384
+ await send_message(
1385
+ message,
1386
+ final_response,
1387
+ files_list=files_list,
1388
+ directories_list=directories_list,
1389
+ )
1188
1390
  logger.info(f"✅ Ответ отправлен пользователю {message.from_user.id}")
1189
-
1391
+
1190
1392
  except Exception as e:
1191
1393
  logger.error(f"❌ КРИТИЧЕСКАЯ ОШИБКА в process_voice_message: {e}")
1192
1394
  logger.exception("Полный стек ошибки:")
1193
1395
  try:
1194
- await message.answer("Произошла критическая ошибка. Попробуйте написать /start")
1195
- except:
1196
- pass
1396
+ await message.answer(
1397
+ "Произошла критическая ошибка. Попробуйте написать /start"
1398
+ )
1399
+ except Exception:
1400
+ pass