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:
@@ -111,6 +111,98 @@ class AIService:
|
||||
"difficulty": "A1"
|
||||
}
|
||||
|
||||
async def translate_word_with_contexts(
|
||||
self,
|
||||
word: str,
|
||||
source_lang: str = "en",
|
||||
translation_lang: str = "ru",
|
||||
max_translations: int = 3
|
||||
) -> Dict:
|
||||
"""
|
||||
Перевести слово и получить несколько переводов с контекстами
|
||||
|
||||
Args:
|
||||
word: Слово для перевода
|
||||
source_lang: Язык исходного слова (ISO2)
|
||||
translation_lang: Язык перевода (ISO2)
|
||||
max_translations: Максимальное количество переводов
|
||||
|
||||
Returns:
|
||||
Dict с переводами, каждый с примером предложения
|
||||
"""
|
||||
prompt = f"""Переведи слово/фразу "{word}" с языка {source_lang} на {translation_lang}.
|
||||
|
||||
Если у слова есть несколько значений в разных контекстах, дай до {max_translations} разных переводов.
|
||||
Для каждого перевода дай пример предложения, показывающий это значение.
|
||||
|
||||
Верни ответ строго в формате JSON:
|
||||
{{
|
||||
"word": "исходное слово на {source_lang}",
|
||||
"transcription": "транскрипция в IPA (если применимо)",
|
||||
"category": "основная категория слова",
|
||||
"difficulty": "уровень сложности (A1/A2/B1/B2/C1/C2)",
|
||||
"translations": [
|
||||
{{
|
||||
"translation": "перевод 1 на {translation_lang}",
|
||||
"context": "пример предложения на {source_lang}, показывающий это значение",
|
||||
"context_translation": "перевод примера на {translation_lang}",
|
||||
"is_primary": true
|
||||
}},
|
||||
{{
|
||||
"translation": "перевод 2 на {translation_lang} (если есть другое значение)",
|
||||
"context": "пример предложения на {source_lang}",
|
||||
"context_translation": "перевод примера на {translation_lang}",
|
||||
"is_primary": false
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
Важно:
|
||||
- Первый перевод должен быть самым распространённым (is_primary: true)
|
||||
- Давай разные переводы только если слово реально имеет разные значения
|
||||
- Примеры должны чётко показывать конкретное значение слова
|
||||
- Верни только JSON, без дополнительного текста"""
|
||||
|
||||
try:
|
||||
logger.info(f"[GPT Request] translate_word_with_contexts: word='{word}', source='{source_lang}', to='{translation_lang}'")
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "Ты - помощник для изучения языков. Отвечай только в формате JSON."},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
|
||||
response_data = await self._make_openai_request(messages, temperature=0.3)
|
||||
|
||||
import json
|
||||
content = response_data['choices'][0]['message']['content']
|
||||
# Убираем markdown обёртку если есть
|
||||
if content.startswith('```'):
|
||||
content = content.split('\n', 1)[1] if '\n' in content else content[3:]
|
||||
if content.endswith('```'):
|
||||
content = content[:-3]
|
||||
content = content.strip()
|
||||
|
||||
result = json.loads(content)
|
||||
translations_count = len(result.get('translations', []))
|
||||
logger.info(f"[GPT Response] translate_word_with_contexts: success, {translations_count} translations")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[GPT Error] translate_word_with_contexts: {type(e).__name__}: {str(e)}")
|
||||
# Fallback в случае ошибки
|
||||
return {
|
||||
"word": word,
|
||||
"transcription": "",
|
||||
"category": "unknown",
|
||||
"difficulty": "A1",
|
||||
"translations": [{
|
||||
"translation": "Ошибка перевода",
|
||||
"context": "",
|
||||
"context_translation": "",
|
||||
"is_primary": True
|
||||
}]
|
||||
}
|
||||
|
||||
async def translate_words_batch(
|
||||
self,
|
||||
words: List[str],
|
||||
@@ -294,7 +386,15 @@ class AIService:
|
||||
"translation": f"Мне нравится {word} каждый день."
|
||||
}
|
||||
|
||||
async def generate_thematic_words(self, theme: str, level: str = "B1", count: int = 10, learning_lang: str = "en", translation_lang: str = "ru") -> List[Dict]:
|
||||
async def generate_thematic_words(
|
||||
self,
|
||||
theme: str,
|
||||
level: str = "B1",
|
||||
count: int = 10,
|
||||
learning_lang: str = "en",
|
||||
translation_lang: str = "ru",
|
||||
exclude_words: List[str] = None
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Сгенерировать подборку слов по теме
|
||||
|
||||
@@ -302,12 +402,28 @@ class AIService:
|
||||
theme: Тема для подборки слов
|
||||
level: Уровень сложности (A1-C2)
|
||||
count: Количество слов
|
||||
learning_lang: Язык изучения
|
||||
translation_lang: Язык перевода
|
||||
exclude_words: Список слов для исключения (уже известные)
|
||||
|
||||
Returns:
|
||||
Список словарей с информацией о словах
|
||||
"""
|
||||
prompt = f"""Создай подборку из {count} слов на языке {learning_lang} по теме "{theme}" для уровня {level}. Переводы дай на {translation_lang}.
|
||||
exclude_instruction = ""
|
||||
exclude_words_set = set()
|
||||
if exclude_words:
|
||||
# Ограничиваем список до 100 слов чтобы не раздувать промпт
|
||||
words_sample = exclude_words[:100]
|
||||
exclude_words_set = set(w.lower() for w in exclude_words)
|
||||
exclude_instruction = f"""
|
||||
|
||||
⚠️ ЗАПРЕЩЁННЫЕ СЛОВА (НЕ ИСПОЛЬЗОВАТЬ!):
|
||||
{', '.join(words_sample)}
|
||||
|
||||
Эти слова пользователь уже знает. ОБЯЗАТЕЛЬНО выбери ДРУГИЕ слова!"""
|
||||
|
||||
prompt = f"""Создай подборку из {count} слов на языке {learning_lang} по теме "{theme}" для уровня {level}. Переводы дай на {translation_lang}.
|
||||
{exclude_instruction}
|
||||
Верни ответ в формате JSON:
|
||||
{{
|
||||
"theme": "{theme}",
|
||||
@@ -316,7 +432,8 @@ class AIService:
|
||||
"word": "слово на {learning_lang}",
|
||||
"translation": "перевод на {translation_lang}",
|
||||
"transcription": "транскрипция в IPA (если применимо)",
|
||||
"example": "пример использования на {learning_lang}"
|
||||
"example": "пример использования на {learning_lang}",
|
||||
"example_translation": "перевод примера на {translation_lang}"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
@@ -339,9 +456,21 @@ class AIService:
|
||||
|
||||
import json
|
||||
result = json.loads(response_data['choices'][0]['message']['content'])
|
||||
words_count = len(result.get('words', []))
|
||||
logger.info(f"[GPT Response] generate_thematic_words: success, generated {words_count} words")
|
||||
return result.get('words', [])
|
||||
words = result.get('words', [])
|
||||
|
||||
# Фильтруем слова которые AI мог вернуть несмотря на инструкцию
|
||||
if exclude_words_set:
|
||||
filtered_words = [
|
||||
w for w in words
|
||||
if w.get('word', '').lower() not in exclude_words_set
|
||||
]
|
||||
filtered_count = len(words) - len(filtered_words)
|
||||
if filtered_count > 0:
|
||||
logger.info(f"[GPT Response] generate_thematic_words: filtered out {filtered_count} excluded words")
|
||||
words = filtered_words
|
||||
|
||||
logger.info(f"[GPT Response] generate_thematic_words: success, generated {len(words)} words")
|
||||
return words
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[GPT Error] generate_thematic_words: {type(e).__name__}: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user