import { memo, useMemo } from "react"; import { Link } from "@/lib/router"; import { useQuery } from "@tanstack/react-query"; import type { Issue } from "@paperclipai/shared"; import { heartbeatsApi, type LiveRunForIssue } from "../api/heartbeats"; import type { TranscriptEntry } from "../adapters"; import { issuesApi } from "../api/issues"; import { queryKeys } from "../lib/queryKeys"; import { cn, relativeTime } from "../lib/utils"; import { ExternalLink } from "lucide-react"; import { Identity } from "./Identity"; import { RunChatSurface } from "./RunChatSurface"; import { useLiveRunTranscripts } from "./transcript/useLiveRunTranscripts"; const MIN_DASHBOARD_RUNS = 4; const DASHBOARD_RUN_CARD_LIMIT = 4; const DASHBOARD_LOG_POLL_INTERVAL_MS = 15_000; const DASHBOARD_LOG_READ_LIMIT_BYTES = 64_000; const DASHBOARD_MAX_CHUNKS_PER_RUN = 40; const EMPTY_TRANSCRIPT: TranscriptEntry[] = []; function isRunActive(run: LiveRunForIssue): boolean { return run.status === "queued" || run.status === "running"; } interface ActiveAgentsPanelProps { companyId: string; } export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) { const { data: liveRuns } = useQuery({ queryKey: [...queryKeys.liveRuns(companyId), "dashboard"], queryFn: () => heartbeatsApi.liveRunsForCompany(companyId, MIN_DASHBOARD_RUNS), }); const runs = liveRuns ?? []; const visibleRuns = useMemo(() => runs.slice(0, DASHBOARD_RUN_CARD_LIMIT), [runs]); const hiddenRunCount = Math.max(0, runs.length - visibleRuns.length); const { data: issues } = useQuery({ queryKey: [...queryKeys.issues.list(companyId), "with-routine-executions"], queryFn: () => issuesApi.list(companyId, { includeRoutineExecutions: true }), enabled: visibleRuns.length > 0, }); const issueById = useMemo(() => { const map = new Map(); for (const issue of issues ?? []) { map.set(issue.id, issue); } return map; }, [issues]); const { transcriptByRun, hasOutputForRun } = useLiveRunTranscripts({ runs: visibleRuns, companyId, maxChunksPerRun: DASHBOARD_MAX_CHUNKS_PER_RUN, logPollIntervalMs: DASHBOARD_LOG_POLL_INTERVAL_MS, logReadLimitBytes: DASHBOARD_LOG_READ_LIMIT_BYTES, enableRealtimeUpdates: false, }); return (

Agents

{runs.length === 0 ? (

No recent agent runs.

) : (
{visibleRuns.map((run) => ( ))}
)} {hiddenRunCount > 0 && (
{hiddenRunCount} more active/recent run{hiddenRunCount === 1 ? "" : "s"}
)}
); } const AgentRunCard = memo(function AgentRunCard({ companyId, run, issue, transcript, hasOutput, isActive, }: { companyId: string; run: LiveRunForIssue; issue?: Issue; transcript: TranscriptEntry[]; hasOutput: boolean; isActive: boolean; }) { return (
{isActive ? ( ) : ( )}
{isActive ? "Live now" : run.finishedAt ? `Finished ${relativeTime(run.finishedAt)}` : `Started ${relativeTime(run.createdAt)}`}
{run.issueId && (
{issue?.identifier ?? run.issueId.slice(0, 8)} {issue?.title ? ` - ${issue.title}` : ""}
)}
); });