mishig HF staff commited on
Commit
4323d20
·
unverified ·
1 Parent(s): 5705707

Remove Playground.svelte container component & just use +page.svelte (#8)

Browse files
src/lib/components/Playground.svelte DELETED
@@ -1,477 +0,0 @@
1
- <script lang="ts">
2
- import PlaygroundCode from './CodeSnippets.svelte';
3
- import {
4
- createHfInference,
5
- prepareRequestMessages,
6
- handleStreamingResponse,
7
- handleNonStreamingResponse
8
- } from './playgroundUtils';
9
- import PlaygroundMessage from '$lib/components/Message.svelte';
10
- import PlaygroundOptions from '$lib/components/GenerationConfig.svelte';
11
- import PlaygroundTokenModal from './HFTokenModal.svelte';
12
- import PlaygroundModelSelector from './ModelSelector.svelte';
13
- import { onDestroy, onMount } from 'svelte';
14
- import { type ModelEntry } from "@huggingface/hub";
15
- import { type ChatCompletionInputMessage } from "@huggingface/tasks";
16
-
17
-
18
- let compatibleModels: ModelEntry[] = [];
19
-
20
- const startMessages: ChatCompletionInputMessage[] = [{ role: 'user', content: '' }];
21
-
22
- let conversations: Conversation[] = [
23
- {
24
- id: String(Math.random()),
25
- model: '01-ai/Yi-1.5-34B-Chat',
26
- config: { temperature: 0.5, maxTokens: 2048, streaming: true, jsonMode: false },
27
- messages: startMessages
28
- }
29
- ];
30
-
31
- $: if (conversations.length > 1) {
32
- viewCode = false;
33
- }
34
-
35
- let currentConversation = conversations[0];
36
- let systemMessage: ChatCompletionInputMessage = { role: 'system', content: '' };
37
- $: messages = currentConversation.messages;
38
-
39
- let hfToken: string | null = import.meta.env.VITE_HF_TOKEN;
40
- let viewCode = false;
41
- let showTokenModal = false;
42
- let loading = false;
43
- let streamingMessage: ChatCompletionInputMessage | null = null;
44
- let tokens = 0;
45
- let latency = 0;
46
- let messageContainer: HTMLDivElement | null = null;
47
- let abortController: AbortController | null = null;
48
- let waitForNonStreaming = true;
49
-
50
- onMount(() => {
51
- (async () => {
52
- // TODO: use hfjs.hub listModels after https://github.com/huggingface/huggingface.js/pull/795
53
- const res = await fetch("https://huggingface.co/api/models?pipeline_tag=text-generation&inferenceStatus=loaded&filter=conversational");
54
- compatibleModels = await res.json() as ModelEntry[];
55
- compatibleModels.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
56
- })();
57
- })
58
-
59
- onDestroy(() => {
60
- if (abortController) {
61
- abortController.abort();
62
- }
63
- });
64
-
65
- function addMessage() {
66
- currentConversation.messages = [
67
- ...currentConversation.messages,
68
- {
69
- role: currentConversation.messages.at(-1)?.role === 'user' ? 'assistant' : 'user',
70
- content: ''
71
- }
72
- ];
73
- conversations = conversations;
74
- }
75
-
76
- function deleteAndGetItem<T>(array: T[], index: number) {
77
- if (index >= 0 && index < array.length) {
78
- return array.splice(index, 1)[0];
79
- }
80
- return undefined;
81
- }
82
-
83
- function deleteMessage(idx: number) {
84
- const deletedMsg = deleteAndGetItem<ChatCompletionInputMessage>(currentConversation.messages, idx);
85
- // delete messages in user/assistant pairs. otherwise, the chat template will be broken
86
- if(deletedMsg){
87
- const { role } = deletedMsg;
88
- const pairIdx = role === "user" ? idx : idx - 1;
89
- deleteAndGetItem<ChatCompletionInputMessage>(currentConversation.messages, pairIdx);
90
- }
91
- conversations = conversations;
92
- }
93
-
94
- function reset() {
95
- currentConversation.messages = [...startMessages];
96
- systemMessage.content = '';
97
- conversations = conversations;
98
- }
99
-
100
- function abort(){
101
- if (streamingMessage && abortController) {
102
- abortController.abort();
103
- abortController = null;
104
- }
105
- loading = false;
106
- streamingMessage = null;
107
- waitForNonStreaming = false;
108
- }
109
-
110
- async function submit() {
111
- // last message has to be from user
112
- if(currentConversation.messages?.at(-1)?.role !== "user"){
113
- addMessage();
114
- return;
115
- }
116
- if (!hfToken) {
117
- showTokenModal = true;
118
- return;
119
- }
120
- (document.activeElement as HTMLElement).blur();
121
- loading = true;
122
- const startTime = performance.now();
123
-
124
- try {
125
- const hf = createHfInference(hfToken);
126
- const requestMessages = prepareRequestMessages(systemMessage, messages);
127
-
128
- if (currentConversation.config.streaming) {
129
- streamingMessage = { role: 'assistant', content: '' };
130
- currentConversation.messages = [...currentConversation.messages, streamingMessage];
131
- abortController = new AbortController();
132
-
133
- await handleStreamingResponse(
134
- hf,
135
- currentConversation.model,
136
- requestMessages,
137
- currentConversation.config.temperature,
138
- currentConversation.config.maxTokens,
139
- currentConversation.config.jsonMode,
140
- (content) => {
141
- if (streamingMessage) {
142
- streamingMessage.content = content;
143
- currentConversation.messages = [...currentConversation.messages];
144
- conversations = conversations;
145
- scrollToBottom();
146
- }
147
- },
148
- abortController
149
- );
150
- } else {
151
- streamingMessage = null;
152
- waitForNonStreaming = true;
153
- const newMessage = await handleNonStreamingResponse(
154
- hf,
155
- currentConversation.model,
156
- requestMessages,
157
- currentConversation.config.temperature,
158
- currentConversation.config.maxTokens,
159
- currentConversation.config.jsonMode
160
- );
161
- // check if the user did not abort the request
162
- if(waitForNonStreaming){
163
- currentConversation.messages = [...currentConversation.messages, newMessage];
164
- conversations = conversations;
165
- scrollToBottom();
166
- }
167
- }
168
-
169
- addMessage();
170
- } catch (error) {
171
- if (error.name !== 'AbortError') {
172
- alert('error: ' + (error as Error).message);
173
- }
174
- } finally {
175
- const endTime = performance.now();
176
- latency = Math.round(endTime - startTime);
177
- loading = false;
178
- streamingMessage = null;
179
- abortController = null;
180
- scrollToBottom();
181
- }
182
- }
183
-
184
- function onKeydown(event: KeyboardEvent) {
185
- if (!event.shiftKey && event.key === 'Enter') {
186
- submit();
187
- }
188
- }
189
-
190
- function scrollToBottom() {
191
- if (messageContainer) {
192
- messageContainer.scrollTop = messageContainer.scrollHeight;
193
- }
194
- }
195
- </script>
196
-
197
- <svelte:window on:keydown={onKeydown} />
198
-
199
- {#if showTokenModal}
200
- <PlaygroundTokenModal
201
- on:close={() => (showTokenModal = false)}
202
- on:submit={(e) => {
203
- const formData = new FormData(e.target);
204
- hfToken = formData.get('hf-token');
205
- submit();
206
- showTokenModal = false;
207
- }}
208
- />
209
- {/if}
210
-
211
- <div
212
- class="w-dvh grid divide-gray-200 overflow-hidden bg-gray-100/50 max-md:divide-y md:h-dvh dark:[color-scheme:dark]
213
- {conversations.length === 1
214
- ? 'md:grid-cols-[clamp(220px,20%,350px),minmax(0,1fr),clamp(270px,25%,300px)]'
215
- : 'md:grid-cols-[clamp(220px,20%,350px),minmax(0,1fr),0]'}
216
-
217
- dark:divide-gray-800 dark:bg-gray-900 dark:text-gray-300"
218
- >
219
- <div class=" flex flex-col overflow-y-auto py-3 pr-3">
220
- <div
221
- class="relative flex flex-1 flex-col gap-6 overflow-y-hidden rounded-r-xl border-x border-y border-gray-200/80 bg-gradient-to-b from-white via-white p-3 shadow-sm dark:border-white/5 dark:from-gray-800/40 dark:via-gray-800/40"
222
- >
223
- <div class="pb-2 text-sm font-semibold">SYSTEM</div>
224
- <textarea
225
- name=""
226
- id=""
227
- placeholder="Enter a custom prompt"
228
- bind:value={systemMessage.content}
229
- class="absolute inset-x-0 bottom-0 h-full resize-none bg-transparent px-3 pt-10 text-sm outline-none"
230
- ></textarea>
231
- </div>
232
- </div>
233
- <div class="relative divide-y divide-gray-200 pt-3 dark:divide-gray-800">
234
- <div
235
- class="flex h-[calc(100dvh-5rem)] divide-x divide-gray-200 {conversations.length === 2
236
- ? '*:w-1/2'
237
- : conversations.length == 3
238
- ? '*:w-1/3'
239
- : '*:w-full'} dark:divide-gray-800"
240
- >
241
- {#each conversations as conversation, index}
242
- <div
243
- class="flex max-h-[calc(100dvh-5.8rem)] flex-col overflow-y-auto overflow-x-hidden @container"
244
- class:pointer-events-none={loading}
245
- class:animate-pulse={loading && !streamingMessage}
246
- bind:this={messageContainer}
247
- >
248
- {#if conversations.length > 1}
249
- <div
250
- class="sticky top-0 flex h-11 flex-none items-center gap-2 whitespace-nowrap rounded-lg border border-gray-200/80 bg-white pl-3 pr-2 text-sm leading-none shadow-sm *:flex-none dark:border-gray-800 dark:bg-gray-800/70 dark:hover:bg-gray-800"
251
- class:mr-3={index === 0}
252
- class:mx-3={index === 1}
253
- >
254
- <div class="size-3.5 rounded bg-black dark:bg-gray-400"></div>
255
- <div>{conversation.model}</div>
256
- <button
257
- class="ml-auto flex size-6 items-center justify-center rounded bg-gray-50 text-xs hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700"
258
- on:click={() => (conversations = conversations.filter((_, i) => i !== index))}
259
- >
260
-
261
- </button>
262
- <button
263
- class=" flex size-6 items-center justify-center rounded bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700"
264
- >
265
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"
266
- ><path
267
- fill="currentColor"
268
- d="M27 16.76v-1.53l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11.35 11.35 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.48 11.48 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11.35 11.35 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.48 11.48 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51ZM25.21 24l-3.43-1.16a8.86 8.86 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.36 9.36 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.86 8.86 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.36 9.36 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20Z"
269
- /><path
270
- fill="currentColor"
271
- d="M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6Zm0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4Z"
272
- /></svg
273
- >
274
- </button>
275
- </div>
276
- {/if}
277
- {#if !viewCode}
278
- {#each messages as message, i}
279
- <PlaygroundMessage
280
- class="border-b"
281
- {message}
282
- on:delete={() => deleteMessage(i)}
283
- autofocus={conversations.length === 1 && !loading && i === messages.length - 1}
284
- />
285
- {/each}
286
-
287
- <button
288
- class="flex px-6 py-6 hover:bg-gray-50 dark:hover:bg-gray-800/50"
289
- on:click={addMessage}
290
- disabled={loading}
291
- >
292
- <div class="flex items-center gap-2 !p-0 text-sm font-semibold">
293
- <svg
294
- xmlns="http://www.w3.org/2000/svg"
295
- width="1em"
296
- height="1em"
297
- viewBox="0 0 32 32"
298
- class="text-lg"
299
- ><path
300
- fill="currentColor"
301
- d="M16 2A14.172 14.172 0 0 0 2 16a14.172 14.172 0 0 0 14 14a14.172 14.172 0 0 0 14-14A14.172 14.172 0 0 0 16 2Zm8 15h-7v7h-2v-7H8v-2h7V8h2v7h7Z"
302
- /><path fill="none" d="M24 17h-7v7h-2v-7H8v-2h7V8h2v7h7v2z" /></svg
303
- >Add message
304
- </div>
305
- </button>
306
- {:else}
307
- <PlaygroundCode {...currentConversation} {...currentConversation.config} />
308
- {/if}
309
- </div>
310
- {/each}
311
- </div>
312
- <div
313
- class="fixed inset-x-0 bottom-0 flex h-20 items-center gap-2 overflow-hidden whitespace-nowrap px-3 md:absolute"
314
- >
315
- <button
316
- type="button"
317
- class="flex h-[39px] flex-none gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
318
- >
319
- <div
320
- class="flex size-5 items-center justify-center rounded border border-black/5 bg-black/5 text-xs"
321
- >
322
- <svg
323
- width="1em"
324
- height="1em"
325
- viewBox="0 0 24 25"
326
- fill="none"
327
- xmlns="http://www.w3.org/2000/svg"
328
- >
329
- <path
330
- fill-rule="evenodd"
331
- clip-rule="evenodd"
332
- d="M5.41 9.41L4 8L12 0L20 8L18.59 9.41L13 3.83L13 17.5H11L11 3.83L5.41 9.41ZM22 17.5V23H2V17.5H0V23C0 23.5304 0.210714 24.0391 0.585786 24.4142C0.960859 24.7893 1.46957 25 2 25H22C22.5304 25 23.0391 24.7893 23.4142 24.4142C23.7893 24.0391 24 23.5304 24 23V17.5H22Z"
333
- fill="currentColor"
334
- />
335
- </svg>
336
- </div>
337
-
338
- Share</button
339
- >
340
-
341
- <button
342
- type="button"
343
- on:click={reset}
344
- class="flex size-[39px] flex-none items-center justify-center rounded-lg border border-gray-200 bg-white text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
345
- ><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"
346
- ><path fill="currentColor" d="M12 12h2v12h-2zm6 0h2v12h-2z" /><path
347
- fill="currentColor"
348
- d="M4 6v2h2v20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h2V6zm4 22V8h16v20zm4-26h8v2h-8z"
349
- /></svg
350
- ></button
351
- >
352
- <div class="flex-1 items-center justify-center text-center text-sm text-gray-500">
353
- <span class="max-xl:hidden">0 tokens · Latency {latency}ms</span>
354
- </div>
355
- <button
356
- type="button"
357
- on:click={() => (viewCode = !viewCode)}
358
- class="flex h-[39px] items-center gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
359
- >
360
- <svg
361
- xmlns="http://www.w3.org/2000/svg"
362
- width="1em"
363
- height="1em"
364
- class="text-base"
365
- viewBox="0 0 32 32"
366
- ><path
367
- fill="currentColor"
368
- d="m31 16l-7 7l-1.41-1.41L28.17 16l-5.58-5.59L24 9l7 7zM1 16l7-7l1.41 1.41L3.83 16l5.58 5.59L8 23l-7-7zm11.42 9.484L17.64 6l1.932.517L14.352 26z"
369
- /></svg
370
- >
371
- {!viewCode ? 'View Code' : 'Hide Code'}</button
372
- >
373
- <button
374
- on:click={() => {
375
- viewCode = false;
376
- loading ? abort() : submit();
377
- }}
378
- type="button"
379
- class="flex h-[39px] w-24 items-center justify-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium text-white focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:focus:ring-gray-700 {loading ? 'bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700' : 'bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700'}"
380
- >
381
- {#if loading}
382
- <div class="flex flex-none items-center gap-[3px]">
383
- <span class="mr-2">Cancel</span>
384
- <div
385
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
386
- style="animation-delay: 0.25s;"
387
- />
388
- <div
389
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
390
- style="animation-delay: 0.5s;"
391
- />
392
- <div
393
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
394
- style="animation-delay: 0.75s;"
395
- />
396
- </div>
397
- {:else}
398
- Run <span
399
- class="inline-flex gap-0.5 rounded border border-white/20 bg-white/10 px-0.5 text-xs text-white/70"
400
- >↵</span
401
- >
402
- {/if}
403
- </button>
404
- </div>
405
- </div>
406
- {#if conversations.length === 1}
407
- <div class="flex flex-col p-3">
408
- <div
409
- class="flex flex-1 flex-col gap-6 overflow-y-hidden rounded-xl border border-gray-200/80 bg-gradient-to-b from-white via-white p-3 shadow-sm dark:border-white/5 dark:from-gray-800/40 dark:via-gray-800/40"
410
- >
411
- <PlaygroundModelSelector {compatibleModels} bind:currentModel={currentConversation.model} />
412
- <div
413
- class="group relative -mt-4 flex h-[26px] w-full items-center justify-center gap-2 rounded-lg bg-black px-5 text-sm text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-gray-700"
414
- >
415
- Compare with...
416
- <svg
417
- class="ml-0.5 flex-none opacity-50 group-hover:opacity-100"
418
- xmlns="http://www.w3.org/2000/svg"
419
- xmlns:xlink="http://www.w3.org/1999/xlink"
420
- aria-hidden="true"
421
- role="img"
422
- width="1em"
423
- height="1em"
424
- preserveAspectRatio="xMidYMid meet"
425
- viewBox="0 0 24 24"
426
- ><path
427
- d="M16.293 9.293L12 13.586L7.707 9.293l-1.414 1.414L12 16.414l5.707-5.707z"
428
- fill="currentColor"
429
- ></path></svg
430
- >
431
- <select
432
- class="absolute inset-0 border-none bg-white text-base opacity-0 outline-none"
433
- on:change|preventDefault={(e) => {
434
- conversations = [
435
- ...conversations,
436
- {
437
- id: String(Math.random()),
438
- model: e.target.value,
439
- config: { temperature: 0.5, maxTokens: 2048, streaming: true, jsonMode: false },
440
- messages: startMessages
441
- }
442
- ];
443
- }}
444
- >
445
- {#each compatibleModels as model}
446
- <option value={model.id}>{model.id}</option>
447
- {/each}
448
- </select>
449
- </div>
450
-
451
- <PlaygroundOptions
452
- bind:temperature={currentConversation.config.temperature}
453
- bind:maxTokens={currentConversation.config.maxTokens}
454
- bind:jsonMode={currentConversation.config.jsonMode}
455
- bind:streaming={currentConversation.config.streaming}
456
- />
457
- <div class="mt-auto">
458
- <div class="mb-3 flex items-center justify-between gap-2">
459
- <label
460
- for="default-range"
461
- class="block text-sm font-medium text-gray-900 dark:text-white">API Quota</label
462
- >
463
- <span
464
- class="rounded bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-300"
465
- >Free</span
466
- >
467
-
468
- <div class="ml-auto w-12 text-right text-sm">76%</div>
469
- </div>
470
- <div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-700">
471
- <div class="h-2 rounded-full bg-black dark:bg-gray-400" style="width: 75%"></div>
472
- </div>
473
- </div>
474
- </div>
475
- </div>
476
- {/if}
477
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/routes/+page.svelte CHANGED
@@ -1,5 +1,477 @@
1
  <script lang="ts">
2
- import Playground from '$lib/components/Playground.svelte';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  </script>
4
 
5
- <Playground />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
+ import PlaygroundCode from '$lib/components/CodeSnippets.svelte';
3
+ import {
4
+ createHfInference,
5
+ prepareRequestMessages,
6
+ handleStreamingResponse,
7
+ handleNonStreamingResponse
8
+ } from '$lib/components/playgroundUtils';
9
+ import PlaygroundMessage from '$lib/components/Message.svelte';
10
+ import PlaygroundOptions from '$lib/components/GenerationConfig.svelte';
11
+ import PlaygroundTokenModal from '$lib/components/HFTokenModal.svelte';
12
+ import PlaygroundModelSelector from '$lib/components/ModelSelector.svelte';
13
+ import { onDestroy, onMount } from 'svelte';
14
+ import { type ModelEntry } from "@huggingface/hub";
15
+ import { type ChatCompletionInputMessage } from "@huggingface/tasks";
16
+
17
+
18
+ let compatibleModels: ModelEntry[] = [];
19
+
20
+ const startMessages: ChatCompletionInputMessage[] = [{ role: 'user', content: '' }];
21
+
22
+ let conversations: Conversation[] = [
23
+ {
24
+ id: String(Math.random()),
25
+ model: '01-ai/Yi-1.5-34B-Chat',
26
+ config: { temperature: 0.5, maxTokens: 2048, streaming: true, jsonMode: false },
27
+ messages: startMessages
28
+ }
29
+ ];
30
+
31
+ $: if (conversations.length > 1) {
32
+ viewCode = false;
33
+ }
34
+
35
+ let currentConversation = conversations[0];
36
+ let systemMessage: ChatCompletionInputMessage = { role: 'system', content: '' };
37
+ $: messages = currentConversation.messages;
38
+
39
+ let hfToken: string | null = import.meta.env.VITE_HF_TOKEN;
40
+ let viewCode = false;
41
+ let showTokenModal = false;
42
+ let loading = false;
43
+ let streamingMessage: ChatCompletionInputMessage | null = null;
44
+ let tokens = 0;
45
+ let latency = 0;
46
+ let messageContainer: HTMLDivElement | null = null;
47
+ let abortController: AbortController | null = null;
48
+ let waitForNonStreaming = true;
49
+
50
+ onMount(() => {
51
+ (async () => {
52
+ // TODO: use hfjs.hub listModels after https://github.com/huggingface/huggingface.js/pull/795
53
+ const res = await fetch("https://huggingface.co/api/models?pipeline_tag=text-generation&inferenceStatus=loaded&filter=conversational");
54
+ compatibleModels = await res.json() as ModelEntry[];
55
+ compatibleModels.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
56
+ })();
57
+ })
58
+
59
+ onDestroy(() => {
60
+ if (abortController) {
61
+ abortController.abort();
62
+ }
63
+ });
64
+
65
+ function addMessage() {
66
+ currentConversation.messages = [
67
+ ...currentConversation.messages,
68
+ {
69
+ role: currentConversation.messages.at(-1)?.role === 'user' ? 'assistant' : 'user',
70
+ content: ''
71
+ }
72
+ ];
73
+ conversations = conversations;
74
+ }
75
+
76
+ function deleteAndGetItem<T>(array: T[], index: number) {
77
+ if (index >= 0 && index < array.length) {
78
+ return array.splice(index, 1)[0];
79
+ }
80
+ return undefined;
81
+ }
82
+
83
+ function deleteMessage(idx: number) {
84
+ const deletedMsg = deleteAndGetItem<ChatCompletionInputMessage>(currentConversation.messages, idx);
85
+ // delete messages in user/assistant pairs. otherwise, the chat template will be broken
86
+ if(deletedMsg){
87
+ const { role } = deletedMsg;
88
+ const pairIdx = role === "user" ? idx : idx - 1;
89
+ deleteAndGetItem<ChatCompletionInputMessage>(currentConversation.messages, pairIdx);
90
+ }
91
+ conversations = conversations;
92
+ }
93
+
94
+ function reset() {
95
+ currentConversation.messages = [...startMessages];
96
+ systemMessage.content = '';
97
+ conversations = conversations;
98
+ }
99
+
100
+ function abort(){
101
+ if (streamingMessage && abortController) {
102
+ abortController.abort();
103
+ abortController = null;
104
+ }
105
+ loading = false;
106
+ streamingMessage = null;
107
+ waitForNonStreaming = false;
108
+ }
109
+
110
+ async function submit() {
111
+ // last message has to be from user
112
+ if(currentConversation.messages?.at(-1)?.role !== "user"){
113
+ addMessage();
114
+ return;
115
+ }
116
+ if (!hfToken) {
117
+ showTokenModal = true;
118
+ return;
119
+ }
120
+ (document.activeElement as HTMLElement).blur();
121
+ loading = true;
122
+ const startTime = performance.now();
123
+
124
+ try {
125
+ const hf = createHfInference(hfToken);
126
+ const requestMessages = prepareRequestMessages(systemMessage, messages);
127
+
128
+ if (currentConversation.config.streaming) {
129
+ streamingMessage = { role: 'assistant', content: '' };
130
+ currentConversation.messages = [...currentConversation.messages, streamingMessage];
131
+ abortController = new AbortController();
132
+
133
+ await handleStreamingResponse(
134
+ hf,
135
+ currentConversation.model,
136
+ requestMessages,
137
+ currentConversation.config.temperature,
138
+ currentConversation.config.maxTokens,
139
+ currentConversation.config.jsonMode,
140
+ (content) => {
141
+ if (streamingMessage) {
142
+ streamingMessage.content = content;
143
+ currentConversation.messages = [...currentConversation.messages];
144
+ conversations = conversations;
145
+ scrollToBottom();
146
+ }
147
+ },
148
+ abortController
149
+ );
150
+ } else {
151
+ streamingMessage = null;
152
+ waitForNonStreaming = true;
153
+ const newMessage = await handleNonStreamingResponse(
154
+ hf,
155
+ currentConversation.model,
156
+ requestMessages,
157
+ currentConversation.config.temperature,
158
+ currentConversation.config.maxTokens,
159
+ currentConversation.config.jsonMode
160
+ );
161
+ // check if the user did not abort the request
162
+ if(waitForNonStreaming){
163
+ currentConversation.messages = [...currentConversation.messages, newMessage];
164
+ conversations = conversations;
165
+ scrollToBottom();
166
+ }
167
+ }
168
+
169
+ addMessage();
170
+ } catch (error) {
171
+ if (error.name !== 'AbortError') {
172
+ alert('error: ' + (error as Error).message);
173
+ }
174
+ } finally {
175
+ const endTime = performance.now();
176
+ latency = Math.round(endTime - startTime);
177
+ loading = false;
178
+ streamingMessage = null;
179
+ abortController = null;
180
+ scrollToBottom();
181
+ }
182
+ }
183
+
184
+ function onKeydown(event: KeyboardEvent) {
185
+ if (!event.shiftKey && event.key === 'Enter') {
186
+ submit();
187
+ }
188
+ }
189
+
190
+ function scrollToBottom() {
191
+ if (messageContainer) {
192
+ messageContainer.scrollTop = messageContainer.scrollHeight;
193
+ }
194
+ }
195
  </script>
196
 
197
+ <svelte:window on:keydown={onKeydown} />
198
+
199
+ {#if showTokenModal}
200
+ <PlaygroundTokenModal
201
+ on:close={() => (showTokenModal = false)}
202
+ on:submit={(e) => {
203
+ const formData = new FormData(e.target);
204
+ hfToken = formData.get('hf-token');
205
+ submit();
206
+ showTokenModal = false;
207
+ }}
208
+ />
209
+ {/if}
210
+
211
+ <div
212
+ class="w-dvh grid divide-gray-200 overflow-hidden bg-gray-100/50 max-md:divide-y md:h-dvh dark:[color-scheme:dark]
213
+ {conversations.length === 1
214
+ ? 'md:grid-cols-[clamp(220px,20%,350px),minmax(0,1fr),clamp(270px,25%,300px)]'
215
+ : 'md:grid-cols-[clamp(220px,20%,350px),minmax(0,1fr),0]'}
216
+
217
+ dark:divide-gray-800 dark:bg-gray-900 dark:text-gray-300"
218
+ >
219
+ <div class=" flex flex-col overflow-y-auto py-3 pr-3">
220
+ <div
221
+ class="relative flex flex-1 flex-col gap-6 overflow-y-hidden rounded-r-xl border-x border-y border-gray-200/80 bg-gradient-to-b from-white via-white p-3 shadow-sm dark:border-white/5 dark:from-gray-800/40 dark:via-gray-800/40"
222
+ >
223
+ <div class="pb-2 text-sm font-semibold">SYSTEM</div>
224
+ <textarea
225
+ name=""
226
+ id=""
227
+ placeholder="Enter a custom prompt"
228
+ bind:value={systemMessage.content}
229
+ class="absolute inset-x-0 bottom-0 h-full resize-none bg-transparent px-3 pt-10 text-sm outline-none"
230
+ ></textarea>
231
+ </div>
232
+ </div>
233
+ <div class="relative divide-y divide-gray-200 pt-3 dark:divide-gray-800">
234
+ <div
235
+ class="flex h-[calc(100dvh-5rem)] divide-x divide-gray-200 {conversations.length === 2
236
+ ? '*:w-1/2'
237
+ : conversations.length == 3
238
+ ? '*:w-1/3'
239
+ : '*:w-full'} dark:divide-gray-800"
240
+ >
241
+ {#each conversations as conversation, index}
242
+ <div
243
+ class="flex max-h-[calc(100dvh-5.8rem)] flex-col overflow-y-auto overflow-x-hidden @container"
244
+ class:pointer-events-none={loading}
245
+ class:animate-pulse={loading && !streamingMessage}
246
+ bind:this={messageContainer}
247
+ >
248
+ {#if conversations.length > 1}
249
+ <div
250
+ class="sticky top-0 flex h-11 flex-none items-center gap-2 whitespace-nowrap rounded-lg border border-gray-200/80 bg-white pl-3 pr-2 text-sm leading-none shadow-sm *:flex-none dark:border-gray-800 dark:bg-gray-800/70 dark:hover:bg-gray-800"
251
+ class:mr-3={index === 0}
252
+ class:mx-3={index === 1}
253
+ >
254
+ <div class="size-3.5 rounded bg-black dark:bg-gray-400"></div>
255
+ <div>{conversation.model}</div>
256
+ <button
257
+ class="ml-auto flex size-6 items-center justify-center rounded bg-gray-50 text-xs hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700"
258
+ on:click={() => (conversations = conversations.filter((_, i) => i !== index))}
259
+ >
260
+
261
+ </button>
262
+ <button
263
+ class=" flex size-6 items-center justify-center rounded bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700"
264
+ >
265
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"
266
+ ><path
267
+ fill="currentColor"
268
+ d="M27 16.76v-1.53l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11.35 11.35 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.48 11.48 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11.35 11.35 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.48 11.48 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51ZM25.21 24l-3.43-1.16a8.86 8.86 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.36 9.36 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.86 8.86 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.36 9.36 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20Z"
269
+ /><path
270
+ fill="currentColor"
271
+ d="M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6Zm0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4Z"
272
+ /></svg
273
+ >
274
+ </button>
275
+ </div>
276
+ {/if}
277
+ {#if !viewCode}
278
+ {#each messages as message, i}
279
+ <PlaygroundMessage
280
+ class="border-b"
281
+ {message}
282
+ on:delete={() => deleteMessage(i)}
283
+ autofocus={conversations.length === 1 && !loading && i === messages.length - 1}
284
+ />
285
+ {/each}
286
+
287
+ <button
288
+ class="flex px-6 py-6 hover:bg-gray-50 dark:hover:bg-gray-800/50"
289
+ on:click={addMessage}
290
+ disabled={loading}
291
+ >
292
+ <div class="flex items-center gap-2 !p-0 text-sm font-semibold">
293
+ <svg
294
+ xmlns="http://www.w3.org/2000/svg"
295
+ width="1em"
296
+ height="1em"
297
+ viewBox="0 0 32 32"
298
+ class="text-lg"
299
+ ><path
300
+ fill="currentColor"
301
+ d="M16 2A14.172 14.172 0 0 0 2 16a14.172 14.172 0 0 0 14 14a14.172 14.172 0 0 0 14-14A14.172 14.172 0 0 0 16 2Zm8 15h-7v7h-2v-7H8v-2h7V8h2v7h7Z"
302
+ /><path fill="none" d="M24 17h-7v7h-2v-7H8v-2h7V8h2v7h7v2z" /></svg
303
+ >Add message
304
+ </div>
305
+ </button>
306
+ {:else}
307
+ <PlaygroundCode {...currentConversation} {...currentConversation.config} />
308
+ {/if}
309
+ </div>
310
+ {/each}
311
+ </div>
312
+ <div
313
+ class="fixed inset-x-0 bottom-0 flex h-20 items-center gap-2 overflow-hidden whitespace-nowrap px-3 md:absolute"
314
+ >
315
+ <button
316
+ type="button"
317
+ class="flex h-[39px] flex-none gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
318
+ >
319
+ <div
320
+ class="flex size-5 items-center justify-center rounded border border-black/5 bg-black/5 text-xs"
321
+ >
322
+ <svg
323
+ width="1em"
324
+ height="1em"
325
+ viewBox="0 0 24 25"
326
+ fill="none"
327
+ xmlns="http://www.w3.org/2000/svg"
328
+ >
329
+ <path
330
+ fill-rule="evenodd"
331
+ clip-rule="evenodd"
332
+ d="M5.41 9.41L4 8L12 0L20 8L18.59 9.41L13 3.83L13 17.5H11L11 3.83L5.41 9.41ZM22 17.5V23H2V17.5H0V23C0 23.5304 0.210714 24.0391 0.585786 24.4142C0.960859 24.7893 1.46957 25 2 25H22C22.5304 25 23.0391 24.7893 23.4142 24.4142C23.7893 24.0391 24 23.5304 24 23V17.5H22Z"
333
+ fill="currentColor"
334
+ />
335
+ </svg>
336
+ </div>
337
+
338
+ Share</button
339
+ >
340
+
341
+ <button
342
+ type="button"
343
+ on:click={reset}
344
+ class="flex size-[39px] flex-none items-center justify-center rounded-lg border border-gray-200 bg-white text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
345
+ ><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"
346
+ ><path fill="currentColor" d="M12 12h2v12h-2zm6 0h2v12h-2z" /><path
347
+ fill="currentColor"
348
+ d="M4 6v2h2v20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h2V6zm4 22V8h16v20zm4-26h8v2h-8z"
349
+ /></svg
350
+ ></button
351
+ >
352
+ <div class="flex-1 items-center justify-center text-center text-sm text-gray-500">
353
+ <span class="max-xl:hidden">0 tokens · Latency {latency}ms</span>
354
+ </div>
355
+ <button
356
+ type="button"
357
+ on:click={() => (viewCode = !viewCode)}
358
+ class="flex h-[39px] items-center gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
359
+ >
360
+ <svg
361
+ xmlns="http://www.w3.org/2000/svg"
362
+ width="1em"
363
+ height="1em"
364
+ class="text-base"
365
+ viewBox="0 0 32 32"
366
+ ><path
367
+ fill="currentColor"
368
+ d="m31 16l-7 7l-1.41-1.41L28.17 16l-5.58-5.59L24 9l7 7zM1 16l7-7l1.41 1.41L3.83 16l5.58 5.59L8 23l-7-7zm11.42 9.484L17.64 6l1.932.517L14.352 26z"
369
+ /></svg
370
+ >
371
+ {!viewCode ? 'View Code' : 'Hide Code'}</button
372
+ >
373
+ <button
374
+ on:click={() => {
375
+ viewCode = false;
376
+ loading ? abort() : submit();
377
+ }}
378
+ type="button"
379
+ class="flex h-[39px] w-24 items-center justify-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium text-white focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:focus:ring-gray-700 {loading ? 'bg-red-900 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700' : 'bg-black hover:bg-gray-900 dark:bg-blue-600 dark:hover:bg-blue-700'}"
380
+ >
381
+ {#if loading}
382
+ <div class="flex flex-none items-center gap-[3px]">
383
+ <span class="mr-2">Cancel</span>
384
+ <div
385
+ class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
386
+ style="animation-delay: 0.25s;"
387
+ />
388
+ <div
389
+ class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
390
+ style="animation-delay: 0.5s;"
391
+ />
392
+ <div
393
+ class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
394
+ style="animation-delay: 0.75s;"
395
+ />
396
+ </div>
397
+ {:else}
398
+ Run <span
399
+ class="inline-flex gap-0.5 rounded border border-white/20 bg-white/10 px-0.5 text-xs text-white/70"
400
+ >↵</span
401
+ >
402
+ {/if}
403
+ </button>
404
+ </div>
405
+ </div>
406
+ {#if conversations.length === 1}
407
+ <div class="flex flex-col p-3">
408
+ <div
409
+ class="flex flex-1 flex-col gap-6 overflow-y-hidden rounded-xl border border-gray-200/80 bg-gradient-to-b from-white via-white p-3 shadow-sm dark:border-white/5 dark:from-gray-800/40 dark:via-gray-800/40"
410
+ >
411
+ <PlaygroundModelSelector {compatibleModels} bind:currentModel={currentConversation.model} />
412
+ <div
413
+ class="group relative -mt-4 flex h-[26px] w-full items-center justify-center gap-2 rounded-lg bg-black px-5 text-sm text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-gray-700"
414
+ >
415
+ Compare with...
416
+ <svg
417
+ class="ml-0.5 flex-none opacity-50 group-hover:opacity-100"
418
+ xmlns="http://www.w3.org/2000/svg"
419
+ xmlns:xlink="http://www.w3.org/1999/xlink"
420
+ aria-hidden="true"
421
+ role="img"
422
+ width="1em"
423
+ height="1em"
424
+ preserveAspectRatio="xMidYMid meet"
425
+ viewBox="0 0 24 24"
426
+ ><path
427
+ d="M16.293 9.293L12 13.586L7.707 9.293l-1.414 1.414L12 16.414l5.707-5.707z"
428
+ fill="currentColor"
429
+ ></path></svg
430
+ >
431
+ <select
432
+ class="absolute inset-0 border-none bg-white text-base opacity-0 outline-none"
433
+ on:change|preventDefault={(e) => {
434
+ conversations = [
435
+ ...conversations,
436
+ {
437
+ id: String(Math.random()),
438
+ model: e.target.value,
439
+ config: { temperature: 0.5, maxTokens: 2048, streaming: true, jsonMode: false },
440
+ messages: startMessages
441
+ }
442
+ ];
443
+ }}
444
+ >
445
+ {#each compatibleModels as model}
446
+ <option value={model.id}>{model.id}</option>
447
+ {/each}
448
+ </select>
449
+ </div>
450
+
451
+ <PlaygroundOptions
452
+ bind:temperature={currentConversation.config.temperature}
453
+ bind:maxTokens={currentConversation.config.maxTokens}
454
+ bind:jsonMode={currentConversation.config.jsonMode}
455
+ bind:streaming={currentConversation.config.streaming}
456
+ />
457
+ <div class="mt-auto">
458
+ <div class="mb-3 flex items-center justify-between gap-2">
459
+ <label
460
+ for="default-range"
461
+ class="block text-sm font-medium text-gray-900 dark:text-white">API Quota</label
462
+ >
463
+ <span
464
+ class="rounded bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-300"
465
+ >Free</span
466
+ >
467
+
468
+ <div class="ml-auto w-12 text-right text-sm">76%</div>
469
+ </div>
470
+ <div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-700">
471
+ <div class="h-2 rounded-full bg-black dark:bg-gray-400" style="width: 75%"></div>
472
+ </div>
473
+ </div>
474
+ </div>
475
+ </div>
476
+ {/if}
477
+ </div>