smart-bot-factory 0.3.6__py3-none-any.whl → 0.3.8__py3-none-any.whl

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

Potentially problematic release.


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

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