Thomas G. Lopes commited on
Commit
25c63d0
·
1 Parent(s): c7f83e1

abstract token to store

Browse files
src/lib/components/InferencePlayground/InferencePlayground.svelte CHANGED
@@ -4,7 +4,6 @@
4
  import { page } from "$app/stores";
5
  import { defaultGenerationConfig } from "./generationConfigSettings";
6
  import {
7
- createHfInference,
8
  FEATURED_MODELS_IDS,
9
  handleNonStreamingResponse,
10
  handleStreamingResponse,
@@ -13,8 +12,10 @@
13
 
14
  import { goto } from "$app/navigation";
15
  import { models } from "$lib/stores/models";
 
16
  import { isMac } from "$lib/utils/platform";
17
- import { onDestroy, onMount } from "svelte";
 
18
  import IconCode from "../Icons/IconCode.svelte";
19
  import IconCompare from "../Icons/IconCompare.svelte";
20
  import IconDelete from "../Icons/IconDelete.svelte";
@@ -61,14 +62,11 @@
61
  session = session;
62
  }
63
 
64
- let hfToken = "";
65
  let viewCode = false;
66
  let viewSettings = false;
67
- let showTokenModal = false;
68
  let loading = false;
69
  let abortControllers: AbortController[] = [];
70
  let waitForNonStreaming = true;
71
- let storeLocallyHfToken = true;
72
  let selectCompareModelOpen = false;
73
 
74
  interface GenerationStatistics {
@@ -79,8 +77,6 @@
79
  | [GenerationStatistics]
80
  | [GenerationStatistics, GenerationStatistics];
81
 
82
- const hfTokenLocalStorageKey = "hf_token";
83
-
84
  $: systemPromptSupported = session.conversations.some(conversation => isSystemPromptSupported(conversation.model));
85
  $: compareActive = session.conversations.length === 2;
86
 
@@ -120,15 +116,9 @@
120
  waitForNonStreaming = false;
121
  }
122
 
