From 1d36ebe2f964c4b70d4d69d2fcc4a1d35938e8f5 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Wed, 4 Mar 2026 09:19:53 +0530 Subject: [PATCH] Update frontend components: TeamHuddleWidget, useAgentHuddleFeed, TeamActivityPage --- frontend/package-lock.json | 10 +- frontend/package.json | 2 +- frontend/src/App.tsx | 1 - .../components/TeamHuddleWidget.tsx | 87 +++++--------- frontend/src/hooks/useAgentHuddleFeed.ts | 17 +-- frontend/src/pages/TeamActivityPage.tsx | 111 ------------------ 6 files changed, 43 insertions(+), 185 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8dd5d5f9..f3817b61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -46,7 +46,7 @@ "@types/jest": "^30.0.0", "@types/node": "^25.0.10", "source-map-explorer": "^2.5.2", - "typescript": "^4.9.5" + "typescript": "^5.3.3" } }, "node_modules/@0no-co/graphql.web": { @@ -24057,16 +24057,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { diff --git a/frontend/package.json b/frontend/package.json index a2b9e2f2..b6e42b7e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,7 +68,7 @@ "@types/jest": "^30.0.0", "@types/node": "^25.0.10", "source-map-explorer": "^2.5.2", - "typescript": "^4.9.5" + "typescript": "^5.3.3" }, "proxy": "http://localhost:8000", "homepage": "/" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bd516a8e..fc168638 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -51,7 +51,6 @@ import BillingPage from './pages/BillingPage'; import ApprovalsPage from './pages/ApprovalsPage'; import TeamActivityPage from './pages/TeamActivityPage'; import StripeDisputesDashboard from './pages/StripeDisputesDashboard'; -import TeamActivityPage from './pages/TeamActivityPage'; import ProtectedRoute from './components/shared/ProtectedRoute'; import GSCAuthCallback from './components/SEODashboard/components/GSCAuthCallback'; import Landing from './components/Landing/Landing'; diff --git a/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx b/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx index 278f9bd2..d6afc7da 100644 --- a/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx +++ b/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Box, Paper, @@ -19,7 +19,7 @@ import { Refresh as RefreshIcon, ExpandMore as ExpandMoreIcon, } from '@mui/icons-material'; -import { apiClient } from '../../../api/client'; +import { useAgentHuddleFeed } from '../../../hooks/useAgentHuddleFeed'; type EventPayload = { phase?: string | null; @@ -34,68 +34,39 @@ type EventPayload = { metadata?: Record; }; -type TeamActivityEvent = { - id: number; - event_type: string; - severity: string; - message?: string | null; - created_at?: string | null; - payload?: EventPayload | null; -}; - -type AgentRun = { - id: number; - agent_type: string; - status: string; - started_at?: string | null; -}; - const TeamHuddleWidget: React.FC = () => { - const [loading, setLoading] = React.useState(true); - const [error, setError] = React.useState(null); - const [timeline, setTimeline] = React.useState>([]); + const { runs, events, connectionMode } = useAgentHuddleFeed({ detailTier: 'detailed' }); - const loadTimeline = React.useCallback(async () => { - setLoading(true); - setError(null); - try { - const runsResp = await apiClient.get('/api/agents/runs', { params: { limit: 5 } }); - const runs: AgentRun[] = runsResp?.data?.data?.runs || []; + // Group events by run_id for the UI + const timeline = useMemo(() => { + // Only take top 5 runs + const topRuns = runs.slice(0, 5); + return topRuns.map(run => { + // Find events for this run + const runEvents = events + .filter(e => e.run_id === run.id) + .map(e => ({ + ...e, + payload: (e.payload || {}) as EventPayload + })); + return { run, events: runEvents }; + }); + }, [runs, events]); - const eventResponses = await Promise.all( - runs.slice(0, 3).map(async (run) => { - const eventsResp = await apiClient.get(`/api/agents/runs/${run.id}/events`, { params: { limit: 25 } }); - return { - run, - events: (eventsResp?.data?.data?.events || []) as TeamActivityEvent[], - }; - }), - ); - - setTimeline(eventResponses); - } catch (e: any) { - setError(e?.message || 'Failed to load team activity'); - } finally { - setLoading(false); - } - }, []); - - React.useEffect(() => { - loadTimeline(); - }, [loadTimeline]); + const loading = connectionMode === 'connecting' && runs.length === 0; return ( Team Activity - + - - - - - {loading && ( @@ -104,15 +75,11 @@ const TeamHuddleWidget: React.FC = () => { )} - {!loading && error && ( - {error} - )} - - {!loading && !error && timeline.length === 0 && ( + {!loading && timeline.length === 0 && ( No team activity yet. )} - {!loading && !error && timeline.length > 0 && ( + {!loading && timeline.length > 0 && ( {timeline.map(({ run, events }, index) => ( diff --git a/frontend/src/hooks/useAgentHuddleFeed.ts b/frontend/src/hooks/useAgentHuddleFeed.ts index 34656733..e691a292 100644 --- a/frontend/src/hooks/useAgentHuddleFeed.ts +++ b/frontend/src/hooks/useAgentHuddleFeed.ts @@ -1,9 +1,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { apiClient, getApiUrl, getAuthTokenGetter } from '../api/client'; -export interface AgentRunItem { id: number; agent_type?: string; status?: string; success?: boolean | null; result_summary?: string | null; finished_at?: string | null; } -export interface AgentEventItem { id: number; agent_type?: string; event_type?: string; message?: string | null; created_at?: string | null; } -export interface AgentAlertItem { id: number; title?: string; message?: string; severity?: string; read_at?: string | null; } +export interface AgentRunItem { id: number; agent_type?: string; status?: string; success?: boolean | null; result_summary?: string | null; error_message?: string | null; finished_at?: string | null; started_at?: string | null; } +export interface AgentEventItem { id: number; run_id?: number; agent_type?: string; event_type?: string; message?: string | null; created_at?: string | null; payload?: any; } +export interface AgentAlertItem { id: number; title?: string; message?: string; severity?: string; read_at?: string | null; payload?: any; } export interface AgentApprovalItem { id: number; action_type?: string; status?: string; risk_level?: number; created_at?: string | null; } interface Cursor { run_id: number; event_id: number; alert_id: number; approval_id: number; } @@ -42,13 +42,14 @@ const parseSseLines = (raw: string): Array<{ event: string; data: string }> => { }); }; -export const useAgentHuddleFeed = () => { +export const useAgentHuddleFeed = (options?: { detailTier?: 'summary' | 'detailed' | 'debug' }) => { const [feed, setFeed] = useState({ runs: [], events: [], alerts: [], approvals: [], cursor: DEFAULT_CURSOR }); const [connectionMode, setConnectionMode] = useState<'connecting' | 'sse' | 'polling'>('connecting'); const [lastHeartbeatAt, setLastHeartbeatAt] = useState(null); const stopRef = useRef(false); const reconnectAttemptRef = useRef(0); const cursorRef = useRef(DEFAULT_CURSOR); + const detailTier = options?.detailTier || 'summary'; const applyPayload = useCallback((payload: Partial, replace = false) => { setFeed((prev) => ({ @@ -74,11 +75,12 @@ export const useAgentHuddleFeed = () => { }, []); const loadSnapshot = useCallback(async (cursor?: Cursor) => { - const resp = await apiClient.get('/api/agents/huddle/feed', { params: cursor || {} }); + const params = { ...(cursor || {}), detail_tier: detailTier }; + const resp = await apiClient.get('/api/agents/huddle/feed', { params }); const data = resp?.data?.data as FeedPayload; applyPayload(data, !cursor); return data; - }, [applyPayload]); + }, [applyPayload, detailTier]); useEffect(() => { stopRef.current = false; @@ -104,7 +106,8 @@ export const useAgentHuddleFeed = () => { const token = tokenGetter ? await tokenGetter() : null; if (!token) throw new Error('No auth token available for SSE stream'); - const response = await fetch(`${getApiUrl()}/api/agents/huddle/stream`, { + const streamUrl = `${getApiUrl()}/api/agents/huddle/stream?detail_tier=${detailTier}`; + const response = await fetch(streamUrl, { headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' }, }); diff --git a/frontend/src/pages/TeamActivityPage.tsx b/frontend/src/pages/TeamActivityPage.tsx index 9f48c668..71026c9d 100644 --- a/frontend/src/pages/TeamActivityPage.tsx +++ b/frontend/src/pages/TeamActivityPage.tsx @@ -1,114 +1,4 @@ import React from 'react'; -<<<<<<< HEAD -import { - Box, - Chip, - Divider, - Paper, - Stack, - Typography, - List, - ListItem, - ListItemText, -} from '@mui/material'; - -const activeRuns = [ - { id: 'run-9142', title: 'Q4 SEO Pillar Refresh', owner: 'SEO Specialist', progress: '67%' }, - { id: 'run-9143', title: 'LinkedIn Carousel Draft', owner: 'Content Strategist', progress: '42%' }, - { id: 'run-9144', title: 'Competitor Monitoring Sweep', owner: 'Competitor Analyst', progress: '88%' }, -]; - -const timelineEvents = [ - { time: '09:20', detail: 'Strategy Architect approved updated campaign goals.' }, - { time: '09:35', detail: 'Social Manager queued 6 posts for review.' }, - { time: '10:05', detail: 'SEO Specialist flagged ranking drop on "AI copy tools".' }, - { time: '10:24', detail: 'Content Strategist generated new briefing packet.' }, -]; - -const alerts = [ - { severity: 'high', label: '2 content briefs are blocked on missing references.' }, - { severity: 'medium', label: 'SERP volatility increased for 3 tracked keywords.' }, -]; - -const approvals = [ - { id: 'ap-112', action: 'Publish LinkedIn thread for Campaign Alpha', requestedBy: 'Social Manager' }, - { id: 'ap-113', action: 'Approve budget reallocation to ad creatives', requestedBy: 'Strategy Architect' }, -]; - -export default function TeamActivityPage() { - return ( - - - Team Activity - - - Real-time view of active agent workstreams, timelines, alerts, and pending approvals. - - - - - Active Runs - - {activeRuns.map((run) => ( - - - {run.title} - {run.owner} - - - - ))} - - - - - Event Timeline - - {timelineEvents.map((event, index) => ( - - {index > 0 && } - - {event.detail}} - secondary={{event.time}} - /> - - - ))} - - - - - Alerts - - {alerts.map((alert) => ( - - ))} - - - - - Approvals Requiring Action - - {approvals.map((approval) => ( - - {approval.action} - Requested by {approval.requestedBy} - - ))} - - - - - ); -} -======= import { Box, Card, CardContent, Chip, Divider, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'; import { useAgentHuddleFeed } from '../hooks/useAgentHuddleFeed'; @@ -174,4 +64,3 @@ const TeamActivityPage: React.FC = () => { }; export default TeamActivityPage; ->>>>>>> pr-368