Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Thomas G. Lopes
commited on
Commit
·
2087f3e
1
Parent(s):
9b4caaa
improve model selector experience
Browse files
src/lib/components/inference-playground/model-selector-modal.svelte
CHANGED
|
@@ -1,83 +1,87 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import type { Conversation } from "$lib/types.js";
|
| 3 |
|
| 4 |
-
import {
|
| 5 |
|
|
|
|
| 6 |
import { models } from "$lib/state/models.svelte.js";
|
| 7 |
import fuzzysearch from "$lib/utils/search.js";
|
|
|
|
| 8 |
import IconSearch from "~icons/carbon/search";
|
| 9 |
import IconStar from "~icons/carbon/star";
|
| 10 |
|
| 11 |
interface Props {
|
|
|
|
|
|
|
| 12 |
conversation: Conversation;
|
| 13 |
}
|
| 14 |
|
| 15 |
-
let { conversation }: Props = $props();
|
| 16 |
|
| 17 |
let backdropEl = $state<HTMLDivElement>();
|
| 18 |
-
let highlightIdx = $state(
|
| 19 |
let ignoreCursorHighlight = $state(false);
|
| 20 |
let containerEl = $state<HTMLDivElement>();
|
| 21 |
let query = $state("");
|
| 22 |
|
| 23 |
-
const
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
function handleKeydown(
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
if (key === "
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
event.preventDefault();
|
| 46 |
-
const highlightedEl = document.querySelector(".highlighted");
|
| 47 |
-
if (highlightedEl) {
|
| 48 |
-
(highlightedEl as HTMLButtonElement).click();
|
| 49 |
-
}
|
| 50 |
-
} else if (key === "ArrowUp") {
|
| 51 |
-
event.preventDefault();
|
| 52 |
-
highlightIdx--;
|
| 53 |
-
scrollLogicalPosition = "start";
|
| 54 |
ignoreCursorHighlight = true;
|
| 55 |
-
} else if (key === "ArrowDown") {
|
| 56 |
-
|
| 57 |
-
highlightIdx++;
|
| 58 |
ignoreCursorHighlight = true;
|
|
|
|
|
|
|
| 59 |
}
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
scrollToResult(
|
| 63 |
}
|
| 64 |
|
| 65 |
-
async function scrollToResult(
|
| 66 |
await tick();
|
| 67 |
-
const highlightedEl = document.querySelector("
|
| 68 |
-
|
| 69 |
-
const { bottom: containerBottom, top: containerTop } = containerEl.getBoundingClientRect();
|
| 70 |
-
const { bottom: highlightedBottom, top: highlightedTop } = highlightedEl.getBoundingClientRect();
|
| 71 |
-
if (highlightedBottom > containerBottom || containerTop > highlightedTop) {
|
| 72 |
-
highlightedEl.scrollIntoView({ block });
|
| 73 |
-
}
|
| 74 |
-
}
|
| 75 |
}
|
| 76 |
|
| 77 |
function highlightRow(idx: number) {
|
| 78 |
-
if (
|
| 79 |
-
|
| 80 |
-
}
|
| 81 |
}
|
| 82 |
|
| 83 |
function handleBackdropClick(event: MouseEvent) {
|
|
@@ -86,7 +90,7 @@
|
|
| 86 |
return;
|
| 87 |
}
|
| 88 |
if (event.target === backdropEl) {
|
| 89 |
-
|
| 90 |
}
|
| 91 |
}
|
| 92 |
</script>
|
|
@@ -109,70 +113,51 @@
|
|
| 109 |
<div class="mr-2 text-sm">
|
| 110 |
<IconSearch />
|
| 111 |
</div>
|
| 112 |
-
<!-- svelte-ignore a11y_autofocus -->
|
| 113 |
<input
|
| 114 |
-
autofocus
|
| 115 |
class="flex h-10 w-full rounded-md bg-transparent py-3 text-sm placeholder-gray-400 outline-hidden"
|
| 116 |
placeholder="Search models ..."
|
| 117 |
bind:value={query}
|
| 118 |
/>
|
| 119 |
</div>
|
| 120 |
<div class="max-h-[300px] overflow-x-hidden overflow-y-auto">
|
| 121 |
-
{#
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
</div>
|
|
|
|
|
|
|
|
|
|
| 149 |
{/if}
|
| 150 |
-
{#if
|
| 151 |
-
<div>
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
{@const [nameSpace, modelName] = model.id.split("/")}
|
| 156 |
-
{@const idx = featuredModels.length + _idx}
|
| 157 |
-
<button
|
| 158 |
-
class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm {highlightIdx === idx
|
| 159 |
-
? 'highlighted bg-gray-100 dark:bg-gray-800'
|
| 160 |
-
: ''}"
|
| 161 |
-
onmouseenter={() => highlightRow(idx)}
|
| 162 |
-
onclick={() => {
|
| 163 |
-
dispatch("modelSelected", model.id);
|
| 164 |
-
dispatch("close");
|
| 165 |
-
}}
|
| 166 |
-
>
|
| 167 |
-
<span class="inline-flex items-center"
|
| 168 |
-
><span class="text-gray-500 dark:text-gray-400">{nameSpace}</span><span
|
| 169 |
-
class="mx-1 text-gray-300 dark:text-gray-700">/</span
|
| 170 |
-
><span class="text-black dark:text-white">{modelName}</span></span
|
| 171 |
-
>
|
| 172 |
-
</button>
|
| 173 |
-
{/each}
|
| 174 |
-
</div>
|
| 175 |
-
</div>
|
| 176 |
{/if}
|
| 177 |
</div>
|
| 178 |
</div>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import type { Conversation, ModelWithTokenizer } from "$lib/types.js";
|
| 3 |
|
| 4 |
+
import { tick } from "svelte";
|
| 5 |
|
| 6 |
+
import { autofocus } from "$lib/actions/autofocus.js";
|
| 7 |
import { models } from "$lib/state/models.svelte.js";
|
| 8 |
import fuzzysearch from "$lib/utils/search.js";
|
| 9 |
+
import { watch } from "runed";
|
| 10 |
import IconSearch from "~icons/carbon/search";
|
| 11 |
import IconStar from "~icons/carbon/star";
|
| 12 |
|
| 13 |
interface Props {
|
| 14 |
+
onModelSelect?: (model: string) => void;
|
| 15 |
+
onClose?: () => void;
|
| 16 |
conversation: Conversation;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
let { onModelSelect, onClose, conversation }: Props = $props();
|
| 20 |
|
| 21 |
let backdropEl = $state<HTMLDivElement>();
|
| 22 |
+
let highlightIdx = $state(-1);
|
| 23 |
let ignoreCursorHighlight = $state(false);
|
| 24 |
let containerEl = $state<HTMLDivElement>();
|
| 25 |
let query = $state("");
|
| 26 |
|
| 27 |
+
const trending = $derived(fuzzysearch({ needle: query, haystack: models.trending, property: "id" }));
|
| 28 |
+
const other = $derived(fuzzysearch({ needle: query, haystack: models.nonTrending, property: "id" }));
|
| 29 |
+
const queried = $derived(trending.concat(other));
|
| 30 |
+
function getModelIdx(model: ModelWithTokenizer) {
|
| 31 |
+
return queried.findIndex(m => m.id === model.id);
|
| 32 |
+
}
|
| 33 |
+
const highlighted = $derived(queried[highlightIdx]);
|
| 34 |
|
| 35 |
+
watch(
|
| 36 |
+
() => queried,
|
| 37 |
+
(curr, prev) => {
|
| 38 |
+
const prevModel = prev?.[highlightIdx];
|
| 39 |
+
if (prevModel) {
|
| 40 |
+
// maintain model selection
|
| 41 |
+
highlightIdx = Math.max(
|
| 42 |
+
0,
|
| 43 |
+
curr.findIndex(model => model.id === prevModel?.id)
|
| 44 |
+
);
|
| 45 |
+
} else {
|
| 46 |
+
highlightIdx = curr.findIndex(model => model.id === conversation.model.id);
|
| 47 |
+
}
|
| 48 |
+
scrollToResult();
|
| 49 |
}
|
| 50 |
+
);
|
| 51 |
|
| 52 |
+
function selectModel(model: ModelWithTokenizer) {
|
| 53 |
+
onModelSelect?.(model.id);
|
| 54 |
+
onClose?.();
|
| 55 |
+
}
|
| 56 |
|
| 57 |
+
function handleKeydown(e: KeyboardEvent) {
|
| 58 |
+
if (e.key === "Escape") {
|
| 59 |
+
onClose?.();
|
| 60 |
+
} else if (e.key === "Enter") {
|
| 61 |
+
if (highlighted) selectModel(highlighted);
|
| 62 |
+
} else if (e.key === "ArrowUp") {
|
| 63 |
+
if (highlightIdx > 0) highlightIdx--;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
ignoreCursorHighlight = true;
|
| 65 |
+
} else if (e.key === "ArrowDown") {
|
| 66 |
+
if (highlightIdx < queried.length - 1) highlightIdx++;
|
|
|
|
| 67 |
ignoreCursorHighlight = true;
|
| 68 |
+
} else {
|
| 69 |
+
return;
|
| 70 |
}
|
| 71 |
+
e.preventDefault();
|
| 72 |
+
|
| 73 |
+
scrollToResult();
|
| 74 |
}
|
| 75 |
|
| 76 |
+
async function scrollToResult() {
|
| 77 |
await tick();
|
| 78 |
+
const highlightedEl = document.querySelector("[data-model][data-highlighted]");
|
| 79 |
+
highlightedEl?.scrollIntoView({ block: "nearest" });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
|
| 82 |
function highlightRow(idx: number) {
|
| 83 |
+
if (ignoreCursorHighlight) return;
|
| 84 |
+
highlightIdx = idx;
|
|
|
|
| 85 |
}
|
| 86 |
|
| 87 |
function handleBackdropClick(event: MouseEvent) {
|
|
|
|
| 90 |
return;
|
| 91 |
}
|
| 92 |
if (event.target === backdropEl) {
|
| 93 |
+
onClose?.();
|
| 94 |
}
|
| 95 |
}
|
| 96 |
</script>
|
|
|
|
| 113 |
<div class="mr-2 text-sm">
|
| 114 |
<IconSearch />
|
| 115 |
</div>
|
|
|
|
| 116 |
<input
|
| 117 |
+
use:autofocus
|
| 118 |
class="flex h-10 w-full rounded-md bg-transparent py-3 text-sm placeholder-gray-400 outline-hidden"
|
| 119 |
placeholder="Search models ..."
|
| 120 |
bind:value={query}
|
| 121 |
/>
|
| 122 |
</div>
|
| 123 |
<div class="max-h-[300px] overflow-x-hidden overflow-y-auto">
|
| 124 |
+
{#snippet modelEntry(model: ModelWithTokenizer, trending?: boolean)}
|
| 125 |
+
{@const idx = getModelIdx(model)}
|
| 126 |
+
{@const [nameSpace, modelName] = model.id.split("/")}
|
| 127 |
+
<button
|
| 128 |
+
class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm
|
| 129 |
+
data-[highlighted]:bg-gray-100 data-[highlighted]:dark:bg-gray-800"
|
| 130 |
+
data-highlighted={highlightIdx === idx ? true : undefined}
|
| 131 |
+
data-model
|
| 132 |
+
onmouseenter={() => highlightRow(idx)}
|
| 133 |
+
onclick={() => {
|
| 134 |
+
onModelSelect?.(model.id);
|
| 135 |
+
onClose?.();
|
| 136 |
+
}}
|
| 137 |
+
>
|
| 138 |
+
{#if trending}
|
| 139 |
+
<div class="lucide lucide-star mr-1.5 size-4 text-yellow-400">
|
| 140 |
+
<IconStar />
|
| 141 |
+
</div>
|
| 142 |
+
{/if}
|
| 143 |
+
<span class="inline-flex items-center"
|
| 144 |
+
><span class="text-gray-500 dark:text-gray-400">{nameSpace}</span><span
|
| 145 |
+
class="mx-1 text-gray-300 dark:text-gray-700">/</span
|
| 146 |
+
><span class="text-black dark:text-white">{modelName}</span></span
|
| 147 |
+
>
|
| 148 |
+
</button>
|
| 149 |
+
{/snippet}
|
| 150 |
+
{#if trending.length > 0}
|
| 151 |
+
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
|
| 152 |
+
{#each trending as model}
|
| 153 |
+
{@render modelEntry(model, true)}
|
| 154 |
+
{/each}
|
| 155 |
{/if}
|
| 156 |
+
{#if other.length > 0}
|
| 157 |
+
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other models</div>
|
| 158 |
+
{#each other as model}
|
| 159 |
+
{@render modelEntry(model, false)}
|
| 160 |
+
{/each}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
{/if}
|
| 162 |
</div>
|
| 163 |
</div>
|
src/lib/components/inference-playground/model-selector.svelte
CHANGED
|
@@ -58,11 +58,7 @@
|
|
| 58 |
</div>
|
| 59 |
|
| 60 |
{#if showModelPickerModal}
|
| 61 |
-
<ModelSelectorModal
|
| 62 |
-
{conversation}
|
| 63 |
-
on:modelSelected={e => changeModel(e.detail)}
|
| 64 |
-
on:close={() => (showModelPickerModal = false)}
|
| 65 |
-
/>
|
| 66 |
{/if}
|
| 67 |
|
| 68 |
<ProviderSelect bind:conversation />
|
|
|
|
| 58 |
</div>
|
| 59 |
|
| 60 |
{#if showModelPickerModal}
|
| 61 |
+
<ModelSelectorModal {conversation} onModelSelect={changeModel} onClose={() => (showModelPickerModal = false)} />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
{/if}
|
| 63 |
|
| 64 |
<ProviderSelect bind:conversation />
|
src/lib/components/inference-playground/playground.svelte
CHANGED
|
@@ -42,10 +42,10 @@
|
|
| 42 |
| [GenerationStatistics, GenerationStatistics]
|
| 43 |
);
|
| 44 |
|
| 45 |
-
|
| 46 |
session.project.conversations.some(conversation => isSystemPromptSupported(conversation.model))
|
| 47 |
);
|
| 48 |
-
|
| 49 |
|
| 50 |
function reset() {
|
| 51 |
session.project.conversations = session.project.conversations.map(conversation => {
|
|
@@ -412,7 +412,7 @@
|
|
| 412 |
{#if selectCompareModelOpen}
|
| 413 |
<ModelSelectorModal
|
| 414 |
conversation={session.project.conversations[0]!}
|
| 415 |
-
|
| 416 |
-
|
| 417 |
/>
|
| 418 |
{/if}
|
|
|
|
| 42 |
| [GenerationStatistics, GenerationStatistics]
|
| 43 |
);
|
| 44 |
|
| 45 |
+
const systemPromptSupported = $derived(
|
| 46 |
session.project.conversations.some(conversation => isSystemPromptSupported(conversation.model))
|
| 47 |
);
|
| 48 |
+
const compareActive = $derived(session.project.conversations.length === 2);
|
| 49 |
|
| 50 |
function reset() {
|
| 51 |
session.project.conversations = session.project.conversations.map(conversation => {
|
|
|
|
| 412 |
{#if selectCompareModelOpen}
|
| 413 |
<ModelSelectorModal
|
| 414 |
conversation={session.project.conversations[0]!}
|
| 415 |
+
onModelSelect={addCompareModel}
|
| 416 |
+
onClose={() => (selectCompareModelOpen = false)}
|
| 417 |
/>
|
| 418 |
{/if}
|
src/lib/state/models.svelte.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ModelWithTokenizer } from "$lib/types.js";
|
|
| 4 |
class Models {
|
| 5 |
all = $derived(page.data.models as ModelWithTokenizer[]);
|
| 6 |
trending = $derived(this.all.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
|
|
|
|
| 7 |
}
|
| 8 |
|
| 9 |
export const models = new Models();
|
|
|
|
| 4 |
class Models {
|
| 5 |
all = $derived(page.data.models as ModelWithTokenizer[]);
|
| 6 |
trending = $derived(this.all.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
|
| 7 |
+
nonTrending = $derived(this.all.filter(m => !this.trending.includes(m)));
|
| 8 |
}
|
| 9 |
|
| 10 |
export const models = new Models();
|