Skip to content

Commit 584d2b5

Browse files
committed
Converts the Generations view into a proper table
1 parent 5789184 commit 584d2b5

File tree

1 file changed

+102
-98
lines changed
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug

1 file changed

+102
-98
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx

Lines changed: 102 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Ariakit from "@ariakit/react";
2-
import { ArrowPathIcon, ChevronUpDownIcon, EyeIcon } from "@heroicons/react/20/solid";
2+
import { ArrowPathIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
33
import { DialogClose } from "@radix-ui/react-dialog";
44
import { type MetaFunction, useFetcher } from "@remix-run/react";
55
import {
@@ -21,7 +21,7 @@ import { OperationsFilter } from "~/components/metrics/OperationsFilter";
2121
import { ProvidersFilter } from "~/components/metrics/ProvidersFilter";
2222
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
2323
import { Badge } from "~/components/primitives/Badge";
24-
import { Button } from "~/components/primitives/Buttons";
24+
import { Button, LinkButton } from "~/components/primitives/Buttons";
2525
import { DateTime } from "~/components/primitives/DateTime";
2626
import { Dialog, DialogContent, DialogHeader } from "~/components/primitives/Dialog";
2727
import { Header3 } from "~/components/primitives/Headers";
@@ -31,14 +31,17 @@ import { InputGroup } from "~/components/primitives/InputGroup";
3131
import { Label } from "~/components/primitives/Label";
3232
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
3333
import { Paragraph } from "~/components/primitives/Paragraph";
34-
import {
35-
Popover,
36-
PopoverContent,
37-
PopoverMenuItem,
38-
PopoverTrigger,
39-
PopoverVerticalEllipseTrigger,
40-
} from "~/components/primitives/Popover";
34+
import { Popover, PopoverContent, PopoverTrigger } from "~/components/primitives/Popover";
4135
import * as Property from "~/components/primitives/PropertyTable";
36+
import {
37+
Table,
38+
TableBody,
39+
TableCell,
40+
TableCellMenu,
41+
TableHeader,
42+
TableHeaderCell,
43+
TableRow,
44+
} from "~/components/primitives/Table";
4245
import { RadioButtonCircle } from "~/components/primitives/RadioButton";
4346
import {
4447
ResizableHandle,
@@ -676,7 +679,12 @@ export default function PromptDetailPage() {
676679
</div>
677680

678681
{/* Tab content */}
679-
<div className="min-h-0 overflow-hidden">
682+
<div
683+
className={cn(
684+
"min-h-0 overflow-hidden",
685+
contentTab === "generations" && "bg-background-bright"
686+
)}
687+
>
680688
{contentTab === "generations" && (
681689
<GenerationsTab
682690
promptSlug={prompt.slug}
@@ -1175,49 +1183,6 @@ function PreviewTab({
11751183
);
11761184
}
11771185

1178-
function GenerationPopoverMenu({
1179-
isSelected,
1180-
runPath,
1181-
onViewDetails,
1182-
}: {
1183-
isSelected: boolean;
1184-
runPath: string;
1185-
onViewDetails: () => void;
1186-
}) {
1187-
const [open, setOpen] = useState(false);
1188-
1189-
return (
1190-
<Popover open={open} onOpenChange={setOpen}>
1191-
<PopoverVerticalEllipseTrigger onClick={(e) => e.stopPropagation()} className="shrink-0" />
1192-
<PopoverContent
1193-
className="min-w-[10rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
1194-
align="end"
1195-
onClick={(e) => e.stopPropagation()}
1196-
>
1197-
<div className="flex flex-col gap-1 p-1">
1198-
<PopoverMenuItem
1199-
to={runPath}
1200-
icon={RunsIcon}
1201-
leadingIconClassName="text-runs"
1202-
title="Jump to run"
1203-
/>
1204-
{!isSelected && (
1205-
<PopoverMenuItem
1206-
icon={EyeIcon}
1207-
leadingIconClassName="text-text-dimmed"
1208-
title="View details"
1209-
onClick={() => {
1210-
onViewDetails();
1211-
setOpen(false);
1212-
}}
1213-
/>
1214-
)}
1215-
</div>
1216-
</PopoverContent>
1217-
</Popover>
1218-
);
1219-
}
1220-
12211186
// ─── Generations Tab ─────────────────────────────────────
12221187

12231188
function GenerationsTab({
@@ -1482,7 +1447,7 @@ function GenerationsTab({
14821447
className="h-full overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
14831448
>
14841449
{newGenerationCount > 0 && (
1485-
<div className="sticky top-0 z-10 flex items-center justify-center gap-2 border-b border-grid-dimmed bg-background-bright px-3 py-1.5">
1450+
<div className="sticky top-0 z-20 flex items-center justify-center gap-2 border-b border-grid-dimmed bg-background-bright px-3 py-1.5">
14861451
<span className="text-xs text-text-dimmed">
14871452
{newGenerationCount} new {newGenerationCount === 1 ? "generation" : "generations"}
14881453
</span>
@@ -1495,51 +1460,90 @@ function GenerationsTab({
14951460
</Button>
14961461
</div>
14971462
)}
1498-
{generations.map((gen, i) => {
1499-
const isSelected = selectedSpan?.spanId === gen.span_id;
1500-
const runPath = v3RunSpanPath(
1501-
organization,
1502-
project,
1503-
environment,
1504-
{ friendlyId: gen.run_id },
1505-
{ spanId: gen.span_id }
1506-
);
1507-
return (
1508-
<div
1509-
key={`${gen.run_id}-${gen.span_id}-${i}`}
1510-
data-generation-item
1511-
onClick={() => onSelectSpan({ runId: gen.run_id, spanId: gen.span_id })}
1512-
className={cn(
1513-
"group/gen flex cursor-pointer items-center gap-3 border-b border-grid-dimmed px-3 py-3 text-sm transition",
1514-
isSelected
1515-
? "bg-indigo-500/10 hover:bg-indigo-500/[0.07]"
1516-
: "hover:bg-charcoal-750"
1517-
)}
1518-
>
1519-
<RadioButtonCircle checked={isSelected} />
1520-
<div className="min-w-0 flex-1">
1521-
<div className="flex items-center">
1522-
<span className="truncate font-medium text-text-bright">
1463+
<Table variant="bright" fullWidth showTopBorder={false}>
1464+
<TableHeader>
1465+
<TableRow>
1466+
<TableHeaderCell className="w-8" />
1467+
<TableHeaderCell>Operation</TableHeaderCell>
1468+
<TableHeaderCell>Version</TableHeaderCell>
1469+
<TableHeaderCell>Model</TableHeaderCell>
1470+
<TableHeaderCell>Tokens</TableHeaderCell>
1471+
<TableHeaderCell>Cost</TableHeaderCell>
1472+
<TableHeaderCell>Duration</TableHeaderCell>
1473+
<TableHeaderCell alignment="right">Time</TableHeaderCell>
1474+
<TableHeaderCell className="w-12" hiddenLabel>
1475+
Actions
1476+
</TableHeaderCell>
1477+
</TableRow>
1478+
</TableHeader>
1479+
<TableBody>
1480+
{generations.map((gen, i) => {
1481+
const isSelected = selectedSpan?.spanId === gen.span_id;
1482+
const runPath = v3RunSpanPath(
1483+
organization,
1484+
project,
1485+
environment,
1486+
{ friendlyId: gen.run_id },
1487+
{ spanId: gen.span_id }
1488+
);
1489+
return (
1490+
<TableRow
1491+
key={`${gen.run_id}-${gen.span_id}-${i}`}
1492+
data-generation-item
1493+
isSelected={isSelected}
1494+
className="cursor-pointer"
1495+
onClick={() => onSelectSpan({ runId: gen.run_id, spanId: gen.span_id })}
1496+
>
1497+
<TableCell>
1498+
<RadioButtonCircle checked={isSelected} />
1499+
</TableCell>
1500+
<TableCell className={cn("font-medium", isSelected && "text-text-bright")}>
15231501
{gen.operation_id || gen.task_identifier}
1524-
</span>
1525-
</div>
1526-
<div className="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-text-dimmed">
1527-
<span className="whitespace-nowrap text-charcoal-400">v{gen.prompt_version}</span>
1528-
<span className="whitespace-nowrap">{gen.response_model}</span>
1529-
<span className="whitespace-nowrap">{gen.input_tokens + gen.output_tokens} tokens</span>
1530-
<span className="whitespace-nowrap">{formatCost(gen.total_cost)}</span>
1531-
<span className="whitespace-nowrap">{Math.round(gen.duration_ms)}ms</span>
1532-
</div>
1533-
</div>
1534-
<span className="shrink-0 text-xs text-text-dimmed">{gen.start_time}</span>
1535-
<GenerationPopoverMenu
1536-
isSelected={isSelected}
1537-
runPath={runPath}
1538-
onViewDetails={() => onSelectSpan({ runId: gen.run_id, spanId: gen.span_id })}
1539-
/>
1540-
</div>
1541-
);
1542-
})}
1502+
</TableCell>
1503+
<TableCell
1504+
className={cn("tabular-nums", isSelected ? "text-text-bright" : "text-charcoal-400")}
1505+
>
1506+
v{gen.prompt_version}
1507+
</TableCell>
1508+
<TableCell className={cn(isSelected && "text-text-bright")}>
1509+
{gen.response_model}
1510+
</TableCell>
1511+
<TableCell className={cn("tabular-nums", isSelected && "text-text-bright")}>
1512+
{gen.input_tokens + gen.output_tokens}
1513+
</TableCell>
1514+
<TableCell className={cn("tabular-nums", isSelected && "text-text-bright")}>
1515+
{formatCost(gen.total_cost)}
1516+
</TableCell>
1517+
<TableCell className={cn("tabular-nums", isSelected && "text-text-bright")}>
1518+
{Math.round(gen.duration_ms)}ms
1519+
</TableCell>
1520+
<TableCell
1521+
alignment="right"
1522+
className={cn("tabular-nums", isSelected && "text-text-bright")}
1523+
>
1524+
{gen.start_time}
1525+
</TableCell>
1526+
<TableCellMenu
1527+
isSticky
1528+
isSelected={isSelected}
1529+
hiddenButtons={
1530+
<LinkButton
1531+
to={runPath}
1532+
onClick={(e) => e.stopPropagation()}
1533+
variant="minimal/small"
1534+
TrailingIcon={RunsIcon}
1535+
trailingIconClassName="text-text-bright"
1536+
className="h-[1.375rem] pl-1.5 pr-2"
1537+
>
1538+
<span className="text-[0.6875rem] text-text-bright">View run</span>
1539+
</LinkButton>
1540+
}
1541+
/>
1542+
</TableRow>
1543+
);
1544+
})}
1545+
</TableBody>
1546+
</Table>
15431547

15441548
{/* Infinite scroll sentinel */}
15451549
<div ref={loadMoreRef} className="h-px" />

0 commit comments

Comments
 (0)