Phase 5: Notifications — WebSocket, APScheduler, AI tool, health review
Backend: - Notification model + Alembic migration - Notification service: CRUD, mark read, unread count, pending scheduled - WebSocket manager singleton for real-time push - WebSocket endpoint /ws/notifications with JWT auth via query param - APScheduler integration: periodic notification sender (every 60s), daily proactive health review job (8 AM) - AI tool: schedule_notification (immediate or scheduled) - Health review worker: analyzes user memory via Claude, creates ai_generated notifications with WebSocket push Frontend: - Notification API client + Zustand store - WebSocket hook with auto-reconnect (exponential backoff) - Notification bell in header with unread count badge + dropdown - Notifications page with type badges, mark read, mark all read - WebSocket initialized in AppLayout for app-wide real-time updates - Enabled notifications nav in sidebar - English + Russian translations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@ from app.services.context_service import DEFAULT_SYSTEM_PROMPT, get_primary_cont
|
||||
from app.services.chat_service import get_chat, save_message
|
||||
from app.services.memory_service import get_critical_memories, create_memory, get_user_memories
|
||||
from app.services.document_service import search_documents
|
||||
from app.services.notification_service import create_notification
|
||||
from app.services.ws_manager import manager
|
||||
|
||||
client = AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY)
|
||||
|
||||
@@ -68,6 +70,28 @@ AI_TOOLS = [
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "schedule_notification",
|
||||
"description": "Schedule a notification or reminder for the user. Can be immediate or scheduled for a future time.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string", "description": "Notification title"},
|
||||
"body": {"type": "string", "description": "Notification body text"},
|
||||
"scheduled_at": {
|
||||
"type": "string",
|
||||
"description": "ISO 8601 datetime for scheduled delivery. Omit for immediate.",
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["reminder", "alert", "info"],
|
||||
"description": "Notification type",
|
||||
"default": "reminder",
|
||||
},
|
||||
},
|
||||
"required": ["title", "body"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -104,6 +128,40 @@ async def _execute_tool(
|
||||
items = [{"category": e.category, "title": e.title, "content": e.content, "importance": e.importance} for e in entries]
|
||||
return json.dumps({"entries": items, "count": len(items)})
|
||||
|
||||
elif tool_name == "schedule_notification":
|
||||
from datetime import datetime
|
||||
scheduled_at = None
|
||||
if tool_input.get("scheduled_at"):
|
||||
scheduled_at = datetime.fromisoformat(tool_input["scheduled_at"])
|
||||
|
||||
notif = await create_notification(
|
||||
db, user_id,
|
||||
title=tool_input["title"],
|
||||
body=tool_input["body"],
|
||||
type=tool_input.get("type", "reminder"),
|
||||
scheduled_at=scheduled_at,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Push immediately if not scheduled
|
||||
if not scheduled_at:
|
||||
await manager.send_to_user(user_id, {
|
||||
"type": "new_notification",
|
||||
"notification": {
|
||||
"id": str(notif.id),
|
||||
"title": notif.title,
|
||||
"body": notif.body,
|
||||
"type": notif.type,
|
||||
"created_at": notif.created_at.isoformat(),
|
||||
},
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"status": "scheduled" if scheduled_at else "sent",
|
||||
"id": str(notif.id),
|
||||
"title": notif.title,
|
||||
})
|
||||
|
||||
return json.dumps({"error": f"Unknown tool: {tool_name}"})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user