123
- function resetToken() {
124
- hfToken = "";
125
- localStorage.removeItem(hfTokenLocalStorageKey);
126
- showTokenModal = true;
127
- }
128
-
129
  async function runInference(conversation: Conversation, conversationIdx: number) {
130
  const startTime = performance.now();
131
- const hf = createHfInference(hfToken);
132
 
133
  if (conversation.streaming) {
134
  let addStreamingMessage = true;
@@ -170,8 +160,8 @@
170
  }
171
 
172
  async function submit() {
173
- if (!hfToken) {
174
- showTokenModal = true;
175
  return;
176
  }
177
 
@@ -201,9 +191,7 @@
201
  }
202
  if (error instanceof Error) {
203
  if (error.message.includes("token seems invalid")) {
204
- hfToken = "";
205
- localStorage.removeItem(hfTokenLocalStorageKey);
206
- showTokenModal = true;
207
  }
208
  if (error.name !== "AbortError") {
209
  alert("error: " + error.message);
@@ -229,12 +217,8 @@
229
  const submittedHfToken = (formData.get("hf-token") as string).trim() ?? "";
230
  const RE_HF_TOKEN = /\bhf_[a-zA-Z0-9]{34}\b/;
231
  if (RE_HF_TOKEN.test(submittedHfToken)) {
232
- hfToken = submittedHfToken;
233
- if (storeLocallyHfToken) {
234
- localStorage.setItem(hfTokenLocalStorageKey, JSON.stringify(hfToken));
235
- }
236
  submit();
237
- showTokenModal = false;
238
  } else {
239
  alert("Please provide a valid HF token.");
240
  }
@@ -279,13 +263,6 @@
279
  }
280
  }
281
 
282
- onMount(() => {
283
- const storedHfToken = localStorage.getItem(hfTokenLocalStorageKey);
284
- if (storedHfToken !== null) {
285
- hfToken = JSON.parse(storedHfToken);
286
- }
287
- });
288
-
289
  onDestroy(() => {
290
  for (const abortController of abortControllers) {
291
  abortController.abort();
@@ -293,8 +270,12 @@
293
  });
294
  </script>
295
 
296
- {#if showTokenModal}
297
- <HFTokenModal bind:storeLocallyHfToken on:close={() => (showTokenModal = false)} on:submit={handleTokenSubmit} />
 
 
 
 
298
  {/if}
299
 
300
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -344,7 +325,6 @@
344
  {loading}
345
  {conversation}
346
  {viewCode}
347
- {hfToken}
348
  {compareActive}
349
  on:addMessage={() => addMessage(conversationIdx)}
350
  on:deleteMessage={e => deleteMessage(conversationIdx, e.detail)}
@@ -460,9 +440,9 @@
460
  </div>
461
 
462
  <GenerationConfig bind:conversation={session.conversations[0]} />
463
- {#if hfToken}
464
  <button
465
- on:click={resetToken}
466
  class="mt-auto flex items-center gap-1 self-end text-sm text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
467
  ><svg xmlns="http://www.w3.org/2000/svg" class="text-xs" width="1em" height="1em" viewBox="0 0 32 32"
468
  ><path
 
4
  import { page } from "$app/stores";
5
  import { defaultGenerationConfig } from "./generationConfigSettings";
6
  import {
 
7
  FEATURED_MODELS_IDS,
8
  handleNonStreamingResponse,
9
  handleStreamingResponse,
 
12
 
13
  import { goto } from "$app/navigation";
14
  import { models } from "$lib/stores/models";
15
+ import { token } from "$lib/stores/token";
16
  import { isMac } from "$lib/utils/platform";
17
+ import { HfInference } from "@huggingface/inference";
18
+ import { onDestroy } from "svelte";
19
  import IconCode from "../Icons/IconCode.svelte";
20
  import IconCompare from "../Icons/IconCompare.svelte";
21
  import IconDelete from "../Icons/IconDelete.svelte";
 
62
  session = session;
63
  }
64
 
 
65
  let viewCode = false;
66
  let viewSettings = false;
 
67
  let loading = false;
68
  let abortControllers: AbortController[] = [];
69
  let waitForNonStreaming = true;
 
70
  let selectCompareModelOpen = false;
71
 
72
  interface GenerationStatistics {
 
77
  | [GenerationStatistics]
78
  | [GenerationStatistics, GenerationStatistics];
79
 
 
 
80
  $: systemPromptSupported = session.conversations.some(conversation => isSystemPromptSupported(conversation.model));
81
  $: compareActive = session.conversations.length === 2;
82
 
 
116
  waitForNonStreaming = false;
117
  }
118
 
 
 
 
 
 
 
119
  async function runInference(conversation: Conversation, conversationIdx: number) {
120
  const startTime = performance.now();
121
+ const hf = new HfInference($token.value);
122
 
123
  if (conversation.streaming) {
124
  let addStreamingMessage = true;
 
160
  }
161
 
162
  async function submit() {
163
+ if (!$token.value) {
164
+ $token.showModal = true;
165
  return;
166
  }
167
 
 
191
  }
192
  if (error instanceof Error) {
193
  if (error.message.includes("token seems invalid")) {
194
+ token.reset();
 
 
195
  }
196
  if (error.name !== "AbortError") {
197
  alert("error: " + error.message);
 
217
  const submittedHfToken = (formData.get("hf-token") as string).trim() ?? "";
218
  const RE_HF_TOKEN = /\bhf_[a-zA-Z0-9]{34}\b/;
219
  if (RE_HF_TOKEN.test(submittedHfToken)) {
220
+ token.setValue(submittedHfToken);
 
 
 
221
  submit();
 
222
  } else {
223
  alert("Please provide a valid HF token.");
224
  }
 
263
  }
264
  }
265
 
 
 
 
 
 
 
 
266
  onDestroy(() => {
267
  for (const abortController of abortControllers) {
268
  abortController.abort();
 
270
  });
271
  </script>
272
 
273
+ {#if $token.showModal}
274
+ <HFTokenModal
275
+ bind:storeLocallyHfToken={$token.writeToLocalStorage}
276
+ on:close={() => ($token.showModal = false)}
277
+ on:submit={handleTokenSubmit}
278
+ />
279
  {/if}
280
 
281
  <!-- svelte-ignore a11y-no-static-element-interactions -->
 
325
  {loading}
326
  {conversation}
327
  {viewCode}
 
328
  {compareActive}
329
  on:addMessage={() => addMessage(conversationIdx)}
330
  on:deleteMessage={e => deleteMessage(conversationIdx, e.detail)}
 
440
  </div>
441
 
442
  <GenerationConfig bind:conversation={session.conversations[0]} />
443
+ {#if $token.value}
444
  <button
445
+ on:click={token.reset}
446
  class="mt-auto flex items-center gap-1 self-end text-sm text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
447
  ><svg xmlns="http://www.w3.org/2000/svg" class="text-xs" width="1em" height="1em" viewBox="0 0 32 32"
448
  ><path
src/lib/components/InferencePlayground/InferencePlaygroundCodeSnippets.svelte CHANGED
@@ -9,13 +9,13 @@
9
 
10
  import IconCopyCode from "../Icons/IconCopyCode.svelte";
11
  import { isSystemPromptSupported } from "./inferencePlaygroundUtils";
 
12
 
13
  hljs.registerLanguage("javascript", javascript);
14
  hljs.registerLanguage("python", python);
15
  hljs.registerLanguage("http", http);
16
 
17
  export let conversation: Conversation;
18
- export let hfToken: string;
19
 
20
  const dispatch = createEventDispatcher<{ closeCode: void }>();
21
 
@@ -66,8 +66,8 @@
66
  const selectedClientIdxByLang: Record<Language, number> = Object.fromEntries(lanuages.map(lang => [lang, 0]));
67
 
68
  function getTokenStr(showToken: boolean) {
69
- if (hfToken && showToken) {
70
- return hfToken;
71
  }
72
  return "YOUR_HF_TOKEN";
73
  }
@@ -487,7 +487,7 @@ print(completion.choices[0].message)`,
487
  <div class="flex items-center justify-between px-2 pt-6 pb-4">
488
  <h2 class="font-semibold">{label}</h2>
489
  <div class="flex items-center gap-x-4">
490
- {#if needsToken && hfToken}
491
  <label class="flex items-center gap-x-1.5 text-sm select-none">
492
  <input type="checkbox" bind:checked={showToken} />
493
  <p class="leading-none">With token</p>
 
9
 
10
  import IconCopyCode from "../Icons/IconCopyCode.svelte";
11
  import { isSystemPromptSupported } from "./inferencePlaygroundUtils";
12
+ import { token } from "$lib/stores/token";
13
 
14
  hljs.registerLanguage("javascript", javascript);
15
  hljs.registerLanguage("python", python);
16
  hljs.registerLanguage("http", http);
17
 
18
  export let conversation: Conversation;
 
19
 
20
  const dispatch = createEventDispatcher<{ closeCode: void }>();
21
 
 
66
  const selectedClientIdxByLang: Record<Language, number> = Object.fromEntries(lanuages.map(lang => [lang, 0]));
67
 
68
  function getTokenStr(showToken: boolean) {
69
+ if ($token.value && showToken) {
70
+ return $token.value;
71
  }
72
  return "YOUR_HF_TOKEN";
73
  }
 
487
  <div class="flex items-center justify-between px-2 pt-6 pb-4">
488
  <h2 class="font-semibold">{label}</h2>
489
  <div class="flex items-center gap-x-4">
490
+ {#if needsToken && $token.value}
491
  <label class="flex items-center gap-x-1.5 text-sm select-none">
492
  <input type="checkbox" bind:checked={showToken} />
493
  <p class="leading-none">With token</p>
src/lib/components/InferencePlayground/InferencePlaygroundConversation.svelte CHANGED
@@ -10,7 +10,6 @@
10
  export let conversation: Conversation;
11
  export let loading: boolean;
12
  export let viewCode: boolean;
13
- export let hfToken: string;
14
  export let compareActive: boolean;
15
 
16
  let shouldScrollToBottom = true;
@@ -101,6 +100,6 @@
101
  </div>
102
  </button>
103
  {:else}
104
- <CodeSnippets {conversation} {hfToken} on:closeCode />
105
  {/if}
106
  </div>
 
10
  export let conversation: Conversation;
11
  export let loading: boolean;
12
  export let viewCode: boolean;
 
13
  export let compareActive: boolean;
14
 
15
  let shouldScrollToBottom = true;
 
100
  </div>
101
  </button>
102
  {:else}
103
+ <CodeSnippets {conversation} on:closeCode />
104
  {/if}
105
  </div>
src/lib/components/InferencePlayground/InferencePlaygroundModelSelector.svelte CHANGED
@@ -9,6 +9,7 @@
9
  import Avatar from "../Avatar.svelte";
10
  import { defaultSystemMessage } from "./InferencePlaygroundGenerationConfig.svelte";
11
  import { models } from "$lib/stores/models";
 
12
 
13
  export let conversation: Conversation;
14
 
@@ -32,6 +33,13 @@
32
  }
33
 
34
  $: [nameSpace, modelName] = conversation.model.id.split("/");
 
 
 
 
 
 
 
35
  </script>
36
 
37
  {#if showModelPickerModal}
@@ -43,11 +51,33 @@
43
  {/if}
44
 
45
  <div class="flex flex-col gap-2">
46
- <label for="countries" class="flex items-baseline text-sm font-medium text-gray-900 dark:text-white"
47
- >Models<span class="ml-4 font-normal text-gray-400">{models.length}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </label>
49
 
50
  <button
 
51
  class="relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110"
52
  on:click={() => (showModelPickerModal = true)}
53
  >
 
9
  import Avatar from "../Avatar.svelte";
10
  import { defaultSystemMessage } from "./InferencePlaygroundGenerationConfig.svelte";
11
  import { models } from "$lib/stores/models";
12
+ import { fetchHuggingFaceModel, type Provider } from "$lib/fetchers/providers";
13
 
14
  export let conversation: Conversation;
15
 
 
33
  }
34
 
35
  $: [nameSpace, modelName] = conversation.model.id.split("/");
36
+
37
+ async function loadProviders(modelId: string) {
38
+ const providers = await fetchHuggingFaceModel;
39
+ }
40
+ let providers: Provider[] = [];
41
+
42
+ const id = crypto.randomUUID();
43
  </script>
44
 
45
  {#if showModelPickerModal}
 
51
  {/if}
52
 
53
  <div class="flex flex-col gap-2">
54
+ <label for={id} class="flex items-baseline gap-2 text-sm font-medium text-gray-900 dark:text-white">
55
+ Models<span class="text-xs font-normal text-gray-400">{$models.length}</span>
56
+ </label>
57
+
58
+ <button
59
+ {id}
60
+ class="relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110"
61
+ on:click={() => (showModelPickerModal = true)}
62
+ >
63
+ <div class="flex flex-col items-start">
64
+ <div class="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-300">
65
+ <Avatar orgName={nameSpace} size="sm" />
66
+ {nameSpace}
67
+ </div>
68
+ <div>{modelName}</div>
69
+ </div>
70
+ <IconCaret classNames="text-xl bg-gray-100 dark:bg-gray-600 rounded-sm size-4 flex-none absolute right-2" />
71
+ </button>
72
+ </div>
73
+
74
+ <div class="flex flex-col gap-2">
75
+ <label for={id} class="flex items-baseline gap-2 text-sm font-medium text-gray-900 dark:text-white">
76
+ Providers<span class="text-xs font-normal text-gray-400"></span>
77
  </label>
78
 
79
  <button
80
+ {id}
81
  class="relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110"
82
  on:click={() => (showModelPickerModal = true)}
83
  >
src/lib/components/InferencePlayground/InferencePlaygroundProviderSelectorModal.svelte ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { Conversation, ModelEntryWithTokenizer } from "./types";
3
+
4
+ import { createEventDispatcher, tick } from "svelte";
5
+
6
+ import { FEATURED_MODELS_IDS } from "./inferencePlaygroundUtils";
7
+ import IconSearch from "../Icons/IconSearch.svelte";
8
+ import IconStar from "../Icons/IconStar.svelte";
9
+ import { models } from "$lib/stores/models";
10
+
11
+ export let conversation: Conversation;
12
+
13
+ let backdropEl: HTMLDivElement;
14
+ let highlightIdx = 0;
15
+ let ignoreCursorHighlight = false;
16
+ let containerEl: HTMLDivElement;
17
+
18
+ const dispatch = createEventDispatcher<{ modelSelected: string; close: void }>();
19
+
20
+ let featuredModels = $models.filter(m => FEATURED_MODELS_IDS.includes(m.id));
21
+ let otherModels = $models.filter(m => !FEATURED_MODELS_IDS.includes(m.id));
22
+
23
+ if (featuredModels.findIndex(model => model.id === conversation.model.id) !== -1) {
24
+ highlightIdx = featuredModels.findIndex(model => model.id === conversation.model.id);
25
+ } else {
26
+ highlightIdx = featuredModels.length + otherModels.findIndex(model => model.id === conversation.model.id);
27
+ }
28
+
29
+ function handleKeydown(event: KeyboardEvent) {
30
+ const { key } = event;
31
+ let scrollLogicalPosition: ScrollLogicalPosition = "end";
32
+ if (key === "Escape") {
33
+ event.preventDefault();
34
+ dispatch("close");
35
+ } else if (key === "Enter") {
36
+ event.preventDefault();
37
+ const highlightedEl = document.querySelector(".highlighted");
38
+ if (highlightedEl) {
39
+ (highlightedEl as HTMLButtonElement).click();
40
+ }
41
+ } else if (key === "ArrowUp") {
42
+ event.preventDefault();
43
+ highlightIdx--;
44
+ scrollLogicalPosition = "start";
45
+ ignoreCursorHighlight = true;
46
+ } else if (key === "ArrowDown") {
47
+ event.preventDefault();
48
+ highlightIdx++;
49
+ ignoreCursorHighlight = true;
50
+ }
51
+ const n = featuredModels.length + otherModels.length;
52
+ highlightIdx = ((highlightIdx % n) + n) % n;
53
+ scrollToResult(scrollLogicalPosition);
54
+ }
55
+
56
+ async function scrollToResult(block: ScrollLogicalPosition) {
57
+ await tick();
58
+ const highlightedEl = document.querySelector(".highlighted");
59
+ if (containerEl && highlightedEl) {
60
+ const { bottom: containerBottom, top: containerTop } = containerEl.getBoundingClientRect();
61
+ const { bottom: highlightedBottom, top: highlightedTop } = highlightedEl.getBoundingClientRect();
62
+ if (highlightedBottom > containerBottom || containerTop > highlightedTop) {
63
+ highlightedEl.scrollIntoView({ block });
64
+ }
65
+ }
66
+ }
67
+
68
+ function highlightRow(idx: number) {
69
+ if (!ignoreCursorHighlight) {
70
+ highlightIdx = idx;
71
+ }
72
+ }
73
+
74
+ function handleBackdropClick(event: MouseEvent) {
75
+ if (window?.getSelection()?.toString()) {
76
+ return;
77
+ }
78
+ if (event.target === backdropEl) {
79
+ dispatch("close");
80
+ }
81
+ }
82
+
83
+ function filterModels(query: string) {
84
+ featuredModels = $models.filter(m =>
85
+ query
86
+ ? FEATURED_MODELS_IDS.includes(m.id) && m.id.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim())
87
+ : FEATURED_MODELS_IDS.includes(m.id)
88
+ );
89
+
90
+ otherModels = $models.filter(m =>
91
+ query
92
+ ? !FEATURED_MODELS_IDS.includes(m.id) && m.id.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim())
93
+ : !FEATURED_MODELS_IDS.includes(m.id)
94
+ );
95
+ }
96
+ </script>
97
+
98
+ <svelte:window on:keydown={handleKeydown} on:mousemove={() => (ignoreCursorHighlight = false)} />
99
+
100
+ <!-- svelte-ignore a11y-no-static-element-interactions a11y-click-events-have-key-events -->
101
+ <div
102
+ class="fixed inset-0 z-10 flex h-screen items-start justify-center bg-black/85 pt-32"
103
+ bind:this={backdropEl}
104
+ on:click|stopPropagation={handleBackdropClick}
105
+ >
106
+ <div class="flex w-full max-w-[600px] items-start justify-center overflow-hidden p-10 text-left whitespace-nowrap">
107
+ <div
108
+ class="flex h-full w-full flex-col overflow-hidden rounded-lg border bg-white text-gray-900 shadow-md dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300"
109
+ bind:this={containerEl}
110
+ >
111
+ <div class="flex items-center border-b px-3 dark:border-gray-800">
112
+ <IconSearch classNames="mr-2 text-sm" />
113
+ <!-- svelte-ignore a11y-autofocus -->
114
+ <input
115
+ autofocus
116
+ class="flex h-10 w-full rounded-md bg-transparent py-3 text-sm placeholder-gray-400 outline-hidden"
117
+ placeholder="Search models ..."
118
+ on:input={e => filterModels(e.currentTarget.value)}
119
+ />
120
+ </div>
121
+ <div class="max-h-[300px] overflow-x-hidden overflow-y-auto">
122
+ {#if featuredModels.length}
123
+ <div>
124
+ <div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
125
+ <div>
126
+ {#each featuredModels as model, idx}
127
+ {@const [nameSpace, modelName] = model.id.split("/")}
128
+ <button
129
+ class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm {highlightIdx === idx
130
+ ? 'highlighted bg-gray-100 dark:bg-gray-800'
131
+ : ''}"
132
+ on:mouseenter={() => highlightRow(idx)}
133
+ on:click={() => {
134
+ dispatch("modelSelected", model.id);
135
+ dispatch("close");
136
+ }}
137
+ >
138
+ <IconStar classNames="lucide lucide-star mr-1.5 size-4 text-yellow-400" />
139
+ <span class="inline-flex items-center"
140
+ ><span class="text-gray-500 dark:text-gray-400">{nameSpace}</span><span
141
+ class="mx-1 text-gray-300 dark:text-gray-700">/</span
142
+ ><span class="text-black dark:text-white">{modelName}</span></span
143
+ >
144
+ </button>
145
+ {/each}
146
+ </div>
147
+ </div>
148
+ {/if}
149
+ {#if otherModels.length}
150
+ <div>
151
+ <div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other Models</div>
152
+ <div>
153
+ {#each otherModels as model, _idx}
154
+ {@const [nameSpace, modelName] = model.id.split("/")}
155
+ {@const idx = featuredModels.length + _idx}
156
+ <button
157
+ class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm {highlightIdx === idx
158
+ ? 'highlighted bg-gray-100 dark:bg-gray-800'
159
+ : ''}"
160
+ on:mouseenter={() => highlightRow(idx)}
161
+ on:click={() => {
162
+ dispatch("modelSelected", model.id);
163
+ dispatch("close");
164
+ }}
165
+ >
166
+ <span class="inline-flex items-center"
167
+ ><span class="text-gray-500 dark:text-gray-400">{nameSpace}</span><span
168
+ class="mx-1 text-gray-300 dark:text-gray-700">/</span
169
+ ><span class="text-black dark:text-white">{modelName}</span></span
170
+ >
171
+ </button>
172
+ {/each}
173
+ </div>
174
+ </div>
175
+ {/if}
176
+ </div>
177
+ </div>
178
+ </div>
179
+ </div>
src/lib/components/InferencePlayground/inferencePlaygroundUtils.ts CHANGED
@@ -3,10 +3,6 @@ import type { Conversation, ModelEntryWithTokenizer } from "./types";
3
 
4
  import { HfInference } from "@huggingface/inference";
5
 
6
- export function createHfInference(token: string): HfInference {
7
- return new HfInference(token);
8
- }
9
-
10
  export async function handleStreamingResponse(
11
  hf: HfInference,
12
  conversation: Conversation,
 
3
 
4
  import { HfInference } from "@huggingface/inference";
5
 
 
 
 
 
6
  export async function handleStreamingResponse(
7
  hf: HfInference,
8
  conversation: Conversation,
src/lib/fetchers/providers.ts ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface HuggingFaceModelResponse {
2
+ _id: string;
3
+ id: string;
4
+ inferenceProviderMapping: InferenceProviderMapping;
5
+ }
6
+
7
+ export type InferenceProviderMapping = {
8
+ [k: string]: Provider;
9
+ };
10
+
11
+ export interface Provider {
12
+ status: string;
13
+ providerId: string;
14
+ task: string;
15
+ }
16
+ /**
17
+ * Error thrown when the Hugging Face API request fails
18
+ */
19
+ export class HuggingFaceApiError extends Error {
20
+ status: number;
21
+ details: string;
22
+
23
+ constructor(message: string, status: number, details: string) {
24
+ super(message);
25
+ this.name = "HuggingFaceApiError";
26
+ this.status = status;
27
+ this.details = details;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Fetches model data from the Hugging Face API
33
+ *
34
+ * @param modelId - The Hugging Face model ID (can include namespace like "username/model-name")
35
+ * @param token - Optional Hugging Face API token for authentication
36
+ * @returns Promise resolving to the model data
37
+ * @throws {HuggingFaceApiError} When the API request fails
38
+ */
39
+ export async function fetchHuggingFaceModel(modelId: string, token?: string): Promise<HuggingFaceModelResponse> {
40
+ if (!modelId) {
41
+ throw new Error("Model ID is required");
42
+ }
43
+
44
+ // Construct the API URL
45
+ const apiUrl = `https://huggingface.co/api/models/${modelId}?expand%5B%5D=inferenceProviderMapping`;
46
+
47
+ // Prepare headers for the request
48
+ const headers: HeadersInit = {};
49
+ if (token) {
50
+ headers["Authorization"] = `Bearer ${token}`;
51
+ }
52
+
53
+ try {
54
+ // Make the request to Hugging Face API
55
+ const response = await fetch(apiUrl, {
56
+ method: "GET",
57
+ headers,
58
+ });
59
+
60
+ if (!response.ok) {
61
+ const errorText = await response.text();
62
+ throw new HuggingFaceApiError("Failed to fetch data from Hugging Face API", response.status, errorText);
63
+ }
64
+
65
+ return (await response.json()) as HuggingFaceModelResponse;
66
+ } catch (error) {
67
+ if (error instanceof HuggingFaceApiError) {
68
+ throw error;
69
+ }
70
+
71
+ // Handle other errors (network, etc.)
72
+ throw new Error(
73
+ `Error fetching Hugging Face model data: ${error instanceof Error ? error.message : "Unknown error"}`
74
+ );
75
+ }
76
+ }
src/lib/stores/token.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { browser } from "$app/environment";
2
+ import { writable } from "svelte/store";
3
+
4
+ const key = "hf_token";
5
+
6
+ function createTokenStore() {
7
+ const store = writable({ value: "", writeToLocalStorage: true, showModal: false });
8
+
9
+ function setValue(token: string) {
10
+ store.update(s => {
11
+ if (s.writeToLocalStorage) localStorage.setItem(key, JSON.stringify(token));
12
+ return { ...s, value: token, showModal: !token.length };
13
+ });
14
+ }
15
+
16
+ if (browser) {
17
+ const storedHfToken = localStorage.getItem(key);
18
+ if (storedHfToken !== null) {
19
+ setValue(JSON.parse(storedHfToken));
20
+ }
21
+ }
22
+
23
+ return {
24
+ ...store,
25
+ setValue,
26
+ reset() {
27
+ setValue("");
28
+ localStorage.removeItem(key);
29
+ store.update(s => ({ ...s, showModal: true }));
30
+ },
31
+ };
32
+ }
33
+
34
+ export const token = createTokenStore();
src/lib/utils/effect.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Stores, StoresValues } from "svelte/store";
2
+ import { derived } from "svelte/store";
3
+ import { safeOnDestroy } from "./lifecycle";
4
+ import { noop } from "./noop";
5
+
6
+ type EffectOptions = {
7
+ /**
8
+ * Whether to skip the first run
9
+ * @default undefined
10
+ */
11
+ skipFirstRun?: boolean;
12
+ };
13
+
14
+ /**
15
+ * A utility function that creates an effect from a set of stores and a function.
16
+ * The effect is automatically cleaned up when the component is destroyed.
17
+ *
18
+ * @template S - The type of the stores object
19
+ * @param stores - The stores object to derive from
20
+ * @param fn - The function to run when the stores change
21
+ * @param opts {@link EffectOptions}
22
+ * @returns A function that can be used to unsubscribe the effect
23
+ */
24
+ export function effect<S extends Stores>(
25
+ stores: S,
26
+ fn: (values: StoresValues<S>) => (() => void) | void,
27
+ opts: EffectOptions = {}
28
+ ): () => void {
29
+ const { skipFirstRun } = opts;
30
+ let isFirstRun = true;
31
+ let cb: (() => void) | void = undefined;
32
+
33
+ // Create a derived store that contains the stores object and an onUnsubscribe function
34
+ const destroy = derived(stores, stores => {
35
+ cb?.();
36
+ if (isFirstRun && skipFirstRun) {
37
+ isFirstRun = false;
38
+ } else {
39
+ cb = fn(stores);
40
+ }
41
+ }).subscribe(noop);
42
+
43
+ const unsub = () => {
44
+ destroy();
45
+ cb?.();
46
+ };
47
+
48
+ // Automatically unsubscribe the effect when the component is destroyed
49
+ safeOnDestroy(unsub);
50
+ return unsub;
51
+ }
src/lib/utils/lifecycle.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { onDestroy, onMount } from "svelte";
2
+
3
+ export const safeOnMount = (fn: (...args: unknown[]) => unknown) => {
4
+ try {
5
+ onMount(fn);
6
+ } catch {
7
+ return fn;
8
+ }
9
+ };
10
+
11
+ export const safeOnDestroy = (fn: (...args: unknown[]) => unknown) => {
12
+ try {
13
+ onDestroy(fn);
14
+ } catch {
15
+ return fn;
16
+ }
17
+ };
src/lib/utils/noop.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /**
2
+ * A no operation function (does nothing)
3
+ */
4
+ export function noop() {
5
+ // do nothing
6
+ }
src/routes/+page.server.ts CHANGED
@@ -18,6 +18,7 @@ export const load: PageServerLoad = async ({ fetch }) => {
18
  return { models: [] };
19
  }
20
  const compatibleModels: ModelEntry[] = await res.json();
 
21
  compatibleModels.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
22
 
23
  const promises = compatibleModels.map(async model => {
 
18
  return { models: [] };
19
  }
20
  const compatibleModels: ModelEntry[] = await res.json();
21
+ console.log(compatibleModels);
22
  compatibleModels.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
23
 
24
  const promises = compatibleModels.map(async model => {