Skip to content

Commit 1a61150

Browse files
committed
refine loop run history cards and model row
1 parent a17e709 commit 1a61150

3 files changed

Lines changed: 101 additions & 65 deletions

File tree

packages/ui/src/features/loops/components/LoopDetailView.tsx

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
navigateToLoops,
1111
} from "@posthog/ui/router/navigationBridge";
1212
import { AlertDialog, Flex, Text } from "@radix-ui/themes";
13-
import { useMemo, useState } from "react";
13+
import { useState } from "react";
1414
import { useLoop } from "../hooks/useLoop";
1515
import {
1616
useDeleteLoop,
@@ -54,10 +54,7 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
5454
const [deleteOpen, setDeleteOpen] = useState(false);
5555

5656
const runsQuery = useLoopRuns(loopId);
57-
const runs = useMemo(
58-
() => runsQuery.data?.pages.flatMap((page) => page.results) ?? [],
59-
[runsQuery.data],
60-
);
57+
const runs = runsQuery.data ?? [];
6158

6259
useSetHeaderContent(
6360
<Flex align="center" gap="2" className="w-full min-w-0">
@@ -201,9 +198,12 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
201198
<ConfigSummarySection loop={loop} />
202199

203200
<Flex direction="column" gap="2">
204-
<Text className="font-medium text-[13px] text-gray-12">
205-
Run history
206-
</Text>
201+
<Flex align="center" gap="2">
202+
<Text className="font-medium text-[13px] text-gray-12">
203+
Run history
204+
</Text>
205+
<Text className="text-[11px] text-gray-10">10 most recent</Text>
206+
</Flex>
207207
{runsQuery.isLoading ? (
208208
<div className="h-16 animate-pulse rounded-(--radius-2) border border-border bg-(--gray-2)" />
209209
) : runs.length === 0 ? (
@@ -215,19 +215,6 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
215215
))}
216216
</Flex>
217217
)}
218-
{runsQuery.hasNextPage ? (
219-
<Button
220-
variant="soft"
221-
color="gray"
222-
size="1"
223-
className="w-fit"
224-
loading={runsQuery.isFetchingNextPage}
225-
disabled={runsQuery.isFetchingNextPage}
226-
onClick={() => void runsQuery.fetchNextPage()}
227-
>
228-
Load more
229-
</Button>
230-
) : null}
231218
</Flex>
232219
</Flex>
233220

@@ -279,8 +266,13 @@ function ConfigSummarySection({ loop }: { loop: LoopSchemas.Loop }) {
279266
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
280267
>
281268
<SummaryRow label="Model">
282-
{loop.runtime_adapter} · {loop.model}
283-
{loop.reasoning_effort ? ` · ${loop.reasoning_effort} reasoning` : ""}
269+
{[
270+
loop.runtime_adapter,
271+
loop.model,
272+
loop.reasoning_effort ? `${loop.reasoning_effort} reasoning` : null,
273+
]
274+
.filter(Boolean)
275+
.join(" · ")}
284276
</SummaryRow>
285277

286278
<SummaryRow label="Repository">
Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { LoopSchemas } from "@posthog/api-client/loops";
22
import { Badge } from "@posthog/ui/primitives/Badge";
3+
import { Button } from "@posthog/ui/primitives/Button";
34
import { navigateToTaskDetail } from "@posthog/ui/router/navigationBridge";
45
import { Flex, Text } from "@radix-ui/themes";
56

@@ -20,43 +21,88 @@ function statusColor(
2021
}
2122
}
2223

