@@ -21,6 +21,13 @@ import {
2121 History ,
2222 Timer ,
2323 ArrowLeft ,
24+ Terminal ,
25+ Send ,
26+ Inbox ,
27+ Code2 ,
28+ Eye ,
29+ EyeOff ,
30+ Zap ,
2431} from 'lucide-react' ;
2532import AgentFlowChart from '../components/task/AgentFlowChart' ;
2633import TaskTimeline from '../components/task/TaskTimeline' ;
@@ -57,6 +64,7 @@ export default function TaskDetailPage() {
5764 const queryClient = useQueryClient ( ) ;
5865 const { data : task , isLoading : isQueryLoading , error } = useTaskDetail ( id ) ;
5966 const [ expandedSteps , setExpandedSteps ] = useState < Record < string , boolean > > ( { } ) ;
67+ const [ rawJsonSteps , setRawJsonSteps ] = useState < Record < string , boolean > > ( { } ) ;
6068 const [ elapsedTime , setElapsedTime ] = useState ( 0 ) ;
6169
6270 const cancelMutation = useMutation ( {
@@ -128,6 +136,35 @@ export default function TaskDetailPage() {
128136 const toggleStep = ( stepId : string ) =>
129137 setExpandedSteps ( prev => ( { ...prev , [ stepId ] : ! prev [ stepId ] } ) ) ;
130138
139+ const toggleRawJson = ( e : React . MouseEvent , stepId : string ) => {
140+ e . stopPropagation ( ) ;
141+ setRawJsonSteps ( prev => ( { ...prev , [ stepId ] : ! prev [ stepId ] } ) ) ;
142+ } ;
143+
144+ /* ── LLM I/O helpers ────────────────────────────────────────────────── */
145+ type StepResult = {
146+ trace ?: string [ ] ;
147+ output ?: string ;
148+ summary ?: string ;
149+ step_id ?: string ;
150+ description ?: string ;
151+ tokens_used ?: { in ?: number ; out ?: number } ;
152+ tool_inputs ?: Array < { tool : string ; args : Record < string , unknown > } > ;
153+ tool_calls_used ?: string [ ] ;
154+ code_artifacts ?: string [ ] ;
155+ } ;
156+
157+ const getStepResult = ( step : typeof task . steps [ 0 ] ) : StepResult | null => {
158+ const payload = step . output_payload as Record < string , unknown > | undefined ;
159+ if ( ! payload ) return null ;
160+ return ( payload . step_result as StepResult ) ?? ( payload . plan as StepResult ) ?? null ;
161+ } ;
162+
163+ const getPromptText = ( sr : StepResult ) : string => {
164+ if ( sr . trace && sr . trace . length > 0 && sr . trace [ 0 ] ) return sr . trace [ 0 ] ;
165+ return sr . description ?? '' ;
166+ } ;
167+
131168 const isRunning =
132169 task . status === TaskStatus . PROCESSING || task . status === TaskStatus . RETRYING ;
133170
@@ -434,16 +471,14 @@ export default function TaskDetailPage() {
434471 }
435472 </ div >
436473
437- { isExpanded && (
438- < div className = "px-5 pb-5 pl-[60px]" >
439- < div
440- className = "rounded-xl p-4 overflow-hidden"
441- style = { { background : '#1b2026' , border : '1px solid rgba(255,255,255,0.08)' } }
442- >
443- < div className = "flex items-center justify-between mb-2" >
444- < p className = "text-[10px] uppercase font-bold" style = { { color : '#848e9c' } } >
445- Step Payload
446- </ p >
474+ { isExpanded && ( ( ) => {
475+ const sr = getStepResult ( step ) ;
476+ const showRaw = rawJsonSteps [ step . id ] ;
477+ return (
478+ < div className = "px-5 pb-5 pl-[60px] space-y-3" >
479+
480+ { /* ── Header bar: model pill + raw toggle ── */ }
481+ < div className = "flex items-center gap-2" >
447482 { step . model_used && (
448483 < code
449484 className = "text-[10px] font-mono px-2 py-0.5 rounded"
@@ -452,16 +487,120 @@ export default function TaskDetailPage() {
452487 { step . model_used }
453488 </ code >
454489 ) }
490+ { sr && (
491+ < >
492+ { sr . tokens_used && (
493+ < span className = "text-[10px] font-mono px-2 py-0.5 rounded flex items-center gap-1"
494+ style = { { background : '#1e232a' , color : '#848e9c' } } >
495+ < Zap className = "w-2.5 h-2.5" />
496+ { sr . tokens_used . in ?? 0 } ↑ { sr . tokens_used . out ?? 0 } ↓
497+ </ span >
498+ ) }
499+ < button
500+ onClick = { e => toggleRawJson ( e , step . id ) }
501+ className = "ml-auto flex items-center gap-1 text-[10px] font-semibold px-2 py-0.5 rounded transition-colors"
502+ style = { { background : '#1e232a' , color : showRaw ? '#7ee787' : '#848e9c' , border : '1px solid rgba(255,255,255,0.08)' } }
503+ >
504+ { showRaw ? < EyeOff className = "w-3 h-3" /> : < Eye className = "w-3 h-3" /> }
505+ { showRaw ? 'Inspector' : 'Raw JSON' }
506+ </ button >
507+ </ >
508+ ) }
455509 </ div >
456- < pre
457- className = "text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto"
458- style = { { color : '#eaecef' } }
459- >
460- { JSON . stringify ( step . output_payload , null , 2 ) }
461- </ pre >
510+
511+ { showRaw || ! sr ? (
512+ /* ── Raw JSON fallback ── */
513+ < div className = "rounded-xl p-4" style = { { background : '#1b2026' , border : '1px solid rgba(255,255,255,0.08)' } } >
514+ < p className = "text-[10px] uppercase font-bold mb-2" style = { { color : '#848e9c' } } > Raw Payload</ p >
515+ < pre className = "text-xs font-mono whitespace-pre-wrap max-h-72 overflow-y-auto" style = { { color : '#eaecef' } } >
516+ { JSON . stringify ( step . output_payload , null , 2 ) }
517+ </ pre >
518+ </ div >
519+ ) : (
520+ /* ── Structured LLM I/O Inspector ── */
521+ < div className = "space-y-3" >
522+
523+ { /* Prompt sent */ }
524+ { getPromptText ( sr ) && (
525+ < div className = "rounded-xl overflow-hidden" style = { { border : '1px solid rgba(159,232,112,0.2)' } } >
526+ < div className = "flex items-center gap-2 px-3 py-2" style = { { background : '#0d1a0f' } } >
527+ < Send className = "w-3 h-3" style = { { color : '#7ee787' } } />
528+ < span className = "text-[10px] uppercase font-bold" style = { { color : '#7ee787' } } > Prompt Sent</ span >
529+ </ div >
530+ < pre className = "text-xs font-mono whitespace-pre-wrap px-3 py-3 max-h-48 overflow-y-auto"
531+ style = { { background : '#0f1f12' , color : '#c8e6c9' } } >
532+ { getPromptText ( sr ) }
533+ </ pre >
534+ </ div >
535+ ) }
536+
537+ { /* Tool calls */ }
538+ { sr . tool_inputs && sr . tool_inputs . length > 0 && (
539+ < div className = "rounded-xl overflow-hidden" style = { { border : '1px solid rgba(255,200,60,0.2)' } } >
540+ < div className = "flex items-center gap-2 px-3 py-2" style = { { background : '#1a1500' } } >
541+ < Code2 className = "w-3 h-3" style = { { color : '#ffd11a' } } />
542+ < span className = "text-[10px] uppercase font-bold" style = { { color : '#ffd11a' } } > Tool Calls ({ sr . tool_inputs . length } )</ span >
543+ </ div >
544+ < div className = "divide-y" style = { { background : '#12100a' , borderColor : 'rgba(255,200,60,0.1)' } } >
545+ { sr . tool_inputs . map ( ( tc , ti ) => (
546+ < div key = { ti } className = "px-3 py-3" >
547+ < div className = "flex items-center gap-2 mb-2" >
548+ < span className = "text-[10px] font-bold px-2 py-0.5 rounded"
549+ style = { { background : '#2a1f00' , color : '#ffd11a' } } >
550+ { tc . tool }
551+ </ span >
552+ < span className = "text-[10px]" style = { { color : '#848e9c' } } > Call #{ ti + 1 } </ span >
553+ </ div >
554+ < pre className = "text-xs font-mono whitespace-pre-wrap max-h-40 overflow-y-auto"
555+ style = { { color : '#e6d080' } } >
556+ { JSON . stringify ( tc . args , null , 2 ) }
557+ </ pre >
558+ </ div >
559+ ) ) }
560+ </ div >
561+ </ div >
562+ ) }
563+
564+ { /* Raw output / LLM response */ }
565+ { sr . output && (
566+ < div className = "rounded-xl overflow-hidden" style = { { border : '1px solid rgba(130,140,160,0.2)' } } >
567+ < div className = "flex items-center gap-2 px-3 py-2" style = { { background : '#13151a' } } >
568+ < Inbox className = "w-3 h-3" style = { { color : '#848e9c' } } />
569+ < span className = "text-[10px] uppercase font-bold" style = { { color : '#848e9c' } } > Raw Output</ span >
570+ </ div >
571+ < pre className = "text-xs font-mono whitespace-pre-wrap px-3 py-3 max-h-60 overflow-y-auto"
572+ style = { { background : '#0e1014' , color : '#b0b8c4' } } >
573+ { sr . output }
574+ </ pre >
575+ </ div >
576+ ) }
577+
578+ { /* Trace messages if multiple (tool round-trips) */ }
579+ { sr . trace && sr . trace . filter ( Boolean ) . length > 2 && (
580+ < details className = "group" >
581+ < summary className = "cursor-pointer flex items-center gap-2 text-[10px] uppercase font-bold py-1 select-none"
582+ style = { { color : '#848e9c' } } >
583+ < Terminal className = "w-3 h-3" />
584+ Full Trace ({ sr . trace . filter ( Boolean ) . length } messages)
585+ </ summary >
586+ < div className = "mt-2 rounded-xl overflow-hidden" style = { { border : '1px solid rgba(255,255,255,0.06)' } } >
587+ { sr . trace . filter ( Boolean ) . map ( ( msg , mi ) => (
588+ < div key = { mi } className = "px-3 py-2" style = { { background : mi % 2 === 0 ? '#0e1014' : '#111318' , borderTop : mi > 0 ? '1px solid rgba(255,255,255,0.06)' : 'none' } } >
589+ < span className = "text-[9px] font-bold uppercase mb-1 block" style = { { color : mi === 0 ? '#7ee787' : '#848e9c' } } >
590+ { mi === 0 ? 'prompt' : `msg ${ mi } ` }
591+ </ span >
592+ < pre className = "text-[11px] font-mono whitespace-pre-wrap max-h-32 overflow-y-auto" style = { { color : '#b0b8c4' } } > { msg } </ pre >
593+ </ div >
594+ ) ) }
595+ </ div >
596+ </ details >
597+ ) }
598+
599+ </ div >
600+ ) }
462601 </ div >
463- </ div >
464- ) }
602+ ) ;
603+ } ) ( ) }
465604 </ div >
466605 ) ;
467606 } )
@@ -569,6 +708,69 @@ export default function TaskDetailPage() {
569708 ) ) }
570709 </ div >
571710 </ section >
711+
712+ { /* LLM Call Summary card */ }
713+ { task . steps && task . steps . length > 0 && ( ( ) => {
714+ const execSteps = task . steps . filter ( s => s . step_type === StepType . EXECUTE || s . step_type === StepType . PLAN || s . step_type === StepType . ANALYZE ) ;
715+ if ( execSteps . length === 0 ) return null ;
716+ const totalIn = execSteps . reduce ( ( s , st ) => s + ( st . tokens_in ?? 0 ) , 0 ) ;
717+ const totalOut = execSteps . reduce ( ( s , st ) => s + ( st . tokens_out ?? 0 ) , 0 ) ;
718+ return (
719+ < section className = "wise-card-dark-surface" >
720+ < h3 className = "font-semibold text-sm mb-4 flex items-center gap-2" style = { { color : '#eaecef' } } >
721+ < div className = "w-6 h-6 rounded-lg flex items-center justify-center" style = { { background : '#1a1500' } } >
722+ < Zap className = "w-3.5 h-3.5" style = { { color : '#ffd11a' } } />
723+ </ div >
724+ LLM Call Summary
725+ </ h3 >
726+
727+ { /* Totals */ }
728+ < div className = "grid grid-cols-2 gap-2 mb-4" >
729+ { [
730+ { label : 'Total ↑ Tokens' , val : totalIn . toLocaleString ( ) , color : '#7ee787' } ,
731+ { label : 'Total ↓ Tokens' , val : totalOut . toLocaleString ( ) , color : '#ffd11a' } ,
732+ ] . map ( m => (
733+ < div key = { m . label } className = "p-2.5 rounded-xl text-center" style = { { background : '#1e232a' } } >
734+ < p className = "text-[9px] uppercase font-bold mb-1" style = { { color : '#848e9c' } } > { m . label } </ p >
735+ < p className = "text-sm font-mono font-bold" style = { { color : m . color } } > { m . val } </ p >
736+ </ div >
737+ ) ) }
738+ </ div >
739+
740+ { /* Per-step rows */ }
741+ < div className = "space-y-2" >
742+ { execSteps . map ( ( st , i ) => {
743+ const sr = getStepResult ( st ) ;
744+ const toolsUsed = sr ?. tool_calls_used ?? [ ] ;
745+ const stepLabel = st . step_type === StepType . PLAN ? 'Plan' :
746+ st . step_type === StepType . ANALYZE ? 'Analyze' :
747+ `Step ${ i } ` ;
748+ return (
749+ < div key = { st . id } className = "rounded-xl p-2.5" style = { { background : '#1b2026' , border : '1px solid rgba(255,255,255,0.06)' } } >
750+ < div className = "flex items-center justify-between mb-1.5" >
751+ < span className = "text-[11px] font-semibold" style = { { color : '#eaecef' } } > { stepLabel } </ span >
752+ < span className = "text-[10px] font-mono" style = { { color : '#848e9c' } } >
753+ { ( st . tokens_in ?? 0 ) } ↑ { ( st . tokens_out ?? 0 ) } ↓
754+ </ span >
755+ </ div >
756+ < p className = "text-[10px] truncate mb-1.5" style = { { color : '#848e9c' } } > { st . agent_name } </ p >
757+ { toolsUsed . length > 0 && (
758+ < div className = "flex flex-wrap gap-1" >
759+ { toolsUsed . map ( t => (
760+ < span key = { t } className = "text-[9px] font-mono px-1.5 py-0.5 rounded"
761+ style = { { background : '#2a1f00' , color : '#ffd11a' } } >
762+ { t }
763+ </ span >
764+ ) ) }
765+ </ div >
766+ ) }
767+ </ div >
768+ ) ;
769+ } ) }
770+ </ div >
771+ </ section >
772+ ) ;
773+ } ) ( ) }
572774 </ div >
573775 </ div >
574776
0 commit comments