enzostvs HF Staff commited on
Commit
955bb3b
·
1 Parent(s): 46cc0db

use v1 router models hf + fixes

Browse files
src/lib/components/chat/Assistant.svelte CHANGED
@@ -16,6 +16,7 @@
16
  import { Button } from '$lib/components/ui/button';
17
  import Message from './Message.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
 
19
 
20
  let { id }: NodeProps = $props();
21
 
@@ -121,11 +122,11 @@
121
  class="group relative inline-flex h-8 shrink-0 items-center justify-center gap-1.5 rounded-md border bg-background px-3 text-sm font-normal! text-gray-600 has-[>svg]:px-2.5 dark:border-input dark:bg-input/30 dark:text-gray-400"
122
  >
123
  <img
124
- src={selectedModel.avatarUrl}
125
- alt={selectedModel.modelName}
126
  class="size-3.5 rounded-full"
127
  />
128
- {selectedModel.modelName}
129
  </div>
130
  </div>
131
  </header>
@@ -143,29 +144,43 @@
143
  {/if}
144
  </div>
145
  {#if usage && !loading && message}
146
- {@const provider = selectedModel.provider}
147
- <p class="mt-3 border-t border-border pt-3 text-xs text-muted-foreground">
148
- {usage.total_tokens} tokens
149
- {#if message.timestamp}
150
- <span class="mx-0.5">&middot;</span> Latency {message.timestamp}ms
151
- {/if}
152
- <span class="inline-flex items-center gap-0.5">
153
- <span>&middot;</span> Using
154
- <span class="inline-flex items-center gap-1 rounded-full bg-muted py-0.5 pr-2 pl-1">
155
- {#if provider === 'auto'}
156
- <Star class="size-3.5 fill-yellow-500 text-yellow-500" />
157
- {:else}
158
- <img
159
- src={`https://huggingface.co/api/avatars/${provider}`}
160
- alt={provider}
161
- class="size-4 rounded-full"
162
- />
163
- {/if}
164
- {provider}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  </span>
166
- provider
167
- </span>
168
- </p>
169
  {/if}
170
  </div>
171
  <Button
 
16
  import { Button } from '$lib/components/ui/button';
17
  import Message from './Message.svelte';
18
  import Spinner from '$lib/components/loading/Spinner.svelte';
19
+ import { formatUsageCost } from '$lib';
20
 
21
  let { id }: NodeProps = $props();
22
 
 
122
  class="group relative inline-flex h-8 shrink-0 items-center justify-center gap-1.5 rounded-md border bg-background px-3 text-sm font-normal! text-gray-600 has-[>svg]:px-2.5 dark:border-input dark:bg-input/30 dark:text-gray-400"
123
  >
124
  <img
125
+ src={`https://huggingface.co/api/avatars/${selectedModel.owned_by}`}
126
+ alt={selectedModel.id}
127
  class="size-3.5 rounded-full"
128
  />
129
+ {selectedModel.id.split('/').pop() ?? selectedModel.id}
130
  </div>
131
  </div>
132
  </header>
 
144
  {/if}
145
  </div>
146
  {#if usage && !loading && message}
147
+ {@const provider = selectedModel.provider ?? 'auto'}
148
+ <div class="mt-3 flex items-center justify-between gap-2 border-t border-border pt-3">
149
+ <p class="text-xs text-muted-foreground">
150
+ {usage.total_tokens} tokens
151
+ {#if formatUsageCost(
152
+ selectedModel.providers.find((p) => p.provider === provider)?.pricing,
153
+ usage
154
+ )}
155
+ <span class="mx-0.5">&middot;</span>
156
+ {formatUsageCost(
157
+ selectedModel.providers.find((p) => p.provider === provider)?.pricing,
158
+ usage
159
+ )}
160
+ {/if}
161
+ {#if message.timestamp}
162
+ <span class="mx-0.5">&middot;</span> Latency {message.timestamp}ms
163
+ {/if}
164
+ </p>
165
+ <p class="text-xs text-muted-foreground">
166
+ <span class="inline-flex items-center gap-0.5">
167
+ Using
168
+ <span class="inline-flex items-center gap-1 rounded-full bg-muted py-0.5 pr-2 pl-1">
169
+ {#if provider === 'auto'}
170
+ <Star class="size-3.5 fill-yellow-500 text-yellow-500" />
171
+ {:else}
172
+ <img
173
+ src={`https://huggingface.co/api/avatars/${provider}`}
174
+ alt={provider}
175
+ class="size-4 rounded-full"
176
+ />
177
+ {/if}
178
+ {provider}
179
+ </span>
180
+ provider
181
  </span>
182
+ </p>
183
+ </div>
 
184
  {/if}
185
  </div>
186
  <Button
src/lib/components/chat/User.svelte CHANGED
@@ -319,6 +319,7 @@
319
  id,
320
  {
321
  ...nodeData.current,
 
322
  selectedModels: selectedModels.map((m) => (m.id === model.id ? model : m))
323
  },
324
  { replace: true }
@@ -331,8 +332,12 @@
331
  size="sm"
332
  class="group relative font-normal! shadow-none!"
333
  >
334
- <img src={model.avatarUrl} alt={model.modelName} class="size-3.5 rounded-full" />
335
- {model.modelName}
 
 
 
 
336
  {#if !lastMessage || model.isError || selectedModels.length > MAX_MODELS_PER_NODE}
337
  <Button
338
  variant="default"
 
319
  id,
320
  {
321
  ...nodeData.current,
322
+ isFirstNode: nodeData.current?.data.isFirstNode ?? false,
323
  selectedModels: selectedModels.map((m) => (m.id === model.id ? model : m))
324
  },
325
  { replace: true }
 
332
  size="sm"
333
  class="group relative font-normal! shadow-none!"
334
  >
335
+ <img
336
+ src={`https://huggingface.co/api/avatars/${model.owned_by}`}
337
+ alt={model.id}
338
+ class="size-3.5 rounded-full"
339
+ />
340
+ {model.id.split('/').pop() ?? model.id}
341
  {#if !lastMessage || model.isError || selectedModels.length > MAX_MODELS_PER_NODE}
342
  <Button
343
  variant="default"
src/lib/components/model/ComboBoxModels.svelte CHANGED
@@ -18,7 +18,7 @@
18
 
19
  const filteredModels = $derived(
20
  modelsState.models.filter((m) => {
21
- const matchesSearch = !search || m.modelId.toLowerCase().includes(search.toLowerCase());
22
  const notExcluded = !excludeIds.includes(m.id);
23
  return matchesSearch && notExcluded;
24
  })
@@ -68,8 +68,12 @@
68
  open = false;
69
  }}
70
  >
71
- <img src={model.avatarUrl} alt="" class="size-4 rounded-full" />
72
- <span>{model.modelName}</span>
 
 
 
 
73
  </Command.Item>
74
  {/each}
75
  <Command.Separator />
@@ -82,9 +86,12 @@
82
  open = false;
83
  }}
84
  >
85
- <span>
86
- <span class="font-medium">{model.author}</span>/{model.modelName}
87
- </span>
 
 
 
88
  </Command.Item>
89
  {/each}
90
  </Command.Group>
 
18
 
19
  const filteredModels = $derived(
20
  modelsState.models.filter((m) => {
21
+ const matchesSearch = !search || m.id.toLowerCase().includes(search.toLowerCase());
22
  const notExcluded = !excludeIds.includes(m.id);
23
  return matchesSearch && notExcluded;
24
  })
 
68
  open = false;
69
  }}
70
  >
71
+ <img
72
+ src={`https://huggingface.co/api/avatars/${model.owned_by}`}
73
+ alt=""
74
+ class="size-4 rounded-full"
75
+ />
76
+ <span>{model.id.split('/').pop() ?? model.id}</span>
77
  </Command.Item>
78
  {/each}
79
  <Command.Separator />
 
86
  open = false;
87
  }}
88
  >
89
+ <img
90
+ src={`https://huggingface.co/api/avatars/${model.owned_by}`}
91
+ alt=""
92
+ class="size-4 rounded-full"
93
+ />
94
+ <span>{model.id.split('/').pop() ?? model.id}</span>
95
  </Command.Item>
96
  {/each}
97
  </Command.Group>
src/lib/components/model/SettingsModel.svelte CHANGED
@@ -4,11 +4,11 @@
4
 
5
  import * as Popover from '$lib/components/ui/popover/index.js';
6
  import type { ChatModel } from '$lib/helpers/types';
 
7
  import { Button } from '$lib/components/ui/button';
8
  import { Slider } from '$lib/components/ui/slider';
9
  import Input from '$lib/components/ui/input/input.svelte';
10
  import * as Select from '$lib/components/ui/select/index.js';
11
- import { modeStorageKey } from 'mode-watcher';
12
 
13
  let {
14
  model,
@@ -29,20 +29,12 @@
29
  let open = $state<boolean>(false);
30
  // svelte-ignore state_referenced_locally
31
  let provider = $state<string>(model.provider ?? 'auto');
32
- let availableProviders = $state<string[]>([]);
33
 
34
- async function getProviders() {
35
- const response = await fetch(`https://router.huggingface.co/v1/models/${model.id}`);
36
- const { data } = await response.json();
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- return data.providers.map((provider: any) => provider.provider);
39
- }
40
-
41
- $effect(() => {
42
- getProviders().then((providers) => {
43
- availableProviders = providers;
44
- });
45
- });
46
  </script>
47
 
48
  <Popover.Root
@@ -96,14 +88,16 @@
96
  <h4 class="text-sm leading-none font-medium">Max Tokens</h4>
97
  <div class="flex items-center gap-1">
98
  {#if max_tokens === undefined}
99
- <Button variant="outline-blue" size="2xs" onclick={() => (max_tokens = 32000)}
100
- >Set</Button
 
 
101
  >
102
  {:else}
103
  <Input
104
  type="number"
105
  min={0}
106
- max={204800}
107
  step={256}
108
  placeholder="—"
109
  class="h-7! w-24!"
@@ -120,7 +114,7 @@
120
  type="single"
121
  bind:value={max_tokens}
122
  min={0}
123
- max={204800}
124
  step={256}
125
  class="mt-2"
126
  />
@@ -176,16 +170,21 @@
176
  <Select.Item value="auto"
177
  ><Star class="size-4 fill-yellow-500 text-yellow-500" /> Auto</Select.Item
178
  >
179
- {#each availableProviders as provider}
180
- <Select.Item value={provider}>
181
  <div class="flex items-center gap-2">
182
  <img
183
- src={`https://huggingface.co/api/avatars/${provider}`}
184
- alt={provider}
185
  class="size-4"
186
  />
187
- {provider}
188
  </div>
 
 
 
 
 
189
  </Select.Item>
190
  {/each}
191
  </Select.Content>
 
4
 
5
  import * as Popover from '$lib/components/ui/popover/index.js';
6
  import type { ChatModel } from '$lib/helpers/types';
7
+ import { formatPricingPerToken } from '$lib/index.js';
8
  import { Button } from '$lib/components/ui/button';
9
  import { Slider } from '$lib/components/ui/slider';
10
  import Input from '$lib/components/ui/input/input.svelte';
11
  import * as Select from '$lib/components/ui/select/index.js';
 
12
 
13
  let {
14
  model,
 
29
  let open = $state<boolean>(false);
30
  // svelte-ignore state_referenced_locally
31
  let provider = $state<string>(model.provider ?? 'auto');
 
32
 
33
+ let maxContentLength = $derived(
34
+ provider === 'auto'
35
+ ? model.providers[0]?.context_length
36
+ : model.providers.find((p) => p.provider === provider)?.context_length
37
+ );
 
 
 
 
 
 
 
38
  </script>
39
 
40
  <Popover.Root
 
88
  <h4 class="text-sm leading-none font-medium">Max Tokens</h4>
89
  <div class="flex items-center gap-1">
90
  {#if max_tokens === undefined}
91
+ <Button
92
+ variant="outline-blue"
93
+ size="2xs"
94
+ onclick={() => (max_tokens = (maxContentLength ?? 32_000) / 2)}>Set</Button
95
  >
96
  {:else}
97
  <Input
98
  type="number"
99
  min={0}
100
+ max={maxContentLength}
101
  step={256}
102
  placeholder="—"
103
  class="h-7! w-24!"
 
114
  type="single"
115
  bind:value={max_tokens}
116
  min={0}
117
+ max={maxContentLength}
118
  step={256}
119
  class="mt-2"
120
  />
 
170
  <Select.Item value="auto"
171
  ><Star class="size-4 fill-yellow-500 text-yellow-500" /> Auto</Select.Item
172
  >
173
+ {#each model.providers as provider}
174
+ <Select.Item value={provider.provider}>
175
  <div class="flex items-center gap-2">
176
  <img
177
+ src={`https://huggingface.co/api/avatars/${provider.provider}`}
178
+ alt={provider.provider}
179
  class="size-4"
180
  />
181
+ {provider.provider}
182
  </div>
183
+ {#if formatPricingPerToken(provider.pricing)}
184
+ <span class="text-xs text-muted-foreground"
185
+ >{formatPricingPerToken(provider.pricing)}</span
186
+ >
187
+ {/if}
188
  </Select.Item>
189
  {/each}
190
  </Select.Content>
src/lib/helpers/types.ts CHANGED
@@ -1,17 +1,34 @@
1
  export interface ChatModel {
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  id: string;
3
- modelId: string;
4
- modelName: string;
5
- author: string;
6
- avatarUrl: string;
7
- pipeline_tag?: 'text-generation' | 'image-text-to-text';
8
- likes?: number;
9
- downloads?: number;
10
  temperature?: number;
11
  max_tokens?: number;
12
  top_p?: number;
 
 
 
13
  provider: string;
14
- isError?: boolean;
 
 
 
 
15
  }
16
 
17
  export interface ChatMessage {
 
1
  export interface ChatModel {
2
+ // id: string;
3
+ // modelId: string;
4
+ // modelName: string;
5
+ // author: string;
6
+ // avatarUrl: string;
7
+ // pipeline_tag?: 'text-generation' | 'image-text-to-text';
8
+ // likes?: number;
9
+ // downloads?: number;
10
+ // temperature?: number;
11
+ // max_tokens?: number;
12
+ // top_p?: number;
13
+ // provider: string;
14
+ // isError?: boolean;
15
  id: string;
16
+ owned_by: string;
17
+ providers: ChatModelProvider[];
18
+ provider: string;
19
+ isError?: boolean;
 
 
 
20
  temperature?: number;
21
  max_tokens?: number;
22
  top_p?: number;
23
+ }
24
+
25
+ export interface ChatModelProvider {
26
  provider: string;
27
+ context_length?: number;
28
+ pricing?: {
29
+ input: number;
30
+ output: number;
31
+ };
32
  }
33
 
34
  export interface ChatMessage {
src/lib/index.ts CHANGED
@@ -1,4 +1,27 @@
 
 
1
  export const MAX_MODELS_PER_NODE = 5;
2
- export const MAX_TRENDING_MODELS = 6;
3
  export const MAX_SUGGESTIONS = 2;
4
  export const MAX_DEFAULT_MODELS = 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ChatModelProvider, TokenUsage } from './helpers/types';
2
+
3
  export const MAX_MODELS_PER_NODE = 5;
4
+ export const MAX_TRENDING_MODELS = 10;
5
  export const MAX_SUGGESTIONS = 2;
6
  export const MAX_DEFAULT_MODELS = 2;
7
+
8
+ export function formatPricingCost(pricing: ChatModelProvider['pricing'], amount: number) {
9
+ if (!pricing) return null;
10
+ return `$${pricing.input * amount} for input, $${pricing.output * amount} for output`;
11
+ }
12
+
13
+ export function formatPricingPerToken(pricing: ChatModelProvider['pricing']) {
14
+ if (!pricing) return null;
15
+ const inPrice = pricing.input.toFixed(2);
16
+ const outPrice = pricing.output.toFixed(2);
17
+ return `In: $${inPrice}/1M • Out: $${outPrice}/1M`;
18
+ }
19
+
20
+ export function formatUsageCost(pricing: ChatModelProvider['pricing'], usage: TokenUsage) {
21
+ if (!pricing || !usage) return null;
22
+ const inputCost = (pricing.input / 1_000_000) * (usage.prompt_tokens ?? 0);
23
+ const outputCost = (pricing.output / 1_000_000) * (usage.completion_tokens ?? 0);
24
+ const total = inputCost + outputCost;
25
+ if (total === 0) return null;
26
+ return `$${total.toFixed(6)}`;
27
+ }
src/lib/state/models.svelte.ts CHANGED
@@ -1,21 +1,21 @@
1
  import type { ChatModel } from '$lib/helpers/types';
2
 
3
- const HF_API = 'https://huggingface.co/api/models';
4
-
5
- function mapHFModelToChatModel(hfModel: ChatModel): ChatModel {
6
- const [author, modelName] = hfModel.modelId.split('/');
7
- const isTextGen = hfModel.pipeline_tag === 'text-generation';
8
- const isImageText = hfModel.pipeline_tag === 'image-text-to-text';
9
- return {
10
- id: hfModel.modelId,
11
- modelName,
12
- author,
13
- modelId: hfModel.modelId,
14
- avatarUrl: `https://huggingface.co/api/avatars/${author}`,
15
- pipeline_tag: isTextGen ? 'text-generation' : isImageText ? 'image-text-to-text' : undefined,
16
- provider: 'auto'
17
- };
18
- }
19
 
20
  export const modelsState = $state({
21
  models: [] as ChatModel[],
@@ -28,18 +28,12 @@ export async function fetchModels() {
28
  modelsState.error = null;
29
 
30
  try {
31
- const responses = await Promise.all([
32
- fetch(`${HF_API}?pipeline_tag=text-generation&limit=100&inference_provider=all`),
33
- fetch(`${HF_API}?pipeline_tag=image-text-to-text&limit=100&inference_provider=all`)
34
- ]);
35
-
36
- for (const res of responses) {
37
- if (!res.ok) throw new Error(res.statusText);
38
- }
39
 
40
- const modelsArrays = (await Promise.all(responses.map((r) => r.json()))) as ChatModel[][];
41
 
42
- modelsState.models = modelsArrays.flatMap((models) => models.map(mapHFModelToChatModel));
 
43
  } catch (e) {
44
  modelsState.error = e instanceof Error ? e.message : 'Failed to fetch models';
45
  modelsState.models = [];
 
1
  import type { ChatModel } from '$lib/helpers/types';
2
 
3
+ const HF_API = 'https://router.huggingface.co/v1/models';
4
+
5
+ // function mapHFModelToChatModel(hfModel: ChatModel): ChatModel {
6
+ // const [author, modelName] = hfModel.modelId.split('/');
7
+ // const isTextGen = hfModel.pipeline_tag === 'text-generation';
8
+ // const isImageText = hfModel.pipeline_tag === 'image-text-to-text';
9
+ // return {
10
+ // id: hfModel.modelId,
11
+ // modelName,
12
+ // author,
13
+ // modelId: hfModel.modelId,
14
+ // avatarUrl: `https://huggingface.co/api/avatars/${author}`,
15
+ // pipeline_tag: isTextGen ? 'text-generation' : isImageText ? 'image-text-to-text' : undefined,
16
+ // provider: 'auto'
17
+ // };
18
+ // }
19
 
20
  export const modelsState = $state({
21
  models: [] as ChatModel[],
 
28
  modelsState.error = null;
29
 
30
  try {
31
+ const response = await fetch(HF_API);
 
 
 
 
 
 
 
32
 
33
+ if (!response.ok) throw new Error(response.statusText);
34
 
35
+ const { data: models } = (await response.json()) as { data: ChatModel[] };
36
+ modelsState.models = models;
37
  } catch (e) {
38
  modelsState.error = e instanceof Error ? e.message : 'Failed to fetch models';
39
  modelsState.models = [];
src/lib/state/view.svelte.ts CHANGED
@@ -1,3 +1,3 @@
1
  export const viewState = $state({
2
- draggable: localStorage.getItem('hf-playground-draggable-option') ?? true
3
  });
 
1
  export const viewState = $state({
2
+ draggable: Boolean(localStorage.getItem('hf-playground-draggable-option') ?? true)
3
  });