Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_SERVICE_ROLE_KEY=
DISCORD_BOT_TOKEN=
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.qodo
8 changes: 5 additions & 3 deletions backend/app/core/events/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ class BaseEvent(BaseModel):
platform: str
event_type: str
timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp of the event")
channel_id: str | None = None
actor_id: str = Field(..., description="ID of the user who triggered the event")
actor_name: Optional[str] = Field(None, description="Name of the user who triggered the event")
raw_data: Dict[str, Any] = Field({}, description="Raw event data from the platform")
metadata: Dict[str, Any] = Field({}, description="Additional metadata for the event")

content: Optional[str] = Field(None, description="Content of the event")

def to_dict(self) -> Dict[str, Any]:
"""Convert event to dictionary for serialization"""
return self.model_dump()

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BaseEvent":
"""Create event from dictionary"""
return cls(**data)
return cls(**data)
24 changes: 21 additions & 3 deletions backend/app/core/events/discord_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,27 @@
class DiscordEvent(BaseEvent):
"""Discord specific event model"""
platform: PlatformType = PlatformType.DISCORD
guild_id: str
channel_id: str
guild_id: Optional[str] = None
channel_id: Optional[str] = None
message_id: Optional[str] = None
content: Optional[str] = None
attachments: List[Dict[str, Any]] = Field(default_factory=list)
mentions: List[str] = Field(default_factory=list)
mentions: List[str] = Field(default_factory=list)

def __init__(self, **data):
"""Ensure event data is properly initialized from raw_data"""
raw_data = data.get("raw_data", {})

super().__init__(
id=data.get("id"),
event_type=data.get("event_type"),
platform=PlatformType.DISCORD,
actor_id=data.get("actor_id") or raw_data.get("actor_id"),
actor_name=data.get("actor_name"),
raw_data=raw_data,
metadata=data.get("metadata", {}),
guild_id=raw_data.get("guild_id"),
message_id=raw_data.get("id"),
content=raw_data.get("content"),
channel_id=data.get("channel_id"),
)
3 changes: 3 additions & 0 deletions backend/app/core/events/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ class PlatformType(str, Enum):

class EventType(str, Enum):
ISSUE_CREATED = "issue.created"
ISSUE_CLOSED = "issue.closed"
ISSUE_UPDATED = "issue.updated"
ISSUE_COMMENTED = "issue.commented"
PR_CREATED = "pr.created"
PR_UPDATED = "pr.updated"
PR_COMMENTED = "pr.commented"
PR_MERGED = "pr.merged"
PR_REVIEWED = "pr.reviewed"

MESSAGE_CREATED = "message.created"
MESSAGE_UPDATED = "message.updated"
MESSAGE_DELETED = "message.deleted"
REACTION_ADDED = "reaction.added"
USER_JOINED = "user.joined"

