import logging import datetime from typing import Optional from sqlalchemy import select from app.database import async_session from app.models import BotUser, BotConversation, BotMessage, BotKnowledgeBase, BotCategory from app.max_api import max_api from app.yandex_gpt import yandex_gpt from app.config_reader import config_reader from app.settings_cache import settings_cache from app.keyboards import consent_keyboard, return_to_consent_keyboard logger = logging.getLogger(__name__) async def handle_greeting( user_id: int, text: Optional[str] = None, first_name: str = "", last_name: str = "", username: str = "", ) -> None: assistant_name = await settings_cache.get("assistant_name", "София") greeting_text = ( f"Здравствуйте! Я {assistant_name}, помощник AegisOne Engineering. " f"Чем я могу быть вам полезна?" ) await max_api.send_message(user_id, greeting_text) async with async_session() as db: existing = await db.execute( select(BotUser).where(BotUser.id == user_id) ) user = existing.scalar_one_or_none() now = datetime.datetime.utcnow() if not user: user = BotUser( id=user_id, first_name=first_name, last_name=last_name, username=username, last_interaction=now, total_conversations=1, created_at=now, ) db.add(user) else: if first_name: user.first_name = first_name if last_name: user.last_name = last_name if username: user.username = username user.last_interaction = now user.total_conversations = (user.total_conversations or 0) + 1 await db.commit() await db.refresh(user) conv = BotConversation( user_id=user_id, state="analyzing", created_at=now, ) db.add(conv) await db.commit() await db.refresh(conv) if text: await analyze_and_respond(user_id, conv.id, text) else: async with async_session() as db: conv.state = "awaiting_input" await db.commit() async def handle_message(user_id: int, text: str) -> None: async with async_session() as db: result = await db.execute( select(BotConversation) .where(BotConversation.user_id == user_id) .order_by(BotConversation.id.desc()) .limit(1) ) conv = result.scalar_one_or_none() if not conv: await handle_greeting(user_id, text) return msg = BotMessage( conversation_id=conv.id, direction="incoming", text=text, created_at=datetime.datetime.utcnow(), ) db.add(msg) await db.commit() state = conv.state if conv else "greeting" if state in ("greeting", "analyzing", "awaiting_input"): await analyze_and_respond(user_id, conv.id, text) elif state == "awaiting_consent": from app.handlers.consent import handle_consent_response await handle_consent_response(user_id, conv.id, text) elif state == "consent_refused": from app.handlers.consent import handle_refused_again await handle_refused_again(user_id, conv.id, text) elif state == "awaiting_contact": from app.handlers.contact import handle_manual_contact await handle_manual_contact(user_id, conv.id, text) elif state == "awaiting_inquiry": from app.handlers.inquiry import handle_inquiry await handle_inquiry(user_id, conv.id, text) elif state in ("completed", "escalated"): await max_api.send_message( user_id, "Ваше обращение уже передано специалисту. " "Если у вас новый вопрос, напишите его, и я помогу.", ) async def _get_user_history_context(user_id: int) -> str: from app.models import BotTicket async with async_session() as db: recent_msgs = await db.execute( select(BotMessage) .join(BotConversation) .where(BotConversation.user_id == user_id) .order_by(BotMessage.id.desc()) .limit(10) ) messages = list(reversed(recent_msgs.scalars().all())) open_tickets = await db.execute( select(BotTicket) .where(BotTicket.user_id == user_id) .where(BotTicket.status != "Закрыта") .order_by(BotTicket.id.desc()) ) tickets = open_tickets.scalars().all() parts = [] if messages: msg_lines = [] for m in messages: who = "Клиент" if m.direction == "incoming" else "Бот" msg_lines.append(f"{who}: {m.text or ''}") parts.append("Последние сообщения из прошлых диалогов:\n" + "\n".join(msg_lines)) if tickets: ticket_lines = [] for t in tickets: ticket_lines.append(f" - Заявка #{t.id}: {t.title} (статус: {t.status})") parts.append("Открытые заявки пользователя:\n" + "\n".join(ticket_lines)) return "\n\n".join(parts) async def analyze_and_respond(user_id: int, conv_id: int, text: str) -> None: gpt_available = await yandex_gpt.is_available() if not gpt_available: await _limited_mode(user_id, conv_id) return history_context = await _get_user_history_context(user_id) intent = await yandex_gpt.analyze_intent(text, history_context) async with async_session() as db: result = await db.execute( select(BotConversation).where(BotConversation.id == conv_id) ) conv = result.scalar_one_or_none() if not conv: return conv.intent = intent if intent == "question": kb_context = await _get_knowledge_base_context(text) answer = await yandex_gpt.generate_answer(text, kb_context, history_context) if answer: await max_api.send_message(user_id, answer) else: await _send_contacts(user_id) conv.state = "completed" elif intent == "ticket": conv.state = "awaiting_consent" consent_text = ( "Для обработки Вашего запроса нам нужны ваши контактные данные " "для обратной связи. Даёте согласие на обработку " "персональных данных?" ) await max_api.send_message( user_id, consent_text, attachments=consent_keyboard() ) else: clarification = await yandex_gpt.clarify_intent(text, history_context) await max_api.send_message(user_id, clarification) conv.state = "awaiting_consent" after_text = ( "\n\nДля обработки Вашего запроса нам нужны ваши контактные данные " "для обратной связи. Даёте согласие на обработку " "персональных данных?" ) await max_api.send_message( user_id, after_text, attachments=consent_keyboard() ) await db.commit() async def _limited_mode(user_id: int, conv_id: int) -> None: async with async_session() as db: result = await db.execute( select(BotConversation).where(BotConversation.id == conv_id) ) conv = result.scalar_one_or_none() if conv: conv.state = "completed" await db.commit() await _send_contacts(user_id) async def _send_contacts(user_id: int) -> None: phones = config_reader.get_phones() email = config_reader.get_site_mail() phone_text = "\n".join( f"📞 {phone} — [позвонить](tel:{link})" for phone, link in phones ) message = ( "Вы можете связаться с нами:\n\n" f"{phone_text}\n" f"📧 [Написать на почту](mailto:{email})\n\n" f"Или написать на почту: {email}" ) await max_api.send_message(user_id, message, format="markdown") async def _get_knowledge_base_context(query: str) -> str: async with async_session() as db: result = await db.execute( select(BotKnowledgeBase) .where(BotKnowledgeBase.is_active == True) .order_by(BotKnowledgeBase.sort_order) ) cards = result.scalars().all() if not cards: return "" parts = [] for card in cards: keywords = " ".join(card.keywords) if card.keywords else "" answer = card.answer or "Нет ответа" cat_name = card.category.name if card.category else "Общее" parts.append(f"[{cat_name}] {card.question}: {answer} (ключевые слова: {keywords})") return "\n\n".join(parts[:20])