← back to Dw Boardroom V2
frontend/src/components/AgentRoster.tsx
126 lines
import { useState } from 'react';
import { api } from '../api';
import { useApi } from '../hooks/useApi';
import type { Agent, Executive } from '../types';
export default function AgentRoster() {
const { data, loading } = useApi(() => api.getAgents(), []);
const [search, setSearch] = useState('');
const [speakingAgent, setSpeakingAgent] = useState<string | null>(null);
const [dialogue, setDialogue] = useState<string | null>(null);
if (loading || !data) return <div style={styles.card}><div style={styles.empty}>Loading agents...</div></div>;
const agents: Agent[] = data.agents || [];
const executives: Executive[] = data.executives || [];
const departments = [...new Set(agents.map(a => a.dept))].sort();
const filtered = search
? agents.filter(a => a.name.toLowerCase().includes(search.toLowerCase()) || a.role.toLowerCase().includes(search.toLowerCase()) || a.id.toLowerCase().includes(search.toLowerCase()))
: agents;
async function speak(agentId: string) {
setSpeakingAgent(agentId);
setDialogue(null);
try {
const res = await api.speakAgent(agentId, 'Share your current status and priorities');
setDialogue(res.dialogue);
} catch {
setDialogue('Unable to generate dialogue.');
}
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{/* Search + Stats */}
<div style={styles.card}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700 }}>Agent Directory</h3>
<span style={{ fontSize: 12, color: '#7a7f96' }}>{agents.length} agents + {executives.length} executives</span>
</div>
<input
style={styles.search}
placeholder="Search agents by name, role, or ID..."
value={search}
onChange={e => setSearch(e.target.value)}
/>
</div>
{/* Dialogue preview */}
{dialogue && (
<div style={{ ...styles.card, borderColor: '#8b5cf666' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: '#8b5cf6', marginBottom: 6 }}>
{speakingAgent} says:
</div>
<div style={{ fontSize: 13, lineHeight: 1.5 }}>{dialogue}</div>
</div>
)}
{/* Executives */}
<div style={styles.card}>
<h4 style={{ margin: '0 0 12px', fontSize: 13, fontWeight: 700, color: '#8b5cf6' }}>Executive Team</h4>
<div style={styles.grid}>
{executives.map(exec => (
<div key={exec.id} style={{ ...styles.agentCard, borderColor: exec.color + '44' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ ...styles.dot, background: exec.color }} />
<div>
<div style={{ fontWeight: 700, fontSize: 13 }}>{exec.name}</div>
<div style={{ fontSize: 10, color: '#7a7f96' }}>{exec.role}</div>
</div>
</div>
<div style={{ fontSize: 10, color: '#7a7f96', marginTop: 6 }}>
ID: {exec.id} | Port: {exec.port}
</div>
</div>
))}
</div>
</div>
{/* Agents by Department */}
{departments.map(dept => {
const deptAgents = filtered.filter(a => a.dept === dept);
if (deptAgents.length === 0) return null;
return (
<div key={dept} style={styles.card}>
<h4 style={{ margin: '0 0 10px', fontSize: 13, fontWeight: 700, color: '#f59e0b' }}>{dept}</h4>
<div style={styles.grid}>
{deptAgents.map(agent => (
<div
key={agent.id}
style={{
...styles.agentCard,
borderColor: agent.color + '44',
cursor: 'pointer',
}}
onClick={() => speak(agent.id)}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ ...styles.dot, background: agent.color }} />
<div>
<div style={{ fontWeight: 600, fontSize: 12 }}>{agent.name}</div>
<div style={{ fontSize: 10, color: '#7a7f96' }}>{agent.role}</div>
</div>
</div>
<div style={{ fontSize: 9, color: '#555', marginTop: 4 }}>
{agent.id} {agent.port ? `| :${agent.port}` : ''} {agent.hasApi ? '| API' : ''}
</div>
</div>
))}
</div>
</div>
);
})}
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40 },
search: { width: '100%', padding: '10px 12px', borderRadius: 8, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 13, fontFamily: 'inherit' },
grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 8 },
agentCard: { background: '#12121f', borderRadius: 8, padding: 10, border: '1px solid #252540', transition: 'all 0.2s' },
dot: { width: 10, height: 10, borderRadius: '50%', flexShrink: 0 },
};