Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
New model selector (design only) (#39)
Browse files- src/lib/components/Icons/IconSearch.svelte +21 -0
- src/lib/components/Icons/IconStar.svelte +22 -0
- src/lib/components/InferencePlayground/InferencePlayground.svelte +23 -5
- src/lib/components/InferencePlayground/InferencePlaygroundCodeSnippets.svelte +1 -1
- src/lib/components/InferencePlayground/InferencePlaygroundGenerationConfig.svelte +1 -1
- src/lib/components/InferencePlayground/InferencePlaygroundModelPickerModal.svelte +96 -0
- src/lib/components/InferencePlayground/InferencePlaygroundModelSelector.svelte +39 -15
src/lib/components/Icons/IconSearch.svelte
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
export let classNames = '';
|
| 3 |
+
</script>
|
| 4 |
+
|
| 5 |
+
<svg
|
| 6 |
+
class={classNames}
|
| 7 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 8 |
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
| 9 |
+
aria-hidden="true"
|
| 10 |
+
focusable="false"
|
| 11 |
+
role="img"
|
| 12 |
+
width="1em"
|
| 13 |
+
height="1em"
|
| 14 |
+
preserveAspectRatio="xMidYMid meet"
|
| 15 |
+
viewBox="0 0 32 32"
|
| 16 |
+
>
|
| 17 |
+
<path
|
| 18 |
+
d="M30 28.59L22.45 21A11 11 0 1 0 21 22.45L28.59 30zM5 14a9 9 0 1 1 9 9a9 9 0 0 1-9-9z"
|
| 19 |
+
fill="currentColor"
|
| 20 |
+
/>
|
| 21 |
+
</svg>
|
src/lib/components/Icons/IconStar.svelte
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
export let classNames = '';
|
| 3 |
+
</script>
|
| 4 |
+
|
| 5 |
+
<svg
|
| 6 |
+
class={classNames}
|
| 7 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 8 |
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
| 9 |
+
aria-hidden="true"
|
| 10 |
+
fill="none"
|
| 11 |
+
focusable="false"
|
| 12 |
+
role="img"
|
| 13 |
+
width="1em"
|
| 14 |
+
height="1em"
|
| 15 |
+
preserveAspectRatio="xMidYMid meet"
|
| 16 |
+
viewBox="0 0 32 32"
|
| 17 |
+
>
|
| 18 |
+
<path
|
| 19 |
+
d="M16 6.52l2.76 5.58l.46 1l1 .15l6.16.89l-4.38 4.3l-.75.73l.18 1l1.05 6.13l-5.51-2.89L16 23l-.93.49l-5.51 2.85l1-6.13l.18-1l-.74-.77l-4.42-4.35l6.16-.89l1-.15l.46-1L16 6.52M16 2l-4.55 9.22l-10.17 1.47l7.36 7.18L6.9 30l9.1-4.78L25.1 30l-1.74-10.13l7.36-7.17l-10.17-1.48z"
|
| 20 |
+
fill="currentColor"
|
| 21 |
+
/>
|
| 22 |
+
</svg>
|
src/lib/components/InferencePlayground/InferencePlayground.svelte
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
import PlaygroundOptions from './InferencePlaygroundGenerationConfig.svelte';
|
| 9 |
import PlaygroundTokenModal from './InferencePlaygroundHFTokenModal.svelte';
|
| 10 |
import PlaygroundModelSelector from './InferencePlaygroundModelSelector.svelte';
|
|
|
|
| 11 |
import Conversation from './InferencePlaygroundConversation.svelte';
|
| 12 |
import { onDestroy } from 'svelte';
|
| 13 |
import { type ChatCompletionInputMessage } from '@huggingface/tasks';
|
|
@@ -16,7 +17,6 @@
|
|
| 16 |
import IconShare from '../Icons/IconShare.svelte';
|
| 17 |
import IconDelete from '../Icons/IconDelete.svelte';
|
| 18 |
import IconCode from '../Icons/IconCode.svelte';
|
| 19 |
-
import IconCaret from '../Icons/IconCaret.svelte';
|
| 20 |
|
| 21 |
export let models: ModelEntryWithTokenizer[];
|
| 22 |
|
|
@@ -40,6 +40,7 @@
|
|
| 40 |
let hfToken: string | null = import.meta.env.VITE_HF_TOKEN;
|
| 41 |
let viewCode = false;
|
| 42 |
let showTokenModal = false;
|
|
|
|
| 43 |
let loading = false;
|
| 44 |
let tokens = 0;
|
| 45 |
let latency = 0;
|
|
@@ -196,6 +197,14 @@
|
|
| 196 |
function changeSelectedModel(modelIdx: number) {
|
| 197 |
conversations[0] = { ...conversations[0], model: models[modelIdx] };
|
| 198 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
</script>
|
| 200 |
|
| 201 |
{#if showTokenModal}
|
|
@@ -210,6 +219,14 @@
|
|
| 210 |
/>
|
| 211 |
{/if}
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
| 214 |
<div
|
| 215 |
class="w-dvh grid divide-gray-200 overflow-hidden bg-gray-100/50 max-md:divide-y md:h-dvh dark:[color-scheme:dark]
|
|
@@ -337,10 +354,11 @@
|
|
| 337 |
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"
|
| 338 |
>
|
| 339 |
<PlaygroundModelSelector
|
| 340 |
-
|
| 341 |
-
|
|
|
|
| 342 |
/>
|
| 343 |
-
<div
|
| 344 |
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"
|
| 345 |
>
|
| 346 |
Compare with...
|
|
@@ -363,7 +381,7 @@
|
|
| 363 |
<option value={model.id}>{model.id}</option>
|
| 364 |
{/each}
|
| 365 |
</select>
|
| 366 |
-
</div>
|
| 367 |
|
| 368 |
<PlaygroundOptions bind:conversation={conversations[0]} />
|
| 369 |
<div class="mt-auto">
|
|
|
|
| 8 |
import PlaygroundOptions from './InferencePlaygroundGenerationConfig.svelte';
|
| 9 |
import PlaygroundTokenModal from './InferencePlaygroundHFTokenModal.svelte';
|
| 10 |
import PlaygroundModelSelector from './InferencePlaygroundModelSelector.svelte';
|
| 11 |
+
import ModelPickerModal from './InferencePlaygroundModelPickerModal.svelte';
|
| 12 |
import Conversation from './InferencePlaygroundConversation.svelte';
|
| 13 |
import { onDestroy } from 'svelte';
|
| 14 |
import { type ChatCompletionInputMessage } from '@huggingface/tasks';
|
|
|
|
| 17 |
import IconShare from '../Icons/IconShare.svelte';
|
| 18 |
import IconDelete from '../Icons/IconDelete.svelte';
|
| 19 |
import IconCode from '../Icons/IconCode.svelte';
|
|
|
|
| 20 |
|
| 21 |
export let models: ModelEntryWithTokenizer[];
|
| 22 |
|
|
|
|
| 40 |
let hfToken: string | null = import.meta.env.VITE_HF_TOKEN;
|
| 41 |
let viewCode = false;
|
| 42 |
let showTokenModal = false;
|
| 43 |
+
let showModelPickerModal = false;
|
| 44 |
let loading = false;
|
| 45 |
let tokens = 0;
|
| 46 |
let latency = 0;
|
|
|
|
| 197 |
function changeSelectedModel(modelIdx: number) {
|
| 198 |
conversations[0] = { ...conversations[0], model: models[modelIdx] };
|
| 199 |
}
|
| 200 |
+
|
| 201 |
+
function changeModel(modelId: string) {
|
| 202 |
+
const model = models.find((m) => m.id === modelId);
|
| 203 |
+
if (!model) {
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
conversations[0].model = model;
|
| 207 |
+
}
|
| 208 |
</script>
|
| 209 |
|
| 210 |
{#if showTokenModal}
|
|
|
|
| 219 |
/>
|
| 220 |
{/if}
|
| 221 |
|
| 222 |
+
{#if showModelPickerModal}
|
| 223 |
+
<ModelPickerModal
|
| 224 |
+
{models}
|
| 225 |
+
on:modelSelected={(e) => changeModel(e.detail)}
|
| 226 |
+
on:close={(e) => (showModelPickerModal = false)}
|
| 227 |
+
/>
|
| 228 |
+
{/if}
|
| 229 |
+
|
| 230 |
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
| 231 |
<div
|
| 232 |
class="w-dvh grid divide-gray-200 overflow-hidden bg-gray-100/50 max-md:divide-y md:h-dvh dark:[color-scheme:dark]
|
|
|
|
| 354 |
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"
|
| 355 |
>
|
| 356 |
<PlaygroundModelSelector
|
| 357 |
+
{models}
|
| 358 |
+
conversation={conversations[0]}
|
| 359 |
+
on:click={() => (showModelPickerModal = open)}
|
| 360 |
/>
|
| 361 |
+
<!-- <div
|
| 362 |
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"
|
| 363 |
>
|
| 364 |
Compare with...
|
|
|
|
| 381 |
<option value={model.id}>{model.id}</option>
|
| 382 |
{/each}
|
| 383 |
</select>
|
| 384 |
+
</div> -->
|
| 385 |
|
| 386 |
<PlaygroundOptions bind:conversation={conversations[0]} />
|
| 387 |
<div class="mt-auto">
|
src/lib/components/InferencePlayground/InferencePlaygroundCodeSnippets.svelte
CHANGED
|
@@ -253,7 +253,7 @@ print(output.choices[0].message)`
|
|
| 253 |
<div class="flex items-center justify-between px-4 pb-4 pt-6">
|
| 254 |
<h2 class="font-semibold">{label}</h2>
|
| 255 |
<button
|
| 256 |
-
class="flex items-center gap-x-1.5 rounded-md bg-gray-200 px-1.5 py-0.5 transition dark:bg-gray-950
|
| 257 |
on:click={(e) => {
|
| 258 |
const el = e.currentTarget;
|
| 259 |
el.classList.add('text-green-500');
|
|
|
|
| 253 |
<div class="flex items-center justify-between px-4 pb-4 pt-6">
|
| 254 |
<h2 class="font-semibold">{label}</h2>
|
| 255 |
<button
|
| 256 |
+
class="flex items-center gap-x-1.5 rounded-md bg-gray-200 px-1.5 py-0.5 text-sm transition dark:bg-gray-950"
|
| 257 |
on:click={(e) => {
|
| 258 |
const el = e.currentTarget;
|
| 259 |
el.classList.add('text-green-500');
|
src/lib/components/InferencePlayground/InferencePlaygroundGenerationConfig.svelte
CHANGED
|
@@ -13,7 +13,7 @@
|
|
| 13 |
$: maxTokens = Math.min(modelMaxLength ?? GENERATION_CONFIG_SETTINGS['max_tokens'].max, 64_000);
|
| 14 |
</script>
|
| 15 |
|
| 16 |
-
<div class="flex flex-col gap-y-
|
| 17 |
{#each GENERATION_CONFIG_KEYS as key}
|
| 18 |
{@const { label, min, step } = GENERATION_CONFIG_SETTINGS[key]}
|
| 19 |
{@const max = key === 'max_tokens' ? maxTokens : GENERATION_CONFIG_SETTINGS[key].max}
|
|
|
|
| 13 |
$: maxTokens = Math.min(modelMaxLength ?? GENERATION_CONFIG_SETTINGS['max_tokens'].max, 64_000);
|
| 14 |
</script>
|
| 15 |
|
| 16 |
+
<div class="flex flex-col gap-y-7 {classNames}">
|
| 17 |
{#each GENERATION_CONFIG_KEYS as key}
|
| 18 |
{@const { label, min, step } = GENERATION_CONFIG_SETTINGS[key]}
|
| 19 |
{@const max = key === 'max_tokens' ? maxTokens : GENERATION_CONFIG_SETTINGS[key].max}
|
src/lib/components/InferencePlayground/InferencePlaygroundModelPickerModal.svelte
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import type { ModelEntryWithTokenizer } from '$lib/types';
|
| 3 |
+
import { createEventDispatcher } from 'svelte';
|
| 4 |
+
import IconSearch from '../Icons/IconSearch.svelte';
|
| 5 |
+
import IconStar from '../Icons/IconStar.svelte';
|
| 6 |
+
|
| 7 |
+
export let models: ModelEntryWithTokenizer[];
|
| 8 |
+
|
| 9 |
+
let backdropEl: HTMLDivElement;
|
| 10 |
+
|
| 11 |
+
const dispatch = createEventDispatcher<{ modelSelected: string; close: void }>();
|
| 12 |
+
|
| 13 |
+
function handleKeydown(event: KeyboardEvent) {
|
| 14 |
+
// close on ESC
|
| 15 |
+
if (event.key === 'Escape') {
|
| 16 |
+
event.preventDefault();
|
| 17 |
+
dispatch('close');
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
function handleBackdropClick(event: MouseEvent) {
|
| 22 |
+
if (window?.getSelection()?.toString()) {
|
| 23 |
+
return;
|
| 24 |
+
}
|
| 25 |
+
if (event.target === backdropEl) {
|
| 26 |
+
dispatch('close');
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
</script>
|
| 30 |
+
|
| 31 |
+
<svelte:window on:keydown={handleKeydown} />
|
| 32 |
+
|
| 33 |
+
<div
|
| 34 |
+
class="fixed inset-0 z-10 flex h-screen items-start justify-center bg-black/85 pt-32"
|
| 35 |
+
bind:this={backdropEl}
|
| 36 |
+
on:click|stopPropagation={handleBackdropClick}
|
| 37 |
+
>
|
| 38 |
+
<div class="flex w-full max-w-[600px] items-start justify-center p-10">
|
| 39 |
+
<div
|
| 40 |
+
class="flex h-full w-full flex-col overflow-hidden rounded-lg border bg-white text-gray-900 shadow-md"
|
| 41 |
+
>
|
| 42 |
+
<div class="flex items-center border-b px-3">
|
| 43 |
+
<IconSearch classNames="mr-2 text-sm" />
|
| 44 |
+
<input
|
| 45 |
+
autofocus
|
| 46 |
+
class="flex h-10 w-full rounded-md bg-transparent py-3 text-sm placeholder-gray-400 outline-none"
|
| 47 |
+
placeholder="Search models ..."
|
| 48 |
+
value=""
|
| 49 |
+
/>
|
| 50 |
+
</div>
|
| 51 |
+
<div class="max-h-[300px] overflow-y-auto overflow-x-hidden">
|
| 52 |
+
<div class="p-1">
|
| 53 |
+
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
|
| 54 |
+
<div>
|
| 55 |
+
<div class="flex cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-gray-100">
|
| 56 |
+
<IconStar classNames="lucide lucide-star mr-2 h-4 w-4 text-yellow-400" />
|
| 57 |
+
<span class="inline-flex items-center"
|
| 58 |
+
><span class="text-gray-500">meta-llama</span><span class="mx-1 text-black">/</span
|
| 59 |
+
><span class="text-black">Meta-Llama-3-70B-Instruct</span></span
|
| 60 |
+
>
|
| 61 |
+
</div>
|
| 62 |
+
<div class="flex cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-gray-100">
|
| 63 |
+
<IconStar classNames="lucide lucide-star mr-2 h-4 w-4 text-yellow-400" />
|
| 64 |
+
<span class="inline-flex items-center"
|
| 65 |
+
><span class="text-gray-500">mistralai</span><span class="mx-1 text-black">/</span
|
| 66 |
+
><span class="text-black">Mixtral-8x7B-Instruct-v0.1</span></span
|
| 67 |
+
>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
<div class="mx-1 h-px bg-gray-200"></div>
|
| 72 |
+
<div class="p-1">
|
| 73 |
+
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other Models</div>
|
| 74 |
+
<div>
|
| 75 |
+
{#each models as model}
|
| 76 |
+
{@const [nameSpace, modelName] = model.id.split('/')}
|
| 77 |
+
<button
|
| 78 |
+
class="flex cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-gray-100"
|
| 79 |
+
on:click={() => {
|
| 80 |
+
dispatch('modelSelected', model.id);
|
| 81 |
+
dispatch('close');
|
| 82 |
+
}}
|
| 83 |
+
>
|
| 84 |
+
<span class="inline-flex items-center"
|
| 85 |
+
><span class="text-gray-500">{nameSpace}</span><span class="mx-1 text-black"
|
| 86 |
+
>/</span
|
| 87 |
+
><span class="text-black">{modelName}</span></span
|
| 88 |
+
>
|
| 89 |
+
</button>
|
| 90 |
+
{/each}
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
src/lib/components/InferencePlayground/InferencePlaygroundModelSelector.svelte
CHANGED
|
@@ -1,26 +1,50 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import {
|
| 3 |
-
import
|
| 4 |
|
| 5 |
-
export let
|
|
|
|
| 6 |
export let disabled = false;
|
| 7 |
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</script>
|
| 10 |
|
| 11 |
-
<div>
|
| 12 |
<label
|
| 13 |
for="countries"
|
| 14 |
-
class="
|
| 15 |
-
>Models<span class="ml-4 font-normal text-gray-400">{
|
| 16 |
</label>
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class="
|
| 20 |
-
on:
|
| 21 |
>
|
| 22 |
-
|
| 23 |
-
<
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
</div>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import type { Conversation, ModelEntryWithTokenizer } from '$lib/types';
|
| 3 |
+
import IconCaret from '../Icons/IconCaret.svelte';
|
| 4 |
|
| 5 |
+
export let models: ModelEntryWithTokenizer[] = [];
|
| 6 |
+
export let conversation: Conversation;
|
| 7 |
export let disabled = false;
|
| 8 |
|
| 9 |
+
async function getAvatarUrl(orgName: string) {
|
| 10 |
+
const url = `https://huggingface.co/api/organizations/${orgName}/avatar`;
|
| 11 |
+
const res = await fetch(url);
|
| 12 |
+
if (!res.ok) {
|
| 13 |
+
console.error(`Error getting avatar url for org: ${orgName}`, res.status, res.statusText);
|
| 14 |
+
return;
|
| 15 |
+
}
|
| 16 |
+
const json = await res.json();
|
| 17 |
+
const { avatarUrl } = json;
|
| 18 |
+
return avatarUrl;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
$: [nameSpace, modelName] = conversation.model.id.split('/');
|
| 22 |
</script>
|
| 23 |
|
| 24 |
+
<div class="flex flex-col gap-2">
|
| 25 |
<label
|
| 26 |
for="countries"
|
| 27 |
+
class="flex items-baseline text-sm font-medium text-gray-900 dark:text-white"
|
| 28 |
+
>Models<span class="ml-4 font-normal text-gray-400">{models.length}</span>
|
| 29 |
</label>
|
| 30 |
+
|
| 31 |
+
<button
|
| 32 |
+
class="flex items-center justify-between gap-6 overflow-hidden whitespace-nowrap rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight shadow dark:bg-gray-700"
|
| 33 |
+
on:click
|
| 34 |
>
|
| 35 |
+
<div class="flex flex-col items-start">
|
| 36 |
+
<div class="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-300">
|
| 37 |
+
{#await getAvatarUrl(nameSpace) then avatarUrl}
|
| 38 |
+
<img
|
| 39 |
+
class="size-3 flex-none rounded bg-gray-200 object-cover"
|
| 40 |
+
src={avatarUrl}
|
| 41 |
+
alt="{nameSpace} avatar"
|
| 42 |
+
/>
|
| 43 |
+
{/await}
|
| 44 |
+
{nameSpace}
|
| 45 |
+
</div>
|
| 46 |
+
<div>{modelName}</div>
|
| 47 |
+
</div>
|
| 48 |
+
<IconCaret classNames="text-xl bg-gray-100 dark:bg-gray-500 rounded" />
|
| 49 |
+
</button>
|
| 50 |
</div>
|