Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня

Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)

Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 15:46:02 +03:00
parent 2c51fa19b6
commit 72a63eeda5
13 changed files with 1781 additions and 23 deletions

View File

@@ -171,6 +171,361 @@ class AIService:
"translation": f"Мне нравится {word} каждый день."
}
async def generate_thematic_words(self, theme: str, level: str = "B1", count: int = 10) -> List[Dict]:
"""
Сгенерировать подборку слов по теме
Args:
theme: Тема для подборки слов
level: Уровень сложности (A1-C2)
count: Количество слов
Returns:
Список словарей с информацией о словах
"""
prompt = f"""Создай подборку из {count} английских слов по теме "{theme}" для уровня {level}.
Верни ответ в формате JSON:
{{
"theme": "{theme}",
"words": [
{{
"word": "английское слово",
"translation": "перевод на русский",
"transcription": "транскрипция в IPA",
"example": "пример использования на английском"
}}
]
}}
Слова должны быть:
- Полезными и часто используемыми
- Соответствовать уровню {level}
- Связаны с темой "{theme}"
- Разнообразными (существительные, глаголы, прилагательные)"""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - преподаватель английского языка. Подбирай полезные и актуальные слова."},
{"role": "user", "content": prompt}
],
temperature=0.7,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result.get('words', [])
except Exception as e:
return []
async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15) -> List[Dict]:
"""
Извлечь ключевые слова из текста для изучения
Args:
text: Текст на английском языке
level: Уровень пользователя (A1-C2)
max_words: Максимальное количество слов для извлечения
Returns:
Список словарей с информацией о словах
"""
prompt = f"""Проанализируй следующий английский текст и извлеки из него до {max_words} самых полезных слов для изучения на уровне {level}.
Текст:
{text}
Верни ответ в формате JSON:
{{
"words": [
{{
"word": "английское слово (в базовой форме)",
"translation": "перевод на русский",
"transcription": "транскрипция в IPA",
"context": "предложение из текста, где используется это слово"
}}
]
}}
Критерии отбора слов:
- Выбирай самые важные и полезные слова из текста
- Слова должны быть интересны для уровня {level}
- Не включай простейшие слова (a, the, is, и т.д.)
- Слова должны быть в базовой форме (инфинитив для глаголов, ед.число для существительных)
- Разнообразие: существительные, глаголы, прилагательные, устойчивые выражения"""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - преподаватель английского языка. Помогаешь извлекать полезные слова для изучения из текстов."},
{"role": "user", "content": prompt}
],
temperature=0.5,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result.get('words', [])
except Exception as e:
return []
async def start_conversation(self, scenario: str, level: str = "B1") -> Dict:
"""
Начать диалоговую практику с AI
Args:
scenario: Сценарий диалога (restaurant, shopping, travel, etc.)
level: Уровень пользователя (A1-C2)
Returns:
Dict с начальной репликой и контекстом
"""
scenarios = {
"restaurant": "ресторан - заказ еды",
"shopping": "магазин - покупка одежды",
"travel": "аэропорт/отель - путешествие",
"work": "офис - рабочая встреча",
"doctor": "клиника - визит к врачу",
"casual": "повседневный разговор"
}
scenario_desc = scenarios.get(scenario, "повседневный разговор")
prompt = f"""Ты - собеседник для практики английского языка уровня {level}.
Начни диалог в сценарии: {scenario_desc}.
Верни ответ в формате JSON:
{{
"message": "твоя первая реплика на английском",
"translation": "перевод на русский",
"context": "краткое описание ситуации на русском",
"suggestions": ["подсказка 1", "подсказка 2", "подсказка 3"]
}}
Требования:
- Говори естественно, используй уровень {level}
- Создай интересную ситуацию
- Задай вопрос или начни разговор
- Подсказки должны помочь пользователю ответить"""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - дружелюбный собеседник для практики английского. Веди естественный диалог."},
{"role": "user", "content": prompt}
],
temperature=0.8,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result
except Exception as e:
return {
"message": "Hello! How are you today?",
"translation": "Привет! Как дела сегодня?",
"context": "Повседневный разговор",
"suggestions": ["I'm fine, thank you!", "Good, and you?", "Not bad!"]
}
async def continue_conversation(
self,
conversation_history: List[Dict],
user_message: str,
scenario: str,
level: str = "B1"
) -> Dict:
"""
Продолжить диалог и проверить ответ пользователя
Args:
conversation_history: История диалога
user_message: Сообщение пользователя
scenario: Сценарий диалога
level: Уровень пользователя
Returns:
Dict с ответом AI, проверкой и подсказками
"""
# Формируем историю для контекста
history_text = "\n".join([
f"{'AI' if msg['role'] == 'assistant' else 'User'}: {msg['content']}"
for msg in conversation_history[-6:] # Последние 6 сообщений
])
prompt = f"""Ты ведешь диалог на английском языке уровня {level} в сценарии "{scenario}".
История диалога:
{history_text}
User: {user_message}
Верни ответ в формате JSON:
{{
"response": "твой ответ на английском",
"translation": "перевод твоего ответа на русский",
"feedback": {{
"has_errors": true/false,
"corrections": "исправления ошибок пользователя (если есть)",
"comment": "краткий комментарий об ответе пользователя"
}},
"suggestions": ["подсказка 1 для следующего ответа", "подсказка 2"]
}}
Требования:
- Продолжай естественный диалог
- Если у пользователя есть грамматические или лексические ошибки, укажи их в corrections
- Будь дружелюбным и поддерживающим
- Используй лексику уровня {level}"""
try:
# Формируем сообщения для API
messages = [
{"role": "system", "content": f"Ты - дружелюбный собеседник для практики английского языка уровня {level}. Веди естественный диалог и помогай исправлять ошибки."}
]
# Добавляем историю
for msg in conversation_history[-6:]:
messages.append(msg)
# Добавляем текущее сообщение пользователя
messages.append({"role": "user", "content": user_message})
# Добавляем инструкцию для форматирования ответа
messages.append({"role": "user", "content": prompt})
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.8,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result
except Exception as e:
return {
"response": "I see. Tell me more about that.",
"translation": "Понятно. Расскажи мне больше об этом.",
"feedback": {
"has_errors": False,
"corrections": "",
"comment": "Good!"
},
"suggestions": ["Sure!", "Well...", "Actually..."]
}
async def generate_level_test(self) -> List[Dict]:
"""
Сгенерировать тест для определения уровня английского
Returns:
Список из 7 вопросов разной сложности
"""
prompt = """Создай тест из 7 вопросов для определения уровня английского языка (A1-C2).
Верни ответ в формате JSON:
{
"questions": [
{
"question": "текст вопроса на английском",
"question_ru": "перевод вопроса на русский",
"options": ["вариант A", "вариант B", "вариант C", "вариант D"],
"correct": 0,
"level": "A1"
}
]
}
Требования:
- Вопросы 1-2: уровень A1 (базовый)
- Вопросы 3-4: уровень A2-B1 (элементарный-средний)
- Вопросы 5-6: уровень B2-C1 (продвинутый)
- Вопрос 7: уровень C2 (профессиональный)
- Каждый вопрос с 4 вариантами ответа
- correct - индекс правильного ответа (0-3)
- Вопросы на грамматику, лексику и понимание"""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - эксперт по тестированию уровня английского языка. Создавай объективные тесты."},
{"role": "user", "content": prompt}
],
temperature=0.7,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result.get('questions', [])
except Exception as e:
# Fallback с базовыми вопросами
return [
{
"question": "What is your name?",
"question_ru": "Как тебя зовут?",
"options": ["My name is", "I am name", "Name my is", "Is name my"],
"correct": 0,
"level": "A1"
},
{
"question": "I ___ to school every day.",
"question_ru": "Я ___ в школу каждый день.",
"options": ["go", "goes", "going", "went"],
"correct": 0,
"level": "A1"
},
{
"question": "She ___ been to Paris twice.",
"question_ru": "Она ___ в Париже дважды.",
"options": ["have", "has", "had", "having"],
"correct": 1,
"level": "A2"
},
{
"question": "If I ___ rich, I would travel the world.",
"question_ru": "Если бы я был богат, я бы путешествовал по миру.",
"options": ["am", "was", "were", "be"],
"correct": 2,
"level": "B1"
},
{
"question": "The project ___ by next Monday.",
"question_ru": "Проект ___ к следующему понедельнику.",
"options": ["will complete", "will be completed", "completes", "is completing"],
"correct": 1,
"level": "B2"
},
{
"question": "Had I known about the meeting, I ___ attended.",
"question_ru": "Если бы я знал о встрече, я бы посетил.",
"options": ["would have", "will have", "would", "will"],
"correct": 0,
"level": "C1"
},
{
"question": "The nuances of his argument were so ___ that few could grasp them.",
"question_ru": "Нюансы его аргумента были настолько ___, что немногие могли их понять.",
"options": ["subtle", "obvious", "simple", "clear"],
"correct": 0,
"level": "C2"
}
]
# Глобальный экземпляр сервиса
ai_service = AIService()

