from datetime import datetime from typing import Optional from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.models import ( BotUser, BotConversation, BotMessage, BotConsentLog, BotCategory, BotSetting, BotFeature, BotResponseTemplate, BotKnowledgeBase ) from app.settings_cache import SettingsCache from app.text_renderer import render_template async def get_or_create_user(db: AsyncSession, max_user_id: int, first_name: str = "", last_name: str = "", username: str = "") -> BotUser: result = await db.execute(select(BotUser).where(BotUser.max_user_id == max_user_id)) user = result.scalar_one_or_none() if user: user.last_active_at = datetime.now() if first_name and not user.first_name: user.first_name = first_name if last_name and not user.last_name: user.last_name = last_name if username and not user.username: user.username = username return user user = BotUser( max_user_id=max_user_id, first_name=first_name, last_name=last_name, username=username ) db.add(user) await db.flush() return user async def get_open_conversation(db: AsyncSession, user_id: int) -> Optional[BotConversation]: result = await db.execute( select(BotConversation).where( BotConversation.user_id == user_id, BotConversation.status == "open" ).order_by(BotConversation.created_at.desc()).limit(1) ) return result.scalar_one_or_none() async def create_conversation(db: AsyncSession, user_id: int) -> BotConversation: conv = BotConversation(user_id=user_id, status="open") db.add(conv) await db.flush() return conv async def add_message(db: AsyncSession, conversation_id: int, direction: str, text: str = "", has_attachment: bool = False, attachment_type: str = "", attachment_path: str = "", max_message_id: int = None) -> BotMessage: msg = BotMessage( conversation_id=conversation_id, direction=direction, text=text, has_attachment=has_attachment, attachment_type=attachment_type, attachment_path=attachment_path, max_message_id=max_message_id ) db.add(msg) await db.flush() return msg async def log_consent(db: AsyncSession, user_id: int, action: str, method: str = "", ip: str = ""): log = BotConsentLog(user_id=user_id, action=action, method=method, ip_address=ip) db.add(log) await db.flush() async def get_template(db: AsyncSession, key: str) -> str: await SettingsCache.refresh(db) return SettingsCache.get_template(key) async def get_template_rendered(db: AsyncSession, key: str, **kwargs) -> str: tmpl = await get_template(db, key) return render_template(tmpl, **kwargs) async def find_in_knowledge_base(db: AsyncSession, query: str) -> Optional[BotKnowledgeBase]: query_lower = query.lower() result = await db.execute( select(BotKnowledgeBase).where(BotKnowledgeBase.active == True) ) entries = result.scalars().all() for entry in entries: if query_lower in entry.question.lower(): return entry keywords = entry.keywords or [] for kw in keywords: if kw.lower() in query_lower: return entry return None async def auto_categorize_text(db: AsyncSession, text: str) -> Optional[BotCategory]: text_lower = text.lower() categories = SettingsCache.get_categories() best_match = None best_score = 0 for cat in categories: keywords = cat.keywords or [] score = sum(1 for kw in keywords if kw.lower() in text_lower) if score > best_score: best_score = score best_match = cat return best_match if best_score > 0 else None async def close_conversation(db: AsyncSession, conversation_id: int): conv = await db.execute( select(BotConversation).where(BotConversation.id == conversation_id) ) conv = conv.scalar_one_or_none() if conv: conv.status = "closed" conv.closed_at = datetime.now() await db.flush() async def update_conversation_status(db: AsyncSession, conversation_id: int, status: str): conv = await db.execute( select(BotConversation).where(BotConversation.id == conversation_id) ) conv = conv.scalar_one_or_none() if conv: conv.status = status await db.flush() async def detect_negative_emotion(text: str) -> bool: negative_words = [ "ужасно", "плохо", "долго", "кошмар", "отвратительно", "невыносимо", "бесполезно", "халтура", "безобразие", "позор", "разочарован", "злюсь", "раздражён", "недоволен", "жалоба", "претензия", "скандал", "суд", "верните деньги", "мошенники", "обман" ] text_lower = text.lower() return any(word in text_lower for word in negative_words)