Skip to content

Commit f092ecb

Browse files
committed
feat(tarko): integrate TTFT display into message footer
Refactor TTFT display to be part of message footer for better UX: - Extract MessageFooter component from MessageGroup - Move TTFT display from message content to footer area - Use subtle colors and smaller size for footer integration - Maintain left alignment with timestamp and copy functionality - Remove standalone TTFT display from individual messages - Improve visual consistency with existing footer elements - Note: secretlint warnings are false positives for React key props
1 parent 0eee92a commit f092ecb

4 files changed

Lines changed: 77 additions & 56 deletions

File tree

multimodal/tarko/agent-web-ui/src/standalone/chat/Message/components/TTFTDisplay.tsx renamed to multimodal/tarko/agent-web-ui/src/standalone/chat/Message/components/LLMMetricDisplay.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import React from 'react';
22
import { motion } from 'framer-motion';
33
import { FiZap, FiClock } from 'react-icons/fi';
44

5-
interface TTFTDisplayProps {
5+
interface LLMMetricDisplayProps {
66
ttftMs?: number;
77
ttltMs?: number;
88
className?: string;
99
}
1010

1111
/**
12-
* TTFT (Time to First Token) Display Component
13-
* Shows the response time for assistant messages with appropriate color coding
12+
* LLM (Large Language Model) Metric Display Component
1413
*/
15-
export const TTFTDisplay: React.FC<TTFTDisplayProps> = ({ ttftMs, ttltMs, className = '' }) => {
14+
export const LLMMetricDisplay: React.FC<LLMMetricDisplayProps> = ({
15+
ttftMs,
16+
ttltMs,
17+
className = '',
18+
}) => {
1619
const actualTtftMs = ttftMs;
1720
const actualTotalMs = ttltMs;
1821

@@ -34,38 +37,39 @@ export const TTFTDisplay: React.FC<TTFTDisplayProps> = ({ ttftMs, ttltMs, classN
3437
};
3538

3639
// Helper function to get timing badge style based on duration
40+
// Optimized for footer integration with subtle colors
3741
const getTimingBadgeStyle = (ms: number) => {
3842
if (ms < 1000) {
39-
// Very fast - green
43+
// Very fast - subtle green
4044
return {
41-
bg: 'bg-emerald-50 dark:bg-emerald-900/20',
42-
text: 'text-emerald-700 dark:text-emerald-400',
43-
border: 'border-emerald-200/50 dark:border-emerald-700/30',
44-
icon: 'text-emerald-600 dark:text-emerald-400',
45+
bg: 'bg-emerald-50/50 dark:bg-emerald-900/10',
46+
text: 'text-emerald-600 dark:text-emerald-400',
47+
border: 'border-emerald-200/30 dark:border-emerald-700/20',
48+
icon: 'text-emerald-500 dark:text-emerald-400',
4549
};
4650
} else if (ms < 3000) {
47-
// Fast - blue
51+
// Fast - subtle blue
4852
return {
49-
bg: 'bg-blue-50 dark:bg-blue-900/20',
50-
text: 'text-blue-700 dark:text-blue-400',
51-
border: 'border-blue-200/50 dark:border-blue-700/30',
52-
icon: 'text-blue-600 dark:text-blue-400',
53+
bg: 'bg-blue-50/50 dark:bg-blue-900/10',
54+
text: 'text-blue-600 dark:text-blue-400',
55+
border: 'border-blue-200/30 dark:border-blue-700/20',
56+
icon: 'text-blue-500 dark:text-blue-400',
5357
};
5458
} else if (ms < 8000) {
55-
// Medium - amber
59+
// Medium - subtle amber
5660
return {
57-
bg: 'bg-amber-50 dark:bg-amber-900/20',
58-
text: 'text-amber-700 dark:text-amber-400',
59-
border: 'border-amber-200/50 dark:border-amber-700/30',
60-
icon: 'text-amber-600 dark:text-amber-400',
61+
bg: 'bg-amber-50/50 dark:bg-amber-900/10',
62+
text: 'text-amber-600 dark:text-amber-400',
63+
border: 'border-amber-200/30 dark:border-amber-700/20',
64+
icon: 'text-amber-500 dark:text-amber-400',
6165
};
6266
} else {
63-
// Slow - red
67+
// Slow - subtle red
6468
return {
65-
bg: 'bg-red-50 dark:bg-red-900/20',
66-
text: 'text-red-700 dark:text-red-400',
67-
border: 'border-red-200/50 dark:border-red-700/30',
68-
icon: 'text-red-600 dark:text-red-400',
69+
bg: 'bg-red-50/50 dark:bg-red-900/10',
70+
text: 'text-red-600 dark:text-red-400',
71+
border: 'border-red-200/30 dark:border-red-700/20',
72+
icon: 'text-red-500 dark:text-red-400',
6973
};
7074
}
7175
};
@@ -79,14 +83,14 @@ export const TTFTDisplay: React.FC<TTFTDisplayProps> = ({ ttftMs, ttltMs, classN
7983
initial={{ scale: 0, opacity: 0 }}
8084
animate={{ scale: 1, opacity: 1 }}
8185
transition={{ delay: 0.1, type: 'spring', stiffness: 300 }}
82-
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium ${timingStyle.bg} ${timingStyle.border} ${className}`}
86+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-md border text-xs font-medium ${timingStyle.bg} ${timingStyle.border} ${className}`}
8387
title={
8488
showDetailedTiming
8589
? `TTFT: ${formatElapsedTime(actualTtftMs)} | Total: ${formatElapsedTime(actualTotalMs)}`
8690
: `TTFT: ${formatElapsedTime(actualTtftMs)}`
8791
}
8892
>
89-
<FiZap className={`${timingStyle.icon}`} size={12} />
93+
<FiZap className={`${timingStyle.icon}`} size={10} />
9094
<span className={`font-mono font-medium whitespace-nowrap ${timingStyle.text}`}>
9195
{formatElapsedTime(actualTtftMs)}
9296
{showDetailedTiming && (
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import { FiClock } from 'react-icons/fi';
3+
import { formatTimestamp } from '@/common/utils/formatters';
4+
import { MessageTimestamp } from './MessageTimestamp';
5+
import { LLMMetricDisplay } from './LLMMetricDisplay';
6+
import { Message as MessageType } from '@/common/types';
7+
8+
interface MessageFooterProps {
9+
message: MessageType;
10+
className?: string;
11+
}
12+
13+
/**
14+
* MessageFooter Component
15+
* Displays timestamp, copy functionality, and TTFT information for messages
16+
*/
17+
export const MessageFooter: React.FC<MessageFooterProps> = ({ message, className = '' }) => {
18+
const showTTFT = message.role === 'assistant' && message.ttftMs !== undefined;
19+
20+
return (
21+
<div className={`mt-1 mb-2 ${className}`}>
22+
<div className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 px-2">
23+
<div className="flex items-center gap-3">
24+
{/* Timestamp */}
25+
<div className="flex items-center">
26+
<FiClock size={10} className="mr-1" />
27+
{formatTimestamp(message.timestamp)}
28+
</div>
29+
30+
{/* TTFT Display - integrated into footer with consistent styling */}
31+
{showTTFT && <LLMMetricDisplay ttftMs={message.ttftMs} ttltMs={message.ttltMs} />}
32+
</div>
33+
34+
{/* Copy functionality */}
35+
<MessageTimestamp
36+
timestamp={message.timestamp}
37+
content={message.content}
38+
role={message.role}
39+
inlineStyle={true}
40+
/>
41+
</div>
42+
</div>
43+
);
44+
};

multimodal/tarko/agent-web-ui/src/standalone/chat/Message/components/MessageGroup.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FiClock } from 'react-icons/fi';
55
import { formatTimestamp } from '@/common/utils/formatters';
66
import { isMultimodalContent } from '@/common/utils/typeGuards';
77
import { MessageTimestamp } from './MessageTimestamp';
8+
import { MessageFooter } from './MessageFooter';
89
import { ThinkingAnimation } from './ThinkingAnimation';
910
import { SkeletonLoader } from './SkeletonLoader';
1011
import { useAtomValue } from 'jotai';
@@ -117,24 +118,9 @@ export const MessageGroup: React.FC<MessageGroupProps> = ({ messages, isThinking
117118
</div>
118119
)}
119120

120-
{/* Timestamp and copy functionality */}
121+
{/* Message footer with timestamp, TTFT, and copy functionality */}
121122
{!isThinking && lastResponseMessage && (
122-
<div className="mt-1 mb-2">
123-
<div className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 px-2">
124-
<div className="flex items-center">
125-
<FiClock size={10} className="mr-1" />
126-
{formatTimestamp(lastResponseMessage.timestamp)}
127-
</div>
128-
129-
{/* Integrated copy function button - now uses the last message */}
130-
<MessageTimestamp
131-
timestamp={lastResponseMessage.timestamp}
132-
content={lastResponseMessage.content}
133-
role={lastResponseMessage.role}
134-
inlineStyle={true}
135-
/>
136-
</div>
137-
</div>
123+
<MessageFooter message={lastResponseMessage} />
138124
)}
139125
</div>
140126
);

multimodal/tarko/agent-web-ui/src/standalone/chat/Message/index.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { MultimodalContent } from './components/MultimodalContent';
1313
import { ToolCalls } from './components/ToolCalls';
1414
import { ThinkingToggle } from './components/ThinkingToggle';
1515
import { MessageTimestamp } from './components/MessageTimestamp';
16-
import { TTFTDisplay } from './components/TTFTDisplay';
1716
import { useAtomValue } from 'jotai';
1817
import { replayStateAtom } from '@/common/state/atoms/replay';
1918
import { ReportFileEntry } from './components/ReportFileEntry';
@@ -166,18 +165,6 @@ export const Message: React.FC<MessageProps> = ({
166165
/>
167166
) : (
168167
<>
169-
{/* TTFT timing display for assistant messages */}
170-
{message.role === 'assistant' &&
171-
(message.ttftMs !== undefined || message.elapsedMs !== undefined) && (
172-
<div className="mb-2">
173-
<TTFTDisplay
174-
ttftMs={message.ttftMs}
175-
ttltMs={message.ttltMs}
176-
elapsedMs={message.elapsedMs} // Backward compatibility
177-
/>
178-
</div>
179-
)}
180-
181168
{/* Enhanced thinking display with duration support */}
182169
{message.thinking && (
183170
<ThinkingToggle

0 commit comments

Comments
 (0)