adk-gateway 1.0.0

Multi-channel AI gateway for adk-rust agents — Telegram, Slack, WhatsApp, Discord, Matrix + control panel
import { useState, useCallback } from 'react';
import { useAgentDetail } from './AgentDetailLayout';
import ConnectionBadge from './components/ConnectionBadge';

export default function AgentOverview() {
  const { agent, refetch } = useAgentDetail();
  const [connecting, setConnecting] = useState(false);
  const [actionMessage, setActionMessage] = useState<string | null>(null);

  const handleConnect = useCallback(async () => {
    setConnecting(true);
    setActionMessage(null);
    try {
      const res = await fetch(`/ui/api/coding-agents/${encodeURIComponent(agent.id)}/connect`, {
        method: 'POST',
        credentials: 'same-origin',
      });
      const data = await res.json();
      if (data.ok) {
        setActionMessage('Connected successfully');
        refetch();
      } else {
        setActionMessage(data.message || 'Failed to connect');
      }
    } catch {
      setActionMessage('Network error');
    } finally {
      setConnecting(false);
    }
  }, [agent.id, refetch]);

  const handleDisconnect = useCallback(async () => {
    setConnecting(true);
    setActionMessage(null);
    try {
      const res = await fetch(`/ui/api/coding-agents/${encodeURIComponent(agent.id)}/disconnect`, {
        method: 'POST',
        credentials: 'same-origin',
      });
      const data = await res.json();
      if (data.ok) {
        setActionMessage('Disconnected');
        refetch();
      } else {
        setActionMessage(data.message || 'Failed to disconnect');
      }
    } catch {
      setActionMessage('Network error');
    } finally {
      setConnecting(false);
    }
  }, [agent.id, refetch]);

  return (
    <div className="space-y-6">
      {/* Status Card */}
      <div className="bg-white rounded-xl shadow-sm p-6">
        <h3 className="text-lg font-semibold mb-4">Agent Overview</h3>
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
          <div>
            <p className="text-sm text-gray-500">Status</p>
            <div className="mt-1">
              <ConnectionBadge status={agent.connection_status} />
              {agent.status_message && (
                <p className="text-xs text-gray-500 mt-1">{agent.status_message}</p>
              )}
              <div className="mt-2">
                {agent.connection_status === 'connected' ? (
                  <button
                    onClick={handleDisconnect}
                    disabled={connecting}
                    className="px-3 py-1 text-xs font-medium text-red-700 bg-red-50 rounded-lg hover:bg-red-100 disabled:opacity-50"
                  >
                    {connecting ? 'Disconnecting…' : 'Disconnect'}
                  </button>
                ) : (
                  <button
                    onClick={handleConnect}
                    disabled={connecting}
                    className="px-3 py-1 text-xs font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50"
                  >
                    {connecting ? 'Connecting…' : '🔌 Connect'}
                  </button>
                )}
              </div>
              {actionMessage && (
                <p className="text-xs text-gray-600 mt-1">{actionMessage}</p>
              )}
            </div>
          </div>
          <div>
            <p className="text-sm text-gray-500">Backend Type</p>
            <p className="text-sm font-medium text-gray-900 mt-1">{agent.display_name}</p>
          </div>
          <div>
            <p className="text-sm text-gray-500">Endpoint</p>
            <p className="text-sm font-mono text-gray-700 mt-1 truncate">{agent.endpoint}</p>
          </div>
          <div>
            <p className="text-sm text-gray-500">Timeout</p>
            <p className="text-sm font-medium text-gray-900 mt-1">{agent.timeout_secs}s</p>
          </div>
          <div>
            <p className="text-sm text-gray-500">Cost Cap</p>
            <p className="text-sm font-medium text-gray-900 mt-1">
              {agent.cost_cap_usd != null ? `$${agent.cost_cap_usd.toFixed(2)}` : 'No limit'}
            </p>
          </div>
          <div>
            <p className="text-sm text-gray-500">Last Task</p>
            <p className="text-sm text-gray-700 mt-1">
              {agent.last_task_at ? new Date(agent.last_task_at).toLocaleString() : 'No tasks yet'}
            </p>
          </div>
        </div>
      </div>

      {/* Workspaces */}
      <div className="bg-white rounded-xl shadow-sm p-6">
        <h3 className="text-lg font-semibold mb-3">Workspaces</h3>
        {agent.workspaces.length > 0 ? (
          <ul className="space-y-1">
            {agent.workspaces.map((ws) => (
              <li key={ws} className="text-sm font-mono text-gray-700 px-3 py-2 bg-gray-50 rounded-lg">
                {ws}
              </li>
            ))}
          </ul>
        ) : (
          <p className="text-sm text-gray-500">No workspaces configured.</p>
        )}
      </div>

      {/* Capabilities */}
      {agent.capabilities && (
        <div className="bg-white rounded-xl shadow-sm p-6">
          <h3 className="text-lg font-semibold mb-3">Capabilities</h3>
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
            <div className="flex items-center gap-2">
              <span className={`w-2 h-2 rounded-full ${agent.capabilities.file_context ? 'bg-green-500' : 'bg-gray-300'}`} />
              <span className="text-sm text-gray-700">File Context</span>
            </div>
            <div className="flex items-center gap-2">
              <span className={`w-2 h-2 rounded-full ${agent.capabilities.streaming_output ? 'bg-green-500' : 'bg-gray-300'}`} />
              <span className="text-sm text-gray-700">Streaming</span>
            </div>
            <div className="flex items-center gap-2">
              <span className={`w-2 h-2 rounded-full ${agent.capabilities.cost_reporting ? 'bg-green-500' : 'bg-gray-300'}`} />
              <span className="text-sm text-gray-700">Cost Reporting</span>
            </div>
            <div className="flex items-center gap-2">
              <span className={`w-2 h-2 rounded-full ${agent.capabilities.cancellation ? 'bg-green-500' : 'bg-gray-300'}`} />
              <span className="text-sm text-gray-700">Cancellation</span>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}