feat(05-04): implement @mention message handler
Add mention_handler for @claude, @gpt, @gemini direct messages: - Parse model name from @mention prefix - Get active discussion for context (if exists) - Query model via query_model_direct with full context - Persist response with is_direct=True flag - Register MessageHandler with regex filter after CommandHandlers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5934d21256
commit
3296874408
2 changed files with 89 additions and 2 deletions
|
|
@ -4,12 +4,13 @@ This module contains handlers for Telegram bot commands including
|
||||||
project management, discussion commands, and export functionality.
|
project management, discussion commands, and export functionality.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from telegram.ext import Application, CommandHandler
|
from telegram.ext import Application, CommandHandler, MessageHandler, filters
|
||||||
|
|
||||||
from moai.bot.handlers.commands import help_command, start_command
|
from moai.bot.handlers.commands import help_command, start_command
|
||||||
from moai.bot.handlers.discussion import (
|
from moai.bot.handlers.discussion import (
|
||||||
ask_command,
|
ask_command,
|
||||||
discuss_command,
|
discuss_command,
|
||||||
|
mention_handler,
|
||||||
next_command,
|
next_command,
|
||||||
open_command,
|
open_command,
|
||||||
stop_command,
|
stop_command,
|
||||||
|
|
@ -41,3 +42,7 @@ def register_handlers(app: Application) -> None:
|
||||||
app.add_handler(CommandHandler("discuss", discuss_command))
|
app.add_handler(CommandHandler("discuss", discuss_command))
|
||||||
app.add_handler(CommandHandler("next", next_command))
|
app.add_handler(CommandHandler("next", next_command))
|
||||||
app.add_handler(CommandHandler("stop", stop_command))
|
app.add_handler(CommandHandler("stop", stop_command))
|
||||||
|
|
||||||
|
# @mention handler - MessageHandler registered AFTER CommandHandlers
|
||||||
|
# Matches messages starting with @claude, @gpt, or @gemini followed by content
|
||||||
|
app.add_handler(MessageHandler(filters.Regex(r"^@(claude|gpt|gemini)\s"), mention_handler))
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ from telegram.ext import ContextTypes
|
||||||
from moai.bot.handlers.projects import get_selected_project
|
from moai.bot.handlers.projects import get_selected_project
|
||||||
from moai.core.ai_client import MODEL_MAP, get_ai_client
|
from moai.core.ai_client import MODEL_MAP, get_ai_client
|
||||||
from moai.core.models import DiscussionType, RoundType
|
from moai.core.models import DiscussionType, RoundType
|
||||||
from moai.core.orchestrator import query_models_parallel, run_discussion_round
|
from moai.core.orchestrator import query_model_direct, query_models_parallel, run_discussion_round
|
||||||
from moai.core.services.discussion import (
|
from moai.core.services.discussion import (
|
||||||
complete_discussion,
|
complete_discussion,
|
||||||
create_discussion,
|
create_discussion,
|
||||||
create_message,
|
create_message,
|
||||||
create_round,
|
create_round,
|
||||||
get_active_discussion,
|
get_active_discussion,
|
||||||
|
get_current_round,
|
||||||
get_discussion,
|
get_discussion,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -343,3 +344,84 @@ async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await update.message.reply_text(f"Error: {e}")
|
await update.message.reply_text(f"Error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def mention_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""Handle @model mention messages.
|
||||||
|
|
||||||
|
Messages starting with @claude, @gpt, or @gemini are routed to that
|
||||||
|
specific model. If a discussion is active, includes full context.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
@claude What do you think about this?
|
||||||
|
@gpt Can you elaborate on your previous point?
|
||||||
|
@gemini Do you agree with Claude?
|
||||||
|
"""
|
||||||
|
message_text = update.message.text
|
||||||
|
if not message_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse model name from first word (e.g., "@claude" -> "claude")
|
||||||
|
parts = message_text.split(maxsplit=1)
|
||||||
|
if not parts:
|
||||||
|
return
|
||||||
|
|
||||||
|
model_tag = parts[0].lower()
|
||||||
|
# Strip the @ prefix
|
||||||
|
model_name = model_tag.lstrip("@")
|
||||||
|
|
||||||
|
# Validate model
|
||||||
|
if model_name not in MODEL_MAP:
|
||||||
|
return # Not a valid model mention, ignore
|
||||||
|
|
||||||
|
# Get the rest of the message as content
|
||||||
|
content = parts[1] if len(parts) > 1 else ""
|
||||||
|
if not content.strip():
|
||||||
|
await update.message.reply_text(
|
||||||
|
f"Usage: @{model_name} <message>\n\nExample: @{model_name} What do you think?"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get project context if available
|
||||||
|
project = await get_selected_project(context)
|
||||||
|
if project is None:
|
||||||
|
await update.message.reply_text("No project selected. Use /project select <name> first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
project_name = project.name
|
||||||
|
|
||||||
|
# Check for active discussion (for context)
|
||||||
|
discussion = await get_active_discussion(project.id)
|
||||||
|
|
||||||
|
# Show typing indicator
|
||||||
|
await update.message.chat.send_action("typing")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Query the model directly with optional discussion context
|
||||||
|
response = await query_model_direct(
|
||||||
|
model=model_name,
|
||||||
|
message=content,
|
||||||
|
discussion=discussion,
|
||||||
|
project_name=project_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
# If there's an active discussion, persist the message
|
||||||
|
if discussion is not None:
|
||||||
|
# Get or create a round for this direct message
|
||||||
|
current_round = await get_current_round(discussion.id)
|
||||||
|
if current_round is not None:
|
||||||
|
await create_message(
|
||||||
|
round_id=current_round.id,
|
||||||
|
model=model_name,
|
||||||
|
content=response,
|
||||||
|
is_direct=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Format response
|
||||||
|
await update.message.reply_text(
|
||||||
|
f"*@{model_name.title()} (direct):*\n{response}",
|
||||||
|
parse_mode="Markdown",
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await update.message.reply_text(f"Error: {e}")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue