2025-12-04 14:30:06 +03:00
|
|
|
|
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
|
2025-12-05 20:15:47 +03:00
|
|
|
|
from database.models import WordSource
|
2025-12-04 14:30:06 +03:00
|
|
|
|
from services.user_service import UserService
|
|
|
|
|
|
from services.task_service import TaskService
|
2025-12-05 20:15:47 +03:00
|
|
|
|
from services.vocabulary_service import VocabularyService
|
2025-12-04 14:30:06 +03:00
|
|
|
|
from services.ai_service import ai_service
|
2025-12-07 16:35:08 +03:00
|
|
|
|
from utils.i18n import t, get_user_lang, get_user_translation_lang
|
2025-12-05 20:15:47 +03:00
|
|
|
|
from utils.levels import get_user_level_for_language
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
router = Router()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskStates(StatesGroup):
|
|
|
|
|
|
"""Состояния для прохождения заданий"""
|
2025-12-05 20:15:47 +03:00
|
|
|
|
choosing_mode = State()
|
2025-12-08 15:16:24 +03:00
|
|
|
|
choosing_type = State() # Выбор типа заданий
|
2025-12-04 14:30:06 +03:00
|
|
|
|
doing_tasks = State()
|
|
|
|
|
|
waiting_for_answer = State()
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Типы заданий
|
|
|
|
|
|
TASK_TYPES = ['mix', 'word_translate', 'fill_blank', 'sentence_translate']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_task_type_keyboard(lang: str) -> InlineKeyboardMarkup:
|
|
|
|
|
|
"""Клавиатура выбора типа заданий"""
|
|
|
|
|
|
return InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'tasks.type_mix'), callback_data="task_type_mix")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'tasks.type_word_translate'), callback_data="task_type_word_translate")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'tasks.type_fill_blank'), callback_data="task_type_fill_blank")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'tasks.type_sentence_translate'), callback_data="task_type_sentence_translate")],
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 14:30:06 +03:00
|
|
|
|
@router.message(Command("task"))
|
|
|
|
|
|
async def cmd_task(message: Message, state: FSMContext):
|
2025-12-05 20:15:47 +03:00
|
|
|
|
"""Обработчик команды /task — показываем меню выбора режима"""
|
2025-12-04 14:30:06 +03:00
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
|
|
|
|
|
|
if not user:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await message.answer(t('ru', 'common.start_first'))
|
2025-12-04 14:30:06 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-05 20:15:47 +03:00
|
|
|
|
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)
|
2025-12-08 15:16:24 +03:00
|
|
|
|
async def choose_vocabulary_task_type(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Показать выбор типа заданий для режима vocabulary"""
|
2025-12-05 20:15:47 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Сохраняем режим и переходим к выбору типа
|
|
|
|
|
|
await state.update_data(user_id=user.id, mode='vocabulary')
|
|
|
|
|
|
await state.set_state(TaskStates.choosing_type)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
await callback.message.edit_text(
|
|
|
|
|
|
t(lang, 'tasks.choose_type'),
|
|
|
|
|
|
reply_markup=get_task_type_keyboard(lang)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
)
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
@router.callback_query(F.data.startswith("task_type_"), TaskStates.choosing_type)
|
|
|
|
|
|
async def start_tasks_with_type(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Начать задания выбранного типа"""
|
2025-12-05 20:15:47 +03:00
|
|
|
|
await callback.answer()
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
task_type = callback.data.replace("task_type_", "") # mix, word_translate, fill_blank, sentence_translate
|
|
|
|
|
|
data = await state.get_data()
|
|
|
|
|
|
mode = data.get('mode', 'vocabulary')
|
|
|
|
|
|
|
2025-12-05 20:15:47 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Получаем количество заданий из настроек пользователя
|
|
|
|
|
|
tasks_count = getattr(user, 'tasks_count', 5) or 5
|
|
|
|
|
|
|
|
|
|
|
|
if mode == 'vocabulary':
|
|
|
|
|
|
# Генерируем задания по словам из словаря
|
|
|
|
|
|
tasks = await TaskService.generate_tasks_by_type(
|
|
|
|
|
|
session, user.id, count=tasks_count,
|
|
|
|
|
|
task_type=task_type,
|
|
|
|
|
|
learning_lang=user.learning_language,
|
|
|
|
|
|
translation_lang=get_user_translation_lang(user),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
|
task_type=task_type
|
|
|
|
|
|
)
|
|
|
|
|
|
await state.set_state(TaskStates.doing_tasks)
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.delete()
|
|
|
|
|
|
await show_current_task(callback.message, state)
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Режим new_words - генерируем новые слова
|
|
|
|
|
|
await generate_new_words_tasks(callback, state, user, task_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_new_words_tasks(callback: CallbackQuery, state: FSMContext, user, task_type: str):
|
|
|
|
|
|
"""Генерация заданий с новыми словами"""
|
|
|
|
|
|
lang = get_user_lang(user)
|
|
|
|
|
|
level = get_user_level_for_language(user)
|
|
|
|
|
|
tasks_count = getattr(user, 'tasks_count', 5) or 5
|
|
|
|
|
|
|
|
|
|
|
|
# Показываем индикатор загрузки
|
|
|
|
|
|
await callback.message.edit_text(t(lang, 'tasks.generating_new'))
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
# Получаем слова для исключения
|
2025-12-06 21:29:41 +03:00
|
|
|
|
vocab_words = await VocabularyService.get_all_user_word_strings(
|
|
|
|
|
|
session, user.id, learning_lang=user.learning_language
|
|
|
|
|
|
)
|
|
|
|
|
|
correct_task_words = await TaskService.get_correctly_answered_words(
|
|
|
|
|
|
session, user.id
|
|
|
|
|
|
)
|
|
|
|
|
|
exclude_words = list(set(vocab_words + correct_task_words))
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Генерируем новые слова через AI
|
|
|
|
|
|
translation_lang = get_user_translation_lang(user)
|
|
|
|
|
|
words = await ai_service.generate_thematic_words(
|
|
|
|
|
|
theme="random everyday vocabulary",
|
|
|
|
|
|
level=level,
|
|
|
|
|
|
count=tasks_count,
|
|
|
|
|
|
learning_lang=user.learning_language,
|
|
|
|
|
|
translation_lang=translation_lang,
|
|
|
|
|
|
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 = await create_tasks_from_words(words, task_type, lang, user.learning_language, translation_lang)
|
|
|
|
|
|
|
|
|
|
|
|
await state.update_data(
|
|
|
|
|
|
tasks=tasks,
|
|
|
|
|
|
current_task_index=0,
|
|
|
|
|
|
correct_count=0,
|
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
|
mode='new_words',
|
|
|
|
|
|
task_type=task_type
|
|
|
|
|
|
)
|
|
|
|
|
|
await state.set_state(TaskStates.doing_tasks)
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.delete()
|
|
|
|
|
|
await show_current_task(callback.message, state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def create_tasks_from_words(words: list, task_type: str, lang: str, learning_lang: str, translation_lang: str) -> list:
|
|
|
|
|
|
"""Создать задания из списка слов в зависимости от типа"""
|
|
|
|
|
|
import random
|
|
|
|
|
|
tasks = []
|
|
|
|
|
|
|
|
|
|
|
|
for word in words:
|
|
|
|
|
|
word_text = word.get('word', '')
|
|
|
|
|
|
translation = word.get('translation', '')
|
|
|
|
|
|
transcription = word.get('transcription', '')
|
|
|
|
|
|
example = word.get('example', '')
|
|
|
|
|
|
example_translation = word.get('example_translation', '')
|
|
|
|
|
|
|
|
|
|
|
|
if task_type == 'mix':
|
|
|
|
|
|
# Случайный тип
|
|
|
|
|
|
chosen_type = random.choice(['word_translate', 'fill_blank', 'sentence_translate'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
chosen_type = task_type
|
|
|
|
|
|
|
|
|
|
|
|
if chosen_type == 'word_translate':
|
|
|
|
|
|
# Перевод слова
|
|
|
|
|
|
translate_prompt = t(lang, 'tasks.translate_to', lang_name=t(lang, f'lang.{translation_lang}'))
|
|
|
|
|
|
tasks.append({
|
|
|
|
|
|
'type': 'translate',
|
|
|
|
|
|
'question': f"{translate_prompt}: <b>{word_text}</b>",
|
|
|
|
|
|
'word': word_text,
|
|
|
|
|
|
'correct_answer': translation,
|
|
|
|
|
|
'transcription': transcription,
|
|
|
|
|
|
'example': example,
|
|
|
|
|
|
'example_translation': example_translation
|
|
|
|
|
|
})
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
elif chosen_type == 'fill_blank':
|
|
|
|
|
|
# Заполнение пропуска - генерируем предложение через AI
|
|
|
|
|
|
sentence_data = await ai_service.generate_fill_in_sentence(
|
|
|
|
|
|
word_text,
|
|
|
|
|
|
learning_lang=learning_lang,
|
|
|
|
|
|
translation_lang=translation_lang
|
|
|
|
|
|
)
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
fill_title = "Fill in the blank:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
fill_title = "空欄を埋めてください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
fill_title = "Заполни пропуск:"
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
|
|
|
|
|
tasks.append({
|
2025-12-08 15:16:24 +03:00
|
|
|
|
'type': 'fill_in',
|
|
|
|
|
|
'question': f"{fill_title}\n\n<b>{sentence_data['sentence']}</b>\n\n<i>{sentence_data.get('translation', '')}</i>",
|
|
|
|
|
|
'word': word_text,
|
|
|
|
|
|
'correct_answer': sentence_data['answer'],
|
|
|
|
|
|
'transcription': transcription,
|
|
|
|
|
|
'example': example,
|
|
|
|
|
|
'example_translation': example_translation
|
2025-12-05 20:15:47 +03:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
elif chosen_type == 'sentence_translate':
|
|
|
|
|
|
# Перевод предложения - генерируем предложение через AI
|
|
|
|
|
|
sentence_data = await ai_service.generate_sentence_for_translation(
|
|
|
|
|
|
word_text,
|
|
|
|
|
|
learning_lang=learning_lang,
|
|
|
|
|
|
translation_lang=translation_lang
|
|
|
|
|
|
)
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
sentence_title = "Translate the sentence:"
|
|
|
|
|
|
word_hint = "Word"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
sentence_title = "文を翻訳してください:"
|
|
|
|
|
|
word_hint = "単語"
|
|
|
|
|
|
else:
|
|
|
|
|
|
sentence_title = "Переведи предложение:"
|
|
|
|
|
|
word_hint = "Слово"
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
tasks.append({
|
|
|
|
|
|
'type': 'sentence_translate',
|
|
|
|
|
|
'question': f"{sentence_title}\n\n<b>{sentence_data['sentence']}</b>\n\n📝 {word_hint}: <code>{word_text}</code> — {translation}",
|
|
|
|
|
|
'word': word_text,
|
|
|
|
|
|
'correct_answer': sentence_data['translation'],
|
|
|
|
|
|
'transcription': transcription,
|
|
|
|
|
|
'example': example,
|
|
|
|
|
|
'example_translation': example_translation
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return tasks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "task_mode_new", TaskStates.choosing_mode)
|
|
|
|
|
|
async def choose_new_words_task_type(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Показать выбор типа заданий для режима new_words"""
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# Сохраняем режим и переходим к выбору типа
|
|
|
|
|
|
await state.update_data(user_id=user.id, mode='new_words')
|
|
|
|
|
|
await state.set_state(TaskStates.choosing_type)
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.edit_text(
|
|
|
|
|
|
t(lang, 'tasks.choose_type'),
|
|
|
|
|
|
reply_markup=get_task_type_keyboard(lang)
|
|
|
|
|
|
)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2025-12-07 16:35:08 +03:00
|
|
|
|
user_id = data.get('user_id')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
if current_index >= len(tasks):
|
|
|
|
|
|
# Все задания выполнены
|
|
|
|
|
|
await finish_tasks(message, state)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
task = tasks[current_index]
|
|
|
|
|
|
|
2025-12-07 16:35:08 +03:00
|
|
|
|
# Определяем язык пользователя (берём user_id из state, т.к. message может быть от бота)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
async with async_session_maker() as session:
|
2025-12-07 16:35:08 +03:00
|
|
|
|
user = await UserService.get_user_by_id(session, user_id)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
2025-12-04 14:30:06 +03:00
|
|
|
|
task_text = (
|
2025-12-04 19:40:01 +03:00
|
|
|
|
t(lang, 'tasks.header', i=current_index + 1, n=len(tasks)) + "\n\n" +
|
2025-12-04 14:30:06 +03:00
|
|
|
|
f"{task['question']}\n"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if task.get('transcription'):
|
|
|
|
|
|
task_text += f"🔊 [{task['transcription']}]\n"
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
task_text += t(lang, 'tasks.write_answer')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
|
|
|
|
# Показываем индикатор проверки
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Язык пользователя
|
|
|
|
|
|
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'))
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
# Проверяем ответ через 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:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
result_text = t(lang, 'tasks.correct') + "\n\n"
|
2025-12-04 14:30:06 +03:00
|
|
|
|
correct_count += 1
|
|
|
|
|
|
else:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
result_text = t(lang, 'tasks.incorrect') + "\n\n"
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
result_text += f"{t(lang, 'tasks.your_answer')}: <i>{user_answer}</i>\n"
|
|
|
|
|
|
result_text += f"{t(lang, 'tasks.right_answer')}: <b>{task['correct_answer']}</b>\n\n"
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
if feedback:
|
|
|
|
|
|
result_text += f"💬 {feedback}\n\n"
|
|
|
|
|
|
|
2025-12-06 21:29:41 +03:00
|
|
|
|
# Показываем пример использования если есть
|
|
|
|
|
|
example = task.get('example', '')
|
|
|
|
|
|
example_translation = task.get('example_translation', '')
|
|
|
|
|
|
if example:
|
|
|
|
|
|
result_text += f"📖 {t(lang, 'tasks.example_label')}:\n"
|
|
|
|
|
|
result_text += f"<i>{example}</i>\n"
|
|
|
|
|
|
if example_translation:
|
|
|
|
|
|
result_text += f"<i>({example_translation})</i>\n"
|
|
|
|
|
|
result_text += "\n"
|
|
|
|
|
|
|
2025-12-04 14:30:06 +03:00
|
|
|
|
# Сохраняем результат в БД
|
|
|
|
|
|
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
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Показываем результат и кнопку "Далее"
|
2025-12-05 20:15:47 +03:00
|
|
|
|
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)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
await message.answer(result_text, reply_markup=keyboard)
|
2025-12-04 16:44:16 +03:00
|
|
|
|
# После показа результата ждём нажатия кнопки – переключаемся в состояние doing_tasks
|
|
|
|
|
|
await state.set_state(TaskStates.doing_tasks)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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()
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-05 20:15:47 +03:00
|
|
|
|
@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', '')
|
2025-12-06 21:29:41 +03:00
|
|
|
|
example = task.get('example', '') # Пример использования как контекст
|
|
|
|
|
|
example_translation = task.get('example_translation', '') # Перевод примера
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
# Добавляем слово в словарь
|
2025-12-06 21:29:41 +03:00
|
|
|
|
new_word = await VocabularyService.add_word(
|
2025-12-05 20:15:47 +03:00
|
|
|
|
session=session,
|
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
|
word_original=word,
|
|
|
|
|
|
word_translation=translation,
|
|
|
|
|
|
source_lang=user.learning_language,
|
2025-12-07 16:35:08 +03:00
|
|
|
|
translation_lang=get_user_translation_lang(user),
|
2025-12-05 20:15:47 +03:00
|
|
|
|
transcription=transcription,
|
|
|
|
|
|
source=WordSource.AI_TASK
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-06 21:29:41 +03:00
|
|
|
|
# Сохраняем перевод в таблицу 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
|
|
|
|
|
|
}]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-05 20:15:47 +03:00
|
|
|
|
await callback.answer(t(lang, 'tasks.word_added', word=word), show_alert=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 16:44:16 +03:00
|
|
|
|
@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)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
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'))
|
2025-12-04 16:44:16 +03:00
|
|
|
|
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()
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Определяем язык пользователя
|
|
|
|
|
|
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'))
|
2025-12-04 16:44:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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()
|
2025-12-04 19:40:01 +03:00
|
|
|
|
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'))
|
2025-12-04 16:44:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 14:30:06 +03:00
|
|
|
|
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)
|
2025-12-07 16:35:08 +03:00
|
|
|
|
user_id = data.get('user_id')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
accuracy = int((correct_count / total_count) * 100) if total_count > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
# Определяем эмодзи на основе результата
|
|
|
|
|
|
if accuracy >= 90:
|
|
|
|
|
|
emoji = "🏆"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
comment_key = 'excellent'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
elif accuracy >= 70:
|
|
|
|
|
|
emoji = "👍"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
comment_key = 'good'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
elif accuracy >= 50:
|
|
|
|
|
|
emoji = "📚"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
comment_key = 'average'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
else:
|
|
|
|
|
|
emoji = "💪"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
comment_key = 'poor'
|
|
|
|
|
|
|
2025-12-07 16:35:08 +03:00
|
|
|
|
# Язык пользователя (берём user_id из state, т.к. message может быть от бота)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
async with async_session_maker() as session:
|
2025-12-07 16:35:08 +03:00
|
|
|
|
user = await UserService.get_user_by_id(session, user_id)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
result_text = (
|
2025-12-04 19:40:01 +03:00
|
|
|
|
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')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await message.answer(t('ru', 'common.start_first'))
|
2025-12-04 14:30:06 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Получаем статистику
|
|
|
|
|
|
stats = await TaskService.get_user_stats(session, user.id)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
stats_text = (
|
2025-12-04 19:40:01 +03:00
|
|
|
|
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"
|
2025-12-04 14:30:06 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if stats['total_words'] == 0:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
stats_text += t(lang, 'stats.hint_add_words')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
elif stats['total_tasks'] == 0:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
stats_text += t(lang, 'stats.hint_first_task')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
else:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
stats_text += t(lang, 'stats.hint_keep_practice')
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
await message.answer(stats_text)
|