mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Adds a notification system for the Copilot (AutoPilot) so users know when background chats finish processing — via in-app indicators, sounds, browser notifications, and document title badges. ### Changes 🏗️ **Backend** - Add `is_processing` field to `SessionSummaryResponse` — batch-checks Redis for active stream status on each session in the list endpoint - Fix `is_processing` always returning `false` due to bytes vs string comparison (`b"running"` → `"running"`) with `decode_responses=True` Redis client - Add `CopilotCompletionPayload` model for WebSocket notification events - Publish `copilot_completion` notification via WebSocket when a session completes in `stream_registry.mark_session_completed` **Frontend — Notification UI** - Add `NotificationBanner` component — amber banner prompting users to enable browser notifications (auto-hides when already enabled or dismissed) - Add `NotificationDialog` component — modal dialog for enabling notifications, supports force-open from sidebar menu for testing - Fix repeated word "response" in dialog copy **Frontend — Sidebar** - Add bell icon in sidebar header with popover menu containing: - Notifications toggle (requests browser permission on enable; shows toast if denied) - Sound toggle (disabled when notifications are off) - "Show notification popup" button (for testing the dialog) - "Clear local data" button (resets all copilot localStorage keys) - Bell icon states: `BellSlash` (disabled), `Bell` (enabled, no sound), `BellRinging` (enabled + sound) - Add processing indicator (PulseLoader) and completion checkmark (CheckCircle) inline with chat title, to the left of the hamburger menu - Processing indicator hides immediately when completion arrives (no overlap with checkmark) - Fix PulseLoader initial flash — start at `scale(0); opacity: 0` with smoother keyframes - Add 10s polling (`refetchInterval`) to session list so `is_processing` updates automatically - Clear document title badge when navigating to a completed chat - Remove duplicate "Your chats" heading that appeared in both SidebarHeader and SidebarContent **Frontend — Notification Hook (`useCopilotNotifications`)** - Listen for `copilot_completion` WebSocket events - Track completed sessions in Zustand store - Play notification sound (only for background sessions, not active chat) - Update `document.title` with unread count badge - Send browser `Notification` when tab is hidden, with click-to-navigate to the completed chat - Reset document title on tab focus **Frontend — Store & Storage** - Add `completedSessionIDs`, `isNotificationsEnabled`, `isSoundEnabled`, `showNotificationDialog`, `clearCopilotLocalData` to Zustand store - Persist notification and sound preferences in localStorage - On init, validate `isNotificationsEnabled` against actual `Notification.permission` - Add localStorage keys: `COPILOT_NOTIFICATIONS_ENABLED`, `COPILOT_SOUND_ENABLED`, `COPILOT_NOTIFICATION_BANNER_DISMISSED`, `COPILOT_NOTIFICATION_DIALOG_DISMISSED` **Mobile** - Add processing/completion indicators and sound toggle to MobileDrawer ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Open copilot, start a chat, switch to another chat — verify processing indicator appears on the background chat - [x] Wait for background chat to complete — verify checkmark appears, processing indicator disappears - [x] Enable notifications via bell menu — verify browser permission prompt appears - [x] With notifications enabled, complete a background chat while on another tab — verify system notification appears with sound - [x] Click system notification — verify it navigates to the completed chat - [x] Verify document title shows unread count and resets when navigating to the chat or focusing the tab - [x] Toggle sound off — verify no sound plays on completion - [x] Toggle notifications off — verify no sound, no system notification, no badge - [x] Clear local data — verify all preferences reset - [x] Verify notification banner hides when notifications already enabled - [x] Verify dialog auto-shows for first-time users and can be force-opened from menu --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
2.4 KiB
Python
102 lines
2.4 KiB
Python
import enum
|
|
from typing import Any, Literal, Optional
|
|
|
|
import pydantic
|
|
from prisma.enums import OnboardingStep
|
|
|
|
from backend.data.auth.api_key import APIKeyInfo, APIKeyPermission
|
|
from backend.data.graph import Graph
|
|
from backend.util.timezone_name import TimeZoneName
|
|
|
|
|
|
class WSMethod(enum.Enum):
|
|
SUBSCRIBE_GRAPH_EXEC = "subscribe_graph_execution"
|
|
SUBSCRIBE_GRAPH_EXECS = "subscribe_graph_executions"
|
|
UNSUBSCRIBE = "unsubscribe"
|
|
GRAPH_EXECUTION_EVENT = "graph_execution_event"
|
|
NODE_EXECUTION_EVENT = "node_execution_event"
|
|
NOTIFICATION = "notification"
|
|
ERROR = "error"
|
|
HEARTBEAT = "heartbeat"
|
|
|
|
|
|
class WSMessage(pydantic.BaseModel):
|
|
method: WSMethod
|
|
data: Optional[dict[str, Any] | list[Any] | str] = None
|
|
success: bool | None = None
|
|
channel: str | None = None
|
|
error: str | None = None
|
|
|
|
|
|
class WSSubscribeGraphExecutionRequest(pydantic.BaseModel):
|
|
graph_exec_id: str
|
|
|
|
|
|
class WSSubscribeGraphExecutionsRequest(pydantic.BaseModel):
|
|
graph_id: str
|
|
|
|
|
|
GraphCreationSource = Literal["builder", "upload"]
|
|
GraphExecutionSource = Literal["builder", "library", "onboarding"]
|
|
|
|
|
|
class CreateGraph(pydantic.BaseModel):
|
|
graph: Graph
|
|
source: GraphCreationSource | None = None
|
|
|
|
|
|
class CreateAPIKeyRequest(pydantic.BaseModel):
|
|
name: str
|
|
permissions: list[APIKeyPermission]
|
|
description: Optional[str] = None
|
|
|
|
|
|
class CreateAPIKeyResponse(pydantic.BaseModel):
|
|
api_key: APIKeyInfo
|
|
plain_text_key: str
|
|
|
|
|
|
class SetGraphActiveVersion(pydantic.BaseModel):
|
|
active_graph_version: int
|
|
|
|
|
|
class UpdatePermissionsRequest(pydantic.BaseModel):
|
|
permissions: list[APIKeyPermission]
|
|
|
|
|
|
class RequestTopUp(pydantic.BaseModel):
|
|
credit_amount: int
|
|
|
|
|
|
class UploadFileResponse(pydantic.BaseModel):
|
|
file_uri: str
|
|
file_name: str
|
|
size: int
|
|
content_type: str
|
|
expires_in_hours: int
|
|
|
|
|
|
class TimezoneResponse(pydantic.BaseModel):
|
|
# Allow "not-set" as a special value, or any valid IANA timezone
|
|
timezone: TimeZoneName | str
|
|
|
|
|
|
class UpdateTimezoneRequest(pydantic.BaseModel):
|
|
timezone: TimeZoneName
|
|
|
|
|
|
class NotificationPayload(pydantic.BaseModel):
|
|
type: str
|
|
event: str
|
|
|
|
model_config = pydantic.ConfigDict(extra="allow")
|
|
|
|
|
|
class OnboardingNotificationPayload(NotificationPayload):
|
|
step: OnboardingStep | None
|
|
|
|
|
|
class CopilotCompletionPayload(NotificationPayload):
|
|
session_id: str
|
|
status: Literal["completed", "failed"]
|