feat: restructure menu and add file import
- Consolidate "Add word" menu with submenu (Manual, Thematic, Import) - Add file import support (.txt, .md) with AI batch translation - Add vocabulary pagination with navigation buttons - Add "Add word" button in tasks for new words mode - Fix undefined variables bug in vocabulary confirm handler - Add localization keys for add_menu in ru/en/ja 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,23 +5,27 @@ 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
|
||||
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"""
|
||||
"""Обработчик команды /task — показываем меню выбора режима"""
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
@@ -29,7 +33,39 @@ async def cmd_task(message: Message, state: FSMContext):
|
||||
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,
|
||||
@@ -37,7 +73,8 @@ async def cmd_task(message: Message, state: FSMContext):
|
||||
)
|
||||
|
||||
if not tasks:
|
||||
await message.answer(t(user.language_interface or 'ru', 'tasks.no_words'))
|
||||
await callback.message.edit_text(t(lang, 'tasks.no_words'))
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Сохраняем задания в состоянии
|
||||
@@ -45,12 +82,70 @@ async def cmd_task(message: Message, state: FSMContext):
|
||||
tasks=tasks,
|
||||
current_task_index=0,
|
||||
correct_count=0,
|
||||
user_id=user.id
|
||||
user_id=user.id,
|
||||
mode='vocabulary'
|
||||
)
|
||||
await state.set_state(TaskStates.doing_tasks)
|
||||
|
||||
# Показываем первое задание
|
||||
await show_current_task(message, state)
|
||||
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'))
|
||||
|
||||
# Генерируем новые слова через 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,
|
||||
)
|
||||
|
||||
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', '')
|
||||
})
|
||||
|
||||
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):
|
||||
@@ -162,10 +257,18 @@ async def process_answer(message: Message, state: FSMContext):
|
||||
)
|
||||
|
||||
# Показываем результат и кнопку "Далее"
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text=t(lang, 'tasks.next_btn'), callback_data="next_task")],
|
||||
[InlineKeyboardButton(text=t(lang, 'tasks.stop_btn'), callback_data="stop_tasks")]
|
||||
])
|
||||
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
|
||||
@@ -180,6 +283,53 @@ async def next_task(callback: CallbackQuery, state: FSMContext):
|
||||
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', '')
|
||||
|
||||
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
|
||||
|
||||
# Добавляем слово в словарь
|
||||
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
|
||||
)
|
||||
|
||||
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):
|
||||
"""Остановить выполнение заданий через кнопку"""
|
||||
|
||||
Reference in New Issue
Block a user