show typing

This commit is contained in:
Daniel O'Connell 2025-11-01 21:13:39 +01:00
parent 3b216953ab
commit ff3ca4f109
4 changed files with 168 additions and 22 deletions

View File

@ -5,9 +5,10 @@ Simple HTTP client that communicates with the Discord collector's API server.
""" """
import logging import logging
import requests
from typing import Any from typing import Any
import requests
from memory.common import settings from memory.common import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,6 +38,23 @@ def send_dm(bot_id: int, user_identifier: str, message: str) -> bool:
return False return False
def trigger_typing_dm(bot_id: int, user_identifier: int | str) -> bool:
"""Trigger typing indicator for a DM via the Discord collector API"""
try:
response = requests.post(
f"{get_api_url()}/typing/dm",
json={"bot_id": bot_id, "user": user_identifier},
timeout=10,
)
response.raise_for_status()
result = response.json()
return result.get("success", False)
except requests.RequestException as e:
logger.error(f"Failed to trigger DM typing for {user_identifier}: {e}")
return False
def send_to_channel(bot_id: int, channel_name: str, message: str) -> bool: def send_to_channel(bot_id: int, channel_name: str, message: str) -> bool:
"""Send a DM via the Discord collector API""" """Send a DM via the Discord collector API"""
try: try:
@ -59,6 +77,23 @@ def send_to_channel(bot_id: int, channel_name: str, message: str) -> bool:
return False return False
def trigger_typing_channel(bot_id: int, channel_name: str) -> bool:
"""Trigger typing indicator for a channel via the Discord collector API"""
try:
response = requests.post(
f"{get_api_url()}/typing/channel",
json={"bot_id": bot_id, "channel_name": channel_name},
timeout=10,
)
response.raise_for_status()
result = response.json()
return result.get("success", False)
except requests.RequestException as e:
logger.error(f"Failed to trigger typing for channel {channel_name}: {e}")
return False
def broadcast_message(bot_id: int, channel_name: str, message: str) -> bool: def broadcast_message(bot_id: int, channel_name: str, message: str) -> bool:
"""Send a message to a channel via the Discord collector API""" """Send a message to a channel via the Discord collector API"""
try: try:

View File

