feat: персональные AI модели, оптимизация задач, фильтрация словаря

- Добавлена поддержка персональных AI моделей для каждого пользователя
- Оптимизация создания заданий: батч-запрос к AI вместо N запросов
- Фильтрация слов по языку изучения (source_lang) в словаре
- Удалены неиспользуемые колонки examples и category из vocabulary
- Миграции для ai_model_id и удаления колонок

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-08 16:43:08 +03:00
parent 6138af4e63
commit 16a7df0343
13 changed files with 507 additions and 142 deletions

View File

@@ -1,7 +1,7 @@
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import AIModel, AIProvider
from typing import Optional, List
from database.models import AIModel, AIProvider, User
from typing import Optional, List, Tuple
# Дефолтная модель если в БД ничего нет
@@ -188,3 +188,81 @@ class AIModelService:
session.add(model)
await session.commit()
@staticmethod
async def get_user_model(session: AsyncSession, user_id: int) -> Optional[AIModel]:
"""
Получить AI модель пользователя.
Если у пользователя не выбрана модель, возвращает глобальную активную.
Args:
user_id: ID пользователя в БД
Returns:
AIModel или None
"""
# Получаем пользователя
result = await session.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if user and user.ai_model_id:
# У пользователя выбрана своя модель
model_result = await session.execute(
select(AIModel).where(AIModel.id == user.ai_model_id)
)
model = model_result.scalar_one_or_none()
if model:
return model
# Fallback на глобальную активную модель
return await AIModelService.get_active_model(session)
@staticmethod
async def get_user_model_info(session: AsyncSession, user_id: int) -> Tuple[str, AIProvider]:
"""
Получить название модели и провайдера для пользователя.
Args:
user_id: ID пользователя в БД
Returns:
Tuple[model_name, provider]
"""
model = await AIModelService.get_user_model(session, user_id)
if model:
return model.model_name, model.provider
return DEFAULT_MODEL, DEFAULT_PROVIDER
@staticmethod
async def set_user_model(session: AsyncSession, user_id: int, model_id: Optional[int]) -> bool:
"""
Установить AI модель для пользователя.
Args:
user_id: ID пользователя в БД
model_id: ID модели или None для сброса на глобальную
Returns:
True если успешно
"""
result = await session.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if not user:
return False
# Проверяем существование модели если указан ID
if model_id is not None:
model_result = await session.execute(
select(AIModel).where(AIModel.id == model_id)
)
if not model_result.scalar_one_or_none():
return False
user.ai_model_id = model_id
await session.commit()
return True