feat: multiple translations with context, improved task examples

- Add WordTranslation model for storing multiple translations per word
- AI generates translations with example sentences and their translations
- Show example usage after answering tasks (learning + interface language)
- Save translations to word_translations table when adding words from tasks
- Improve word exclusion in new_words mode (stronger prompt + client filtering)
- Add migration for word_translations table

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-06 21:29:41 +03:00
parent 63e2615243
commit d937b37a3b
10 changed files with 543 additions and 30 deletions

View File

@@ -109,6 +109,18 @@ async def start_new_words_tasks(callback: CallbackQuery, state: FSMContext):
# Показываем индикатор загрузки
await callback.message.edit_text(t(lang, 'tasks.generating_new'))
# Получаем слова для исключения:
# 1. Все слова из словаря пользователя
vocab_words = await VocabularyService.get_all_user_word_strings(
session, user.id, learning_lang=user.learning_language
)
# 2. Слова из предыдущих заданий new_words, на которые ответили правильно
correct_task_words = await TaskService.get_correctly_answered_words(
session, user.id
)
# Объединяем списки исключений
exclude_words = list(set(vocab_words + correct_task_words))
# Генерируем новые слова через AI
words = await ai_service.generate_thematic_words(
theme="random everyday vocabulary",
@@ -116,6 +128,7 @@ async def start_new_words_tasks(callback: CallbackQuery, state: FSMContext):
count=5,
learning_lang=user.learning_language,
translation_lang=user.language_interface,
exclude_words=exclude_words if exclude_words else None,
)
if not words:
@@ -132,7 +145,9 @@ async def start_new_words_tasks(callback: CallbackQuery, state: FSMContext):
'question': f"{translate_prompt}: {word.get('word', '')}",
'word': word.get('word', ''),
'correct_answer': word.get('translation', ''),
'transcription': word.get('transcription', '')
'transcription': word.get('transcription', ''),
'example': word.get('example', ''), # Пример на изучаемом языке
'example_translation': word.get('example_translation', '') # Перевод примера
})
await state.update_data(
@@ -226,6 +241,16 @@ async def process_answer(message: Message, state: FSMContext):
if feedback:
result_text += f"💬 {feedback}\n\n"
# Показываем пример использования если есть
example = task.get('example', '')
example_translation = task.get('example_translation', '')
if example:
result_text += f"📖 {t(lang, 'tasks.example_label')}:\n"
result_text += f"<i>{example}</i>\n"
if example_translation:
result_text += f"<i>({example_translation})</i>\n"
result_text += "\n"
# Сохраняем результат в БД
async with async_session_maker() as session:
await TaskService.save_task_result(
@@ -298,6 +323,8 @@ async def add_task_word(callback: CallbackQuery, state: FSMContext):
word = task.get('word', '')
translation = task.get('correct_answer', '')
transcription = task.get('transcription', '')
example = task.get('example', '') # Пример использования как контекст
example_translation = task.get('example_translation', '') # Перевод примера
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
@@ -316,7 +343,7 @@ async def add_task_word(callback: CallbackQuery, state: FSMContext):
return
# Добавляем слово в словарь
await VocabularyService.add_word(
new_word = await VocabularyService.add_word(
session=session,
user_id=user.id,
word_original=word,
@@ -327,6 +354,18 @@ async def add_task_word(callback: CallbackQuery, state: FSMContext):
source=WordSource.AI_TASK
)
# Сохраняем перевод в таблицу word_translations
await VocabularyService.add_translations_bulk(
session=session,
vocabulary_id=new_word.id,
translations=[{
'translation': translation,
'context': example if example else None,
'context_translation': example_translation if example_translation else None,
'is_primary': True
}]
)
await callback.answer(t(lang, 'tasks.word_added', word=word), show_alert=True)