Files
tg_bot_language/bot/handlers/settings.py
mamonov.ep eb666ec9bc feat: мульти-провайдер AI, выбор типов заданий, настройка количества
- Добавлена поддержка нескольких AI провайдеров (OpenAI, Google Gemini)
- Добавлена админ-панель (/admin) для переключения AI моделей
- Добавлен AIModelService для управления моделями в БД
- Добавлен выбор типа заданий (микс, перевод слов, подстановка, перевод предложений)
- Добавлена настройка количества заданий (5-15)
- ai_service динамически выбирает провайдера на основе активной модели
- Обработка ограничений моделей (temperature, response_format)
- Очистка markdown обёртки из ответов Gemini

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 15:16:24 +03:00

359 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.fsm.context import FSMContext
from database.db import async_session_maker
from bot.handlers.start import main_menu_keyboard
from services.user_service import UserService
from utils.i18n import t, get_user_lang
from utils.levels import (
get_user_level_for_language,
get_available_levels,
get_level_system,
get_level_key_for_i18n,
)
router = Router()
def get_translation_language(user) -> str:
"""Получить язык перевода (translation_language или language_interface как fallback)"""
return getattr(user, 'translation_language', None) or getattr(user, 'language_interface', 'ru') or 'ru'
def get_settings_keyboard(user) -> InlineKeyboardMarkup:
"""Создать клавиатуру настроек"""
lang = get_user_lang(user)
ui_lang_code = getattr(user, 'language_interface', 'ru') or 'ru'
translation_lang_code = get_translation_language(user)
current_level = get_user_level_for_language(user)
tasks_count = getattr(user, 'tasks_count', 5) or 5
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
text=t(lang, 'settings.level_prefix') + f"{current_level}",
callback_data="settings_level"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.learning_prefix') + user.learning_language.upper(),
callback_data="settings_learning"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.interface_prefix') + t(lang, f'settings.lang_name.{ui_lang_code}'),
callback_data="settings_language"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.translation_prefix') + t(lang, f'settings.lang_name.{translation_lang_code}'),
callback_data="settings_translation"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.tasks_count_prefix') + str(tasks_count),
callback_data="settings_tasks_count"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.close'),
callback_data="settings_close"
)]
])
return keyboard
def get_level_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора уровня (CEFR или JLPT в зависимости от языка изучения)"""
lang = get_user_lang(user)
learning_lang = getattr(user, 'learning_language', 'en') or 'en'
available_levels = get_available_levels(learning_lang)
keyboard = []
for level in available_levels:
# Ключ локализации: settings.level.a1 или settings.jlpt.n5
i18n_key = get_level_key_for_i18n(learning_lang, level)
level_name = t(lang, i18n_key)
keyboard.append([InlineKeyboardButton(text=level_name, callback_data=f"set_level_{level}")])
keyboard.append([InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")])
return InlineKeyboardMarkup(inline_keyboard=keyboard)
def get_language_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора языка интерфейса"""
lang = get_user_lang(user)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ru'), callback_data="set_lang_ru")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.en'), callback_data="set_lang_en")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ja'), callback_data="set_lang_ja")],
[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]
])
return keyboard
def get_translation_language_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора языка перевода"""
lang = get_user_lang(user)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ru'), callback_data="set_translation_ru")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.en'), callback_data="set_translation_en")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ja'), callback_data="set_translation_ja")],
[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]
])
return keyboard
def get_learning_language_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора языка изучения"""
lang = get_user_lang(user)
options = [
("en", t(lang, 'settings.learning_lang.en')),
("ja", t(lang, 'settings.learning_lang.ja')),
# TODO: добавить позже
# ("es", t(lang, 'settings.learning_lang.es')),
# ("de", t(lang, 'settings.learning_lang.de')),
# ("fr", t(lang, 'settings.learning_lang.fr')),
]
keyboard = [[InlineKeyboardButton(text=label, callback_data=f"set_learning_{code}")] for code, label in options]
keyboard.append([InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")])
return InlineKeyboardMarkup(inline_keyboard=keyboard)
def get_tasks_count_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора количества заданий"""
lang = get_user_lang(user)
current_count = getattr(user, 'tasks_count', 5) or 5
# Создаём кнопки для каждого значения (5, 7, 10, 12, 15)
counts = [5, 7, 10, 12, 15]
keyboard = []
for count in counts:
marker = "" if count == current_count else ""
keyboard.append([InlineKeyboardButton(
text=f"{marker}{count}",
callback_data=f"set_tasks_count_{count}"
)])
keyboard.append([InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")])
return InlineKeyboardMarkup(inline_keyboard=keyboard)
@router.message(Command("settings"))
async def cmd_settings(message: Message):
"""Обработчик команды /settings"""
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)
ui_lang_code = getattr(user, 'language_interface', 'ru') or 'ru'
lang_value = t(lang, f'settings.lang_name.{ui_lang_code}')
current_level = get_user_level_for_language(user)
settings_text = (
t(lang, 'settings.title') +
t(lang, 'settings.level_prefix') + f"<b>{current_level}</b>\n" +
t(lang, 'settings.interface_prefix') + f"<b>{lang_value}</b>\n\n" +
t(lang, 'settings.choose')
)
await message.answer(settings_text, reply_markup=get_settings_keyboard(user))
@router.callback_query(F.data == "settings_level")
async def settings_level(callback: CallbackQuery):
"""Показать выбор уровня"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
learning_lang = getattr(user, 'learning_language', 'en') or 'en'
level_system = get_level_system(learning_lang)
# Выбираем правильное описание групп уровней
groups_key = 'settings.jlpt_groups' if level_system == 'jlpt' else 'settings.level_groups'
text = t(lang, 'settings.level_title') + t(lang, groups_key) + t(lang, 'settings.level_hint')
await callback.message.edit_text(text, reply_markup=get_level_keyboard(user))
await callback.answer()
@router.callback_query(F.data == "settings_learning")
async def settings_learning(callback: CallbackQuery):
"""Показать выбор языка изучения"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
await callback.message.edit_text(t(lang, 'settings.learning_title'), reply_markup=get_learning_language_keyboard(user))
await callback.answer()
@router.callback_query(F.data.startswith("set_learning_"))
async def set_learning_language(callback: CallbackQuery):
"""Установить язык изучения"""
code = callback.data.split("_")[-1]
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
await UserService.update_user_learning_language(session, user.id, code)
lang = get_user_lang(user)
text = t(lang, 'settings.learning_changed', code=code.upper())
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(lang, 'settings.back_to_settings'), callback_data="settings_back")]])
)
await callback.answer()
@router.callback_query(F.data.startswith("set_level_"))
async def set_level(callback: CallbackQuery):
"""Установить уровень (CEFR или JLPT)"""
level_str = callback.data.split("_")[-1] # A1, A2, B1, B2, C1, C2 или N5, N4, N3, N2, N1
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
# Передаём строковый уровень, UserService сам разберётся с системой
await UserService.update_user_level(session, user.id, level_str)
lang = get_user_lang(user)
msg = t(lang, 'settings.level_changed', level=level_str) + t(lang, 'settings.level_changed_hint')
await callback.message.edit_text(
msg,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(lang, 'settings.back_to_settings'), callback_data="settings_back")]])
)
await callback.answer()
@router.callback_query(F.data == "settings_language")
async def settings_language(callback: CallbackQuery):
"""Показать выбор языка"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
await callback.message.edit_text(
t(lang, 'settings.lang_title') + t(lang, 'settings.lang_desc'),
reply_markup=get_language_keyboard(user)
)
await callback.answer()
@router.callback_query(F.data.startswith("set_lang_"))
async def set_language(callback: CallbackQuery):
"""Установить язык"""
new_lang = callback.data.split("_")[-1] # ru | en | ja
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
await UserService.update_user_language(session, user.id, new_lang)
# Используем новый язык для сообщений
text = t(new_lang, 'settings.lang_changed')
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(new_lang, 'settings.back'), callback_data="settings_back")]])
)
# Обновляем клавиатуру чата на выбранный язык
await callback.message.answer(t(new_lang, 'settings.menu_updated'), reply_markup=main_menu_keyboard(new_lang))
await callback.answer()
@router.callback_query(F.data == "settings_translation")
async def settings_translation(callback: CallbackQuery):
"""Показать выбор языка перевода"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
await callback.message.edit_text(
t(lang, 'settings.translation_title') + t(lang, 'settings.translation_desc'),
reply_markup=get_translation_language_keyboard(user)
)
await callback.answer()
@router.callback_query(F.data.startswith("set_translation_"))
async def set_translation_language(callback: CallbackQuery):
"""Установить язык перевода"""
new_translation_lang = callback.data.split("_")[-1] # ru | en | ja
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
await UserService.update_user_translation_language(session, user.id, new_translation_lang)
lang = get_user_lang(user)
lang_name = t(lang, f'settings.lang_name.{new_translation_lang}')
text = t(lang, 'settings.translation_changed', lang_name=lang_name)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]])
)
await callback.answer()
@router.callback_query(F.data == "settings_tasks_count")
async def settings_tasks_count(callback: CallbackQuery):
"""Показать выбор количества заданий"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
await callback.message.edit_text(
t(lang, 'settings.tasks_count_title') + t(lang, 'settings.tasks_count_desc'),
reply_markup=get_tasks_count_keyboard(user)
)
await callback.answer()
@router.callback_query(F.data.startswith("set_tasks_count_"))
async def set_tasks_count(callback: CallbackQuery):
"""Установить количество заданий"""
new_count = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
await UserService.update_user_tasks_count(session, user.id, new_count)
lang = get_user_lang(user)
text = t(lang, 'settings.tasks_count_changed', count=new_count)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]])
)
await callback.answer()
@router.callback_query(F.data == "settings_back")
async def settings_back(callback: CallbackQuery):
"""Вернуться к настройкам"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
lang = get_user_lang(user)
ui_lang_code = getattr(user, 'language_interface', 'ru') or 'ru'
lang_value = t(lang, f'settings.lang_name.{ui_lang_code}')
current_level = get_user_level_for_language(user)
settings_text = (
t(lang, 'settings.title') +
t(lang, 'settings.level_prefix') + f"<b>{current_level}</b>\n" +
t(lang, 'settings.interface_prefix') + f"<b>{lang_value}</b>\n\n" +
t(lang, 'settings.choose')
)
await callback.message.edit_text(settings_text, reply_markup=get_settings_keyboard(user))
await callback.answer()
@router.callback_query(F.data == "settings_close")
async def settings_close(callback: CallbackQuery):
"""Закрыть настройки"""
await callback.message.delete()
await callback.answer()