Expand Down
6 changes: 2 additions & 4 deletions backend/app/core/events/event_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ def register_handler(self, event_type: Union[EventType, List[EventType]], handle
self._add_handler(et, handler_func)
else:
self._add_handler(event_type, handler_func)
logger.info(f"Handler {handler_func.__name__} registered for event type {event_type}")
pass

def _add_handler(self, event_type: EventType, handler_func: callable):
Expand All @@ -35,22 +34,21 @@ def _add_handler(self, event_type: EventType, handler_func: callable):
def register_global_handler(self, handler_func):
"""Register a handler that will receive all events"""
self.global_handlers.append(handler_func)
logger.info(f"Global handler {handler_func._name_} registered")
pass

async def dispatch(self, event: BaseEvent):
"""Dispatch an event to all registered handlers"""
logger.info(f"Dispatching event {event.id} of type {event.event_type}")

# Call global handlers first
for handler in self.global_handlers:
logger.info(f"Calling global handler: {handler.__name__}")
asyncio.create_task(handler(event))

# Call event-specific handlers
if event.event_type in self.handlers:
for handler in self.handlers[event.event_type]:
logger.info(f"Calling handler: {handler.__name__} for event type: {event.event_type}")
asyncio.create_task(handler(event))

else:
logger.info(f"No handlers registered for event type {event.event_type}")
pass
55 changes: 45 additions & 10 deletions backend/app/core/handler/faq_handler.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,63 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from .base import BaseHandler
from app.core.events.base import BaseEvent
from app.core.events.enums import EventType, PlatformType
from app.core.handler.base import BaseHandler

logger = logging.getLogger(__name__)

class FAQHandler(BaseHandler):
"""Handler for FAQ and knowledge base queries"""


def __init__(self, bot=None):
self.bot = bot
self.faq_responses = {
"what is devr.ai?": "Devr.AI is an AI-powered Developer Relations assistant that helps open-source communities by automating engagement and issue tracking.",
"how do i contribute?": "You can contribute by visiting our GitHub repo, checking open issues, and submitting pull requests.",
"what platforms does devr.ai support?": "Devr.AI integrates with Discord, Slack, GitHub, and Discourse to assist developers and maintainers.",
"who maintains devr.ai?": "Devr.AI is maintained by an open-source community of developers.",
"how do i report a bug?": "You can report a bug by opening an issue on our GitHub repository.",
}

async def is_faq(self, message: str):
"""Check if the question is in the FAQ and return the response"""
for key in self.faq_responses:
if message.lower() == key.lower():
return True, self.faq_responses[key]
return False, None

async def handle(self, event: BaseEvent) -> Dict[str, Any]:
logger.info(f"Handling FAQ request event: {event.event_type}")

if event.event_type == EventType.FAQ_REQUESTED:
return await self._handle_faq_request(event)
elif event.event_type == EventType.KNOWLEDGE_UPDATED:
return await self._handle_knowledge_update(event)
else:
logger.warning(f"Unsupported FAQ event type: {event.event_type}")
return {"success": False, "reason": "Unsupported event type"}

async def _handle_faq_request(self, event: BaseEvent) -> Dict[str, Any]:
# Implementation for FAQ request
return {"success": True, "action": "faq_processed"}

question = (event.content or "").strip().lower()
response = self.get_faq_response(question)

logger.info("Question: " + question + ", Response: " + response)

await self._send_discord_response(event.channel_id, response)
return {"success": True, "action": "faq_response_sent"}

def get_faq_response(self, question: str) -> str:
return self.faq_responses.get(question.lower(), "I'm not sure about that, but I can find out!")

async def _handle_knowledge_update(self, event: BaseEvent) -> Dict[str, Any]:
# Implementation for updating knowledge base
return {"success": True, "action": "knowledge_updated"}
"""Handles knowledge base updates."""
return {"success": True, "action": "knowledge_updated"}

async def _send_discord_response(self, channel_id: str, response: str):
"""Sends a response message to the specified Discord channel."""
if self.bot:
channel = self.bot.get_channel(int(channel_id))
if channel:
await channel.send(response)
else:
logger.error(f"Could not find Discord channel with ID {channel_id}")
3 changes: 2 additions & 1 deletion backend/app/core/handler/issue_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from ..events.base import BaseEvent
from ..events.enums import EventType
from .base import BaseHandler

logger = logging.getLogger(__name__)
Expand Down
35 changes: 29 additions & 6 deletions backend/app/core/handler/message_handler.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,54 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from ..events.base import BaseEvent
from ..events.enums import EventType
from .base import BaseHandler

from .faq_handler import FAQHandler
logger = logging.getLogger(__name__)

class MessageHandler(BaseHandler):
"""Handler for Discord/Slack message events"""


def __init__(self, bot=None):
self.bot = bot
self.faq_handler = FAQHandler()

async def handle(self, event: BaseEvent) -> Dict[str, Any]:
logger.info(f"Handling message event from {event.platform}: {event.event_type}")

if event.event_type == EventType.MESSAGE_CREATED:
return await self._handle_message_created(event)
elif event.event_type == EventType.MESSAGE_UPDATED:
return await self._handle_message_updated(event)
else:
logger.warning(f"Unsupported message event type: {event.event_type}")
return {"success": False, "reason": "Unsupported event type"}

async def _handle_message_created(self, event: BaseEvent) -> Dict[str, Any]:

user_message = (event.content or "").strip().lower()

if not user_message:
logger.error(f"Received empty message event: {event}")
return {"success": False, "reason": "Empty message"}

# Check if it's a FAQ request
if await self.faq_handler.is_faq(user_message):
faq_event = BaseEvent(
id=event.id,
event_type=EventType.FAQ_REQUESTED,
platform=event.platform,
channel_id=event.raw_data.get("channel_id"),
actor_id=event.actor_id,
data={"content": user_message},
)
return await self.faq_handler.handle(faq_event)

# Implementation for new message creation
# - Check if it's a command
# - Check if it's a question
# - Process natural language
return {"success": True, "action": "message_processed"}

async def _handle_message_updated(self, event: BaseEvent) -> Dict[str, Any]:
# Implementation for message updates
return {"success": True, "action": "message_updated"}
3 changes: 2 additions & 1 deletion backend/app/core/handler/onboarding_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from ..events.base import BaseEvent
from ..events.enums import EventType
from .base import BaseHandler

logger = logging.getLogger(__name__)
Expand Down
3 changes: 2 additions & 1 deletion backend/app/core/handler/pr_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Dict, Any
from ..events.base import BaseEvent, EventType
from ..events.base import BaseEvent
from ..events.enums import EventType
from .base import BaseHandler

logger = logging.getLogger(__name__)
Expand Down
Loading