import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useState } from 'react'; import { IconChevronDown, IconChevronUp } from 'twenty-ui/display'; import { JsonTree } from 'twenty-ui/json-visualizer'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; import { useLingui } from '@lingui/react/macro'; import { type DataMessagePart } from 'twenty-shared/ai'; import { type JsonValue } from 'type-fest'; import { useCopyToClipboard } from '~/hooks/useCopyToClipboard'; const StyledContainer = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(2)}; margin-top: ${({ theme }) => theme.spacing(2)}; `; const StyledToggleButton = styled.div` align-items: center; background: none; border: none; cursor: pointer; display: flex; color: ${({ theme }) => theme.font.color.tertiary}; gap: ${({ theme }) => theme.spacing(1)}; padding: ${({ theme }) => theme.spacing(1)} 0; transition: color ${({ theme }) => theme.animation.duration.normal}s; font-size: ${({ theme }) => theme.font.size.sm}; &:hover { color: ${({ theme }) => theme.font.color.secondary}; } `; const StyledContentContainer = styled.div` background: ${({ theme }) => theme.background.transparent.lighter}; border: 1px solid ${({ theme }) => theme.border.color.light}; border-radius: ${({ theme }) => theme.border.radius.sm}; min-width: 0; padding: ${({ theme }) => theme.spacing(3)}; `; const StyledJsonTreeContainer = styled.div` overflow-x: auto; ul { min-width: 0; } `; const StyledTabContainer = styled.div` border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; display: flex; gap: ${({ theme }) => theme.spacing(3)}; margin-bottom: ${({ theme }) => theme.spacing(3)}; `; const StyledTab = styled.div<{ isActive: boolean }>` color: ${({ theme, isActive }) => isActive ? theme.font.color.primary : theme.font.color.tertiary}; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme, isActive }) => isActive ? theme.font.weight.medium : theme.font.weight.regular}; cursor: pointer; transition: color ${({ theme }) => theme.animation.duration.normal}s; padding-bottom: ${({ theme }) => theme.spacing(2)}; &:hover { color: ${({ theme }) => theme.font.color.secondary}; } `; const StyledTimingSection = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(2)}; `; const StyledTimingRow = styled.div` align-items: center; display: flex; font-size: ${({ theme }) => theme.font.size.sm}; justify-content: space-between; padding: ${({ theme }) => theme.spacing(1)} 0; `; const StyledTimingLabel = styled.span` color: ${({ theme }) => theme.font.color.secondary}; `; const StyledTimingValue = styled.span` color: ${({ theme }) => theme.font.color.primary}; font-weight: ${({ theme }) => theme.font.weight.medium}; `; type TabType = 'timing' | 'details' | 'context'; type TimingRowProps = { label: string; value: string | number | undefined; }; const TimingRow = ({ label, value }: TimingRowProps) => { if (value === undefined) { return null; } return ( {label} {value} ); }; const formatBytes = (bytes: number) => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`; }; const formatNumber = (num: number) => num.toLocaleString(); const formatTokenBreakdown = ( total: number, prompt?: number, completion?: number, ) => { const formattedTotal = formatNumber(total); const hasValidBreakdown = prompt !== undefined && completion !== undefined && prompt > 0 && completion > 0; if (hasValidBreakdown) { return `${formattedTotal} (${formatNumber(prompt)} → ${formatNumber(completion)})`; } return formattedTotal; }; type DebugInfo = NonNullable; type TimingTabProps = { debug: DebugInfo; }; const TimingTab = ({ debug }: TimingTabProps) => { const { t } = useLingui(); const totalTime = debug.agentExecutionStartTimeMs !== undefined ? `${debug.agentExecutionStartTimeMs + (debug.agentExecutionTimeMs || 0)}ms` : undefined; const totalCost = debug.totalCostInCredits !== undefined ? formatNumber(debug.totalCostInCredits) : undefined; return ( ); }; type DetailsTabProps = { debug: DebugInfo; copyToClipboard: (value: string) => void; }; const DetailsTab = ({ debug, copyToClipboard }: DetailsTabProps) => { const { t } = useLingui(); const detailsData = { selectedAgent: { id: debug.selectedAgentId, label: debug.selectedAgentLabel, }, fastModel: debug.fastModel, smartModel: debug.smartModel, agentModel: debug.agentModel, availableAgents: debug.availableAgents, }; return ( true} emptyArrayLabel={t`Empty Array`} emptyObjectLabel={t`Empty Object`} emptyStringLabel={t`[empty string]`} arrowButtonCollapsedLabel={t`Expand`} arrowButtonExpandedLabel={t`Collapse`} onNodeValueClick={copyToClipboard} /> ); }; type ContextTabProps = { debug: DebugInfo; copyToClipboard: (value: string) => void; }; const ContextTab = ({ debug, copyToClipboard }: ContextTabProps) => { const { t } = useLingui(); if (!debug.context) { return ( {t`No context was provided for this request`} ); } try { const contextData = JSON.parse(debug.context); return ( false} emptyArrayLabel={t`Empty Array`} emptyObjectLabel={t`Empty Object`} emptyStringLabel={t`[empty string]`} arrowButtonCollapsedLabel={t`Expand`} arrowButtonExpandedLabel={t`Collapse`} onNodeValueClick={copyToClipboard} /> ); } catch { const contextValue = debug.context; return ( {t`Failed to parse context: ${contextValue}`} ); } }; type RoutingDebugDisplayProps = { debug: DebugInfo; }; export const RoutingDebugDisplay = ({ debug }: RoutingDebugDisplayProps) => { const { t } = useLingui(); const theme = useTheme(); const { copyToClipboard } = useCopyToClipboard(); const [isExpanded, setIsExpanded] = useState(false); const [activeTab, setActiveTab] = useState('timing'); return ( setIsExpanded(!isExpanded)}> {t`Debug Info`} {isExpanded ? ( ) : ( )} setActiveTab('timing')} > {t`Timing`} setActiveTab('details')} > {t`Details`} {debug.context && ( setActiveTab('context')} > {t`Context`} )} {activeTab === 'timing' && } {activeTab === 'details' && ( )} {activeTab === 'context' && ( )} ); };