diff --git a/src/greek_lang/glossaries/models.py b/src/greek_lang/glossaries/models.py
index d19b4a0..1bd7a4d 100644
--- a/src/greek_lang/glossaries/models.py
+++ b/src/greek_lang/glossaries/models.py
@@ -11,7 +11,8 @@ from ..database.base import Base
from ..languages import LanguageEnum
-class LexicalCategoryEnum(str, enum.Enum):
+@enum.unique
+class LexicalCategoryEnum(enum.StrEnum):
noun = "noun"
verb = "verb"
adjective = "adjective"
diff --git a/src/greek_lang/languages.py b/src/greek_lang/languages.py
index 51a44fe..857b651 100644
--- a/src/greek_lang/languages.py
+++ b/src/greek_lang/languages.py
@@ -2,7 +2,7 @@ import enum
@enum.unique
-class LanguageEnum(str, enum.Enum):
+class LanguageEnum(enum.StrEnum):
ru = "ru"
en = "en"
el = "el"
diff --git a/src/greek_lang/srs/models.py b/src/greek_lang/srs/models.py
index 7057a69..f4d49ff 100644
--- a/src/greek_lang/srs/models.py
+++ b/src/greek_lang/srs/models.py
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from greek_lang.database.base import Base
+@enum.unique
class ReviewState(enum.StrEnum):
learning = "learning"
review = "review"
diff --git a/src/greek_lang/tg_bot/dialogs/__init__.py b/src/greek_lang/tg_bot/dialogs/__init__.py
index c0dd330..914dbae 100644
--- a/src/greek_lang/tg_bot/dialogs/__init__.py
+++ b/src/greek_lang/tg_bot/dialogs/__init__.py
@@ -1,8 +1,11 @@
from aiogram_dialog import Dialog
from .main_menu import windows as main_windows
+from .add_word import windows as add_word_windows
dialog = Dialog(
main_windows.main_window,
+ add_word_windows.add_word_window,
+ add_word_windows.add_word_result_window,
)
diff --git a/src/greek_lang/tg_bot/dialogs/add_word/__init__.py b/src/greek_lang/tg_bot/dialogs/add_word/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/greek_lang/tg_bot/dialogs/add_word/handlers.py b/src/greek_lang/tg_bot/dialogs/add_word/handlers.py
new file mode 100644
index 0000000..703f084
--- /dev/null
+++ b/src/greek_lang/tg_bot/dialogs/add_word/handlers.py
@@ -0,0 +1,65 @@
+from aiogram.types import Message, CallbackQuery, BufferedInputFile
+from aiogram_dialog import DialogManager
+from aiogram_dialog.widgets.input import MessageInput
+from aiogram_dialog.widgets.kbd import Button
+from greek_lang.languages import LanguageEnum
+from greek_lang.translator import translate
+from ..states import States
+
+
+async def add_word(
+ message: Message,
+ source: MessageInput | Button,
+ manager: DialogManager,
+) -> None:
+ if not message.text:
+ return
+ word = message.text.strip()
+ if not word:
+ return
+ source_lang = LanguageEnum.ru
+ target_lang = LanguageEnum.el
+ glossary_word = await translate(word, source_lang, target_lang=target_lang)
+
+ # Try to send audio pronunciation back to the user
+ try:
+ audio_bytes = getattr(glossary_word, "audio_file", None)
+ if audio_bytes:
+ # aiogTTS produces MP3 data; send as audio
+ caption = (
+ f"{glossary_word.term} → {glossary_word.translation}"
+ )
+ input_file = BufferedInputFile(
+ audio_bytes, filename=f"{glossary_word.term}.mp3"
+ )
+ await message.answer_audio(
+ audio=input_file, caption=caption, parse_mode="HTML"
+ )
+ except Exception:
+ # Silently ignore audio sending issues to not break the flow
+ pass
+
+ # Store data for the result window
+ manager.dialog_data.update(
+ {
+ "term": glossary_word.term,
+ "translation": glossary_word.translation,
+ "transcription": glossary_word.transcription or "",
+ "lexical_category": getattr(glossary_word, "lexical_category", ""),
+ "description": glossary_word.description or "",
+ "example": glossary_word.example or "",
+ "note": glossary_word.note or "",
+ }
+ )
+
+ # Switch to the result window state
+ await manager.switch_to(States.add_word_result)
+
+
+async def on_add_another(
+ callback: CallbackQuery,
+ button: Button,
+ manager: DialogManager,
+) -> None:
+ await callback.answer()
+ await manager.switch_to(States.add_word)
diff --git a/src/greek_lang/tg_bot/dialogs/add_word/windows.py b/src/greek_lang/tg_bot/dialogs/add_word/windows.py
new file mode 100644
index 0000000..441fe37
--- /dev/null
+++ b/src/greek_lang/tg_bot/dialogs/add_word/windows.py
@@ -0,0 +1,73 @@
+from typing import Any
+
+from aiogram.enums import ParseMode
+from aiogram_dialog import DialogManager, Window
+from aiogram_dialog.widgets.input import MessageInput
+from aiogram_dialog.widgets.kbd import Button, Row
+from aiogram_dialog.widgets.markup.reply_keyboard import ReplyKeyboardFactory
+from aiogram_dialog.widgets.text import Format, Const
+
+from ..states import States
+from ..base_handlers import cancel_handler
+from . import handlers
+
+
+async def add_word_window_getter(
+ dialog_manager: DialogManager, **kwargs: Any
+) -> dict[str, Any]:
+ return {}
+
+
+add_word_window = Window(
+ Const("Введите слово:"),
+ MessageInput(func=handlers.add_word),
+ Row(
+ Button(
+ Format("❌ Отмена"),
+ on_click=cancel_handler, # type: ignore[arg-type]
+ id="cancel_add_word",
+ ),
+ ),
+ markup_factory=ReplyKeyboardFactory(
+ input_field_placeholder=Format("Слово..."),
+ resize_keyboard=True,
+ one_time_keyboard=True,
+ ),
+ state=States.add_word,
+ parse_mode=ParseMode.HTML,
+ getter=add_word_window_getter,
+)
+
+
+async def add_word_result_getter(
+ dialog_manager: DialogManager, **kwargs: Any
+) -> dict[str, Any]:
+ # Expose dialog_data fields directly for Format widgets
+ return dialog_manager.dialog_data
+
+
+add_word_result_window = Window(
+ Format(
+ "✅ Слово добавлено!\n\n"
+ "{term} → {translation}\n"
+ "{transcription}\n"
+ "Часть речи: {lexical_category!s}\n"
+ "Описание: {description}\n"
+ "Пример: {example}"
+ ),
+ Row(
+ Button(
+ Const("➕ Добавить ещё"),
+ id="add_another",
+ on_click=handlers.on_add_another,
+ ),
+ Button(
+ Const("🏠 В меню"),
+ on_click=cancel_handler, # type: ignore[arg-type]
+ id="to_main_menu",
+ ),
+ ),
+ state=States.add_word_result,
+ parse_mode=ParseMode.HTML,
+ getter=add_word_result_getter,
+)
diff --git a/src/greek_lang/tg_bot/dialogs/base_handlers.py b/src/greek_lang/tg_bot/dialogs/base_handlers.py
new file mode 100644
index 0000000..b153988
--- /dev/null
+++ b/src/greek_lang/tg_bot/dialogs/base_handlers.py
@@ -0,0 +1,16 @@
+from aiogram_dialog import DialogManager, ShowMode
+from aiogram_dialog.api.internal import ReplyCallbackQuery
+from aiogram_dialog.widgets.kbd import Cancel
+
+from .states import States
+
+
+async def cancel_handler(
+ callback_query: ReplyCallbackQuery,
+ button: Cancel,
+ manager: DialogManager,
+) -> None:
+ await manager.switch_to(
+ States.main_menu,
+ show_mode=ShowMode.DELETE_AND_SEND,
+ )
diff --git a/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py b/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py
index e69de29..8c91b5d 100644
--- a/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py
+++ b/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py
@@ -0,0 +1,26 @@
+from aiogram.types import CallbackQuery
+from aiogram_dialog import DialogManager, ShowMode
+from aiogram_dialog.api.internal import ReplyCallbackQuery
+from aiogram_dialog.widgets.kbd import Cancel, Button
+
+from ..states import States
+
+
+async def on_add_word(
+ callback: CallbackQuery,
+ button: Button,
+ manager: DialogManager,
+) -> None:
+ await callback.answer()
+ await manager.switch_to(States.add_word)
+
+
+async def cancel_handler(
+ callback_query: ReplyCallbackQuery,
+ button: Cancel,
+ manager: DialogManager,
+) -> None:
+ await manager.switch_to(
+ States.add_word,
+ show_mode=ShowMode.DELETE_AND_SEND,
+ )
diff --git a/src/greek_lang/tg_bot/dialogs/main_menu/windows.py b/src/greek_lang/tg_bot/dialogs/main_menu/windows.py
index 1520c79..7a88633 100644
--- a/src/greek_lang/tg_bot/dialogs/main_menu/windows.py
+++ b/src/greek_lang/tg_bot/dialogs/main_menu/windows.py
@@ -2,9 +2,11 @@ from typing import Any
from aiogram.enums import ParseMode
from aiogram_dialog import DialogManager, Window
-from aiogram_dialog.widgets.text import Format
+from aiogram_dialog.widgets.kbd import Row, Button
+from aiogram_dialog.widgets.text import Format, Const
from ..states import States
+from . import handlers
async def main_getter(dialog_manager: DialogManager, **kwargs: Any) -> dict[str, Any]:
@@ -13,10 +15,17 @@ async def main_getter(dialog_manager: DialogManager, **kwargs: Any) -> dict[str,
main_window = Window(
Format(
- "Выбери действие:",
+ "Выбери действие:",
when=lambda data, widget, dialog_manager: data["dialog_data"].get("action")
is None,
),
+ Row(
+ Button(
+ Const("Добавить слово"),
+ id="add_word",
+ on_click=handlers.on_add_word,
+ ),
+ ),
state=States.main_menu,
getter=main_getter,
parse_mode=ParseMode.HTML,
diff --git a/src/greek_lang/tg_bot/dialogs/states.py b/src/greek_lang/tg_bot/dialogs/states.py
index 6914ee5..9f74921 100644
--- a/src/greek_lang/tg_bot/dialogs/states.py
+++ b/src/greek_lang/tg_bot/dialogs/states.py
@@ -3,3 +3,5 @@ from aiogram.fsm.state import State, StatesGroup
class States(StatesGroup):
main_menu = State()
+ add_word = State()
+ add_word_result = State()