]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
6906adbb1734e0a8c057e9fbb2a66094cec679ff
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
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';
10
11 interface Props {
12 predictedTokens?: number;
13 predictedMs?: number;
14 promptTokens?: number;
15 promptMs?: number;
16 isLive?: boolean;
17 isProcessingPrompt?: boolean;
18 initialView?: ChatMessageStatsView;
19 agenticTimings?: ChatMessageAgenticTimings;
20 onActiveViewChange?: (view: ChatMessageStatsView) => void;
21 hideSummary?: boolean;
22 }
23
24 let {
25 predictedTokens,
26 predictedMs,
27 promptTokens,
28 promptMs,
29 isLive = false,
30 isProcessingPrompt = false,
31 initialView = ChatMessageStatsView.GENERATION,
32 agenticTimings,
33 onActiveViewChange,
34 hideSummary = false
35 }: Props = $props();
36
37 let activeView: ChatMessageStatsView = $derived(initialView);
38 let hasAutoSwitchedToGeneration = $state(false);
39
40 $effect(() => {
41 onActiveViewChange?.(activeView);
42 });
43
44 // In live mode: auto-switch to GENERATION tab when prompt processing completes
45 $effect(() => {
46 if (isLive) {
47 // Auto-switch to generation tab only when prompt processing is done (once)
48 if (
49 !hasAutoSwitchedToGeneration &&
50 !isProcessingPrompt &&
51 predictedTokens &&
52 predictedTokens > 0
53 ) {
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;
59 }
60 }
61 });
62
63 let hasGenerationStats = $derived(
64 predictedTokens !== undefined &&
65 predictedTokens > 0 &&
66 predictedMs !== undefined &&
67 predictedMs > 0
68 );
69
70 let tokensPerSecond = $derived(
71 hasGenerationStats ? (predictedTokens! / predictedMs!) * MS_PER_SECOND : 0
72 );
73 let formattedTime = $derived(
74 predictedMs !== undefined ? formatPerformanceTime(predictedMs) : DEFAULT_PERFORMANCE_TIME
75 );
76
77 let promptTokensPerSecond = $derived(
78 promptTokens !== undefined && promptMs !== undefined && promptMs > 0
79 ? (promptTokens / promptMs) * MS_PER_SECOND
80 : undefined
81 );
82
83 let formattedPromptTime = $derived(
84 promptMs !== undefined ? formatPerformanceTime(promptMs) : undefined
85 );
86
87 let hasPromptStats = $derived(
88 promptTokens !== undefined &&
89 promptMs !== undefined &&
90 promptTokensPerSecond !== undefined &&
91 formattedPromptTime !== undefined
92 );
93
94 // In live mode, generation tab is disabled until we have generation stats
95 let isGenerationDisabled = $derived(isLive && !hasGenerationStats);
96
97 let hasAgenticStats = $derived(agenticTimings !== undefined && agenticTimings.toolCallsCount > 0);
98
99 let agenticToolsPerSecond = $derived(
100 hasAgenticStats && agenticTimings!.toolsMs > 0
101 ? (agenticTimings!.toolCallsCount / agenticTimings!.toolsMs) * MS_PER_SECOND
102 : 0
103 );
104
105 let formattedAgenticToolsTime = $derived(
106 hasAgenticStats ? formatPerformanceTime(agenticTimings!.toolsMs) : DEFAULT_PERFORMANCE_TIME
107 );
108
109 let agenticTotalTimeMs = $derived(
110 hasAgenticStats
111 ? agenticTimings!.toolsMs + agenticTimings!.llm.predicted_ms + agenticTimings!.llm.prompt_ms
112 : 0
113 );
114
115 let formattedAgenticTotalTime = $derived(formatPerformanceTime(agenticTotalTimeMs));
116 </script>
117
118 {#snippet viewButton(opts: {
119 view: ChatMessageStatsView;
120 icon: Component;
121 label: string;
122 tooltipText: string;
123 disabled?: boolean;
124 })}
125 {@const IconComponent = opts.icon}
126 <Tooltip.Root>
127 <Tooltip.Trigger>
128 <!-- prevent another nested button element -->
129 {#snippet child({ props })}
130 <button
131 {...props}
132 type="button"
133 class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
134 opts.view
135 ? 'bg-background text-foreground shadow-sm'
136 : opts.disabled
137 ? 'cursor-not-allowed opacity-40'
138 : 'hover:text-foreground'}"
139 onclick={() => !opts.disabled && (activeView = opts.view)}
140 disabled={opts.disabled}
141 >
142 <IconComponent class="h-3 w-3" />
143
144 <span class="sr-only">{opts.label}</span>
145 </button>
146 {/snippet}
147 </Tooltip.Trigger>
148
149 <Tooltip.Content>
150 <p>{opts.tooltipText}</p>
151 </Tooltip.Content>
152 </Tooltip.Root>
153 {/snippet}
154
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,
160 icon: BookOpenText,
161 label: 'Reading',
162 tooltipText: 'Reading (prompt processing)'
163 })}
164 {/if}
165
166 {@render viewButton({
167 view: ChatMessageStatsView.GENERATION,
168 icon: Sparkles,
169 label: 'Generation',
170 tooltipText: isGenerationDisabled
171 ? 'Generation (waiting for tokens...)'
172 : 'Generation (token output)',
173 disabled: isGenerationDisabled
174 })}
175
176 {#if hasAgenticStats}
177 {@render viewButton({
178 view: ChatMessageStatsView.TOOLS,
179 icon: Wrench,
180 label: 'Tools',
181 tooltipText: 'Tool calls'
182 })}
183
184 {#if !hideSummary}
185 {@render viewButton({
186 view: ChatMessageStatsView.SUMMARY,
187 icon: Layers,
188 label: 'Summary',
189 tooltipText: 'Agentic summary'
190 })}
191 {/if}
192 {/if}
193 </div>
194
195 <div class="flex items-center gap-1 px-2">
196 {#if activeView === ChatMessageStatsView.GENERATION && hasGenerationStats}
197 <ChatMessageStatisticsBadge
198 class="bg-transparent"
199 icon={WholeWord}
200 value="{predictedTokens?.toLocaleString()} tokens"
201 tooltipLabel="Generated tokens"
202 />
203
204 <ChatMessageStatisticsBadge
205 class="bg-transparent"
206 icon={Clock}
207 value={formattedTime}
208 tooltipLabel="Generation time"
209 />
210
211 <ChatMessageStatisticsBadge
212 class="bg-transparent"
213 icon={Gauge}
214 value="{tokensPerSecond.toFixed(2)} t/s"
215 tooltipLabel="Generation speed"
216 />
217 {:else if activeView === ChatMessageStatsView.TOOLS && hasAgenticStats}
218 <ChatMessageStatisticsBadge
219 class="bg-transparent"
220 icon={Wrench}
221 value="{agenticTimings!.toolCallsCount} calls"
222 tooltipLabel="Tool calls executed"
223 />
224
225 <ChatMessageStatisticsBadge
226 class="bg-transparent"
227 icon={Clock}
228 value={formattedAgenticToolsTime}
229 tooltipLabel="Tool execution time"
230 />
231
232 <ChatMessageStatisticsBadge
233 class="bg-transparent"
234 icon={Gauge}
235 value="{agenticToolsPerSecond.toFixed(2)} calls/s"
236 tooltipLabel="Tool execution rate"
237 />
238 {:else if activeView === ChatMessageStatsView.SUMMARY && hasAgenticStats}
239 <ChatMessageStatisticsBadge
240 class="bg-transparent"
241 icon={Layers}
242 value="{agenticTimings!.turns} turns"
243 tooltipLabel="Agentic turns (LLM calls)"
244 />
245
246 <ChatMessageStatisticsBadge
247 class="bg-transparent"
248 icon={WholeWord}
249 value="{agenticTimings!.llm.predicted_n.toLocaleString()} tokens"
250 tooltipLabel="Total tokens generated"
251 />
252
253 <ChatMessageStatisticsBadge
254 class="bg-transparent"
255 icon={Clock}
256 value={formattedAgenticTotalTime}
257 tooltipLabel="Total time (LLM + tools)"
258 />
259 {:else if hasPromptStats}
260 <ChatMessageStatisticsBadge
261 class="bg-transparent"
262 icon={WholeWord}
263 value="{promptTokens} tokens"
264 tooltipLabel="Prompt tokens"
265 />
266
267 <ChatMessageStatisticsBadge
268 class="bg-transparent"
269 icon={Clock}
270 value={formattedPromptTime ?? '0s'}
271 tooltipLabel="Prompt processing time"
272 />
273
274 <ChatMessageStatisticsBadge
275 class="bg-transparent"
276 icon={Gauge}
277 value="{promptTokensPerSecond!.toFixed(2)} tokens/s"
278 tooltipLabel="Prompt processing speed"
279 />
280 {/if}
281 </div>
282 </div>