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