View File

@@ -0,0 +1,141 @@
import logging
from datetime import datetime, timedelta
from typing import List
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import User
from database.db import async_session_maker
logger = logging.getLogger(__name__)
class ReminderService:
"""Сервис для управления напоминаниями"""
def __init__(self, bot):
self.bot = bot
self.scheduler = AsyncIOScheduler()
def start(self):
"""Запустить планировщик"""
# Проверяем напоминания каждые 5 минут
self.scheduler.add_job(
self.check_and_send_reminders,
trigger='interval',
minutes=5,
id='check_reminders',
replace_existing=True
)
self.scheduler.start()
logger.info("Планировщик напоминаний запущен")
def shutdown(self):
"""Остановить планировщик"""
self.scheduler.shutdown()
logger.info("Планировщик напоминаний остановлен")
async def check_and_send_reminders(self):
"""Проверить и отправить напоминания пользователям"""
try:
async with async_session_maker() as session:
# Получаем всех пользователей с включенными напоминаниями
result = await session.execute(
select(User).where(
User.reminders_enabled == True,
User.daily_task_time.isnot(None)
)
)
users = list(result.scalars().all())
current_time = datetime.utcnow()
for user in users:
if await self._should_send_reminder(user, current_time):
await self._send_reminder(user, session)
except Exception as e:
logger.error(f"Ошибка при проверке напоминаний: {e}")
async def _should_send_reminder(self, user: User, current_time: datetime) -> bool:
"""
Проверить, нужно ли отправлять напоминание пользователю
Args:
user: Пользователь
current_time: Текущее время (UTC)
Returns:
True если нужно отправить
"""
if not user.daily_task_time:
return False
# Парсим время напоминания (формат HH:MM)
try:
hour, minute = map(int, user.daily_task_time.split(':'))
except:
return False
# Создаем datetime для времени напоминания сегодня (UTC)
reminder_time = current_time.replace(hour=hour, minute=minute, second=0, microsecond=0)
# Проверяем, не отправляли ли мы уже напоминание сегодня
if user.last_reminder_sent:
last_sent_date = user.last_reminder_sent.date()
current_date = current_time.date()
# Если уже отправляли сегодня, не отправляем снова
if last_sent_date == current_date:
return False
# Проверяем, наступило ли время напоминания (с погрешностью 5 минут)
time_diff = abs((current_time - reminder_time).total_seconds())
return time_diff < 300 # 5 минут в секундах
async def _send_reminder(self, user: User, session: AsyncSession):
"""
Отправить напоминание пользователю
Args:
user: Пользователь
session: Сессия базы данных
"""
try:
message_text = (
"⏰ <b>Время для практики!</b>\n\n"
"Не забудь потренироваться сегодня:\n"
"• /task - выполни задания\n"
"• /practice - попрактикуй диалог\n"
"• /words - добавь новые слова\n\n"
"💪 Регулярная практика - ключ к успеху!"
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=message_text
)
# Обновляем время последнего напоминания
user.last_reminder_sent = datetime.utcnow()
await session.commit()
logger.info(f"Напоминание отправлено пользователю {user.telegram_id}")
except Exception as e:
logger.error(f"Ошибка при отправке напоминания пользователю {user.telegram_id}: {e}")
# Глобальный экземпляр сервиса (будет инициализирован в main.py)
reminder_service: ReminderService = None
def init_reminder_service(bot):
"""Инициализировать сервис напоминаний"""
global reminder_service
reminder_service = ReminderService(bot)
return reminder_service

View File

@@ -121,3 +121,23 @@ class VocabularyService:
.where(Vocabulary.word_original.ilike(f"%{word}%"))
)
return result.scalar_one_or_none()
@staticmethod
async def get_word_by_original(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
"""
Получить слово по точному совпадению
Args:
session: Сессия базы данных
user_id: ID пользователя
word: Слово для поиска (точное совпадение)
Returns:
Объект слова или None
"""
result = await session.execute(
select(Vocabulary)
.where(Vocabulary.user_id == user_id)
.where(Vocabulary.word_original == word.lower())
)
return result.scalar_one_or_none()