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 database.models import WordSource from services.user_service import UserService from services.task_service import TaskService from services.vocabulary_service import VocabularyService from services.ai_service import ai_service from utils.i18n import t, get_user_lang from utils.levels import get_user_level_for_language router = Router() class TaskStates(StatesGroup): """Состояния для прохождения заданий""" choosing_mode = State() doing_tasks = State() waiting_for_answer = State() @router.message(Command("task")) async def cmd_task(message: Message, state: FSMContext): """Обработчик команды /task — показываем меню выбора режима""" 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(t('ru', 'common.start_first')) return lang = get_user_lang(user) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton( text=t(lang, 'tasks.mode_vocabulary'), callback_data="task_mode_vocabulary" )], [InlineKeyboardButton( text=t(lang, 'tasks.mode_new_words'), callback_data="task_mode_new" )] ]) await state.update_data(user_id=user.id) await state.set_state(TaskStates.choosing_mode) await message.answer(t(lang, 'tasks.choose_mode'), reply_markup=keyboard) @router.callback_query(F.data == "task_mode_vocabulary", TaskStates.choosing_mode) async def start_vocabulary_tasks(callback: CallbackQuery, state: FSMContext): """Начать задания по словам из словаря""" await callback.answer() async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.message.edit_text(t('ru', 'common.start_first')) return lang = get_user_lang(user) # Генерируем задания по словам из словаря tasks = await TaskService.generate_mixed_tasks( session, user.id, count=5, learning_lang=user.learning_language, translation_lang=user.language_interface, ) if not tasks: await callback.message.edit_text(t(lang, 'tasks.no_words')) await state.clear() return # Сохраняем задания в состоянии await state.update_data( tasks=tasks, current_task_index=0, correct_count=0, user_id=user.id, mode='vocabulary' ) await state.set_state(TaskStates.doing_tasks) await callback.message.delete() await show_current_task(callback.message, state) @router.callback_query(F.data == "task_mode_new", TaskStates.choosing_mode) async def start_new_words_tasks(callback: CallbackQuery, state: FSMContext): """Начать задания с новыми словами""" await callback.answer() async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.message.edit_text(t('ru', 'common.start_first')) return lang = get_user_lang(user) level = get_user_level_for_language(user) # Показываем индикатор загрузки await callback.message.edit_text(t(lang, 'tasks.generating_new')) # Получаем слова для исключения: # 1. Все слова из словаря пользователя vocab_words = await VocabularyService.get_all_user_word_strings( session, user.id, learning_lang=user.learning_language ) # 2. Слова из предыдущих заданий new_words, на которые ответили правильно correct_task_words = await TaskService.get_correctly_answered_words( session, user.id ) # Объединяем списки исключений exclude_words = list(set(vocab_words + correct_task_words)) # Генерируем новые слова через AI words = await ai_service.generate_thematic_words( theme="random everyday vocabulary", level=level, count=5, learning_lang=user.learning_language, translation_lang=user.language_interface, exclude_words=exclude_words if exclude_words else None, ) if not words: await callback.message.edit_text(t(lang, 'tasks.generate_failed')) await state.clear() return # Преобразуем слова в задания tasks = [] translate_prompt = t(lang, 'tasks.translate_to', lang_name=t(lang, f'lang.{user.language_interface}')) for word in words: tasks.append({ 'type': 'translate', 'question': f"{translate_prompt}: {word.get('word', '')}", 'word': word.get('word', ''), 'correct_answer': word.get('translation', ''), 'transcription': word.get('transcription', ''), 'example': word.get('example', ''), # Пример на изучаемом языке 'example_translation': word.get('example_translation', '') # Перевод примера }) await state.update_data( tasks=tasks, current_task_index=0, correct_count=0, user_id=user.id, mode='new_words' ) await state.set_state(TaskStates.doing_tasks) await callback.message.delete() await show_current_task(callback.message, state) async def show_current_task(message: Message, state: FSMContext): """Показать текущее задание""" data = await state.get_data() tasks = data.get('tasks', []) current_index = data.get('current_task_index', 0) if current_index >= len(tasks): # Все задания выполнены await finish_tasks(message, state) return task = tasks[current_index] # Определяем язык пользователя async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) lang = (user.language_interface if user else 'ru') or 'ru' task_text = ( t(lang, 'tasks.header', i=current_index + 1, n=len(tasks)) + "\n\n" + f"{task['question']}\n" ) if task.get('transcription'): task_text += f"🔊 [{task['transcription']}]\n" task_text += t(lang, 'tasks.write_answer') await state.set_state(TaskStates.waiting_for_answer) await message.answer(task_text) @router.message(TaskStates.waiting_for_answer) async def process_answer(message: Message, state: FSMContext): """Обработка ответа пользователя""" user_answer = message.text.strip() data = await state.get_data() tasks = data.get('tasks', []) current_index = data.get('current_task_index', 0) correct_count = data.get('correct_count', 0) user_id = data.get('user_id') task = tasks[current_index] # Показываем индикатор проверки # Язык пользователя async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) lang = (user.language_interface if user else 'ru') or 'ru' checking_msg = await message.answer(t(lang, 'tasks.checking')) # Проверяем ответ через AI check_result = await ai_service.check_answer( question=task['question'], correct_answer=task['correct_answer'], user_answer=user_answer ) await checking_msg.delete() is_correct = check_result.get('is_correct', False) feedback = check_result.get('feedback', '') # Формируем ответ if is_correct: result_text = t(lang, 'tasks.correct') + "\n\n" correct_count += 1 else: result_text = t(lang, 'tasks.incorrect') + "\n\n" result_text += f"{t(lang, 'tasks.your_answer')}: {user_answer}\n" result_text += f"{t(lang, 'tasks.right_answer')}: {task['correct_answer']}\n\n" if feedback: result_text += f"💬 {feedback}\n\n" # Показываем пример использования если есть example = task.get('example', '') example_translation = task.get('example_translation', '') if example: result_text += f"📖 {t(lang, 'tasks.example_label')}:\n" result_text += f"{example}\n" if example_translation: result_text += f"({example_translation})\n" result_text += "\n" # Сохраняем результат в БД async with async_session_maker() as session: await TaskService.save_task_result( session=session, user_id=user_id, task_type=task['type'], content={ 'question': task['question'], 'word': task['word'] }, user_answer=user_answer, correct_answer=task['correct_answer'], is_correct=is_correct, ai_feedback=feedback ) # Обновляем статистику слова if 'word_id' in task: await TaskService.update_word_statistics( session=session, word_id=task['word_id'], is_correct=is_correct ) # Обновляем счетчик await state.update_data( current_task_index=current_index + 1, correct_count=correct_count ) # Показываем результат и кнопку "Далее" mode = data.get('mode') buttons = [[InlineKeyboardButton(text=t(lang, 'tasks.next_btn'), callback_data="next_task")]] # Для режима new_words добавляем кнопку "Добавить слово" if mode == 'new_words': buttons.append([InlineKeyboardButton( text=t(lang, 'tasks.add_word_btn'), callback_data=f"add_task_word_{current_index}" )]) buttons.append([InlineKeyboardButton(text=t(lang, 'tasks.stop_btn'), callback_data="stop_tasks")]) keyboard = InlineKeyboardMarkup(inline_keyboard=buttons) await message.answer(result_text, reply_markup=keyboard) # После показа результата ждём нажатия кнопки – переключаемся в состояние doing_tasks await state.set_state(TaskStates.doing_tasks) @router.callback_query(F.data == "next_task", TaskStates.doing_tasks) async def next_task(callback: CallbackQuery, state: FSMContext): """Переход к следующему заданию""" await callback.message.delete() await show_current_task(callback.message, state) await callback.answer() @router.callback_query(F.data.startswith("add_task_word_"), TaskStates.doing_tasks) async def add_task_word(callback: CallbackQuery, state: FSMContext): """Добавить слово из задания в словарь""" task_index = int(callback.data.split("_")[-1]) data = await state.get_data() tasks = data.get('tasks', []) if task_index >= len(tasks): await callback.answer() return task = tasks[task_index] word = task.get('word', '') translation = task.get('correct_answer', '') transcription = task.get('transcription', '') example = task.get('example', '') # Пример использования как контекст example_translation = task.get('example_translation', '') # Перевод примера async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.answer() return lang = get_user_lang(user) # Проверяем, есть ли слово уже в словаре existing = await VocabularyService.get_word_by_original(session, user.id, word) if existing: await callback.answer(t(lang, 'tasks.word_already_exists', word=word), show_alert=True) return # Добавляем слово в словарь new_word = await VocabularyService.add_word( session=session, user_id=user.id, word_original=word, word_translation=translation, source_lang=user.learning_language, translation_lang=user.language_interface, transcription=transcription, source=WordSource.AI_TASK ) # Сохраняем перевод в таблицу word_translations await VocabularyService.add_translations_bulk( session=session, vocabulary_id=new_word.id, translations=[{ 'translation': translation, 'context': example if example else None, 'context_translation': example_translation if example_translation else None, 'is_primary': True }] ) await callback.answer(t(lang, 'tasks.word_added', word=word), show_alert=True) @router.callback_query(F.data == "stop_tasks", TaskStates.doing_tasks) async def stop_tasks_callback(callback: CallbackQuery, state: FSMContext): """Остановить выполнение заданий через кнопку""" await state.clear() await callback.message.edit_reply_markup(reply_markup=None) async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) lang = (user.language_interface if user else 'ru') or 'ru' await callback.message.answer(t(lang, 'tasks.finished')) await callback.answer() @router.message(Command("stop"), TaskStates.doing_tasks) @router.message(Command("stop"), TaskStates.waiting_for_answer) async def stop_tasks(message: Message, state: FSMContext): """Остановить выполнение заданий командой /stop""" await state.clear() # Определяем язык пользователя async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) await message.answer(t((user.language_interface if user else 'ru') or 'ru', 'tasks.stopped')) @router.message(Command("cancel"), TaskStates.doing_tasks) @router.message(Command("cancel"), TaskStates.waiting_for_answer) async def cancel_tasks(message: Message, state: FSMContext): """Отмена выполнения заданий командой /cancel""" await state.clear() async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) lang = (user.language_interface if user else 'ru') or 'ru' await message.answer(t(lang, 'tasks.cancelled')) async def finish_tasks(message: Message, state: FSMContext): """Завершение всех заданий""" data = await state.get_data() tasks = data.get('tasks', []) correct_count = data.get('correct_count', 0) total_count = len(tasks) accuracy = int((correct_count / total_count) * 100) if total_count > 0 else 0 # Определяем эмодзи на основе результата if accuracy >= 90: emoji = "🏆" comment_key = 'excellent' elif accuracy >= 70: emoji = "👍" comment_key = 'good' elif accuracy >= 50: emoji = "📚" comment_key = 'average' else: emoji = "💪" comment_key = 'poor' # Язык пользователя async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) lang = (user.language_interface if user else 'ru') or 'ru' result_text = ( t(lang, 'tasks.finish_title', emoji=emoji) + "\n\n" + t(lang, 'tasks.correct_of', correct=correct_count, total=total_count) + "\n" + t(lang, 'tasks.accuracy', accuracy=accuracy) + "\n\n" + t(lang, f"tasks.comment.{comment_key}") + "\n\n" + t(lang, 'tasks.use_task') + "\n" + t(lang, 'tasks.use_stats') ) await state.clear() await message.answer(result_text) @router.message(Command("stats")) async def cmd_stats(message: Message): """Обработчик команды /stats""" 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(t('ru', 'common.start_first')) return # Получаем статистику stats = await TaskService.get_user_stats(session, user.id) lang = (user.language_interface if user else 'ru') or 'ru' stats_text = ( t(lang, 'stats.header') + "\n\n" + t(lang, 'stats.total_words', n=stats['total_words']) + "\n" + t(lang, 'stats.studied_words', n=stats['reviewed_words']) + "\n" + t(lang, 'stats.total_tasks', n=stats['total_tasks']) + "\n" + t(lang, 'stats.correct_tasks', n=stats['correct_tasks']) + "\n" + t(lang, 'stats.accuracy', n=stats['accuracy']) + "\n\n" ) if stats['total_words'] == 0: stats_text += t(lang, 'stats.hint_add_words') elif stats['total_tasks'] == 0: stats_text += t(lang, 'stats.hint_first_task') else: stats_text += t(lang, 'stats.hint_keep_practice') await message.answer(stats_text)