@ -35,6 +35,16 @@ class SendChannelRequest(BaseModel):
message: str message: str
class TypingDMRequest(BaseModel):
bot_id: int
user: int | str
class TypingChannelRequest(BaseModel):
bot_id: int
channel_name: str
class Collector: class Collector:
collector: MessageCollector collector: MessageCollector
collector_task: asyncio.Task collector_task: asyncio.Task
@ -109,6 +119,32 @@ async def send_dm_endpoint(request: SendDMRequest):
} }
@app.post("/typing/dm")
async def trigger_dm_typing(request: TypingDMRequest):
"""Trigger a typing indicator for a DM via the collector"""
collector = app.bots.get(request.bot_id)
if not collector:
raise HTTPException(status_code=404, detail="Bot not found")
try:
success = await collector.collector.trigger_typing_dm(request.user)
except Exception as e:
logger.error(f"Failed to trigger DM typing: {e}")
raise HTTPException(status_code=500, detail=str(e))
if not success:
raise HTTPException(
status_code=400,
detail=f"Failed to trigger typing for {request.user}",
)
return {
"success": True,
"user": request.user,
"message": f"Typing triggered for {request.user}",
}
@app.post("/send_channel") @app.post("/send_channel")
async def send_channel_endpoint(request: SendChannelRequest): async def send_channel_endpoint(request: SendChannelRequest):
"""Send a message to a channel via the collector's Discord client""" """Send a message to a channel via the collector's Discord client"""
@ -120,23 +156,48 @@ async def send_channel_endpoint(request: SendChannelRequest):
success = await collector.collector.send_to_channel( success = await collector.collector.send_to_channel(
request.channel_name, request.message request.channel_name, request.message
) )
if success:
return {
"success": True,
"message": f"Message sent to channel {request.channel_name}",
"channel": request.channel_name,
}
else:
raise HTTPException(
status_code=400,
detail=f"Failed to send message to channel {request.channel_name}",
)
except Exception as e: except Exception as e:
logger.error(f"Failed to send channel message: {e}") logger.error(f"Failed to send channel message: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
if success:
return {
"success": True,
"message": f"Message sent to channel {request.channel_name}",
"channel": request.channel_name,
}
raise HTTPException(
status_code=400,
detail=f"Failed to send message to channel {request.channel_name}",
)
@app.post("/typing/channel")
async def trigger_channel_typing(request: TypingChannelRequest):
"""Trigger a typing indicator for a channel via the collector"""
collector = app.bots.get(request.bot_id)
if not collector:
raise HTTPException(status_code=404, detail="Bot not found")
try:
success = await collector.collector.trigger_typing_channel(request.channel_name)
except Exception as e:
logger.error(f"Failed to trigger channel typing: {e}")
raise HTTPException(status_code=500, detail=str(e))
if not success:
raise HTTPException(
status_code=400,
detail=f"Failed to trigger typing for channel {request.channel_name}",
)
return {
"success": True,
"channel": request.channel_name,
"message": f"Typing triggered for channel {request.channel_name}",
}
@app.get("/health") @app.get("/health")
async def health_check(): async def health_check():

View File

@ -381,6 +381,26 @@ class MessageCollector(commands.Bot):
logger.error(f"Failed to send DM to {user_identifier}: {e}") logger.error(f"Failed to send DM to {user_identifier}: {e}")
return False return False
async def trigger_typing_dm(self, user_identifier: int | str) -> bool:
"""Trigger typing indicator in a DM"""
try:
user = await self.get_user(user_identifier)
if not user:
logger.error(f"User {user_identifier} not found")
return False
channel = user.dm_channel or await user.create_dm()
if not channel:
logger.error(f"DM channel not available for {user_identifier}")
return False
await channel.trigger_typing()
return True
except Exception as e:
logger.error(f"Failed to trigger DM typing for {user_identifier}: {e}")
return False
async def send_to_channel(self, channel_name: str, message: str) -> bool: async def send_to_channel(self, channel_name: str, message: str) -> bool:
"""Send a message to a channel by name across all guilds""" """Send a message to a channel by name across all guilds"""
if not settings.DISCORD_NOTIFICATIONS_ENABLED: if not settings.DISCORD_NOTIFICATIONS_ENABLED:
@ -400,6 +420,24 @@ class MessageCollector(commands.Bot):
logger.error(f"Failed to send message to channel {channel_name}: {e}") logger.error(f"Failed to send message to channel {channel_name}: {e}")
return False return False
async def trigger_typing_channel(self, channel_name: str) -> bool:
"""Trigger typing indicator in a channel"""
if not settings.DISCORD_NOTIFICATIONS_ENABLED:
return False
try:
channel = await self.get_channel_by_name(channel_name)
if not channel:
logger.error(f"Channel {channel_name} not found")
return False
await channel.trigger_typing()
return True
except Exception as e:
logger.error(f"Failed to trigger typing for channel {channel_name}: {e}")
return False
async def run_collector(): async def run_collector():
"""Run the Discord message collector""" """Run the Discord message collector"""

View File

@ -170,14 +170,6 @@ def process_discord_message(message_id: int) -> dict[str, Any]:
"message_id": message_id, "message_id": message_id,
} }
response = call_llm(session, discord_message, settings.DISCORD_MODEL)
if not response:
return {
"status": "processed",
"message_id": message_id,
}
bot_id = _resolve_bot_id(discord_message) bot_id = _resolve_bot_id(discord_message)
if not bot_id: if not bot_id:
logger.warning( logger.warning(
@ -189,6 +181,26 @@ def process_discord_message(message_id: int) -> dict[str, Any]:
"message_id": message_id, "message_id": message_id,
} }
if discord_message.channel and discord_message.channel.server:
discord.trigger_typing_channel(
bot_id, discord_message.channel.name
)
else:
discord.trigger_typing_dm(bot_id, discord_message.from_id)
response: str | None = None
try:
response = call_llm(session, discord_message, settings.DISCORD_MODEL)
except Exception:
logger.exception("Failed to generate Discord response")
if not response:
return {
"status": "processed",
"message_id": message_id,
}
if discord_message.channel.server: if discord_message.channel.server:
discord.send_to_channel(bot_id, discord_message.channel.name, response) discord.send_to_channel(bot_id, discord_message.channel.name, response)
else: else: