Files
paperclip/ui/src/components/AgentProperties.tsx
Dotta 4c630bc66e Merge pull request #263 from mvanhorn/feat/180-agent-role-labels
feat(ui): show human-readable role labels in agent list and properties
2026-03-07 21:19:58 -06:00

106 lines
3.7 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { Link } from "@/lib/router";
import { AGENT_ROLE_LABELS, type Agent, type AgentRuntimeState } from "@paperclipai/shared";
import { agentsApi } from "../api/agents";
import { useCompany } from "../context/CompanyContext";
import { queryKeys } from "../lib/queryKeys";
import { StatusBadge } from "./StatusBadge";
import { Identity } from "./Identity";
import { formatDate, agentUrl } from "../lib/utils";
import { Separator } from "@/components/ui/separator";
interface AgentPropertiesProps {
agent: Agent;
runtimeState?: AgentRuntimeState;
}
const adapterLabels: Record<string, string> = {
claude_local: "Claude (local)",
codex_local: "Codex (local)",
opencode_local: "OpenCode (local)",
openclaw_gateway: "OpenClaw Gateway",
cursor: "Cursor (local)",
process: "Process",
http: "HTTP",
};
const roleLabels = AGENT_ROLE_LABELS as Record<string, string>;
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="flex items-center gap-3 py-1.5">
<span className="text-xs text-muted-foreground shrink-0 w-20">{label}</span>
<div className="flex items-center gap-1.5 min-w-0">{children}</div>
</div>
);
}
export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) {
const { selectedCompanyId } = useCompany();
const { data: agents } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!),
queryFn: () => agentsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId && !!agent.reportsTo,
});
const reportsToAgent = agent.reportsTo ? agents?.find((a) => a.id === agent.reportsTo) : null;
return (
<div className="space-y-4">
<div className="space-y-1">
<PropertyRow label="Status">
<StatusBadge status={agent.status} />
</PropertyRow>
<PropertyRow label="Role">
<span className="text-sm">{roleLabels[agent.role] ?? agent.role}</span>
</PropertyRow>
{agent.title && (
<PropertyRow label="Title">
<span className="text-sm">{agent.title}</span>
</PropertyRow>
)}
<PropertyRow label="Adapter">
<span className="text-sm font-mono">{adapterLabels[agent.adapterType] ?? agent.adapterType}</span>
</PropertyRow>
</div>
<Separator />
<div className="space-y-1">
{(runtimeState?.sessionDisplayId ?? runtimeState?.sessionId) && (
<PropertyRow label="Session">
<span className="text-xs font-mono">
{String(runtimeState.sessionDisplayId ?? runtimeState.sessionId).slice(0, 12)}...
</span>
</PropertyRow>
)}
{runtimeState?.lastError && (
<PropertyRow label="Last error">
<span className="text-xs text-red-600 dark:text-red-400 truncate max-w-[160px]">{runtimeState.lastError}</span>
</PropertyRow>
)}
{agent.lastHeartbeatAt && (
<PropertyRow label="Last Heartbeat">
<span className="text-sm">{formatDate(agent.lastHeartbeatAt)}</span>
</PropertyRow>
)}
{agent.reportsTo && (
<PropertyRow label="Reports To">
{reportsToAgent ? (
<Link to={agentUrl(reportsToAgent)} className="hover:underline">
<Identity name={reportsToAgent.name} size="sm" />
</Link>
) : (
<span className="text-sm font-mono">{agent.reportsTo.slice(0, 8)}</span>
)}
</PropertyRow>
)}
<PropertyRow label="Created">
<span className="text-sm">{formatDate(agent.createdAt)}</span>
</PropertyRow>
</div>
</div>
);
}