Skip to content
Open
Show file tree
Hide file tree
Changes from all 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: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
Expand All @@ -108,7 +108,6 @@ ipython_config.py
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
Expand Down
4 changes: 2 additions & 2 deletions backend/app/agents/devrel/nodes/gather_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from datetime import datetime
from datetime import datetime, timezone
from typing import Any, Dict

from app.agents.state import AgentState
Expand Down Expand Up @@ -27,7 +27,7 @@ async def gather_context_node(state: AgentState) -> Dict[str, Any]:
new_message = {
"role": "user",
"content": original_message,
"timestamp": datetime.now().isoformat()
"timestamp": datetime.now(timezone.utc).isoformat()
}

profile_data: Dict[str, Any] = dict(state.user_profile or {})
Expand Down
1 change: 1 addition & 0 deletions backend/app/core/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Settings(BaseSettings):
# Platforms
github_token: str = ""
discord_bot_token: str = ""
bot_owner_id: Optional[int] = None

# DB configuration
supabase_url: str
Expand Down
6 changes: 3 additions & 3 deletions backend/app/database/supabase/services.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from typing import Dict, Any, Optional
from datetime import datetime
from datetime import datetime, timezone
import uuid
from app.database.supabase.client import get_supabase_client

Expand Down Expand Up @@ -43,7 +43,7 @@ async def ensure_user_exists(
# Update last_active timestamp
last_active_column = f"last_active_{platform}"
await supabase.table("users").update({
last_active_column: datetime.now().isoformat()
last_active_column: datetime.now(timezone.utc).isoformat()
}).eq("id", user_uuid).execute()

return user_uuid
Expand All @@ -64,7 +64,7 @@ async def ensure_user_exists(

# Set last_active timestamp
last_active_column = f"last_active_{platform}"
new_user[last_active_column] = datetime.now().isoformat()
new_user[last_active_column] = datetime.now(timezone.utc).isoformat()

insert_response = await supabase.table("users").insert(new_user).execute()

Expand Down
37 changes: 36 additions & 1 deletion backend/app/models/database/weaviate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ class WeaviatePullRequest(BaseModel):
labels: List[str] = Field(default_factory=list, description="Labels associated with the pull request.")
url: str = Field(..., description="The URL of the pull request.")


class WeaviateCodeChunk(BaseModel):
"""Represents a code chunk stored in Weaviate for semantic code search."""
supabase_chunk_id: str = Field(..., description="The unique identifier linking to Supabase code chunk record.")
code_content: str = Field(..., description="Raw code content for the chunk.")
language: str = Field(..., description="Programming language of the code chunk.")
function_names: List[str] = Field(default_factory=list, description="Function names detected in the code chunk.")
embedding: List[float] = Field(..., description="Vector embedding representation for semantic search.")
created_at: datetime = Field(default_factory=datetime.now, description="Creation timestamp for the chunk record.")
last_updated: datetime = Field(default_factory=datetime.now, description="Last update timestamp for the chunk record.")

model_config = ConfigDict(from_attributes=True)


class WeaviateInteraction(BaseModel):
"""Represents an interaction summary stored in Weaviate for semantic retrieval."""
supabase_interaction_id: str = Field(..., description="The unique identifier linking to Supabase interaction record.")
conversation_summary: str = Field(..., description="Summarized interaction content.")
platform: str = Field(..., description="Origin platform of the interaction (e.g., Discord, Web).")
topics: List[str] = Field(default_factory=list, description="Topics extracted from the interaction.")
embedding: List[float] = Field(..., description="Vector embedding representation for semantic search.")
created_at: datetime = Field(default_factory=datetime.now, description="Creation timestamp for the interaction record.")
last_updated: datetime = Field(default_factory=datetime.now, description="Last update timestamp for the interaction record.")

model_config = ConfigDict(from_attributes=True)

class WeaviateUserProfile(BaseModel):
"""
Represents a user's profile data to be stored and indexed in Weaviate.
Expand Down Expand Up @@ -125,4 +151,13 @@ class WeaviateUserProfile(BaseModel):
}
}

)
)


__all__ = [
"WeaviateRepository",
"WeaviatePullRequest",
"WeaviateCodeChunk",
"WeaviateInteraction",
"WeaviateUserProfile",
]
15 changes: 15 additions & 0 deletions backend/app/services/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .bot_stats_service import BotStatsService
from .health_check_service import HealthCheckService
from .user_info_service import UserInfoService
from .queue_service import QueueService
from .cache_service import CacheService
from .user_management_service import UserManagementService

__all__ = [
"BotStatsService",
"HealthCheckService",
"UserInfoService",
"QueueService",
"CacheService",
"UserManagementService",
]
111 changes: 111 additions & 0 deletions backend/app/services/admin/bot_stats_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import logging
import psutil
import os
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from dataclasses import dataclass

from app.database.supabase.client import get_supabase_client

logger = logging.getLogger(__name__)


@dataclass
class BotStats:
guild_count: int
total_members: int
active_threads: int
latency_ms: int
uptime_seconds: float
memory_mb: float
messages_today: int
messages_week: int
queue_high: int
queue_medium: int
queue_low: int


class BotStatsService:
def __init__(self, bot=None, queue_manager=None):
self.bot = bot
self.queue_manager = queue_manager
self._start_time = datetime.now()

async def get_message_stats(self) -> Dict[str, int]:
try:
supabase = get_supabase_client()
now = datetime.now()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=7)

today_res = await supabase.table("message_logs").select(
"id", count="exact"
).gte("created_at", today_start.isoformat()).execute()

week_res = await supabase.table("message_logs").select(
"id", count="exact"
).gte("created_at", week_start.isoformat()).execute()

return {
"today": today_res.count or 0,
"week": week_res.count or 0,
}
except Exception as e:
logger.warning(f"Could not get message stats: {e}")
return {"today": 0, "week": 0}

async def get_queue_stats(self) -> Dict[str, int]:
try:
if not self.queue_manager or not self.queue_manager.channel:
return {"high": 0, "medium": 0, "low": 0}

stats = {}
for priority, queue_name in self.queue_manager.queues.items():
try:
queue = await self.queue_manager.channel.declare_queue(
queue_name, durable=True, passive=True
)
stats[priority.value] = queue.declaration_result.message_count
except Exception:
stats[priority.value] = 0
return stats
except Exception as e:
logger.warning(f"Could not get queue stats: {e}")
return {"high": 0, "medium": 0, "low": 0}

def get_system_stats(self) -> Dict[str, Any]:
try:
process = psutil.Process(os.getpid())
memory_mb = process.memory_info().rss / 1024 / 1024
uptime = (datetime.now() - self._start_time).total_seconds()
return {
"memory_mb": round(memory_mb, 2),
"uptime_seconds": uptime,
}
except Exception as e:
logger.warning(f"Could not get system stats: {e}")
return {"memory_mb": 0, "uptime_seconds": 0}

async def get_all_stats(self) -> BotStats:
message_stats = await self.get_message_stats()
queue_stats = await self.get_queue_stats()
system_stats = self.get_system_stats()

guild_count = len(self.bot.guilds) if self.bot else 0
total_members = sum(g.member_count or 0 for g in self.bot.guilds) if self.bot else 0
active_threads = len(self.bot.active_threads) if self.bot else 0
latency_ms = round(self.bot.latency * 1000) if self.bot else 0

return BotStats(
guild_count=guild_count,
total_members=total_members,
active_threads=active_threads,
latency_ms=latency_ms,
uptime_seconds=system_stats["uptime_seconds"],
memory_mb=system_stats["memory_mb"],
messages_today=message_stats["today"],
messages_week=message_stats["week"],
queue_high=queue_stats.get("high", 0),
queue_medium=queue_stats.get("medium", 0),
queue_low=queue_stats.get("low", 0),
)
60 changes: 60 additions & 0 deletions backend/app/services/admin/cache_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging
from typing import Dict, Any, Optional
from dataclasses import dataclass

logger = logging.getLogger(__name__)


@dataclass
class CacheInfo:
name: str
size: int
cleared: bool


class CacheService:
def __init__(self, bot=None):
self.bot = bot

async def get_cache_sizes(self) -> Dict[str, int]:
sizes = {
"active_threads": 0,
"embeddings": 0,
"memories": 0,
}

if self.bot:
sizes["active_threads"] = len(self.bot.active_threads)

return sizes

async def clear_active_threads(self) -> int:
if not self.bot:
return 0

count = len(self.bot.active_threads)
self.bot.active_threads.clear()
logger.info(f"Cleared {count} active threads from cache")
return count

async def clear_embeddings(self) -> int:
logger.info("Embeddings cache clear requested (no-op for now)")
return 0

async def clear_memories(self) -> int:
logger.info("Memories cache clear requested (no-op for now)")
return 0

async def clear_cache(self, cache_type: str = "all") -> Dict[str, int]:
cleared = {}

if cache_type in ("all", "active_threads"):
cleared["active_threads"] = await self.clear_active_threads()

if cache_type in ("all", "embeddings"):
cleared["embeddings"] = await self.clear_embeddings()

if cache_type in ("all", "memories"):
cleared["memories"] = await self.clear_memories()

return cleared
Loading