feat: restructure menu and add file import
- Consolidate "Add word" menu with submenu (Manual, Thematic, Import) - Add file import support (.txt, .md) with AI batch translation - Add vocabulary pagination with navigation buttons - Add "Add word" button in tasks for new words mode - Fix undefined variables bug in vocabulary confirm handler - Add localization keys for add_menu in ru/en/ja 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,92 @@ class AIService:
|
||||
"difficulty": "A1"
|
||||
}
|
||||
|
||||
async def translate_words_batch(
|
||||
self,
|
||||
words: List[str],
|
||||
source_lang: str = "en",
|
||||
translation_lang: str = "ru"
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Перевести список слов пакетно
|
||||
|
||||
Args:
|
||||
words: Список слов для перевода
|
||||
source_lang: Язык исходных слов (ISO2)
|
||||
translation_lang: Язык перевода (ISO2)
|
||||
|
||||
Returns:
|
||||
List[Dict] с переводами, транскрипциями
|
||||
"""
|
||||
if not words:
|
||||
return []
|
||||
|
||||
words_list = "\n".join(f"- {w}" for w in words[:50]) # Максимум 50 слов за раз
|
||||
|
||||
# Добавляем инструкцию для фуриганы если японский
|
||||
furigana_instruction = ""
|
||||
if source_lang == "ja":
|
||||
furigana_instruction = '\n "reading": "чтение хираганой (только для кандзи)",'
|
||||
|
||||
prompt = f"""Переведи следующие слова/фразы с языка {source_lang} на {translation_lang}:
|
||||
|
||||
{words_list}
|
||||
|
||||
Верни ответ строго в формате JSON массива:
|
||||
[
|
||||
{{
|
||||
"word": "исходное слово",
|
||||
"translation": "перевод",
|
||||
"transcription": "транскрипция (IPA или ромадзи для японского)",{furigana_instruction}
|
||||
}},
|
||||
...
|
||||
]
|
||||
|
||||
Важно:
|
||||
- Верни только JSON массив, без дополнительного текста
|
||||
- Сохрани порядок слов как в исходном списке
|
||||
- Для каждого слова укажи точный перевод и транскрипцию"""
|
||||
|
||||
try:
|
||||
logger.info(f"[GPT Request] translate_words_batch: {len(words)} words, {source_lang} -> {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)
|
||||
|
||||
# Если вернулся dict с ключом типа "words" или "translations" — извлекаем список
|
||||
if isinstance(result, dict):
|
||||
for key in ['words', 'translations', 'result', 'data']:
|
||||
if key in result and isinstance(result[key], list):
|
||||
result = result[key]
|
||||
break
|
||||
|
||||
if not isinstance(result, list):
|
||||
logger.warning(f"[GPT Warning] translate_words_batch: unexpected format, got {type(result)}")
|
||||
return [{"word": w, "translation": "", "transcription": ""} for w in words]
|
||||
|
||||
logger.info(f"[GPT Response] translate_words_batch: success, got {len(result)} translations")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[GPT Error] translate_words_batch: {type(e).__name__}: {str(e)}")
|
||||
# Возвращаем слова без перевода в случае ошибки
|
||||
return [{"word": w, "translation": "", "transcription": ""} for w in words]
|
||||
|
||||
async def check_answer(self, question: str, correct_answer: str, user_answer: str) -> Dict:
|
||||
"""
|
||||
Проверить ответ пользователя с помощью ИИ
|
||||
|
||||
Reference in New Issue
Block a user