11import * as Ariakit from "@ariakit/react" ;
2- import { ArrowPathIcon , ChevronUpDownIcon , EyeIcon } from "@heroicons/react/20/solid" ;
2+ import { ArrowPathIcon , ChevronUpDownIcon } from "@heroicons/react/20/solid" ;
33import { DialogClose } from "@radix-ui/react-dialog" ;
44import { type MetaFunction , useFetcher } from "@remix-run/react" ;
55import {
@@ -21,7 +21,7 @@ import { OperationsFilter } from "~/components/metrics/OperationsFilter";
2121import { ProvidersFilter } from "~/components/metrics/ProvidersFilter" ;
2222import { AppliedFilter } from "~/components/primitives/AppliedFilter" ;
2323import { Badge } from "~/components/primitives/Badge" ;
24- import { Button } from "~/components/primitives/Buttons" ;
24+ import { Button , LinkButton } from "~/components/primitives/Buttons" ;
2525import { DateTime } from "~/components/primitives/DateTime" ;
2626import { Dialog , DialogContent , DialogHeader } from "~/components/primitives/Dialog" ;
2727import { Header3 } from "~/components/primitives/Headers" ;
@@ -31,14 +31,17 @@ import { InputGroup } from "~/components/primitives/InputGroup";
3131import { Label } from "~/components/primitives/Label" ;
3232import { NavBar , PageAccessories , PageTitle } from "~/components/primitives/PageHeader" ;
3333import { 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" ;
4135import * 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" ;
4245import { RadioButtonCircle } from "~/components/primitives/RadioButton" ;
4346import {
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
12231188function 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