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.vocabulary_service import VocabularyService from services.ai_service import ai_service from utils.i18n import t, get_user_lang, get_user_translation_lang from utils.levels import get_user_level_for_language router = Router() class WordsStates(StatesGroup): """Состояния для работы с тематическими подборками""" viewing_words = State() @router.message(Command("words")) async def cmd_words(message: Message, state: FSMContext): """Обработчик команды /words [тема]""" 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 # Извлекаем тему из команды command_parts = message.text.split(maxsplit=1) if len(command_parts) < 2: lang = user.language_interface or 'ru' help_text = ( t(lang, 'words.help_title') + "\n\n" + t(lang, 'words.help_usage') + "\n\n" + t(lang, 'words.help_examples') + "\n\n" + t(lang, 'words.help_note') ) await message.answer(help_text) return theme = command_parts[1].strip() await generate_words_for_theme(message, state, theme, message.from_user.id) async def generate_words_for_theme(message: Message, state: FSMContext, theme: str, user_id: int): """Генерация слов по теме (вызывается из cmd_words и callback)""" async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, user_id) # Показываем индикатор генерации lang = user.language_interface or 'ru' generating_msg = await message.answer(t(lang, 'words.generating', theme=theme)) # Генерируем слова через AI current_level = get_user_level_for_language(user) words = await ai_service.generate_thematic_words( theme=theme, level=current_level, count=10, learning_lang=user.learning_language, translation_lang=get_user_translation_lang(user), user_id=user.id ) await generating_msg.delete() if not words: await message.answer(t(lang, 'words.generate_failed')) return # Сохраняем данные в состоянии await state.update_data( theme=theme, words=words, user_id=user.id, level=current_level ) await state.set_state(WordsStates.viewing_words) # Показываем подборку await show_words_list(message, words, theme, user_id) async def show_words_list(message: Message, words: list, theme: str, user_id: int): """Показать список слов с кнопками для добавления""" # Определяем язык интерфейса для заголовка/подсказок async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, user_id) lang = (user.language_interface if user else 'ru') or 'ru' text = t(lang, 'words.header', theme=theme) + "\n\n" for idx, word_data in enumerate(words, 1): text += ( f"{idx}. {word_data['word']} " f"[{word_data.get('transcription', '')}]\n" f" {word_data['translation']}\n" f" {word_data.get('example', '')}\n\n" ) text += t(lang, 'words.choose') # Создаем кнопки для каждого слова (по 2 в ряд) keyboard = [] for idx, word_data in enumerate(words): button = InlineKeyboardButton( text=f"➕ {word_data['word']}", callback_data=f"add_word_{idx}" ) # Добавляем по 2 кнопки в ряд if len(keyboard) == 0 or len(keyboard[-1]) == 2: keyboard.append([button]) else: keyboard[-1].append(button) # Кнопка "Добавить все" keyboard.append([ InlineKeyboardButton(text=t(lang, 'words.add_all_btn'), callback_data="add_all_words") ]) # Кнопка "Закрыть" keyboard.append([ InlineKeyboardButton(text=t(lang, 'words.close_btn'), callback_data="close_words") ]) reply_markup = InlineKeyboardMarkup(inline_keyboard=keyboard) await message.answer(text, reply_markup=reply_markup) @router.callback_query(F.data.startswith("add_word_"), WordsStates.viewing_words) async def add_single_word(callback: CallbackQuery, state: FSMContext): """Добавить одно слово из подборки""" # Отвечаем сразу, операция может занять время await callback.answer() word_index = int(callback.data.split("_")[2]) data = await state.get_data() words = data.get('words', []) user_id = data.get('user_id') if word_index >= len(words): 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.answer(t(lang, 'words.err_not_found')) return word_data = words[word_index] async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) # Проверяем, нет ли уже такого слова existing = await VocabularyService.get_word_by_original( session, user_id, word_data['word'], source_lang=user.learning_language ) if existing: lang = get_user_lang(user) await callback.answer(t(lang, 'words.already_exists', word=word_data['word']), show_alert=True) return # Добавляем слово translation_lang = get_user_translation_lang(user) await VocabularyService.add_word( session=session, user_id=user_id, word_original=word_data['word'], word_translation=word_data['translation'], source_lang=user.learning_language if user else None, translation_lang=translation_lang, transcription=word_data.get('transcription'), difficulty_level=data.get('level'), source=WordSource.SUGGESTED ) 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, 'words.added_single', word=word_data['word'])) @router.callback_query(F.data == "add_all_words", WordsStates.viewing_words) async def add_all_words(callback: CallbackQuery, state: FSMContext): """Добавить все слова из подборки""" # Сразу отвечаем на callback, так как добавление может занять время await callback.answer() data = await state.get_data() words = data.get('words', []) user_id = data.get('user_id') added_count = 0 skipped_count = 0 async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) for word_data in words: # Проверяем, нет ли уже такого слова existing = await VocabularyService.get_word_by_original( session, user_id, word_data['word'], source_lang=user.learning_language ) if existing: skipped_count += 1 continue # Добавляем слово translation_lang = get_user_translation_lang(user) await VocabularyService.add_word( session=session, user_id=user_id, word_original=word_data['word'], word_translation=word_data['translation'], source_lang=user.learning_language if user else None, translation_lang=translation_lang, transcription=word_data.get('transcription'), difficulty_level=data.get('level'), source=WordSource.SUGGESTED ) added_count += 1 lang = (user.language_interface if user else 'ru') or 'ru' result_text = t(lang, 'import.added_count', n=added_count) if skipped_count > 0: result_text += "\n" + t(lang, 'import.skipped_count', n=skipped_count) await callback.message.edit_reply_markup(reply_markup=None) await callback.message.answer(result_text) await state.clear() @router.callback_query(F.data == "close_words", WordsStates.viewing_words) async def close_words(callback: CallbackQuery, state: FSMContext): """Закрыть подборку слов""" await callback.message.delete() await state.clear() await callback.answer()