diff --git a/ui/src/components/LiveRunWidget.tsx b/ui/src/components/LiveRunWidget.tsx index cbe6d5e1..27e1b9f2 100644 --- a/ui/src/components/LiveRunWidget.tsx +++ b/ui/src/components/LiveRunWidget.tsx @@ -34,6 +34,11 @@ function readString(value: unknown): string | null { return typeof value === "string" && value.trim().length > 0 ? value : null; } +function toIsoString(value: string | Date | null | undefined): string | null { + if (!value) return null; + return typeof value === "string" ? value : value.toISOString(); +} + function summarizeEntry(entry: TranscriptEntry): { text: string; tone: FeedTone } | null { if (entry.kind === "assistant") { const text = entry.text.trim(); @@ -169,11 +174,42 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) { const { data: liveRuns } = useQuery({ queryKey: queryKeys.issues.liveRuns(issueId), queryFn: () => heartbeatsApi.liveRunsForIssue(issueId), - enabled: !!companyId, + enabled: !!issueId, refetchInterval: 3000, }); - const runs = liveRuns ?? []; + const { data: activeRun } = useQuery({ + queryKey: queryKeys.issues.activeRun(issueId), + queryFn: () => heartbeatsApi.activeRunForIssue(issueId), + enabled: !!issueId, + refetchInterval: 3000, + }); + + const runs = useMemo(() => { + const deduped = new Map(); + for (const run of liveRuns ?? []) { + deduped.set(run.id, run); + } + if (activeRun) { + deduped.set(activeRun.id, { + id: activeRun.id, + status: activeRun.status, + invocationSource: activeRun.invocationSource, + triggerDetail: activeRun.triggerDetail, + startedAt: toIsoString(activeRun.startedAt), + finishedAt: toIsoString(activeRun.finishedAt), + createdAt: toIsoString(activeRun.createdAt) ?? new Date().toISOString(), + agentId: activeRun.agentId, + agentName: activeRun.agentName, + adapterType: activeRun.adapterType, + issueId, + }); + } + return [...deduped.values()].sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + }, [activeRun, issueId, liveRuns]); + const runById = useMemo(() => new Map(runs.map((run) => [run.id, run])), [runs]); const activeRunIds = useMemo(() => new Set(runs.map((run) => run.id)), [runs]); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index fcc54238..03d5a58d 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -201,11 +201,18 @@ export function IssueDetail() { const { data: liveRuns } = useQuery({ queryKey: queryKeys.issues.liveRuns(issueId!), queryFn: () => heartbeatsApi.liveRunsForIssue(issueId!), - enabled: !!issueId && !!selectedCompanyId, + enabled: !!issueId, refetchInterval: 3000, }); - const hasLiveRuns = (liveRuns ?? []).length > 0; + const { data: activeRun } = useQuery({ + queryKey: queryKeys.issues.activeRun(issueId!), + queryFn: () => heartbeatsApi.activeRunForIssue(issueId!), + enabled: !!issueId, + refetchInterval: 3000, + }); + + const hasLiveRuns = (liveRuns ?? []).length > 0 || !!activeRun; const { data: allIssues } = useQuery({ queryKey: queryKeys.issues.list(selectedCompanyId!), @@ -758,7 +765,7 @@ export function IssueDetail() { onAttachImage={async (file) => { await uploadAttachment.mutateAsync(file); }} - liveRunSlot={} + liveRunSlot={} />