'use client' import { useMemo } from 'react' import { useTranslations } from 'next-intl' import { Button } from '@nextsparkjs/core/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@nextsparkjs/core/components/ui/card' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@nextsparkjs/core/components/ui/accordion' import type { Trace, Span } from '../../types/observability.types' import { TraceStatusBadge } from './TraceStatusBadge' import { SpansList } from './SpansList' import { ConversationFlow } from './ConversationFlow' import { CompactTimeline } from './CompactTimeline' interface ParentTraceInfo { traceId: string agentName: string } interface TraceDetailProps { trace: Trace spans: Span[] childTraces?: Trace[] childSpansMap?: Record parentTrace?: ParentTraceInfo onBack: () => void onSelectChildTrace?: (traceId: string) => void onSelectParentTrace?: (traceId: string) => void className?: string } export function TraceDetail({ trace, spans, childTraces = [], childSpansMap = {}, parentTrace, onBack, onSelectChildTrace, onSelectParentTrace, className = '' }: TraceDetailProps) { const t = useTranslations('observability') const formatDuration = (ms?: number) => { if (!ms) return '-' if (ms < 1000) return `${ms}ms` return `${(ms / 1000).toFixed(2)}s` } const formatTokens = (tokens: number) => { if (tokens === 0) return '-' return tokens.toLocaleString() } // Extract unique tools used in this trace const toolsUsed = useMemo(() => { return [...new Set( spans .filter((s) => s.type === 'tool' && s.toolName) .map((s) => s.toolName!) )] }, [spans]) // Extract model info from LLM spans const modelInfo = useMemo(() => { const llmSpan = spans.find((s) => s.type === 'llm' && s.model) if (!llmSpan) return null return { model: llmSpan.model, provider: llmSpan.provider, } }, [spans]) return (
{parentTrace && onSelectParentTrace && ( <> / / {trace.agentName} )}

{t('detail.title')}

{trace.traceId}

{t('detail.agent')}

{trace.agentName}

{trace.agentType && (

{trace.agentType}

)}
{t('detail.model')}

{modelInfo?.model || '-'}

{modelInfo?.provider && (

{modelInfo.provider}

)}
{t('detail.duration')}

{formatDuration(trace.durationMs)}

{t('detail.tokens')}

{formatTokens(trace.totalTokens)}

{formatTokens(trace.inputTokens)} in / {formatTokens(trace.outputTokens)} out

{t('detail.calls')}

{trace.llmCalls} LLM / {trace.toolCalls} {t('detail.tools')}

{toolsUsed.length > 0 && (

{toolsUsed.join(', ')}

)}
{/* Error box - shown first when there's an error */} {trace.error && ( {t('detail.error')}
                {trace.error}
              
{trace.errorStack && (
{t('detail.stackTrace')}
                    {trace.errorStack}
                  
)}
)} {t('detail.input')}
              {trace.input}
            
{/* Conversation Flow - Human-readable execution view */} {/* Child Traces - Sub-agent invocations with accordion */} {childTraces.length > 0 && ( {t('detail.childTraces')} {childTraces.map((child) => (

{child.agentName}

{child.traceId.slice(0, 8)}...

{formatDuration(child.durationMs)}

{formatTokens(child.totalTokens)} tokens

{/* Stats row */}

{t('detail.duration')}

{formatDuration(child.durationMs)}

{t('detail.tokens')}

{formatTokens(child.inputTokens)} / {formatTokens(child.outputTokens)}

{t('detail.calls')}

{child.llmCalls} LLM / {child.toolCalls} tools

{/* Compact Timeline - execution flow visualization */} {childSpansMap[child.traceId] && childSpansMap[child.traceId].length > 0 && (
)} {/* Error if exists */} {child.error && (

{t('detail.error')}

                              {child.error}
                            
)} {/* Input */}

{t('detail.input')}

                            {child.input}
                          
{/* Output */} {child.output && (

{t('detail.output')}

                              {child.output}
                            
)}
))}
)} {t('detail.spans')}
) }