1+ import { useState } from 'react' ;
12import { Bot , User , Info } from 'lucide-react' ;
23import { cn , formatTime , formatTokens } from '../../../lib/utils' ;
34import ReactMarkdown from 'react-markdown' ;
@@ -11,6 +12,7 @@ interface ConversationMessageProps {
1112
1213export function ConversationMessage ( { turn } : ConversationMessageProps ) {
1314 const isUser = turn . role === "user" ;
15+ const [ tooltip , setTooltip ] = useState < 'input' | 'output' | 'hint' | null > ( null ) ;
1416 const isFromWeb = turn . source === 'web' ;
1517
1618 return (
@@ -106,38 +108,66 @@ export function ConversationMessage({ turn }: ConversationMessageProps) {
106108 </ div >
107109
108110 { /* Token annotation — both user and assistant messages */ }
109- { turn . tokens && (
110- < div className = { cn (
111- 'text-xs mt-1 flex items-center gap-1.5 flex-wrap' ,
112- isUser ? 'opacity-70' : 'text-muted-foreground/60'
113- ) } >
114- < span className = "relative group/in flex items-center gap-1 cursor-default" >
115- < span > ↑ { formatTokens ( turn . tokens . input_tokens ?? 0 ) } </ span >
116- < span className = "pointer-events-none absolute bottom-full left-0 mb-1.5 w-44 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md opacity-0 group-hover/in:opacity-100 transition-opacity z-50" >
117- Input tokens — context sent to the model for this turn
111+ { turn . tokens && ( ( ) => {
112+ const inputTokens = turn . tokens . input_tokens ?? 0 ;
113+ const outputTokens = turn . tokens . output_tokens ?? 0 ;
114+ const showSystemHint = isUser && inputTokens > 50 ;
115+ return (
116+ < div className = { cn (
117+ 'text-xs mt-1 flex items-center gap-1.5 flex-wrap' ,
118+ isUser ? 'opacity-70' : 'text-muted-foreground/60'
119+ ) } >
120+ { /* Input tokens */ }
121+ < span
122+ className = "relative cursor-help"
123+ onMouseEnter = { ( ) => setTooltip ( 'input' ) }
124+ onMouseLeave = { ( ) => setTooltip ( null ) }
125+ >
126+ ↑ { formatTokens ( inputTokens ) }
127+ { tooltip === 'input' && (
128+ < span className = "absolute bottom-full left-0 mb-2 w-48 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md z-50 whitespace-normal" >
129+ Input tokens — context sent to the model for this turn
130+ </ span >
131+ ) }
118132 </ span >
119- </ span >
120- { isUser && ( turn . tokens . input_tokens ?? 0 ) > 50 && (
121- < span className = "relative group/hint" >
122- < Info className = "h-3 w-3 opacity-50 cursor-default" />
123- < span className = "pointer-events-none absolute bottom-full right-0 mb-1.5 w-52 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md opacity-0 group-hover/hint:opacity-100 transition-opacity z-50" >
124- Includes system prompt tokens sent with the first message of this run
133+
134+ { /* System prompt hint icon */ }
135+ { showSystemHint && (
136+ < span
137+ className = "relative cursor-help"
138+ onMouseEnter = { ( ) => setTooltip ( 'hint' ) }
139+ onMouseLeave = { ( ) => setTooltip ( null ) }
140+ >
141+ < Info className = "h-3 w-3 opacity-50" />
142+ { tooltip === 'hint' && (
143+ < span className = "absolute bottom-full right-0 mb-2 w-56 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md z-50 whitespace-normal" >
144+ Includes system prompt tokens — Claude Code injects its full instructions on the first message of every run
145+ </ span >
146+ ) }
125147 </ span >
126- </ span >
127- ) }
128- { ( turn . tokens . output_tokens ?? 0 ) > 0 && (
129- < >
130- < span className = "opacity-40" > ·</ span >
131- < span className = "relative group/out flex items-center gap-1 cursor-default" >
132- < span > ↓ { formatTokens ( turn . tokens . output_tokens ?? 0 ) } </ span >
133- < span className = "pointer-events-none absolute bottom-full left-0 mb-1.5 w-44 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md opacity-0 group-hover/out:opacity-100 transition-opacity z-50" >
134- Output tokens — text generated by the model in this turn
148+ ) }
149+
150+ { /* Output tokens */ }
151+ { outputTokens > 0 && (
152+ < >
153+ < span className = "opacity-40" > ·</ span >
154+ < span
155+ className = "relative cursor-help"
156+ onMouseEnter = { ( ) => setTooltip ( 'output' ) }
157+ onMouseLeave = { ( ) => setTooltip ( null ) }
158+ >
159+ ↓ { formatTokens ( outputTokens ) }
160+ { tooltip === 'output' && (
161+ < span className = "absolute bottom-full left-0 mb-2 w-48 rounded-md bg-popover border border-border px-2.5 py-1.5 text-[11px] leading-snug text-popover-foreground shadow-md z-50 whitespace-normal" >
162+ Output tokens — text generated by the model in this turn
163+ </ span >
164+ ) }
135165 </ span >
136- </ span >
137- </ >
138- ) }
139- </ div >
140- ) }
166+ </ >
167+ ) }
168+ </ div >
169+ ) ;
170+ } ) ( ) }
141171 </ div >
142172 </ div >
143173
0 commit comments