from aiogram import Router, F from aiogram.filters import Command from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from database.db import async_session_maker from services.user_service import UserService from services.ai_service import ai_service router = Router() class PracticeStates(StatesGroup): """Состояния для диалоговой практики""" choosing_scenario = State() in_conversation = State() # Доступные сценарии SCENARIOS = { "restaurant": "🍽️ Ресторан", "shopping": "🛍️ Магазин", "travel": "✈️ Путешествие", "work": "💼 Работа", "doctor": "🏥 Врач", "casual": "💬 Общение" } @router.message(Command("practice")) async def cmd_practice(message: Message, state: FSMContext): """Обработчик команды /practice""" async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) if not user: await message.answer("Сначала запусти бота командой /start") return # Показываем выбор сценария keyboard = [] for scenario_id, scenario_name in SCENARIOS.items(): keyboard.append([ InlineKeyboardButton( text=scenario_name, callback_data=f"scenario_{scenario_id}" ) ]) reply_markup = InlineKeyboardMarkup(inline_keyboard=keyboard) await state.update_data(user_id=user.id, level=user.level.value) await state.set_state(PracticeStates.choosing_scenario) await message.answer( "💬 Диалоговая практика с AI\n\n" "Выбери сценарий для разговора:\n\n" "• AI будет играть роль собеседника\n" "• Ты можешь общаться на английском\n" "• AI будет исправлять твои ошибки\n" "• Используй /stop для завершения диалога\n\n" "Выбери сценарий:", reply_markup=reply_markup ) @router.callback_query(F.data.startswith("scenario_"), PracticeStates.choosing_scenario) async def start_scenario(callback: CallbackQuery, state: FSMContext): """Начать диалог с выбранным сценарием""" scenario = callback.data.split("_")[1] data = await state.get_data() level = data.get('level', 'B1') # Удаляем клавиатуру await callback.message.edit_reply_markup(reply_markup=None) # Показываем индикатор thinking_msg = await callback.message.answer("🤔 AI готовится к диалогу...") # Начинаем диалог conversation_start = await ai_service.start_conversation(scenario, level) await thinking_msg.delete() # Сохраняем данные в состоянии await state.update_data( scenario=scenario, scenario_name=SCENARIOS[scenario], conversation_history=[], message_count=0 ) await state.set_state(PracticeStates.in_conversation) # Формируем сообщение text = ( f"{SCENARIOS[scenario]}\n\n" f"📝 {conversation_start.get('context', '')}\n\n" f"AI: {conversation_start.get('message', '')}\n" f"({conversation_start.get('translation', '')})\n\n" "💡 Подсказки:\n" ) for suggestion in conversation_start.get('suggestions', []): text += f"• {suggestion}\n" text += "\n📝 Напиши свой ответ на английском или используй /stop для завершения" # Кнопки управления keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="💡 Показать подсказки", callback_data="show_hints")], [InlineKeyboardButton(text="🔚 Завершить диалог", callback_data="stop_practice")] ]) await callback.message.answer(text, reply_markup=keyboard) await callback.answer() @router.message(Command("stop"), PracticeStates.in_conversation) async def stop_practice(message: Message, state: FSMContext): """Завершить диалоговую практику""" data = await state.get_data() message_count = data.get('message_count', 0) await state.clear() await message.answer( f"✅ Диалог завершён!\n\n" f"Сообщений обменено: {message_count}\n\n" "Отличная работа! Продолжай практиковаться.\n" "Используй /practice для нового диалога." ) @router.callback_query(F.data == "stop_practice", PracticeStates.in_conversation) async def stop_practice_callback(callback: CallbackQuery, state: FSMContext): """Завершить диалог через кнопку""" data = await state.get_data() message_count = data.get('message_count', 0) await callback.message.delete() await state.clear() await callback.message.answer( f"✅ Диалог завершён!\n\n" f"Сообщений обменено: {message_count}\n\n" "Отличная работа! Продолжай практиковаться.\n" "Используй /practice для нового диалога." ) await callback.answer() @router.message(PracticeStates.in_conversation) async def handle_conversation(message: Message, state: FSMContext): """Обработка сообщений в диалоге""" user_message = message.text.strip() if not user_message: await message.answer("Напиши что-нибудь на английском или используй /stop для завершения") return data = await state.get_data() conversation_history = data.get('conversation_history', []) scenario = data.get('scenario', 'casual') level = data.get('level', 'B1') message_count = data.get('message_count', 0) # Показываем индикатор thinking_msg = await message.answer("🤔 AI думает...") # Добавляем сообщение пользователя в историю conversation_history.append({ "role": "user", "content": user_message }) # Получаем ответ от AI ai_response = await ai_service.continue_conversation( conversation_history=conversation_history, user_message=user_message, scenario=scenario, level=level ) await thinking_msg.delete() # Добавляем ответ AI в историю conversation_history.append({ "role": "assistant", "content": ai_response.get('response', '') }) # Обновляем состояние message_count += 1 await state.update_data( conversation_history=conversation_history, message_count=message_count ) # Формируем ответ text = "" # Показываем feedback, если есть ошибки feedback = ai_response.get('feedback', {}) if feedback.get('has_errors') and feedback.get('corrections'): text += f"⚠️ Исправления:\n{feedback['corrections']}\n\n" if feedback.get('comment'): text += f"💬 {feedback['comment']}\n\n" # Ответ AI text += ( f"AI: {ai_response.get('response', '')}\n" f"({ai_response.get('translation', '')})\n\n" ) # Подсказки suggestions = ai_response.get('suggestions', []) if suggestions: text += "💡 Подсказки:\n" for suggestion in suggestions[:3]: text += f"• {suggestion}\n" # Кнопки keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔚 Завершить диалог", callback_data="stop_practice")] ]) await message.answer(text, reply_markup=keyboard)