23-
function formatRelativeDate(iso: string | null): string {
24-
if (!iso) return "";
25-
const date = new Date(iso);
26-
if (Number.isNaN(date.getTime())) return "";
27-
return date.toLocaleString();
24+
function formatRelative(iso: string): string {
25+
const then = new Date(iso).getTime();
26+
if (Number.isNaN(then)) return "";
27+
const sec = Math.round((Date.now() - then) / 1000);
28+
if (sec < 45) return "just now";
29+
const min = Math.round(sec / 60);
30+
if (min < 60) return `${min}m ago`;
31+
const hr = Math.round(min / 60);
32+
if (hr < 24) return `${hr}h ago`;
33+
const day = Math.round(hr / 24);
34+
if (day < 7) return `${day}d ago`;
35+
return new Date(iso).toLocaleDateString();
36+
}
37+
38+
function formatDuration(ms: number): string {
39+
const sec = Math.max(0, Math.round(ms / 1000));
40+
if (sec < 60) return `${sec}s`;
41+
const min = Math.floor(sec / 60);
42+
const rem = sec % 60;
43+
if (min < 60) return rem ? `${min}m ${rem}s` : `${min}m`;
44+
const hr = Math.floor(min / 60);
45+
const remMin = min % 60;
46+
return remMin ? `${hr}h ${remMin}m` : `${hr}h`;
47+
}
48+
49+
function durationLabel(run: LoopSchemas.LoopRun): string {
50+
const start = new Date(run.created_at).getTime();
51+
if (Number.isNaN(start)) return "";
52+
if (run.completed_at) {
53+
const end = new Date(run.completed_at).getTime();
54+
if (!Number.isNaN(end)) return `ran ${formatDuration(end - start)}`;
55+
}
56+
if (run.status === "in_progress")
57+
return `running ${formatDuration(Date.now() - start)}`;
58+
if (run.status === "queued") return "queued";
59+
return "";
2860
}
2961

3062
export function LoopRunRow({ run }: { run: LoopSchemas.LoopRun }) {
63+
const meta = [
64+
formatRelative(run.created_at),
65+
durationLabel(run),
66+
run.environment,
67+
run.loop_trigger_id ? "Triggered" : "Manual",
68+
].filter(Boolean);
69+
3170
return (
32-
<button
33-
type="button"
34-
onClick={() => navigateToTaskDetail(run.task_id)}
35-
title="Open this run to see the agent's activity"
36-
className="w-full rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-3 py-2.5 text-left transition-colors hover:bg-(--gray-3)"
71+
<Flex
72+
align="center"
73+
justify="between"
74+
gap="3"
75+
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-3 py-2.5"
3776
>
38-
<Flex align="center" justify="between" gap="3">
39-
<Flex direction="column" className="min-w-0 gap-0.5">
40-
<Flex align="center" gap="2">
41-
<Badge color={statusColor(run.status)}>{run.status}</Badge>
42-
<Text className="text-[12px] text-gray-11">
43-
{formatRelativeDate(run.created_at)}
44-
</Text>
45-
</Flex>
46-
{run.error_message ? (
47-
<Text className="truncate text-(--red-11) text-[11.5px]">
48-
{run.error_message}
49-
</Text>
50-
) : run.branch ? (
51-
<Text className="truncate text-[11.5px] text-gray-10 [font-family:var(--font-mono)]">
52-
{run.branch}
53-
</Text>
54-
) : null}
77+
<Flex direction="column" className="min-w-0 gap-1">
78+
<Flex align="center" gap="2" wrap="wrap">
79+
<Badge color={statusColor(run.status)}>{run.status}</Badge>
80+
<Text
81+
className="text-[12px] text-gray-11"
82+
title={new Date(run.created_at).toLocaleString()}
83+
>
84+
{meta.join(" · ")}
85+
</Text>
5586
</Flex>
56-
<Text className="shrink-0 text-[11px] text-gray-10 uppercase">
57-
{run.environment}
58-
</Text>
87+
{run.error_message ? (
88+
<Text className="truncate text-(--red-11) text-[11.5px]">
89+
{run.error_message}
90+
</Text>
91+
) : run.branch ? (
92+
<Text className="truncate text-[11.5px] text-gray-10 [font-family:var(--font-mono)]">
93+
{run.branch}
94+
</Text>
95+
) : null}
5996
</Flex>
60-
</button>
97+
<Button
98+
variant="soft"
99+
color="gray"
100+
size="1"
101+
className="shrink-0"
102+
onClick={() => navigateToTaskDetail(run.task_id)}
103+
>
104+
View run
105+
</Button>
106+
</Flex>
61107
);
62108
}
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
import { type LoopSchemas, listLoopRuns } from "@posthog/api-client/loops";
22
import { AUTH_SCOPED_QUERY_META } from "@posthog/ui/features/auth/useCurrentUser";
3-
import { useInfiniteQuery } from "@tanstack/react-query";
3+
import { useQuery } from "@tanstack/react-query";
44
import { loopsKeys } from "./loopsKeys";
55
import { useLoopsClient } from "./useLoopsClient";
66

7-
const LOOP_RUNS_PAGE_LIMIT = 20;
7+
const RECENT_RUNS_LIMIT = 10;
88

9+
/** The 10 most recent runs for a loop, polled so the detail view stays live. */
910
export function useLoopRuns(loopId: string | undefined) {
1011
const loopsClient = useLoopsClient();
1112

12-
return useInfiniteQuery<LoopSchemas.LoopRunPage>({
13+
return useQuery<LoopSchemas.LoopRunPage, Error, LoopSchemas.LoopRun[]>({
1314
queryKey: loopsKeys.runs(loopsClient?.projectId ?? null, loopId ?? ""),
14-
queryFn: async ({ pageParam }) => {
15+
queryFn: async () => {
1516
if (!loopsClient || !loopId) throw new Error("Not authenticated");
1617
return await listLoopRuns(
1718
loopsClient.client,
1819
loopsClient.projectId,
1920
loopId,
20-
{
21-
limit: LOOP_RUNS_PAGE_LIMIT,
22-
cursor: pageParam as string | undefined,
23-
},
21+
{ limit: RECENT_RUNS_LIMIT },
2422
);
2523
},
24+
select: (page) => page.results.slice(0, RECENT_RUNS_LIMIT),
2625
enabled: !!loopsClient && !!loopId,
27-
initialPageParam: undefined as string | undefined,
28-
getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,
29-
staleTime: 15_000,
26+
staleTime: 10_000,
27+
refetchInterval: 15_000,
3028
meta: AUTH_SCOPED_QUERY_META,
3129
});
3230
}

0 commit comments

Comments
 (0)