enzostvs HF Staff commited on
Commit
843cdb6
·
1 Parent(s): 09fc27e

display latency, tokens and prvider

Browse files
src/lib/components/chat/Assistant.svelte CHANGED
@@ -12,7 +12,7 @@
12
  import { MessageCirclePlus, Star } from '@lucide/svelte';
13
  import { mode } from 'mode-watcher';
14
 
15
- import type { ChatModel, ChatMessage } from '$lib/helpers/types';
16
  import { Button } from '$lib/components/ui/button';
17
  import Message from './Message.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
@@ -27,6 +27,7 @@
27
  let selectedModel = $derived((nodeData.current?.data.selectedModel as ChatModel) ?? null);
28
  let messages = $derived((nodeData.current?.data.messages as ChatMessage[]) ?? []);
29
  let loading = $derived((nodeData.current?.data.loading as boolean) ?? false);
 
30
  let message = $derived(
31
  nodeData.current?.data.content
32
  ? ({
@@ -134,9 +135,34 @@
134
  {/if}
135
  {#if message}
136
  <div bind:this={containerRef}>
137
- <Message {message} provider={selectedModel.provider} />
138
  </div>
139
  {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  </div>
141
  <Button
142
  variant={mode.current === 'dark' ? 'default' : 'outline'}
 
12
  import { MessageCirclePlus, Star } from '@lucide/svelte';
13
  import { mode } from 'mode-watcher';
14
 
15
+ import type { ChatModel, ChatMessage, TokenUsage } from '$lib/helpers/types';
16
  import { Button } from '$lib/components/ui/button';
17
  import Message from './Message.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
 
27
  let selectedModel = $derived((nodeData.current?.data.selectedModel as ChatModel) ?? null);
28
  let messages = $derived((nodeData.current?.data.messages as ChatMessage[]) ?? []);
29
  let loading = $derived((nodeData.current?.data.loading as boolean) ?? false);
30
+ let usage = $derived((nodeData.current?.data.usage as TokenUsage) ?? null);
31
  let message = $derived(
32
  nodeData.current?.data.content
33
  ? ({
 
135
  {/if}
136
  {#if message}
137
  <div bind:this={containerRef}>
138
+ <Message {message} />
139
  </div>
140
  {/if}
141
+ {#if usage && !loading && message}
142
+ {@const provider = selectedModel.provider}
143
+ <p class="mt-3 border-t border-border pt-3 text-xs text-muted-foreground">
144
+ {usage.total_tokens} tokens
145
+ {#if message.timestamp}
146
+ <span class="mx-0.5">&middot;</span> Latency {message.timestamp}ms
147
+ {/if}
148
+ <span class="inline-flex items-center gap-0.5">
149
+ <span>&middot;</span> Using
150
+ <span class="inline-flex items-center gap-0.5 rounded-full bg-muted px-1 py-0.5">
151
+ {#if provider === 'auto'}
152
+ <Star class="size-3.5 fill-yellow-500 text-yellow-500" />
153
+ {:else}
154
+ <img
155
+ src={`https://huggingface.co/api/avatars/${provider}`}
156
+ alt={provider}
157
+ class="size-4 rounded"
158
+ />
159
+ {/if}
160
+ {provider}
161
+ </span>
162
+ provider
163
+ </span>
164
+ </p>
165
+ {/if}
166
  </div>
167
  <Button
168
  variant={mode.current === 'dark' ? 'default' : 'outline'}
src/lib/components/chat/Message.svelte CHANGED
@@ -11,9 +11,8 @@
11
  import ListItem from './markdown/ListItem.svelte';
12
  import Link from './markdown/Link.svelte';
13
  import Hr from './markdown/Hr.svelte';
14
- import { Star } from '@lucide/svelte';
15
 
16
- let { message, provider }: { message: ChatMessage; provider?: string } = $props();
17
 
18
  const renderers = {
19
  paragraph: Paragraph,
@@ -37,24 +36,5 @@
37
  </p>
38
  {:else}
39
  <SvelteMarkdown source={message.content} renderers={renderers as any} />
40
- {#if message.timestamp}
41
- <p class="flex items-center gap-1 text-xs text-muted-foreground/70 select-none">
42
- Generated in
43
- {message.timestamp / 1000}s using
44
- <span class="flex items-center gap-1 rounded bg-muted py-0.5 pr-1 pl-0.5">
45
- {#if provider === 'auto'}
46
- <Star class="size-4 fill-yellow-500 text-yellow-500" />
47
- {:else}
48
- <img
49
- src={`https://huggingface.co/api/avatars/${provider}`}
50
- alt={provider}
51
- class="size-4 rounded"
52
- />
53
- {/if}
54
- {provider}
55
- </span>
56
- provider
57
- </p>
58
- {/if}
59
  {/if}
60
  </main>
 
11
  import ListItem from './markdown/ListItem.svelte';
12
  import Link from './markdown/Link.svelte';
13
  import Hr from './markdown/Hr.svelte';
 
14
 
15
+ let { message }: { message: ChatMessage } = $props();
16
 
17
  const renderers = {
18
  paragraph: Paragraph,
 
36
  </p>
37
  {:else}
38
  <SvelteMarkdown source={message.content} renderers={renderers as any} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  {/if}
40
  </main>
src/lib/components/chat/User.svelte CHANGED
@@ -12,7 +12,7 @@
12
  useSvelteFlow
13
  } from '@xyflow/svelte';
14
 
15
- import type { ChatModel, ChatMessage } from '$lib/helpers/types';
16
  import { Button } from '$lib/components/ui/button';
17
  import ComboBoxModels from '$lib/components/model/ComboBoxModels.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
@@ -143,6 +143,7 @@
143
  if (!response.body) throw new Error('No response body');
144
 
145
  let content = '';
 
146
 
147
  const reader = response.body.getReader();
148
  const decoder = new TextDecoder();
@@ -154,6 +155,16 @@
154
  const errorMessage = content.split('__ERROR__').pop() ?? 'Unknown error';
155
  throw new Error(errorMessage);
156
  }
 
 
 
 
 
 
 
 
 
 
157
  const newNodeId = `user-${crypto.randomUUID()}`;
158
  const newNode: Node = {
159
  id: newNodeId,
@@ -178,7 +189,14 @@
178
  const end = Date.now();
179
  updateNodeData(
180
  node.id,
181
- { ...node.data, content, timestamp: end - start, loading: false, messages },
 
 
 
 
 
 
 
182
  { replace: true }
183
  );
184
  break;
 
12
  useSvelteFlow
13
  } from '@xyflow/svelte';
14
 
15
+ import type { ChatModel, ChatMessage, TokenUsage } from '$lib/helpers/types';
16
  import { Button } from '$lib/components/ui/button';
17
  import ComboBoxModels from '$lib/components/model/ComboBoxModels.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
 
143
  if (!response.body) throw new Error('No response body');
144
 
145
  let content = '';
146
+ let usage: TokenUsage | null = null;
147
 
148
  const reader = response.body.getReader();
149
  const decoder = new TextDecoder();
 
155
  const errorMessage = content.split('__ERROR__').pop() ?? 'Unknown error';
156
  throw new Error(errorMessage);
157
  }
158
+ if (content.includes('__USAGE__')) {
159
+ const usageParts = content.split('__USAGE__');
160
+ const usageJson = usageParts.pop() ?? '';
161
+ content = usageParts.join('').trimEnd();
162
+ try {
163
+ usage = JSON.parse(usageJson) as TokenUsage;
164
+ } catch {
165
+ // ignore malformed usage JSON
166
+ }
167
+ }
168
  const newNodeId = `user-${crypto.randomUUID()}`;
169
  const newNode: Node = {
170
  id: newNodeId,
 
189
  const end = Date.now();
190
  updateNodeData(
191
  node.id,
192
+ {
193
+ ...node.data,
194
+ content,
195
+ timestamp: end - start,
196
+ loading: false,
197
+ messages,
198
+ usage
199
+ },
200
  { replace: true }
201
  );
202
  break;
src/lib/helpers/types.ts CHANGED
@@ -19,6 +19,13 @@ export interface ChatMessage {
19
  isHidden?: boolean;
20
  }
21
 
 
 
 
 
 
 
 
22
  export interface HFUser {
23
  id: string;
24
  name: string;
 
19
  isHidden?: boolean;
20
  }
21
 
22
+ export interface TokenUsage {
23
+ prompt_tokens: number;
24
+ completion_tokens: number;
25
+ total_tokens: number;
26
+ reasoning_tokens: number;
27
+ }
28
+
29
  export interface HFUser {
30
  id: string;
31
  name: string;
src/routes/api/+server.ts CHANGED
@@ -33,6 +33,20 @@ export async function POST({ request }: RequestEvent) {
33
  try {
34
  for await (const chunk of stream) {
35
  const content = chunk.choices?.[0]?.delta?.content ?? '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  if (content) {
37
  controller.enqueue(encoder.encode(content));
38
  }
 
33
  try {
34
  for await (const chunk of stream) {
35
  const content = chunk.choices?.[0]?.delta?.content ?? '';
36
+ const usage = chunk.usage;
37
+ if (usage) {
38
+ const usageData = {
39
+ prompt_tokens: usage.prompt_tokens ?? 0,
40
+ completion_tokens: usage.completion_tokens ?? 0,
41
+ total_tokens: usage.total_tokens ?? 0,
42
+ reasoning_tokens:
43
+ (usage.completion_tokens_details as Record<string, number>)
44
+ ?.reasoning_tokens ?? 0
45
+ };
46
+ controller.enqueue(
47
+ encoder.encode(`\n\n__USAGE__${JSON.stringify(usageData)}`)
48
+ );
49
+ }
50
  if (content) {
51
  controller.enqueue(encoder.encode(content));
52
  }