1- import React from "react" ;
2- import { Button , Empty , List , Space , Tag , Typography } from "antd" ;
3- import { CheckOutlined , CloseOutlined } from "@ant-design/icons" ;
1+ import React , { useMemo , useState } from "react" ;
2+ import { Button , Empty , Input , List , Select , Space , Tag , Typography } from "antd" ;
43import { useWorkflow } from "../contexts/WorkflowContext" ;
4+ import AgentProposalCard from "./chat/AgentProposalCard" ;
5+ import {
6+ DEFAULT_TIMELINE_FILTERS ,
7+ filterTimelineEvents ,
8+ normalizeTimelineFilters ,
9+ TIMELINE_ACTOR_OPTIONS ,
10+ } from "../contexts/workflow/timelineFilters" ;
511
612const { Text } = Typography ;
713
@@ -14,6 +20,12 @@ const STAGE_LABELS = {
1420 evaluation : "Evaluation" ,
1521} ;
1622
23+ const SEVERITY_COLORS = {
24+ low : "default" ,
25+ medium : "orange" ,
26+ high : "red" ,
27+ } ;
28+
1729function formatEventTime ( value ) {
1830 if ( ! value ) return "" ;
1931 const date = new Date ( value ) ;
@@ -24,14 +36,22 @@ function formatEventTime(value) {
2436function WorkflowTimeline ( { limit = 8 } ) {
2537 const workflowContext = useWorkflow ( ) ;
2638 const workflow = workflowContext ?. workflow ;
27- const events = workflowContext ?. events || [ ] ;
39+ const events = workflowContext ?. events ;
40+ const hotspots = workflowContext ?. hotspots || [ ] ;
41+ const impactPreview = workflowContext ?. impactPreview ;
42+ const refreshInsights = workflowContext ?. refreshInsights ;
2843 const approveAgentAction = workflowContext ?. approveAgentAction ;
2944 const rejectAgentAction = workflowContext ?. rejectAgentAction ;
45+ const [ filters , setFilters ] = useState ( DEFAULT_TIMELINE_FILTERS ) ;
3046
31- if ( ! workflowContext ) return null ;
32-
33- const visibleEvents = events . slice ( - limit ) . reverse ( ) ;
47+ const reversedEvents = useMemo ( ( ) => [ ...( events || [ ] ) ] . reverse ( ) , [ events ] ) ;
48+ const visibleEvents = useMemo ( ( ) => {
49+ return filterTimelineEvents ( reversedEvents , filters ) . slice ( 0 , limit ) ;
50+ } , [ reversedEvents , filters , limit ] ) ;
3451 const stageLabel = STAGE_LABELS [ workflow ?. stage ] || workflow ?. stage || "Loading" ;
52+ const topHotspot = hotspots [ 0 ] || null ;
53+
54+ if ( ! workflowContext ) return null ;
3555
3656 return (
3757 < div
@@ -55,9 +75,87 @@ function WorkflowTimeline({ limit = 8 }) {
5575 { stageLabel }
5676 </ Tag >
5777 </ Space >
58- < Text type = "secondary" style = { { fontSize : 12 } } >
59- { workflow ?. title || "Segmentation Workflow" }
60- </ Text >
78+ < Space size = "small" >
79+ < Text type = "secondary" style = { { fontSize : 12 } } >
80+ { workflow ?. title || "Segmentation Workflow" }
81+ </ Text >
82+ < Button
83+ size = "small"
84+ type = "text"
85+ onClick = { ( ) => refreshInsights ?. ( ) }
86+ style = { { paddingInline : 4 } }
87+ >
88+ Refresh Insights
89+ </ Button >
90+ </ Space >
91+ </ Space >
92+
93+ { ( topHotspot || impactPreview ) && (
94+ < div
95+ style = { {
96+ marginTop : 8 ,
97+ padding : "6px 8px" ,
98+ background : "#fff" ,
99+ border : "1px solid #f0f0f0" ,
100+ borderRadius : 6 ,
101+ } }
102+ >
103+ { topHotspot && (
104+ < Space size = "small" wrap >
105+ < Text style = { { fontSize : 12 } } strong >
106+ Hotspot
107+ </ Text >
108+ < Tag color = { SEVERITY_COLORS [ topHotspot . severity ] || "default" } >
109+ { topHotspot . severity }
110+ </ Tag >
111+ < Text style = { { fontSize : 12 } } > { topHotspot . summary } </ Text >
112+ </ Space >
113+ ) }
114+ { impactPreview && (
115+ < div style = { { marginTop : topHotspot ? 4 : 0 } } >
116+ < Text style = { { fontSize : 12 } } strong >
117+ Impact
118+ </ Text >
119+ < Text style = { { fontSize : 12 } } >
120+ { ` ${ impactPreview . summary } (confidence: ${ impactPreview . confidence } )` }
121+ </ Text >
122+ </ div >
123+ ) }
124+ </ div >
125+ ) }
126+
127+ < Space size = "small" style = { { marginTop : 8 , display : "flex" } } wrap >
128+ < Select
129+ aria-label = "Actor filter"
130+ size = "small"
131+ value = { filters . actor }
132+ style = { { minWidth : 124 } }
133+ options = { TIMELINE_ACTOR_OPTIONS . map ( ( value ) => ( {
134+ value,
135+ label : value === "all" ? "All actors" : value ,
136+ } ) ) }
137+ onChange = { ( value ) =>
138+ setFilters ( ( prev ) => normalizeTimelineFilters ( { ...prev , actor : value } ) )
139+ }
140+ />
141+ < Input
142+ aria-label = "Event type filter"
143+ size = "small"
144+ value = { filters . eventType }
145+ placeholder = "Filter event type"
146+ style = { { maxWidth : 180 } }
147+ onChange = { ( event ) =>
148+ setFilters ( ( prev ) =>
149+ normalizeTimelineFilters ( { ...prev , eventType : event . target . value } ) ,
150+ )
151+ }
152+ />
153+ < Button
154+ size = "small"
155+ onClick = { ( ) => setFilters ( DEFAULT_TIMELINE_FILTERS ) }
156+ >
157+ Clear
158+ </ Button >
61159 </ Space >
62160
63161 { visibleEvents . length === 0 ? (
@@ -85,22 +183,9 @@ function WorkflowTimeline({ limit = 8 }) {
85183 actions = {
86184 isPendingProposal
87185 ? [
88- < Button
89- key = "approve"
90- size = "small"
91- icon = { < CheckOutlined /> }
92- onClick = { ( ) => approveAgentAction ?. ( event . id ) }
93- >
94- Approve
95- </ Button > ,
96- < Button
97- key = "reject"
98- size = "small"
99- icon = { < CloseOutlined /> }
100- onClick = { ( ) => rejectAgentAction ?. ( event . id ) }
101- >
102- Reject
103- </ Button > ,
186+ < Tag key = "pending" color = "gold" >
187+ Needs review
188+ </ Tag > ,
104189 ]
105190 : [ ]
106191 }
@@ -117,17 +202,31 @@ function WorkflowTimeline({ limit = 8 }) {
117202 </ Space >
118203 }
119204 description = {
120- < Space size = "small" wrap >
121- < Text type = "secondary" style = { { fontSize : 11 } } >
122- { event . actor }
123- </ Text >
124- < Text type = "secondary" style = { { fontSize : 11 } } >
125- { event . event_type }
126- </ Text >
127- < Text type = "secondary" style = { { fontSize : 11 } } >
128- { formatEventTime ( event . created_at ) }
129- </ Text >
130- </ Space >
205+ < div >
206+ < Space size = "small" wrap >
207+ < Text type = "secondary" style = { { fontSize : 11 } } >
208+ { event . actor }
209+ </ Text >
210+ < Text type = "secondary" style = { { fontSize : 11 } } >
211+ { event . event_type }
212+ </ Text >
213+ < Text type = "secondary" style = { { fontSize : 11 } } >
214+ { formatEventTime ( event . created_at ) }
215+ </ Text >
216+ </ Space >
217+ { isPendingProposal && (
218+ < AgentProposalCard
219+ proposal = { {
220+ ...( event . payload || { } ) ,
221+ type : event . payload ?. action || "agent_proposal" ,
222+ rationale : event . summary ,
223+ ...( event . payload ?. params || { } ) ,
224+ } }
225+ onApprove = { ( ) => approveAgentAction ?. ( event . id ) }
226+ onReject = { ( ) => rejectAgentAction ?. ( event . id ) }
227+ />
228+ ) }
229+ </ div >
131230 }
132231 />
133232 </ List . Item >
0 commit comments