mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
Add Identity component (avatar + name) used across agent/issue displays. Add LiveRunWidget for real-time streaming of active heartbeat runs on issue detail pages via WebSocket. Display issue identifiers (PAP-42) instead of UUID fragments throughout Issues, Inbox, CommandPalette, and detail pages. Enhance CommentThread with re-open checkbox, Cmd+Enter submit, sorted display, and run linking. Improve Activity page with richer formatting and filtering. Update Dashboard with live metrics. Add reports-to agent link in AgentProperties. Various small fixes: StatusIcon centering, CopyText ref init, agent detail run-issue cross-links. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import { Link } from "react-router-dom";
|
|
import type { Agent, AgentRuntimeState } from "@paperclip/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 { formatCents, formatDate } 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)",
|
|
process: "Process",
|
|
http: "HTTP",
|
|
};
|
|
|
|
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex items-center justify-between py-1.5">
|
|
<span className="text-xs text-muted-foreground">{label}</span>
|
|
<div className="flex items-center gap-1.5">{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">{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">
|
|
<PropertyRow label="Budget">
|
|
<span className="text-sm">
|
|
{formatCents(agent.spentMonthlyCents)} / {formatCents(agent.budgetMonthlyCents)}
|
|
</span>
|
|
</PropertyRow>
|
|
<PropertyRow label="Utilization">
|
|
<span className="text-sm">
|
|
{agent.budgetMonthlyCents > 0
|
|
? Math.round((agent.spentMonthlyCents / agent.budgetMonthlyCents) * 100)
|
|
: 0}
|
|
%
|
|
</span>
|
|
</PropertyRow>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className="space-y-1">
|
|
{runtimeState?.sessionId && (
|
|
<PropertyRow label="Session">
|
|
<span className="text-xs font-mono">{runtimeState.sessionId.slice(0, 12)}...</span>
|
|
</PropertyRow>
|
|
)}
|
|
{runtimeState?.lastError && (
|
|
<PropertyRow label="Last error">
|
|
<span className="text-xs 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={`/agents/${reportsToAgent.id}`} 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>
|
|
);
|
|
}
|