feat: scheduled reminders + less aggressive article summary
Add scheduled messages/reminders system: - New scheduled_messages table in memory-service with CRUD endpoints - schedule_message, list_reminders, cancel_reminder tools for the bot - Background scheduler loop (30s) sends due reminders automatically - Supports one-time, daily, weekly, weekdays, monthly repeat patterns Make article URL handling non-blocking: - Show 3 options (discuss, text summary, audio) instead of forcing audio wizard - Default to passing article context to AI if user just keeps chatting - New AWAITING_LANGUAGE state for cleaner audio flow FSM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,11 @@ class ArticleSummaryHandler:
|
||||
return await self._check_for_url(room_id, sender, body)
|
||||
|
||||
elif session.state == ArticleState.URL_DETECTED:
|
||||
# Waiting for language selection
|
||||
# Waiting for user to pick action (discuss, text summary, audio)
|
||||
return await self._on_action_choice(room_id, sender, body, body_lower)
|
||||
|
||||
elif session.state == ArticleState.AWAITING_LANGUAGE:
|
||||
# Audio flow: waiting for language selection
|
||||
return self._on_language(room_id, sender, body_lower)
|
||||
|
||||
elif session.state == ArticleState.LANGUAGE:
|
||||
@@ -143,10 +147,11 @@ class ArticleSummaryHandler:
|
||||
|
||||
return (
|
||||
f"**Found:** {session.title} (~{read_time} min read){topics_hint}\n\n"
|
||||
f"Want an audio summary? What language?\n"
|
||||
f"1️⃣ English\n"
|
||||
f"2️⃣ German\n\n"
|
||||
f"_(or say \"cancel\" to skip)_"
|
||||
f"What would you like to do?\n"
|
||||
f"1\ufe0f\u20e3 **Discuss** \u2014 I'll read the article and we can talk about it\n"
|
||||
f"2\ufe0f\u20e3 **Text summary** \u2014 Quick written summary\n"
|
||||
f"3\ufe0f\u20e3 **Audio summary** \u2014 Blinkist-style MP3\n\n"
|
||||
f"_(or just keep chatting \u2014 I won't interrupt)_"
|
||||
)
|
||||
|
||||
def _on_language(
|
||||
@@ -224,6 +229,79 @@ class ArticleSummaryHandler:
|
||||
self.sessions.touch(sender, room_id)
|
||||
return "__GENERATE__"
|
||||
|
||||
async def _on_action_choice(
|
||||
self, room_id: str, sender: str, body: str, body_lower: str
|
||||
) -> str | None:
|
||||
"""Handle user's choice after URL detection: discuss, text summary, or audio."""
|
||||
session = self.sessions.get(sender, room_id)
|
||||
|
||||
# Option 1: Discuss — reset FSM, return article context for AI handler
|
||||
if body_lower in ("1", "discuss", "diskutieren", "besprechen"):
|
||||
article_context = session.content[:8000]
|
||||
title = session.title
|
||||
self.sessions.reset(sender, room_id)
|
||||
return f"__DISCUSS__{title}\n{article_context}"
|
||||
|
||||
# Option 2: Text summary — generate and return text, no TTS
|
||||
if body_lower in ("2", "text", "text summary", "zusammenfassung"):
|
||||
return await self._generate_text_summary(room_id, sender)
|
||||
|
||||
# Option 3: Audio summary — enter language selection (existing flow)
|
||||
if body_lower in ("3", "audio", "audio summary"):
|
||||
return self._prompt_language(room_id, sender)
|
||||
|
||||
# Anything else — user is just chatting, reset and pass through with article context
|
||||
article_context = session.content[:8000]
|
||||
title = session.title
|
||||
self.sessions.reset(sender, room_id)
|
||||
return f"__DISCUSS__{title}\n{article_context}"
|
||||
|
||||
def _prompt_language(self, room_id: str, sender: str) -> str:
|
||||
"""Present language selection for audio summary."""
|
||||
session = self.sessions.get(sender, room_id)
|
||||
session.state = ArticleState.AWAITING_LANGUAGE
|
||||
self.sessions.touch(sender, room_id)
|
||||
return (
|
||||
"What language for the audio summary?\n"
|
||||
"1\ufe0f\u20e3 English\n"
|
||||
"2\ufe0f\u20e3 German"
|
||||
)
|
||||
|
||||
async def _generate_text_summary(self, room_id: str, sender: str) -> str | None:
|
||||
"""Generate a text-only summary of the article."""
|
||||
session = self.sessions.get(sender, room_id)
|
||||
try:
|
||||
resp = await self.llm.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Summarize this article concisely in 3-5 paragraphs. "
|
||||
"Respond in the same language as the article."
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Article: {session.title}\n\n{session.content[:12000]}",
|
||||
},
|
||||
],
|
||||
max_tokens=1000,
|
||||
temperature=0.3,
|
||||
)
|
||||
summary = resp.choices[0].message.content.strip()
|
||||
session.summary_text = summary
|
||||
session.state = ArticleState.COMPLETE
|
||||
self.sessions.touch(sender, room_id)
|
||||
return (
|
||||
f"**Summary: {session.title}**\n\n{summary}\n\n"
|
||||
f"_Ask follow-up questions or share a new link._"
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("Text summary failed", exc_info=True)
|
||||
self.sessions.reset(sender, room_id)
|
||||
return None
|
||||
|
||||
async def generate_and_post(self, bot, room_id: str, sender: str) -> None:
|
||||
"""Run the full pipeline: summarize → TTS → upload MP3."""
|
||||
session = self.sessions.get(sender, room_id)
|
||||
|
||||
Reference in New Issue
Block a user