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.
- smart_bot_factory/admin/__init__.py +7 -7
- smart_bot_factory/admin/admin_events.py +483 -383
- smart_bot_factory/admin/admin_logic.py +234 -158
- smart_bot_factory/admin/admin_manager.py +68 -53
- smart_bot_factory/admin/admin_tester.py +46 -40
- smart_bot_factory/admin/timeout_checker.py +201 -153
- smart_bot_factory/aiogram_calendar/__init__.py +11 -3
- smart_bot_factory/aiogram_calendar/common.py +12 -18
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
- smart_bot_factory/aiogram_calendar/schemas.py +49 -28
- smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
- smart_bot_factory/analytics/analytics_manager.py +414 -392
- smart_bot_factory/cli.py +204 -148
- smart_bot_factory/config.py +123 -102
- smart_bot_factory/core/bot_utils.py +480 -324
- smart_bot_factory/core/conversation_manager.py +287 -200
- smart_bot_factory/core/decorators.py +1145 -739
- smart_bot_factory/core/message_sender.py +287 -266
- smart_bot_factory/core/router.py +170 -100
- smart_bot_factory/core/router_manager.py +121 -83
- smart_bot_factory/core/states.py +4 -3
- smart_bot_factory/creation/__init__.py +1 -1
- smart_bot_factory/creation/bot_builder.py +320 -242
- smart_bot_factory/creation/bot_testing.py +440 -365
- smart_bot_factory/dashboard/__init__.py +1 -3
- smart_bot_factory/event/__init__.py +2 -7
- smart_bot_factory/handlers/handlers.py +682 -466
- smart_bot_factory/integrations/openai_client.py +218 -168
- smart_bot_factory/integrations/supabase_client.py +928 -637
- smart_bot_factory/message/__init__.py +18 -22
- smart_bot_factory/router/__init__.py +2 -2
- smart_bot_factory/setup_checker.py +162 -126
- smart_bot_factory/supabase/__init__.py +1 -1
- smart_bot_factory/supabase/client.py +631 -515
- smart_bot_factory/utils/__init__.py +2 -3
- smart_bot_factory/utils/debug_routing.py +38 -27
- smart_bot_factory/utils/prompt_loader.py +153 -120
- smart_bot_factory/utils/user_prompt_loader.py +55 -56
- smart_bot_factory/utm_link_generator.py +123 -116
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
- smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
- smart_bot_factory-0.3.6.dist-info/RECORD +0 -59
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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
|
|
9
|
+
from aiogram.types import CallbackQuery, Message
|
|
9
10
|
|
|
10
|
-
from ..core.bot_utils import
|
|
11
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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[
|
|
116
|
-
event_type = event[
|
|
117
|
-
event_category = event[
|
|
118
|
-
is_global = event.get(
|
|
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(
|
|
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(
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
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,
|
|
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
|
-
|
|
159
|
-
|
|
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(
|
|
180
|
-
bot = get_global_var(
|
|
181
|
-
admin_manager = get_global_var(
|
|
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 =
|
|
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(
|
|
252
|
-
system_prompt = data.get(
|
|
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(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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[
|
|
264
|
-
system_prompt = session_info[
|
|
265
|
-
|
|
310
|
+
session_id = session_info["id"]
|
|
311
|
+
system_prompt = session_info["system_prompt"]
|
|
312
|
+
|
|
266
313
|
# Сохраняем в state для следующих сообщений
|
|
267
|
-
await state.update_data(
|
|
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(
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
[
|
|
285
|
-
|
|
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=
|
|
364
|
+
parse_mode="HTML",
|
|
300
365
|
)
|
|
301
|
-
|
|
302
|
-
logger.info(
|
|
366
|
+
|
|
367
|
+
logger.info("✅ Показаны кнопки подтверждения голосового сообщения")
|
|
303
368
|
else:
|
|
304
|
-
logger.warning(
|
|
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
|
-
|
|
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(
|
|
332
|
-
prompt_loader = get_global_var(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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[
|
|
390
|
-
logger.info(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
421
|
-
|
|
422
|
-
|
|
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=
|
|
516
|
+
role="assistant",
|
|
427
517
|
content=welcome_message,
|
|
428
|
-
message_type=
|
|
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(
|
|
526
|
+
start_handlers = get_global_var("start_handlers")
|
|
435
527
|
if start_handlers:
|
|
436
|
-
logger.info(
|
|
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(
|
|
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(
|
|
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(
|
|
458
|
-
supabase_client = get_global_var(
|
|
459
|
-
conversation_manager = get_global_var(
|
|
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(
|
|
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(
|
|
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(
|
|
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[
|
|
484
|
-
role=
|
|
590
|
+
session_id=session_info["id"],
|
|
591
|
+
role="user",
|
|
485
592
|
content=message.text,
|
|
486
|
-
message_type=
|
|
487
|
-
metadata={
|
|
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(
|
|
495
|
-
await state.set_state(
|
|
496
|
-
await message.answer(
|
|
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(
|
|
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[
|
|
508
|
-
system_prompt = session_info[
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
531
|
-
supabase_client = get_global_var(
|
|
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[
|
|
551
|
-
role=
|
|
665
|
+
session_id=session_info["id"],
|
|
666
|
+
role="user",
|
|
552
667
|
content=message.text,
|
|
553
|
-
message_type=
|
|
554
|
-
metadata={
|
|
668
|
+
message_type="text",
|
|
669
|
+
metadata={
|
|
670
|
+
"in_admin_chat": True,
|
|
671
|
+
"admin_id": conversation["admin_id"],
|
|
672
|
+
},
|
|
555
673
|
)
|
|
556
|
-
logger.info(
|
|
557
|
-
|
|
674
|
+
logger.info("💾 Сообщение сохранено в БД")
|
|
675
|
+
|
|
558
676
|
# Пересылаем админу
|
|
559
677
|
await conversation_manager.forward_message_to_admin(message, conversation)
|
|
560
|
-
logger.info(
|
|
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(
|
|
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(
|
|
573
|
-
system_prompt = data.get(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
595
|
-
|
|
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(
|
|
729
|
+
|
|
730
|
+
logger.info("🤖 Обычный диалог с ботом")
|
|
606
731
|
data = await state.get_data()
|
|
607
|
-
session_id = data.get(
|
|
608
|
-
system_prompt = data.get(
|
|
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(
|
|
612
|
-
await send_message(
|
|
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(
|
|
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(
|
|
628
|
-
session_id = data.get(
|
|
629
|
-
system_prompt = data.get(
|
|
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(
|
|
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(
|
|
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(
|
|
711
|
-
system_prompt = data.get(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
884
|
+
logger.info("👑 Необработанное сообщение админа")
|
|
742
885
|
await message.answer("Команда не распознана. Используйте /help для справки.")
|
|
743
886
|
else:
|
|
744
|
-
logger.info(
|
|
887
|
+
logger.info("👤 Необработанное сообщение пользователя")
|
|
745
888
|
await message.answer("Не понимаю. Напишите /start для начала диалога.")
|
|
746
889
|
|
|
747
|
-
|
|
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(
|
|
750
|
-
openai_client = get_global_var(
|
|
751
|
-
config = get_global_var(
|
|
752
|
-
bot = get_global_var(
|
|
753
|
-
prompt_loader = get_global_var(
|
|
754
|
-
message_hooks = get_global_var(
|
|
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(
|
|
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(
|
|
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=
|
|
925
|
+
role="user",
|
|
777
926
|
content=message.text,
|
|
778
|
-
message_type=
|
|
927
|
+
message_type="text",
|
|
779
928
|
)
|
|
780
|
-
logger.info(
|
|
781
|
-
|
|
929
|
+
logger.info("✅ Сообщение пользователя сохранено в БД")
|
|
930
|
+
|
|
782
931
|
# Получаем историю сообщений
|
|
783
|
-
chat_history = await supabase_client.get_chat_history(
|
|
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(
|
|
938
|
+
moscow_tz = pytz.timezone("Europe/Moscow")
|
|
788
939
|
current_time = datetime.now(moscow_tz)
|
|
789
|
-
time_info = current_time.strftime(
|
|
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(
|
|
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(
|
|
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[
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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(
|
|
824
|
-
|
|
974
|
+
logger.info(
|
|
975
|
+
f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)"
|
|
976
|
+
)
|
|
977
|
+
|
|
825
978
|
# ============ ХУК 3: ОБОГАЩЕНИЕ КОНТЕКСТА ============
|
|
826
|
-
context_enrichers = message_hooks.get(
|
|
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(
|
|
835
|
-
|
|
836
|
-
|
|
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(
|
|
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(
|
|
854
|
-
|
|
1010
|
+
logger.warning("❌ OpenAI вернул пустой/пробельный ответ!")
|
|
1011
|
+
|
|
855
1012
|
# Проверяем, были ли использованы токены при пустом ответе
|
|
856
|
-
if hasattr(openai_client,
|
|
857
|
-
logger.warning(
|
|
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(
|
|
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(
|
|
875
|
-
logger.info(
|
|
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(
|
|
1045
|
+
logger.warning(
|
|
1046
|
+
"⚠️ JSON найден, но текст ответа пустой! Используем исходный ответ."
|
|
1047
|
+
)
|
|
885
1048
|
response_text = ai_response
|
|
886
1049
|
|
|
887
|
-
logger.info(
|
|
888
|
-
|
|
1050
|
+
logger.info(
|
|
1051
|
+
f"✅ Финальный текст для отправки: {len(response_text)} символов"
|
|
1052
|
+
)
|
|
1053
|
+
|
|
889
1054
|
# ============ ХУК 4: ОБРАБОТКА ОТВЕТА ============
|
|
890
|
-
response_processors = message_hooks.get(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
946
|
-
|
|
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=
|
|
1151
|
+
role="assistant",
|
|
983
1152
|
content=response_text,
|
|
984
|
-
message_type=
|
|
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(
|
|
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(
|
|
1166
|
+
logger.info("🐛 Режим отладки: отправляем полный ответ с JSON")
|
|
998
1167
|
else:
|
|
999
1168
|
# В обычном режиме показываем только текст без JSON
|
|
1000
1169
|
final_response = response_text
|
|
1001
|
-
logger.info(
|
|
1170
|
+
logger.info("👤 Обычный режим: отправляем очищенный текст")
|
|
1002
1171
|
|
|
1003
1172
|
# Проверяем, что есть что отправлять
|
|
1004
1173
|
if not final_response or not final_response.strip():
|
|
1005
|
-
logger.error(
|
|
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(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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(
|
|
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(
|
|
1198
|
+
logger.info(
|
|
1199
|
+
f"⛔ Фильтр '{filter_func.__name__}' заблокировал отправку (вернул True)"
|
|
1200
|
+
)
|
|
1026
1201
|
return # Не отправляем
|
|
1027
1202
|
except Exception as e:
|
|
1028
|
-
logger.error(
|
|
1029
|
-
|
|
1203
|
+
logger.error(
|
|
1204
|
+
f"❌ Ошибка в фильтре отправки '{filter_func.__name__}': {e}"
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1030
1207
|
# Отправляем ответ пользователю
|
|
1031
1208
|
try:
|
|
1032
|
-
await send_message(
|
|
1033
|
-
|
|
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(
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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(
|
|
1053
|
-
openai_client = get_global_var(
|
|
1054
|
-
config = get_global_var(
|
|
1055
|
-
bot = get_global_var(
|
|
1056
|
-
prompt_loader = get_global_var(
|
|
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=
|
|
1261
|
+
role="user",
|
|
1066
1262
|
content=recognized_text,
|
|
1067
|
-
message_type=
|
|
1068
|
-
metadata={
|
|
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(
|
|
1071
|
-
|
|
1269
|
+
logger.info("✅ Распознанное сообщение сохранено в БД")
|
|
1270
|
+
|
|
1072
1271
|
# Получаем историю сообщений
|
|
1073
|
-
chat_history = await supabase_client.get_chat_history(
|
|
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(
|
|
1278
|
+
moscow_tz = pytz.timezone("Europe/Moscow")
|
|
1078
1279
|
current_time = datetime.now(moscow_tz)
|
|
1079
|
-
time_info = current_time.strftime(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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=
|
|
1358
|
+
role="assistant",
|
|
1152
1359
|
content=response_text,
|
|
1153
|
-
message_type=
|
|
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
|
|
1171
|
-
logger.info(
|
|
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(
|
|
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(
|
|
1183
|
-
|
|
1184
|
-
|
|
1396
|
+
await message.answer(
|
|
1397
|
+
"Произошла критическая ошибка. Попробуйте написать /start"
|
|
1398
|
+
)
|
|
1399
|
+
except Exception:
|
|
1400
|
+
pass
|