"""Search provider for unified_search integration.""" from __future__ import annotations import logging import uuid from typing import Any from sqlalchemy import select, func, or_ from sqlalchemy.ext.asyncio import AsyncSession from app.plugins.builtins.kommunikation.models import ( CommConversation, CommMessage, CommParticipant, ) logger = logging.getLogger(__name__) class CommSearchProvider: """Provider for unified_search — searches conversations and messages.""" async def search( self, db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID, query: str, limit: int = 20, ) -> list[dict[str, Any]]: """Search in messages and conversations the user has access to.""" results: list[dict[str, Any]] = [] # Get user's conversation IDs conv_result = await db.execute( select(CommParticipant.conversation_id).where( CommParticipant.participant_id == user_id, CommParticipant.participant_type == "user", CommParticipant.left_at.is_(None), CommParticipant.tenant_id == tenant_id, ) ) conv_ids = [row[0] for row in conv_result.fetchall()] if not conv_ids: return results # Search in messages msg_result = await db.execute( select(CommMessage, CommConversation.title) .join(CommConversation, CommConversation.id == CommMessage.conversation_id) .where( CommMessage.conversation_id.in_(conv_ids), CommMessage.tenant_id == tenant_id, CommMessage.deleted_at.is_(None), CommMessage.content.ilike(f"%{query}%"), ) .order_by(CommMessage.created_at.desc()) .limit(limit) ) for msg, conv_title in msg_result.fetchall(): # Build snippet around match content = msg.content or "" idx = content.lower().find(query.lower()) if idx >= 0: start = max(0, idx - 30) end = min(len(content), idx + len(query) + 30) snippet = ("..." if start > 0 else "") + content[start:end] + ("..." if end < len(content) else "") else: snippet = content[:100] results.append({ "type": "message", "id": str(msg.id), "conversation_id": str(msg.conversation_id), "conversation_title": conv_title, "content": msg.content, "sender_type": msg.sender_type, "created_at": msg.created_at.isoformat() if msg.created_at else None, "snippet": snippet, }) # Search in conversation titles conv_title_result = await db.execute( select(CommConversation).where( CommConversation.id.in_(conv_ids), CommConversation.tenant_id == tenant_id, CommConversation.deleted_at.is_(None), CommConversation.title.ilike(f"%{query}%"), ) .limit(limit) ) for conv in conv_title_result.scalars().all(): results.append({ "type": "conversation", "id": str(conv.id), "title": conv.title, "last_msg_at": conv.last_msg_at.isoformat() if conv.last_msg_at else None, }) return results async def index_message(self, message: dict[str, Any]) -> None: """Index a message for search (placeholder for future full-text indexing).""" pass async def reindex_all(self, db: AsyncSession, tenant_id: uuid.UUID) -> None: """Full reindex (placeholder for future full-text indexing).""" pass