diff --git a/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx b/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx index 79bd709d..3523cd6a 100644 --- a/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx +++ b/frontend/src/components/MainDashboard/components/TeamHuddleWidget.tsx @@ -4,7 +4,6 @@ import { Paper, Typography, Avatar, - AvatarGroup, Chip, List, ListItem, @@ -12,7 +11,8 @@ import { ListItemText, Divider, IconButton, - Tooltip + Tooltip, + CircularProgress } from '@mui/material'; import { Psychology as StrategyIcon, @@ -20,11 +20,13 @@ import { Search as SeoIcon, Campaign as SocialIcon, CompareArrows as CompetitorIcon, + SmartToy as AgentIcon, Refresh as RefreshIcon, - MoreVert as MoreVertIcon + WarningAmber as WarningIcon } from '@mui/icons-material'; +import { apiClient } from '../../../api/client'; -interface AgentStatus { +interface AgentCard { id: string; name: string; role: string; @@ -34,57 +36,159 @@ interface AgentStatus { color: string; } -// Mock data - In real implementation, this would come from a backend endpoint -// /api/agents/status or similar -const AGENT_TEAM: AgentStatus[] = [ - { - id: 'strategy_architect', +interface AgentMeta { + name: string; + role: string; + icon: React.ElementType; + color: string; +} + +const AGENT_META: Record = { + strategy: { name: 'Strategy Architect', role: 'Team Lead', - status: 'active', - current_activity: 'Analyzing content pillar performance', icon: StrategyIcon, - color: '#6366f1' // Indigo + color: '#6366f1' }, - { - id: 'content_strategist', + content: { name: 'Content Strategist', role: 'Creative', - status: 'thinking', - current_activity: 'Identifying semantic gaps in "AI Tools"', icon: ContentIcon, - color: '#10b981' // Emerald + color: '#10b981' }, - { - id: 'seo_specialist', + seo: { name: 'SEO Specialist', role: 'Technical', - status: 'idle', - current_activity: 'Monitoring SERP rankings', icon: SeoIcon, - color: '#f59e0b' // Amber + color: '#f59e0b' }, - { - id: 'social_manager', + social: { name: 'Social Manager', role: 'Engagement', - status: 'idle', - current_activity: 'Waiting for new content to schedule', icon: SocialIcon, - color: '#ec4899' // Pink + color: '#ec4899' }, - { - id: 'competitor_analyst', + competitor: { name: 'Competitor Analyst', role: 'Intelligence', - status: 'active', - current_activity: 'Scanning competitor X for new posts', icon: CompetitorIcon, - color: '#ef4444' // Red + color: '#ef4444' } -]; +}; + +const normalizeAgentKey = (rawKey: string): string => { + const key = (rawKey || '').toLowerCase(); + + if (key.includes('strategy')) return 'strategy'; + if (key.includes('content')) return 'content'; + if (key.includes('seo') || key.includes('search')) return 'seo'; + if (key.includes('social')) return 'social'; + if (key.includes('competitor') || key.includes('competition')) return 'competitor'; + + return key; +}; + +const normalizeStatus = (status: string): AgentCard['status'] => { + const value = (status || '').toLowerCase(); + + if (value === 'active' || value === 'thinking' || value === 'idle' || value === 'offline') { + return value; + } + + if (value === 'running' || value === 'busy') return 'active'; + if (value === 'processing' || value === 'analyzing') return 'thinking'; + if (value === 'inactive' || value === 'stopped') return 'offline'; + + return 'idle'; +}; const TeamHuddleWidget: React.FC = () => { + const [agents, setAgents] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(null); + const [refreshing, setRefreshing] = React.useState(false); + const [alertCount, setAlertCount] = React.useState(0); + + const fetchTeamData = React.useCallback(async (isRefresh = false) => { + if (isRefresh) { + setRefreshing(true); + } else { + setLoading(true); + } + + setError(null); + + try { + const [statusResp, runsResp, alertsResp] = await Promise.allSettled([ + apiClient.get('/api/agents/status'), + apiClient.get('/api/agents/runs', { params: { limit: 20 } }), + apiClient.get('/api/agents/alerts', { params: { unread_only: true, limit: 20 } }) + ]); + + if (statusResp.status !== 'fulfilled') { + throw new Error('Unable to load team status'); + } + + const statusPayload = statusResp.value?.data?.data?.agents || statusResp.value?.data?.agents || []; + const runsPayload = runsResp.status === 'fulfilled' + ? (runsResp.value?.data?.data?.runs || runsResp.value?.data?.runs || []) + : []; + const alertsPayload = alertsResp.status === 'fulfilled' + ? (alertsResp.value?.data?.data?.alerts || alertsResp.value?.data?.alerts || []) + : []; + + const runByAgent = new Map(); + runsPayload.forEach((run: Record) => { + const key = normalizeAgentKey(run.agent_key || run.agent || run.agent_id || ''); + if (!key || runByAgent.has(key)) return; + + runByAgent.set( + key, + run.summary || run.context || run.activity || run.status_message || 'Working on recent tasks' + ); + }); + + const normalizedAgents: AgentCard[] = statusPayload.map((agent: Record, index: number) => { + const rawKey = agent.key || agent.agent_key || agent.id || agent.name || `agent-${index}`; + const normalizedKey = normalizeAgentKey(rawKey); + const meta = AGENT_META[normalizedKey] || { + name: agent.display_name || agent.name || rawKey, + role: 'AI Agent', + icon: AgentIcon, + color: '#64748b' + }; + + return { + id: String(rawKey), + name: agent.display_name || meta.name, + role: agent.role || meta.role, + status: normalizeStatus(agent.status || agent.operational_status || ''), + current_activity: + agent.current_activity || + runByAgent.get(normalizedKey) || + agent.last_message || + 'Waiting for next instruction', + icon: meta.icon, + color: meta.color + }; + }); + + setAgents(normalizedAgents); + setAlertCount(Array.isArray(alertsPayload) ? alertsPayload.length : 0); + } catch (err) { + console.error('Failed to load team huddle data:', err); + setError('Could not load team huddle data. Please try again.'); + setAgents([]); + } finally { + setLoading(false); + setRefreshing(false); + } + }, []); + + React.useEffect(() => { + fetchTeamData(); + }, [fetchTeamData]); + return ( { Team Huddle - + {alertCount > 0 && ( + } + label={`${alertCount} alert${alertCount > 1 ? 's' : ''}`} + size="small" + color="warning" + sx={{ height: 20, fontSize: '0.65rem', fontWeight: 700 }} + /> + )} - - - - + + + fetchTeamData(true)} disabled={loading || refreshing}> + {refreshing ? : } + + - - {AGENT_TEAM.map((agent, index) => ( - - {index > 0 && } - + {loading ? ( + + + + ) : error ? ( + + + {error} + + fetchTeamData(true)} + > + Retry + + + ) : agents.length === 0 ? ( + + + No active agent activity right now. + + + ) : ( + + {agents.map((agent, index) => ( + + {index > 0 && } + { } }} /> - - } - > - - - - - - - - {agent.name} - - - {agent.role} - - + } - secondary={ - + + - {agent.current_activity} - - } - /> - - - ))} - - + + + + + + {agent.name} + + + {agent.role} + + + } + secondary={ + + {agent.current_activity} + + } + /> + + + ))} + + )} + View Full Team Activity