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>
This commit is contained in:
@@ -15,7 +15,8 @@ class TaskService:
|
||||
async def generate_translation_tasks(
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
count: int = 5
|
||||
count: int = 5,
|
||||
learning_lang: str = 'en'
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Генерация заданий на перевод слов
|
||||
@@ -28,10 +29,11 @@ class TaskService:
|
||||
Returns:
|
||||
Список заданий
|
||||
"""
|
||||
# Получаем слова пользователя
|
||||
# Получаем слова пользователя на изучаемом языке
|
||||
result = await session.execute(
|
||||
select(Vocabulary)
|
||||
.where(Vocabulary.user_id == user_id)
|
||||
.where(Vocabulary.source_lang == learning_lang)
|
||||
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
||||
.limit(count * 2) # Берем больше, чтобы было из чего выбрать
|
||||
)
|
||||
@@ -90,10 +92,11 @@ class TaskService:
|
||||
Returns:
|
||||
Список заданий разных типов
|
||||
"""
|
||||
# Получаем слова пользователя
|
||||
# Получаем слова пользователя на изучаемом языке
|
||||
result = await session.execute(
|
||||
select(Vocabulary)
|
||||
.where(Vocabulary.user_id == user_id)
|
||||
.where(Vocabulary.source_lang == learning_lang)
|
||||
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
||||
.limit(count * 2)
|
||||
)
|
||||
@@ -230,6 +233,159 @@ class TaskService:
|
||||
|
||||
return tasks
|
||||
|
||||
@staticmethod
|
||||
async def generate_tasks_by_type(
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
count: int = 5,
|
||||
task_type: str = 'mix',
|
||||
learning_lang: str = 'en',
|
||||
translation_lang: str = 'ru'
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Генерация заданий определённого типа
|
||||
|
||||
Args:
|
||||
session: Сессия базы данных
|
||||
user_id: ID пользователя
|
||||
count: Количество заданий
|
||||
task_type: Тип заданий (mix, word_translate, fill_blank, sentence_translate)
|
||||
learning_lang: Язык обучения
|
||||
translation_lang: Язык перевода
|
||||
|
||||
Returns:
|
||||
Список заданий
|
||||
"""
|
||||
# Получаем слова пользователя на изучаемом языке
|
||||
result = await session.execute(
|
||||
select(Vocabulary)
|
||||
.where(Vocabulary.user_id == user_id)
|
||||
.where(Vocabulary.source_lang == learning_lang)
|
||||
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
||||
.limit(count * 2)
|
||||
)
|
||||
words = list(result.scalars().all())
|
||||
|
||||
if not words:
|
||||
return []
|
||||
|
||||
# Выбираем случайные слова
|
||||
selected_words = random.sample(words, min(count, len(words)))
|
||||
|
||||
tasks = []
|
||||
for word in selected_words:
|
||||
# Получаем переводы из таблицы WordTranslation
|
||||
translations_result = await session.execute(
|
||||
select(WordTranslation)
|
||||
.where(WordTranslation.vocabulary_id == word.id)
|
||||
.order_by(WordTranslation.is_primary.desc())
|
||||
)
|
||||
translations = list(translations_result.scalars().all())
|
||||
|
||||
# Определяем тип задания
|
||||
if task_type == 'mix':
|
||||
chosen_type = random.choice(['word_translate', 'fill_blank', 'sentence_translate'])
|
||||
else:
|
||||
chosen_type = task_type
|
||||
|
||||
# Определяем правильный перевод
|
||||
correct_translation = word.word_translation
|
||||
if translations:
|
||||
primary = next((tr for tr in translations if tr.is_primary), translations[0] if translations else None)
|
||||
if primary:
|
||||
correct_translation = primary.translation
|
||||
|
||||
if chosen_type == 'word_translate':
|
||||
# Задание на перевод слова
|
||||
direction = random.choice(['learn_to_tr', 'tr_to_learn'])
|
||||
|
||||
# Локализация
|
||||
if translation_lang == 'en':
|
||||
prompt = "Translate the word:"
|
||||
elif translation_lang == 'ja':
|
||||
prompt = "単語を訳してください:"
|
||||
else:
|
||||
prompt = "Переведи слово:"
|
||||
|
||||
if direction == 'learn_to_tr':
|
||||
task = {
|
||||
'type': f'translate_to_{translation_lang}',
|
||||
'word_id': word.id,
|
||||
'question': f"{prompt} <b>{word.word_original}</b>",
|
||||
'word': word.word_original,
|
||||
'correct_answer': correct_translation,
|
||||
'transcription': word.transcription,
|
||||
'all_translations': [tr.translation for tr in translations] if translations else [correct_translation]
|
||||
}
|
||||
else:
|
||||
task = {
|
||||
'type': f'translate_to_{learning_lang}',
|
||||
'word_id': word.id,
|
||||
'question': f"{prompt} <b>{correct_translation}</b>",
|
||||
'word': correct_translation,
|
||||
'correct_answer': word.word_original,
|
||||
'transcription': word.transcription
|
||||
}
|
||||
|
||||
elif chosen_type == 'fill_blank':
|
||||
# Задание на заполнение пропуска
|
||||
sentence_data = await ai_service.generate_fill_in_sentence(
|
||||
word.word_original,
|
||||
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 = "Заполни пропуск:"
|
||||
|
||||
task = {
|
||||
'type': 'fill_in',
|
||||
'word_id': word.id,
|
||||
'question': (
|
||||
f"{fill_title}\n\n"
|
||||
f"<b>{sentence_data['sentence']}</b>\n\n"
|
||||
f"<i>{sentence_data.get('translation', '')}</i>"
|
||||
),
|
||||
'word': word.word_original,
|
||||
'correct_answer': sentence_data['answer'],
|
||||
'sentence': sentence_data['sentence']
|
||||
}
|
||||
|
||||
elif chosen_type == 'sentence_translate':
|
||||
# Задание на перевод предложения
|
||||
sentence_data = await ai_service.generate_sentence_for_translation(
|
||||
word.word_original,
|
||||
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 = "Слово"
|
||||
|
||||
task = {
|
||||
'type': 'sentence_translate',
|
||||
'word_id': word.id,
|
||||
'question': f"{sentence_title}\n\n<b>{sentence_data['sentence']}</b>\n\n📝 {word_hint}: <code>{word.word_original}</code> — {correct_translation}",
|
||||
'word': word.word_original,
|
||||
'correct_answer': sentence_data['translation'],
|
||||
'sentence': sentence_data['sentence']
|
||||
}
|
||||
|
||||
tasks.append(task)
|
||||
|
||||
return tasks
|
||||
|
||||
@staticmethod
|
||||
async def save_task_result(
|
||||
session: AsyncSession,
|
||||
|
||||
Reference in New Issue
Block a user