11import React , { useState } from "react" ;
2- import { motion } from "framer-motion" ;
2+ import { motion , AnimatePresence } from "framer-motion" ;
33import { Play , Clock , Hash , Bot } from "lucide-react" ;
44import { Card , CardContent } from "@/components/ui/card" ;
55import { Badge } from "@/components/ui/badge" ;
@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils";
88import { formatISOTimestamp } from "@/lib/date-utils" ;
99import type { AgentRunWithMetrics } from "@/lib/api" ;
1010import { AGENT_ICONS } from "./CCAgents" ;
11+ import { AgentRunOutputViewer } from "./AgentRunOutputViewer" ;
1112
1213interface AgentRunsListProps {
1314 /**
@@ -41,6 +42,7 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
4142 className,
4243} ) => {
4344 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
45+ const [ selectedRun , setSelectedRun ] = useState < AgentRunWithMetrics | null > ( null ) ;
4446
4547 // Calculate pagination
4648 const totalPages = Math . ceil ( runs . length / ITEMS_PER_PAGE ) ;
@@ -75,6 +77,16 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
7577 return tokens . toString ( ) ;
7678 } ;
7779
80+ const handleRunClick = ( run : AgentRunWithMetrics ) => {
81+ // If there's a callback, use it (for full-page navigation)
82+ if ( onRunClick ) {
83+ onRunClick ( run ) ;
84+ } else {
85+ // Otherwise, open in modal preview
86+ setSelectedRun ( run ) ;
87+ }
88+ } ;
89+
7890 if ( runs . length === 0 ) {
7991 return (
8092 < div className = { cn ( "text-center py-8 text-muted-foreground" , className ) } >
@@ -83,92 +95,114 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
8395 </ div >
8496 ) ;
8597 }
86-
98+
8799 return (
88- < div className = { cn ( "space-y-4" , className ) } >
89- < div className = "space-y-2" >
90- { currentRuns . map ( ( run , index ) => (
91- < motion . div
92- key = { run . id }
93- initial = { { opacity : 0 , y : 20 } }
94- animate = { { opacity : 1 , y : 0 } }
95- transition = { {
96- duration : 0.3 ,
97- delay : index * 0.05 ,
98- ease : [ 0.4 , 0 , 0.2 , 1 ] ,
99- } }
100- >
101- < Card
102- className = { cn (
103- "transition-all hover:shadow-md cursor-pointer" ,
104- onRunClick && "hover:shadow-lg hover:border-primary/50 active:scale-[0.99]"
105- ) }
106- onClick = { ( ) => onRunClick ?.( run ) }
100+ < >
101+ < div className = { cn ( "space-y-2" , className ) } >
102+ < AnimatePresence mode = "popLayout" >
103+ { currentRuns . map ( ( run , index ) => (
104+ < motion . div
105+ key = { run . id }
106+ initial = { { opacity : 0 , y : 20 } }
107+ animate = { { opacity : 1 , y : 0 } }
108+ exit = { { opacity : 0 , y : - 20 } }
109+ transition = { {
110+ duration : 0.3 ,
111+ delay : index * 0.05 ,
112+ ease : [ 0.4 , 0 , 0.2 , 1 ] ,
113+ } }
107114 >
108- < CardContent className = "p-3" >
109- < div className = "flex items-start justify-between gap-3" >
110- < div className = "flex items-start gap-3 flex-1 min-w-0" >
111- < div className = "mt-0.5" >
115+ < Card
116+ className = { cn (
117+ "cursor-pointer transition-all hover:shadow-md hover:scale-[1.01] active:scale-[0.99]" ,
118+ run . status === "running" && "border-green-500/50"
119+ ) }
120+ onClick = { ( ) => handleRunClick ( run ) }
121+ >
122+ < CardContent className = "p-3" >
123+ < div className = "flex items-center gap-3" >
124+ < div className = "flex-shrink-0" >
112125 { renderIcon ( run . agent_icon ) }
113126 </ div >
114- < div className = "flex-1 min-w-0 space-y-1" >
115- < div className = "flex items-center gap-2" >
116- < p className = "text-sm font-medium truncate" > { run . task } </ p >
117- < Badge variant = "outline" className = "text-xs" >
118- { run . model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet' }
119- </ Badge >
127+
128+ < div className = "flex-1 min-w-0" >
129+ < div className = "flex items-center gap-2 mb-1" >
130+ < h4 className = "text-sm font-medium truncate" >
131+ { run . agent_name }
132+ </ h4 >
133+ { run . status === "running" && (
134+ < div className = "flex items-center gap-1" >
135+ < div className = "w-2 h-2 bg-green-500 rounded-full animate-pulse" > </ div >
136+ < span className = "text-xs text-green-600 font-medium" > Running</ span >
137+ </ div >
138+ ) }
120139 </ div >
140+
141+ < p className = "text-xs text-muted-foreground truncate mb-1" >
142+ { run . task }
143+ </ p >
144+
121145 < div className = "flex items-center gap-3 text-xs text-muted-foreground" >
122- < span className = "truncate" > by { run . agent_name } </ span >
123- { run . completed_at && (
124- < >
125- < span > •</ span >
126- < div className = "flex items-center gap-1" >
127- < Clock className = "h-3 w-3" />
128- < span > { formatDuration ( run . metrics ?. duration_ms ) } </ span >
129- </ div >
130- </ >
146+ < div className = "flex items-center gap-1" >
147+ < Clock className = "h-3 w-3" />
148+ < span > { formatISOTimestamp ( run . created_at ) } </ span >
149+ </ div >
150+
151+ { run . metrics ?. duration_ms && (
152+ < span > { formatDuration ( run . metrics . duration_ms ) } </ span >
131153 ) }
154+
132155 { run . metrics ?. total_tokens && (
133- < >
134- < span > •</ span >
135- < div className = "flex items-center gap-1" >
136- < Hash className = "h-3 w-3" />
137- < span > { formatTokens ( run . metrics ?. total_tokens ) } </ span >
138- </ div >
139- </ >
140- ) }
141- { run . metrics ?. cost_usd && (
142- < >
143- < span > •</ span >
144- < span > ${ run . metrics ?. cost_usd ?. toFixed ( 4 ) } </ span >
145- </ >
156+ < div className = "flex items-center gap-1" >
157+ < Hash className = "h-3 w-3" />
158+ < span > { formatTokens ( run . metrics . total_tokens ) } </ span >
159+ </ div >
146160 ) }
147161 </ div >
148- < p className = "text-xs text-muted-foreground" >
149- { formatISOTimestamp ( run . created_at ) }
150- </ p >
162+ </ div >
163+
164+ < div className = "flex-shrink-0" >
165+ < Badge
166+ variant = {
167+ run . status === "completed" ? "default" :
168+ run . status === "running" ? "secondary" :
169+ run . status === "failed" ? "destructive" :
170+ "outline"
171+ }
172+ className = "text-xs"
173+ >
174+ { run . status === "completed" ? "Completed" :
175+ run . status === "running" ? "Running" :
176+ run . status === "failed" ? "Failed" :
177+ "Pending" }
178+ </ Badge >
151179 </ div >
152180 </ div >
153- { ! run . completed_at && (
154- < Badge variant = "secondary" className = "text-xs" >
155- Running
156- </ Badge >
157- ) }
158- </ div >
159- </ CardContent >
160- </ Card >
161- </ motion . div >
162- ) ) }
181+ </ CardContent >
182+ </ Card >
183+ </ motion . div >
184+ ) ) }
185+ </ AnimatePresence >
186+
187+ { /* Pagination */ }
188+ { totalPages > 1 && (
189+ < div className = "pt-2" >
190+ < Pagination
191+ currentPage = { currentPage }
192+ totalPages = { totalPages }
193+ onPageChange = { setCurrentPage }
194+ />
195+ </ div >
196+ ) }
163197 </ div >
164-
165- { totalPages > 1 && (
166- < Pagination
167- currentPage = { currentPage }
168- totalPages = { totalPages }
169- onPageChange = { setCurrentPage }
198+
199+ { /* Agent Run Output Viewer Modal */ }
200+ { selectedRun && (
201+ < AgentRunOutputViewer
202+ run = { selectedRun }
203+ onClose = { ( ) => setSelectedRun ( null ) }
170204 />
171205 ) }
172- </ div >
206+ </ >
173207 ) ;
174208} ;
0 commit comments