229 lines
8.3 KiB
Python
229 lines
8.3 KiB
Python
|
|
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(
|
|||
|
|
"💬 <b>Диалоговая практика с AI</b>\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"<b>{SCENARIOS[scenario]}</b>\n\n"
|
|||
|
|
f"📝 <i>{conversation_start.get('context', '')}</i>\n\n"
|
|||
|
|
f"<b>AI:</b> {conversation_start.get('message', '')}\n"
|
|||
|
|
f"<i>({conversation_start.get('translation', '')})</i>\n\n"
|
|||
|
|
"💡 <b>Подсказки:</b>\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"✅ <b>Диалог завершён!</b>\n\n"
|
|||
|
|
f"Сообщений обменено: <b>{message_count}</b>\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"✅ <b>Диалог завершён!</b>\n\n"
|
|||
|
|
f"Сообщений обменено: <b>{message_count}</b>\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"⚠️ <b>Исправления:</b>\n{feedback['corrections']}\n\n"
|
|||
|
|
|
|||
|
|
if feedback.get('comment'):
|
|||
|
|
text += f"💬 {feedback['comment']}\n\n"
|
|||
|
|
|
|||
|
|
# Ответ AI
|
|||
|
|
text += (
|
|||
|
|
f"<b>AI:</b> {ai_response.get('response', '')}\n"
|
|||
|
|
f"<i>({ai_response.get('translation', '')})</i>\n\n"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Подсказки
|
|||
|
|
suggestions = ai_response.get('suggestions', [])
|
|||
|
|
if suggestions:
|
|||
|
|
text += "💡 <b>Подсказки:</b>\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)
|