1- import React from "react" ;
1+ import React , {
2+ useCallback ,
3+ useEffect ,
4+ useMemo ,
5+ useRef ,
6+ useState ,
7+ } from "react" ;
8+ import { keepPreviousData } from "@tanstack/react-query" ;
29import { ListTree } from "lucide-react" ;
310
11+ import useTraceById from "@/api/traces/useTraceById" ;
12+ import useSpansList from "@/api/traces/useSpansList" ;
13+ import Loader from "@/shared/Loader/Loader" ;
14+ import AgentTraceTree from "./AgentTraceTree" ;
15+ import TraceDataViewer from "@/v2/pages-shared/traces/TraceDetailsPanel/TraceDataViewer/TraceDataViewer" ;
16+ import { useDetailsActionSectionState } from "@/v2/pages-shared/traces/DetailsActionSection" ;
17+ import { Span , Trace } from "@/types/traces" ;
18+ import { SPANS_COLORS_MAP , TRACE_TYPE_FOR_TREE } from "@/constants/traces" ;
19+ import useTreeDetailsStore , {
20+ TreeNode ,
21+ } from "@/v2/pages-shared/traces/TraceDetailsPanel/TreeDetailsStore" ;
22+ import {
23+ ResizableHandle ,
24+ ResizablePanel ,
25+ ResizablePanelGroup ,
26+ } from "@/ui/resizable" ;
27+ import find from "lodash/find" ;
28+
29+ const MAX_SPANS_LOAD_SIZE = 15000 ;
30+ const SPANS_POLL_INTERVAL = 3000 ;
31+
432type AgentRunnerExecutionPanelProps = {
5- jobId : string | null ;
33+ traceId : string | null ;
34+ projectId : string ;
35+ isJobRunning : boolean ;
36+ hasJob : boolean ;
37+ } ;
38+
39+ const buildTree = ( trace : Trace , spans : Span [ ] ) : TreeNode [ ] => {
40+ const sharedData = {
41+ maxStartTime : new Date ( trace . start_time ) . getTime ( ) ,
42+ maxEndTime : new Date ( trace . end_time ) . getTime ( ) ,
43+ maxDuration : trace . duration ,
44+ } ;
45+
46+ const lookup : Record < string , TreeNode > = {
47+ [ trace . id ] : {
48+ id : trace . id ,
49+ name : trace . name ,
50+ data : {
51+ ...trace ,
52+ ...sharedData ,
53+ spanColor : SPANS_COLORS_MAP [ TRACE_TYPE_FOR_TREE ] ,
54+ parent_span_id : "" ,
55+ trace_id : trace . id ,
56+ type : TRACE_TYPE_FOR_TREE ,
57+ tokens : trace . usage ?. total_tokens ,
58+ duration : trace . duration ,
59+ startTimestamp : new Date ( trace . start_time ) . getTime ( ) ,
60+ name : trace . name ,
61+ hasError : Boolean ( trace . error_info ) ,
62+ } ,
63+ children : [ ] ,
64+ } ,
65+ } ;
66+
67+ const sortedSpans = [ ...spans ]
68+ . filter ( ( span ) => span . trace_id === trace . id )
69+ . sort ( ( s1 , s2 ) => s1 . start_time . localeCompare ( s2 . start_time ) ) ;
70+
71+ sortedSpans . forEach ( ( span ) => {
72+ lookup [ span . id ] = {
73+ id : span . id ,
74+ name : span . name ,
75+ data : {
76+ ...span ,
77+ ...sharedData ,
78+ spanColor : SPANS_COLORS_MAP [ span . type ] ,
79+ tokens : span . usage ?. total_tokens ,
80+ duration : span . duration ,
81+ startTimestamp : new Date ( span . start_time ) . getTime ( ) ,
82+ hasError : Boolean ( span . error_info ) ,
83+ } ,
84+ children : [ ] ,
85+ } ;
86+ } ) ;
87+
88+ sortedSpans . forEach ( ( span ) => {
89+ const parentKey = span . parent_span_id ;
90+ if ( ! parentKey ) {
91+ lookup [ trace . id ] . children ?. push ( lookup [ span . id ] ) ;
92+ } else if ( lookup [ parentKey ] ) {
93+ lookup [ parentKey ] . children ?. push ( lookup [ span . id ] ) ;
94+ }
95+ } ) ;
96+
97+ return [ lookup [ trace . id ] ] ;
698} ;
799
8100const AgentRunnerExecutionPanel : React . FC < AgentRunnerExecutionPanelProps > = ( {
9- jobId,
101+ traceId,
102+ projectId,
103+ isJobRunning,
104+ hasJob,
10105} ) => {
11- return (
12- < div className = "flex h-full flex-col" >
13- < span className = "comet-body-s-accented mb-4" > Trajectory</ span >
106+ const [ selectedSpanId , setSelectedSpanId ] = useState < string > ( "" ) ;
107+ const [ activeSection , setActiveSection ] = useDetailsActionSectionState (
108+ "agentSandboxSection" ,
109+ ) ;
110+ const scrollRef = useRef < HTMLDivElement > ( null ) ;
111+ const setTree = useTreeDetailsStore ( ( s ) => s . setTree ) ;
112+
113+ useEffect ( ( ) => {
114+ setSelectedSpanId ( "" ) ;
115+ } , [ traceId ] ) ;
116+
117+ const { data : trace } = useTraceById (
118+ { traceId : traceId ?? "" , stripAttachments : true } ,
119+ {
120+ placeholderData : keepPreviousData ,
121+ enabled : Boolean ( traceId ) ,
122+ refetchInterval : isJobRunning ? SPANS_POLL_INTERVAL : false ,
123+ } ,
124+ ) ;
125+
126+ const { data : spansData } = useSpansList (
127+ {
128+ traceId : traceId ?? "" ,
129+ projectId,
130+ page : 1 ,
131+ size : MAX_SPANS_LOAD_SIZE ,
132+ stripAttachments : true ,
133+ } ,
134+ {
135+ placeholderData : keepPreviousData ,
136+ enabled : Boolean ( traceId ) && Boolean ( projectId ) ,
137+ refetchInterval : isJobRunning ? SPANS_POLL_INTERVAL : false ,
138+ } ,
139+ ) ;
140+
141+ const spans = useMemo ( ( ) => spansData ?. content ?? [ ] , [ spansData ?. content ] ) ;
142+
143+ useEffect ( ( ) => {
144+ if ( ! trace ) {
145+ setTree ( [ ] ) ;
146+ return ;
147+ }
148+ setTree ( buildTree ( trace , spans ) ) ;
149+ } , [ trace , spans , setTree ] ) ;
150+
151+ const dataToView = useMemo ( ( ) => {
152+ if ( ! trace ) return null ;
153+ if ( selectedSpanId ) {
154+ return find ( spans , ( span : Span ) => span . id === selectedSpanId ) ?? trace ;
155+ }
156+ return trace ;
157+ } , [ selectedSpanId , spans , trace ] ) ;
14158
15- { ! jobId && (
159+ const handleSelectRow = useCallback (
160+ ( id : string ) => {
161+ setSelectedSpanId ( id === traceId ? "" : id ) ;
162+ } ,
163+ [ traceId ] ,
164+ ) ;
165+
166+ if ( ! hasJob ) {
167+ return (
168+ < div className = "flex h-full flex-col p-6" >
169+ < span className = "comet-body-s-accented mb-4" > Trajectory</ span >
16170 < div className = "flex flex-1 flex-col items-center justify-center text-muted-slate" >
17171 < ListTree className = "mb-2 size-5" />
18172 < p className = "comet-body-s font-medium" > No run trace yet</ p >
@@ -22,13 +176,69 @@ const AgentRunnerExecutionPanel: React.FC<AgentRunnerExecutionPanelProps> = ({
22176 executes step by step
23177 </ p >
24178 </ div >
25- ) }
179+ </ div >
180+ ) ;
181+ }
182+
183+ if ( isJobRunning && ! trace ) {
184+ return (
185+ < div className = "flex h-full flex-col p-6" >
186+ < span className = "comet-body-s-accented mb-4" > Trajectory</ span >
187+ < div className = "flex flex-1 flex-col items-center justify-center text-muted-slate" >
188+ < Loader className = "mb-2 size-5" />
189+ < p className = "comet-body-s font-medium" > Running agent...</ p >
190+ < p className = "comet-body-xs mt-1 text-center" >
191+ Collecting trace data as your agent executes
192+ </ p >
193+ </ div >
194+ </ div >
195+ ) ;
196+ }
197+
198+ if ( ! trace ) {
199+ return (
200+ < div className = "flex h-full flex-col p-6" >
201+ < span className = "comet-body-s-accented mb-4" > Trajectory</ span >
202+ < div className = "flex flex-1 flex-col items-center justify-center text-muted-slate" >
203+ < Loader className = "mb-2 size-5" />
204+ < p className = "comet-body-xs" > Loading trace...</ p >
205+ </ div >
206+ </ div >
207+ ) ;
208+ }
26209
27- { jobId && (
28- < p className = "comet-body-xs text-muted-slate" >
29- Execution trace will appear here once the agent completes.
30- </ p >
31- ) }
210+ return (
211+ < div className = "flex h-full flex-col" >
212+ < ResizablePanelGroup
213+ direction = "horizontal"
214+ autoSaveId = "agent-sandbox-trajectory"
215+ >
216+ < ResizablePanel id = "agent-tree" defaultSize = { 50 } minSize = { 20 } >
217+ < div className = "size-full overflow-auto" ref = { scrollRef } >
218+ < AgentTraceTree
219+ scrollRef = { scrollRef }
220+ spanCount = { spans . length }
221+ rowId = { selectedSpanId || trace . id }
222+ onSelectRow = { handleSelectRow }
223+ isJobRunning = { isJobRunning }
224+ />
225+ </ div >
226+ </ ResizablePanel >
227+ < ResizableHandle />
228+ < ResizablePanel id = "agent-data" defaultSize = { 50 } minSize = { 20 } >
229+ { dataToView && (
230+ < TraceDataViewer
231+ data = { dataToView }
232+ projectId = { projectId }
233+ spanId = { selectedSpanId }
234+ traceId = { trace . id }
235+ activeSection = { activeSection }
236+ setActiveSection = { setActiveSection }
237+ isSpansLazyLoading = { false }
238+ />
239+ ) }
240+ </ ResizablePanel >
241+ </ ResizablePanelGroup >
32242 </ div >
33243 ) ;
34244} ;
0 commit comments