2 import { Clock, Gauge, WholeWord, BookOpenText, Sparkles, Wrench, Layers } from '@lucide/svelte';
3 import { ChatMessageStatisticsBadge } from '$lib/components/app';
4 import * as Tooltip from '$lib/components/ui/tooltip';
5 import { ChatMessageStatsView } from '$lib/enums';
6 import type { ChatMessageAgenticTimings } from '$lib/types/chat';
7 import { formatPerformanceTime } from '$lib/utils';
8 import { MS_PER_SECOND, DEFAULT_PERFORMANCE_TIME } from '$lib/constants';
11 predictedTokens?: number;
13 promptTokens?: number;
16 isProcessingPrompt?: boolean;
17 initialView?: ChatMessageStatsView;
18 agenticTimings?: ChatMessageAgenticTimings;
19 onActiveViewChange?: (view: ChatMessageStatsView) => void;
20 hideSummary?: boolean;
29 isProcessingPrompt = false,
30 initialView = ChatMessageStatsView.GENERATION,
36 let activeView: ChatMessageStatsView = $derived(initialView);
37 let hasAutoSwitchedToGeneration = $state(false);
40 onActiveViewChange?.(activeView);
43 // In live mode: auto-switch to GENERATION tab when prompt processing completes
46 // Auto-switch to generation tab only when prompt processing is done (once)
48 !hasAutoSwitchedToGeneration &&
49 !isProcessingPrompt &&
53 activeView = ChatMessageStatsView.GENERATION;
54 hasAutoSwitchedToGeneration = true;
55 } else if (!hasAutoSwitchedToGeneration) {
56 // Stay on READING while prompt is still being processed
57 activeView = ChatMessageStatsView.READING;
62 let hasGenerationStats = $derived(
63 predictedTokens !== undefined &&
64 predictedTokens > 0 &&
65 predictedMs !== undefined &&
69 let tokensPerSecond = $derived(
70 hasGenerationStats ? (predictedTokens! / predictedMs!) * MS_PER_SECOND : 0
72 let formattedTime = $derived(
73 predictedMs !== undefined ? formatPerformanceTime(predictedMs) : DEFAULT_PERFORMANCE_TIME
76 let promptTokensPerSecond = $derived(
77 promptTokens !== undefined && promptMs !== undefined && promptMs > 0
78 ? (promptTokens / promptMs) * MS_PER_SECOND
82 let formattedPromptTime = $derived(
83 promptMs !== undefined ? formatPerformanceTime(promptMs) : undefined
86 let hasPromptStats = $derived(
87 promptTokens !== undefined &&
88 promptMs !== undefined &&
89 promptTokensPerSecond !== undefined &&
90 formattedPromptTime !== undefined
93 // In live mode, generation tab is disabled until we have generation stats
94 let isGenerationDisabled = $derived(isLive && !hasGenerationStats);
96 let hasAgenticStats = $derived(agenticTimings !== undefined && agenticTimings.toolCallsCount > 0);
98 let agenticToolsPerSecond = $derived(
99 hasAgenticStats && agenticTimings!.toolsMs > 0
100 ? (agenticTimings!.toolCallsCount / agenticTimings!.toolsMs) * MS_PER_SECOND
104 let formattedAgenticToolsTime = $derived(
105 hasAgenticStats ? formatPerformanceTime(agenticTimings!.toolsMs) : DEFAULT_PERFORMANCE_TIME
108 let agenticTotalTimeMs = $derived(
110 ? agenticTimings!.toolsMs + agenticTimings!.llm.predicted_ms + agenticTimings!.llm.prompt_ms
114 let formattedAgenticTotalTime = $derived(formatPerformanceTime(agenticTotalTimeMs));
117 <div class="inline-flex items-center text-xs text-muted-foreground">
118 <div class="inline-flex items-center rounded-sm bg-muted-foreground/15 p-0.5">
119 {#if hasPromptStats || isLive}
124 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
125 ChatMessageStatsView.READING
126 ? 'bg-background text-foreground shadow-sm'
127 : 'hover:text-foreground'}"
128 onclick={() => (activeView = ChatMessageStatsView.READING)}
130 <BookOpenText class="h-3 w-3" />
132 <span class="sr-only">Reading</span>
137 <p>Reading (prompt processing)</p>
145 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
146 ChatMessageStatsView.GENERATION
147 ? 'bg-background text-foreground shadow-sm'
148 : isGenerationDisabled
149 ? 'cursor-not-allowed opacity-40'
150 : 'hover:text-foreground'}"
151 onclick={() => !isGenerationDisabled && (activeView = ChatMessageStatsView.GENERATION)}
152 disabled={isGenerationDisabled}
154 <Sparkles class="h-3 w-3" />
156 <span class="sr-only">Generation</span>
162 {isGenerationDisabled
163 ? 'Generation (waiting for tokens...)'
164 : 'Generation (token output)'}
169 {#if hasAgenticStats}
174 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
175 ChatMessageStatsView.TOOLS
176 ? 'bg-background text-foreground shadow-sm'
177 : 'hover:text-foreground'}"
178 onclick={() => (activeView = ChatMessageStatsView.TOOLS)}
180 <Wrench class="h-3 w-3" />
182 <span class="sr-only">Tools</span>
196 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
197 ChatMessageStatsView.SUMMARY
198 ? 'bg-background text-foreground shadow-sm'
199 : 'hover:text-foreground'}"
200 onclick={() => (activeView = ChatMessageStatsView.SUMMARY)}
202 <Layers class="h-3 w-3" />
204 <span class="sr-only">Summary</span>
209 <p>Agentic summary</p>
216 <div class="flex items-center gap-1 px-2">
217 {#if activeView === ChatMessageStatsView.GENERATION && hasGenerationStats}
218 <ChatMessageStatisticsBadge
219 class="bg-transparent"
221 value="{predictedTokens?.toLocaleString()} tokens"
222 tooltipLabel="Generated tokens"
225 <ChatMessageStatisticsBadge
226 class="bg-transparent"
228 value={formattedTime}
229 tooltipLabel="Generation time"
232 <ChatMessageStatisticsBadge
233 class="bg-transparent"
235 value="{tokensPerSecond.toFixed(2)} t/s"
236 tooltipLabel="Generation speed"
238 {:else if activeView === ChatMessageStatsView.TOOLS && hasAgenticStats}
239 <ChatMessageStatisticsBadge
240 class="bg-transparent"
242 value="{agenticTimings!.toolCallsCount} calls"
243 tooltipLabel="Tool calls executed"
246 <ChatMessageStatisticsBadge
247 class="bg-transparent"
249 value={formattedAgenticToolsTime}
250 tooltipLabel="Tool execution time"
253 <ChatMessageStatisticsBadge
254 class="bg-transparent"
256 value="{agenticToolsPerSecond.toFixed(2)} calls/s"
257 tooltipLabel="Tool execution rate"
259 {:else if activeView === ChatMessageStatsView.SUMMARY && hasAgenticStats}
260 <ChatMessageStatisticsBadge
261 class="bg-transparent"
263 value="{agenticTimings!.turns} turns"
264 tooltipLabel="Agentic turns (LLM calls)"
267 <ChatMessageStatisticsBadge
268 class="bg-transparent"
270 value="{agenticTimings!.llm.predicted_n.toLocaleString()} tokens"
271 tooltipLabel="Total tokens generated"
274 <ChatMessageStatisticsBadge
275 class="bg-transparent"
277 value={formattedAgenticTotalTime}
278 tooltipLabel="Total time (LLM + tools)"
280 {:else if hasPromptStats}
281 <ChatMessageStatisticsBadge
282 class="bg-transparent"
284 value="{promptTokens} tokens"
285 tooltipLabel="Prompt tokens"
288 <ChatMessageStatisticsBadge
289 class="bg-transparent"
291 value={formattedPromptTime ?? '0s'}
292 tooltipLabel="Prompt processing time"
295 <ChatMessageStatisticsBadge
296 class="bg-transparent"
298 value="{promptTokensPerSecond!.toFixed(2)} tokens/s"
299 tooltipLabel="Prompt processing speed"