← 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 },
};