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';
9 import type { Component } from 'svelte';
12 predictedTokens?: number;
14 promptTokens?: number;
17 isProcessingPrompt?: boolean;
18 initialView?: ChatMessageStatsView;
19 agenticTimings?: ChatMessageAgenticTimings;
20 onActiveViewChange?: (view: ChatMessageStatsView) => void;
21 hideSummary?: boolean;
30 isProcessingPrompt = false,
31 initialView = ChatMessageStatsView.GENERATION,
37 let activeView: ChatMessageStatsView = $derived(initialView);
38 let hasAutoSwitchedToGeneration = $state(false);
41 onActiveViewChange?.(activeView);
44 // In live mode: auto-switch to GENERATION tab when prompt processing completes
47 // Auto-switch to generation tab only when prompt processing is done (once)
49 !hasAutoSwitchedToGeneration &&
50 !isProcessingPrompt &&
54 activeView = ChatMessageStatsView.GENERATION;
55 hasAutoSwitchedToGeneration = true;
56 } else if (!hasAutoSwitchedToGeneration) {
57 // Stay on READING while prompt is still being processed
58 activeView = ChatMessageStatsView.READING;
63 let hasGenerationStats = $derived(
64 predictedTokens !== undefined &&
65 predictedTokens > 0 &&
66 predictedMs !== undefined &&
70 let tokensPerSecond = $derived(
71 hasGenerationStats ? (predictedTokens! / predictedMs!) * MS_PER_SECOND : 0
73 let formattedTime = $derived(
74 predictedMs !== undefined ? formatPerformanceTime(predictedMs) : DEFAULT_PERFORMANCE_TIME
77 let promptTokensPerSecond = $derived(
78 promptTokens !== undefined && promptMs !== undefined && promptMs > 0
79 ? (promptTokens / promptMs) * MS_PER_SECOND
83 let formattedPromptTime = $derived(
84 promptMs !== undefined ? formatPerformanceTime(promptMs) : undefined
87 let hasPromptStats = $derived(
88 promptTokens !== undefined &&
89 promptMs !== undefined &&
90 promptTokensPerSecond !== undefined &&
91 formattedPromptTime !== undefined
94 // In live mode, generation tab is disabled until we have generation stats
95 let isGenerationDisabled = $derived(isLive && !hasGenerationStats);
97 let hasAgenticStats = $derived(agenticTimings !== undefined && agenticTimings.toolCallsCount > 0);
99 let agenticToolsPerSecond = $derived(
100 hasAgenticStats && agenticTimings!.toolsMs > 0
101 ? (agenticTimings!.toolCallsCount / agenticTimings!.toolsMs) * MS_PER_SECOND
105 let formattedAgenticToolsTime = $derived(
106 hasAgenticStats ? formatPerformanceTime(agenticTimings!.toolsMs) : DEFAULT_PERFORMANCE_TIME
109 let agenticTotalTimeMs = $derived(
111 ? agenticTimings!.toolsMs + agenticTimings!.llm.predicted_ms + agenticTimings!.llm.prompt_ms
115 let formattedAgenticTotalTime = $derived(formatPerformanceTime(agenticTotalTimeMs));
118 {#snippet viewButton(opts: {
119 view: ChatMessageStatsView;
125 {@const IconComponent = opts.icon}
128 <!-- prevent another nested button element -->
129 {#snippet child({ props })}
133 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
135 ? 'bg-background text-foreground shadow-sm'
137 ? 'cursor-not-allowed opacity-40'
138 : 'hover:text-foreground'}"
139 onclick={() => !opts.disabled && (activeView = opts.view)}
140 disabled={opts.disabled}
142 <IconComponent class="h-3 w-3" />
144 <span class="sr-only">{opts.label}</span>
150 <p>{opts.tooltipText}</p>
155 <div class="inline-flex items-center text-xs text-muted-foreground">
156 <div class="inline-flex items-center rounded-sm bg-muted-foreground/15 p-0.5">
157 {#if hasPromptStats || isLive}
158 {@render viewButton({
159 view: ChatMessageStatsView.READING,
162 tooltipText: 'Reading (prompt processing)'
166 {@render viewButton({
167 view: ChatMessageStatsView.GENERATION,
170 tooltipText: isGenerationDisabled
171 ? 'Generation (waiting for tokens...)'
172 : 'Generation (token output)',
173 disabled: isGenerationDisabled
176 {#if hasAgenticStats}
177 {@render viewButton({
178 view: ChatMessageStatsView.TOOLS,
181 tooltipText: 'Tool calls'
185 {@render viewButton({
186 view: ChatMessageStatsView.SUMMARY,
189 tooltipText: 'Agentic summary'
195 <div class="flex items-center gap-1 px-2">
196 {#if activeView === ChatMessageStatsView.GENERATION && hasGenerationStats}
197 <ChatMessageStatisticsBadge
198 class="bg-transparent"
200 value="{predictedTokens?.toLocaleString()} tokens"
201 tooltipLabel="Generated tokens"
204 <ChatMessageStatisticsBadge
205 class="bg-transparent"
207 value={formattedTime}
208 tooltipLabel="Generation time"
211 <ChatMessageStatisticsBadge
212 class="bg-transparent"
214 value="{tokensPerSecond.toFixed(2)} t/s"
215 tooltipLabel="Generation speed"
217 {:else if activeView === ChatMessageStatsView.TOOLS && hasAgenticStats}
218 <ChatMessageStatisticsBadge
219 class="bg-transparent"
221 value="{agenticTimings!.toolCallsCount} calls"
222 tooltipLabel="Tool calls executed"
225 <ChatMessageStatisticsBadge
226 class="bg-transparent"
228 value={formattedAgenticToolsTime}
229 tooltipLabel="Tool execution time"
232 <ChatMessageStatisticsBadge
233 class="bg-transparent"
235 value="{agenticToolsPerSecond.toFixed(2)} calls/s"
236 tooltipLabel="Tool execution rate"
238 {:else if activeView === ChatMessageStatsView.SUMMARY && hasAgenticStats}
239 <ChatMessageStatisticsBadge
240 class="bg-transparent"
242 value="{agenticTimings!.turns} turns"
243 tooltipLabel="Agentic turns (LLM calls)"
246 <ChatMessageStatisticsBadge
247 class="bg-transparent"
249 value="{agenticTimings!.llm.predicted_n.toLocaleString()} tokens"
250 tooltipLabel="Total tokens generated"
253 <ChatMessageStatisticsBadge
254 class="bg-transparent"
256 value={formattedAgenticTotalTime}
257 tooltipLabel="Total time (LLM + tools)"
259 {:else if hasPromptStats}
260 <ChatMessageStatisticsBadge
261 class="bg-transparent"
263 value="{promptTokens} tokens"
264 tooltipLabel="Prompt tokens"
267 <ChatMessageStatisticsBadge
268 class="bg-transparent"
270 value={formattedPromptTime ?? '0s'}
271 tooltipLabel="Prompt processing time"
274 <ChatMessageStatisticsBadge
275 class="bg-transparent"
277 value="{promptTokensPerSecond!.toFixed(2)} tokens/s"
278 tooltipLabel="Prompt processing speed"