11import {
22 ArrowPathIcon ,
3- ArrowRightIcon ,
43 BookOpenIcon ,
54 CheckIcon ,
65 ChevronUpIcon ,
6+ ClipboardDocumentIcon ,
77 ClockIcon ,
88 CloudArrowDownIcon ,
99 EnvelopeIcon ,
@@ -42,6 +42,8 @@ import {
4242 PopoverMenuItem ,
4343 PopoverTrigger ,
4444} from "~/components/primitives/Popover" ;
45+ import { ToastUI } from "~/components/primitives/Toast" ;
46+ import { toast } from "sonner" ;
4547import * as Property from "~/components/primitives/PropertyTable" ;
4648import { Spinner } from "~/components/primitives/Spinner" ;
4749import {
@@ -79,7 +81,6 @@ import { useOrganization } from "~/hooks/useOrganizations";
7981import { useProject } from "~/hooks/useProject" ;
8082import { useSearchParams } from "~/hooks/useSearchParam" ;
8183import { useHasAdminAccess } from "~/hooks/useUser" ;
82- import { useCanViewLogsPage } from "~/hooks/useCanViewLogsPage" ;
8384import { redirectWithErrorMessage } from "~/models/message.server" ;
8485import { type Span , SpanPresenter , type SpanRun } from "~/presenters/v3/SpanPresenter.server" ;
8586import { logger } from "~/services/logger.server" ;
@@ -91,7 +92,6 @@ import {
9192 v3BatchPath ,
9293 v3SessionPath ,
9394 v3DeploymentVersionPath ,
94- v3LogsPath ,
9595 v3RunDownloadLogsPath ,
9696 v3RunIdempotencyKeyResetPath ,
9797 v3RunPath ,
@@ -386,7 +386,6 @@ function RunBody({
386386 const { value, replace } = useSearchParams ( ) ;
387387 const tab = value ( "tab" ) ;
388388 const resetFetcher = useTypedFetcher < typeof resetIdempotencyKeyAction > ( ) ;
389- const canViewLogsPage = useCanViewLogsPage ( ) ;
390389
391390 return (
392391 < div className = "grid h-full max-h-full grid-rows-[2.5rem_2rem_1fr_minmax(3.25rem,auto)] overflow-hidden bg-background-bright" >
@@ -1105,64 +1104,84 @@ function RunBody({
11051104 </ div >
11061105 < div className = "flex items-center" >
11071106 { run . logsDeletedAt === null ? (
1108- canViewLogsPage ? (
1109- < div className = "flex" >
1110- < LinkButton
1111- to = { `${ v3LogsPath ( organization , project , environment ) } ?runId=${ runParam } &from=${
1112- new Date ( run . createdAt ) . getTime ( ) - 60000
1113- } `}
1107+ < Popover >
1108+ < PopoverTrigger asChild >
1109+ < Button
11141110 variant = "secondary/medium"
1115- className = "rounded-r-none border-r-0"
1111+ LeadingIcon = { CloudArrowDownIcon }
1112+ leadingIconClassName = "text-indigo-400"
1113+ TrailingIcon = { ChevronUpIcon }
11161114 >
1117- View logs
1118- </ LinkButton >
1119- < Popover >
1120- < PopoverTrigger asChild >
1121- < Button
1122- variant = "secondary/medium"
1123- className = "rounded-l-none border-l-charcoal-700 px-1.5"
1124- >
1125- < ChevronUpIcon className = "size-4 transition group-hover/button:text-text-bright" />
1126- </ Button >
1127- </ PopoverTrigger >
1128- < PopoverContent className = "min-w-[140px] p-1" align = "end" >
1129- < PopoverMenuItem
1130- to = { `${ v3LogsPath (
1131- organization ,
1132- project ,
1133- environment
1134- ) } ?runId=${ runParam } &from=${ new Date ( run . createdAt ) . getTime ( ) - 60000 } `}
1135- title = "View logs"
1136- icon = { ArrowRightIcon }
1137- leadingIconClassName = "text-blue-500"
1138- />
1139- < PopoverMenuItem
1140- to = { v3RunDownloadLogsPath ( { friendlyId : runParam } ) }
1141- title = "Download logs"
1142- icon = { CloudArrowDownIcon }
1143- leadingIconClassName = "text-indigo-500"
1144- openInNewTab
1145- />
1146- </ PopoverContent >
1147- </ Popover >
1148- </ div >
1149- ) : (
1150- < LinkButton
1151- to = { v3RunDownloadLogsPath ( { friendlyId : runParam } ) }
1152- LeadingIcon = { CloudArrowDownIcon }
1153- leadingIconClassName = "text-indigo-400"
1154- variant = "secondary/medium"
1155- >
1156- Download logs
1157- </ LinkButton >
1158- )
1115+ Export trace
1116+ </ Button >
1117+ </ PopoverTrigger >
1118+ < PopoverContent className = "min-w-[180px] p-1" align = "end" >
1119+ < TraceExportMenuItems runParam = { runParam } />
1120+ </ PopoverContent >
1121+ </ Popover >
11591122 ) : null }
11601123 </ div >
11611124 </ div >
11621125 </ div >
11631126 ) ;
11641127}
11651128
1129+ // Trace export menu items: copy the trace as Markdown (for pasting into an AI
1130+ // assistant) plus a download per format. The export route streams `?format=`
1131+ // server-side.
1132+ function TraceExportMenuItems ( { runParam } : { runParam : string } ) {
1133+ const downloadPath = v3RunDownloadLogsPath ( { friendlyId : runParam } ) ;
1134+
1135+ const copyForAI = async ( ) => {
1136+ try {
1137+ const response = await fetch ( `${ downloadPath } ?format=markdown` , { credentials : "include" } ) ;
1138+ if ( ! response . ok ) {
1139+ throw new Error ( `Request failed with ${ response . status } ` ) ;
1140+ }
1141+ await navigator . clipboard . writeText ( await response . text ( ) ) ;
1142+ toast . custom ( ( t ) => (
1143+ < ToastUI variant = "success" message = "Copied trace as Markdown" t = { t as string } />
1144+ ) ) ;
1145+ } catch {
1146+ toast . custom ( ( t ) => (
1147+ < ToastUI variant = "error" message = "Couldn't copy the trace" t = { t as string } />
1148+ ) ) ;
1149+ }
1150+ } ;
1151+
1152+ return (
1153+ < >
1154+ < PopoverMenuItem
1155+ title = "Copy for AI"
1156+ icon = { ClipboardDocumentIcon }
1157+ leadingIconClassName = "text-emerald-500"
1158+ onClick = { copyForAI }
1159+ />
1160+ < PopoverMenuItem
1161+ to = { `${ downloadPath } ?format=markdown` }
1162+ title = "Download · Markdown"
1163+ icon = { CloudArrowDownIcon }
1164+ leadingIconClassName = "text-indigo-500"
1165+ openInNewTab
1166+ />
1167+ < PopoverMenuItem
1168+ to = { `${ downloadPath } ?format=log` }
1169+ title = "Download · Log"
1170+ icon = { CloudArrowDownIcon }
1171+ leadingIconClassName = "text-indigo-500"
1172+ openInNewTab
1173+ />
1174+ < PopoverMenuItem
1175+ to = { `${ downloadPath } ?format=jsonl` }
1176+ title = "Download · JSON Lines"
1177+ icon = { CloudArrowDownIcon }
1178+ leadingIconClassName = "text-indigo-500"
1179+ openInNewTab
1180+ />
1181+ </ >
1182+ ) ;
1183+ }
1184+
11661185function RunError ( { error } : { error : TaskRunError } ) {
11671186 const enhancedError = taskRunErrorEnhancer ( error ) ;
11681187
0 commit comments