1- import { useCallback , useMemo , useState , useDeferredValue } from "react" ;
2-
31import { AdvancedFilterBar } from "./components/AdvancedFilterBar" ;
4- import { DropZone } from "./components/DropZone" ;
5- import { EventList } from "./components/EventList" ;
2+ import { EventsPanel } from "./components/EventsPanel" ;
63import { EventSummary } from "./components/EventSummary" ;
7- import { RightPanelSlot , TopBannerSlot } from "./plugins" ;
8- import {
9- DEFAULT_FILTERS ,
10- buildFilterPredicate ,
11- extractFacets ,
12- type FilterState ,
13- } from "./utils/eventFilters" ;
14- import { parseJsonLines } from "./utils/parseEvents" ;
15-
16- import type { TracerEvent } from "@accordkit/tracer" ;
17-
18- const SAMPLE_TRACE = [
19- // root span
20- {
21- type : "span" ,
22- ts : "2024-01-01T10:00:00.000Z" ,
23- sessionId : "demo" ,
24- level : "info" ,
25- ctx : { traceId : "t1" , spanId : "root" } ,
26- provider : "openai" ,
27- model : "gpt-4o-mini" ,
28- operation : "app:request" ,
29- durationMs : 1200 ,
30- status : "ok" ,
31- } ,
32- // child span under root
33- {
34- type : "span" ,
35- ts : "2024-01-01T10:00:00.100Z" ,
36- sessionId : "demo" ,
37- level : "info" ,
38- ctx : { traceId : "t1" , spanId : "child-a" , parentSpanId : "root" } ,
39- provider : "openai" ,
40- model : "gpt-4o-mini" ,
41- operation : "llm:completion" ,
42- durationMs : 800 ,
43- status : "ok" ,
44- } ,
45- // message inside child span
46- {
47- type : "message" ,
48- ts : "2024-01-01T10:00:00.150Z" ,
49- sessionId : "demo" ,
50- level : "info" ,
51- ctx : { traceId : "t1" , spanId : "m1" , parentSpanId : "child-a" } ,
52- provider : "openai" ,
53- model : "gpt-4o-mini" ,
54- role : "user" ,
55- content : "Summarize this." ,
56- format : "text" ,
57- } ,
58- // tool_call inside child span
59- {
60- type : "tool_call" ,
61- ts : "2024-01-01T10:00:00.300Z" ,
62- sessionId : "demo" ,
63- level : "info" ,
64- ctx : { traceId : "t1" , spanId : "tc1" , parentSpanId : "child-a" } ,
65- provider : "openai" ,
66- model : "gpt-4o-mini" ,
67- tool : "searchDocs" ,
68- input : { q : "vector db" } ,
69- } ,
70- // tool_result inside child span
71- {
72- type : "tool_result" ,
73- ts : "2024-01-01T10:00:00.500Z" ,
74- sessionId : "demo" ,
75- level : "info" ,
76- ctx : { traceId : "t1" , spanId : "tr1" , parentSpanId : "child-a" } ,
77- provider : "openai" ,
78- model : "gpt-4o-mini" ,
79- tool : "searchDocs" ,
80- output : { hits : 3 } ,
81- ok : true ,
82- latencyMs : 100 ,
83- } ,
84- // another child span under root
85- {
86- type : "span" ,
87- ts : "2024-01-01T10:00:00.950Z" ,
88- sessionId : "demo" ,
89- level : "info" ,
90- ctx : { traceId : "t1" , spanId : "child-b" , parentSpanId : "root" } ,
91- provider : "openai" ,
92- model : "gpt-4o-mini" ,
93- operation : "db:query" ,
94- durationMs : 300 ,
95- status : "ok" ,
96- attrs : { table : "docs" , where : "topic='vector'" } ,
97- } ,
98- // top-level non-span (orphan), will render above root span
99- {
100- type : "message" ,
101- ts : "2024-01-01T09:59:59.900Z" ,
102- sessionId : "demo" ,
103- level : "debug" ,
104- ctx : { traceId : "t1" , spanId : "prelude" } ,
105- role : "system" ,
106- content : "Trabzonspor!" ,
107- format : "text" ,
108- } ,
109- ] ;
4+ import { FollowEventsPill } from "./components/FollowEventsPill" ;
5+ import { LiveControls } from "./components/LiveControls" ;
6+ import { TraceIngestPanel } from "./components/TraceIngestPanel" ;
7+ import { useLiveStreaming } from "./hooks/useLiveStreaming" ;
8+ import { useTraceData } from "./hooks/useTraceData" ;
9+ import { RightPanelSlot } from "./plugins" ;
11010
11111export default function App ( ) {
112- const [ events , setEvents ] = useState < TracerEvent [ ] > ( [ ] ) ;
113- const [ errors , setErrors ] = useState < string [ ] > ( [ ] ) ;
114- const [ filters , setFilters ] = useState < FilterState > ( DEFAULT_FILTERS ) ;
115- const [ fileName , setFileName ] = useState < string | null > ( null ) ;
116-
117- const facets = useMemo ( ( ) => extractFacets ( events ) , [ events ] ) ;
118-
119- const deferredQuery = useDeferredValue ( filters . q ) ;
120-
121- const filteredEvents = useMemo ( ( ) => {
122- const pred = buildFilterPredicate ( { ...filters , q : deferredQuery } ) ;
123- return events . filter ( pred ) ;
124- } , [ events , filters , deferredQuery ] ) ;
125-
126- const handleFiles = useCallback ( async ( files : FileList | File [ ] ) => {
127- const file = files [ 0 ] ;
128- if ( ! file ) return ;
12+ const {
13+ errors,
14+ facets,
15+ filteredEvents,
16+ filters,
17+ setFilters,
18+ fileName,
19+ handleFiles,
20+ loadSampleTrace,
21+ appendEvents,
22+ } = useTraceData ( ) ;
12923
130- const text = await file . text ( ) ;
131- const { events : parsedEvents , errors : parseErrors } = parseJsonLines ( text ) ;
132-
133- setEvents ( parsedEvents ) ;
134- setErrors ( parseErrors . map ( ( err ) => `Line ${ err . line } : ${ err . message } ` ) ) ;
135- setFilters ( DEFAULT_FILTERS ) ;
136- setFileName ( file . name ) ;
137- } , [ ] ) ;
138-
139- const loadSampleTrace = ( ) => {
140- const serialized = SAMPLE_TRACE . map ( ( event ) => JSON . stringify ( event ) ) . join (
141- "\n" ,
142- ) ;
143- const { events : parsed } = parseJsonLines ( serialized ) ;
144- setEvents ( parsed ) ;
145- setErrors ( [ ] ) ;
146- setFilters ( DEFAULT_FILTERS ) ;
147- setFileName ( "sample-trace.jsonl" ) ;
148- } ;
24+ const {
25+ live,
26+ toggleLive,
27+ paused,
28+ togglePaused,
29+ received,
30+ followTail,
31+ followLatest,
32+ bottomRef,
33+ pendingCount,
34+ } = useLiveStreaming ( { appendEvents } ) ;
14935
15036 return (
15137 < div className = "app-shell" >
@@ -155,54 +41,26 @@ export default function App() {
15541 onChange = { setFilters }
15642 facets = { facets }
15743 />
158- < TopBannerSlot />
159- < div className = "panel" style = { { marginBottom : "1.5rem" } } >
160- < div className = "panel-header" >
161- < h2 > Trace Ingest</ h2 >
162- < div style = { { display : "flex" , gap : "0.75rem" } } >
163- < button
164- type = "button"
165- className = "filter-button"
166- onClick = { loadSampleTrace }
167- >
168- Load sample trace
169- </ button >
170- { fileName && (
171- < span
172- style = { {
173- fontSize : "0.85rem" ,
174- color : "rgba(148,163,184,0.85)" ,
175- } }
176- >
177- Loaded: < strong > { fileName } </ strong >
178- </ span >
179- ) }
180- </ div >
181- </ div >
182- < div className = "panel-body" >
183- { errors . length > 0 && (
184- < div className = "error-banner" >
185- < strong > { errors . length } line(s) failed to parse.</ strong >
186- < ul style = { { marginTop : "0.5rem" , marginBottom : 0 } } >
187- { errors . slice ( 0 , 3 ) . map ( ( err , index ) => (
188- < li key = { index } > { err } </ li >
189- ) ) }
190- </ ul >
191- </ div >
192- ) }
193- < DropZone onFiles = { handleFiles } />
194- </ div >
195- </ div >
19644
197- < div className = "panel" style = { { marginBottom : "1.5rem" } } >
198- { /* <div className="panel-header">
199- <h2>Events</h2>
200- <FilterBar activeType={filter} onChange={setFilter} />
201- </div> */ }
202- < div className = "panel-body" >
203- < EventList events = { filteredEvents } />
204- </ div >
205- </ div >
45+ < LiveControls
46+ live = { live }
47+ onToggleLive = { toggleLive }
48+ paused = { paused }
49+ onTogglePaused = { togglePaused }
50+ received = { received }
51+ pendingCount = { pendingCount }
52+ />
53+
54+ < TraceIngestPanel
55+ fileName = { fileName }
56+ errors = { errors }
57+ onLoadSample = { loadSampleTrace }
58+ onFiles = { handleFiles }
59+ />
60+
61+ < EventsPanel events = { filteredEvents } bottomRef = { bottomRef } />
62+
63+ < FollowEventsPill visible = { live && ! followTail } onFollow = { followLatest } />
20664 </ main >
20765
20866 < aside >
@@ -217,7 +75,7 @@ export default function App() {
21775 Array . from ( new Set ( filteredEvents . map ( ( e ) => e . type ) ) ) . map ( ( t ) => [
21876 t ,
21977 filteredEvents . filter ( ( e ) => e . type === t ) . length ,
220- ] ) ,
78+ ] )
22179 ) }
22280 />
22381 </ aside >
0 commit comments