mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-25 22:21:03 +02:00
feat: WIP
This commit is contained in:
@@ -57,26 +57,22 @@
|
||||
|
||||
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog.Content
|
||||
class="flex flex-col md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-360!"
|
||||
class="grid gap-0 p-0 md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-360!" style="grid-template-columns: auto 1fr;"
|
||||
>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<Boxes class="h-4 w-4" />
|
||||
Discover Models
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="flex min-h-0 flex-1">
|
||||
<aside class="flex w-88 shrink-0 flex-col border-r border-border/40">
|
||||
<div class="p-2">
|
||||
<!-- <div class="flex min-h-0 flex-1"> -->
|
||||
<aside
|
||||
class="sticky top-0 w-100 shrink-0 self-start border-r border-border/40 bg-background overflow-y-auto md:p-4 h-full space-y-1"
|
||||
>
|
||||
<div class="p-2 sticky top-0 z-99">
|
||||
<SearchInput
|
||||
class=""
|
||||
bind:value={searchQuery}
|
||||
placeholder="Search models..."
|
||||
onInput={handleSearchInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||
<div>
|
||||
{#if modelsHubStore.loading}
|
||||
<p class="p-4 text-sm text-muted-foreground">Loading models...</p>
|
||||
{:else if modelsHubStore.error}
|
||||
@@ -94,11 +90,11 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="min-w-0 flex-1 overflow-y-auto">
|
||||
<main>
|
||||
{#if selectedId}
|
||||
<ModelsDiscoverDetails modelId={selectedId} />
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
modalities?: ModelModalities;
|
||||
supportsThinking?: boolean;
|
||||
draftVariants?: DraftVariant[];
|
||||
/** Allow badges to wrap onto new lines instead of truncating. */
|
||||
wrap?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -35,6 +37,7 @@
|
||||
showRawTooltip = false,
|
||||
supportsThinking = false,
|
||||
tags,
|
||||
wrap = false,
|
||||
...rest
|
||||
}: Props = $props();
|
||||
|
||||
@@ -73,7 +76,7 @@
|
||||
{#if !hideOrgName && parsed.orgName}{parsed.orgName}/{/if}{displayName}
|
||||
</span>
|
||||
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<span class="inline-flex items-center gap-1 {wrap ? 'flex-wrap' : ''}">
|
||||
{#if parsed.variant}
|
||||
<span class={variantBadgeClass} title={`${parsed.variant.toUpperCase()} draft model`}>
|
||||
{parsed.variant}
|
||||
@@ -116,7 +119,7 @@
|
||||
</span>
|
||||
{/snippet}
|
||||
|
||||
<span class="flex min-w-0 items-center gap-1.5 {className}" {...rest}>
|
||||
<span class="flex min-w-0 items-center gap-1.5 {wrap ? 'flex-wrap' : ''} {className}" {...rest}>
|
||||
{#if showRawTooltip}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger class="flex min-w-0 items-center gap-1.5">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { HuggingFaceService } from '$lib/services';
|
||||
import { DARK_INVERT_AVATAR_ORGS } from '$lib/constants';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
|
||||
interface Props {
|
||||
@@ -14,6 +15,9 @@
|
||||
let avatarError = $state(false);
|
||||
let quantError = $state(false);
|
||||
|
||||
let invertAvatar = $derived(DARK_INVERT_AVATAR_ORGS.includes(org));
|
||||
let invertQuant = $derived(DARK_INVERT_AVATAR_ORGS.includes(quantOrg ?? ''));
|
||||
|
||||
// Monogram fallback: org initial on a hue derived from its name, so each org
|
||||
// gets a stable distinct color.
|
||||
let hue = $derived.by(() => {
|
||||
@@ -44,19 +48,21 @@
|
||||
{org.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
{:else}
|
||||
<img
|
||||
src={HuggingFaceService.getAvatarUrl(org)}
|
||||
onerror={() => (avatarError = true)}
|
||||
class="h-9 w-9 rounded-md bg-gray-200 p-0.5"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="rounded-md">
|
||||
<img
|
||||
src={HuggingFaceService.getAvatarUrl(org)}
|
||||
onerror={() => (avatarError = true)}
|
||||
class="h-9 w-9 rounded-md {invertAvatar ? 'dark:invert' : ''}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if quantOrg}
|
||||
{#if quantOrg && quantOrg !== org}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger
|
||||
class="absolute -bottom-0.75 -right-0.75 h-4.25 w-4.25 overflow-hidden rounded-full border border-background"
|
||||
class="absolute -bottom-0.75 -right-0.75 h-4.25 w-4.25 overflow-hidden rounded-full border border-background bg-muted "
|
||||
>
|
||||
{#if quantError}
|
||||
<span
|
||||
@@ -70,7 +76,7 @@
|
||||
<img
|
||||
src={HuggingFaceService.getAvatarUrl(quantOrg)}
|
||||
onerror={() => (quantError = true)}
|
||||
class="h-full w-full rounded-full bg-gray-200"
|
||||
class="h-full w-full rounded-full {invertQuant ? 'dark:invert' : ''}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</script>
|
||||
|
||||
<span class="min-w-0 flex-1">
|
||||
<ModelId modelId={model.id} hideOrgName {modalities} {draftVariants} class="min-w-0" />
|
||||
<ModelId modelId={model.id} hideOrgName {modalities} {draftVariants} wrap class="min-w-0" />
|
||||
|
||||
<span class="mt-0.5 block truncate text-xs text-muted-foreground">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
|
||||
@@ -6,6 +6,7 @@ export const CURATED_MODEL_IDS = [
|
||||
'ggml-org/DeepSeek-V4-Flash-0731-GGUF',
|
||||
'ggml-org/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-GGUF',
|
||||
'ggml-org/Qwen3.6-35B-A3B-GGUF',
|
||||
'ggml-org/Laguna-S-2.1-GGUF',
|
||||
'ggml-org/gemma-4-31B-it-GGUF',
|
||||
'ggml-org/gemma-4-26B-A4B-it-GGUF',
|
||||
'ggml-org/gemma-4-12B-it-GGUF',
|
||||
@@ -13,5 +14,5 @@ export const CURATED_MODEL_IDS = [
|
||||
'ggml-org/gemma-4-E2B-it-GGUF',
|
||||
'ggml-org/gemma-4-E4B-it-GGUF',
|
||||
'ggml-org/gpt-oss-120b-GGUF',
|
||||
'ggml-org/gpt-oss-20b-GGUF',
|
||||
'ggml-org/gpt-oss-20b-GGUF'
|
||||
];
|
||||
|
||||
@@ -45,6 +45,9 @@ export const STATS_UNITS = {
|
||||
|
||||
export const DEFAULT_MOBILE_BREAKPOINT = 768;
|
||||
|
||||
/** Orgs whose avatar is dark and needs inverting in dark mode. */
|
||||
export const DARK_INVERT_AVATAR_ORGS = ['openai'];
|
||||
|
||||
/** Icon used for the model selector and the `/model` slash command. */
|
||||
export const MODEL_SELECTOR_ICON = Package;
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@ import { HuggingFaceService } from '$lib/services';
|
||||
import type { HfModelInfo } from '$lib/types/huggingface';
|
||||
|
||||
class ModelsHubStore {
|
||||
/** Org whose models the sidebar lists by default. */
|
||||
private static readonly DEFAULT_AUTHOR = 'ggml-org';
|
||||
|
||||
models = $state<HfModelInfo[]>([]);
|
||||
loading = $state(false);
|
||||
error = $state<string | null>(null);
|
||||
@@ -29,8 +26,10 @@ class ModelsHubStore {
|
||||
private searchRequestId = 0;
|
||||
|
||||
/**
|
||||
* Fetch the default list: the curated ggml-org models in display order.
|
||||
* No-op when already loaded or in flight.
|
||||
* Fetch the default list: the curated ggml--org models in display order.
|
||||
* Each curated model is fetched directly by ID, so the list is independent
|
||||
* of download ranking (curated models may fall outside the top-50 search
|
||||
* window). No-op when already loaded or in flight.
|
||||
*/
|
||||
async fetch(): Promise<void> {
|
||||
if (this.loading || this.fetched) return;
|
||||
@@ -39,31 +38,11 @@ class ModelsHubStore {
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const results = await HuggingFaceService.search({
|
||||
author: ModelsHubStore.DEFAULT_AUTHOR,
|
||||
sort: 'downloads',
|
||||
limit: 50,
|
||||
full: true
|
||||
});
|
||||
|
||||
const byId = new Map(results.map((m) => [m.id, m]));
|
||||
|
||||
// Keep only the curated models, in the curated display order.
|
||||
const curated = CURATED_MODEL_IDS.flatMap((id) => {
|
||||
const model = byId.get(id);
|
||||
|
||||
return model ? [model] : [];
|
||||
});
|
||||
|
||||
// The list endpoint omits gguf metadata (context length, architecture),
|
||||
// so fetch it per model and merge it in.
|
||||
this.defaultModels = await Promise.all(
|
||||
curated.map(async (model) => {
|
||||
const details = await HuggingFaceService.getDetails(model.id);
|
||||
|
||||
return details?.gguf ? { ...model, gguf: details.gguf } : model;
|
||||
})
|
||||
);
|
||||
// getDetails returns full metadata (downloads, likes, lastModified,
|
||||
// siblings, tags, gguf) for a single model.
|
||||
this.defaultModels = (
|
||||
await Promise.all(CURATED_MODEL_IDS.map((id) => HuggingFaceService.getDetails(id)))
|
||||
).filter((m): m is HfModelInfo => m !== null);
|
||||
this.models = this.defaultModels;
|
||||
this.fetched = true;
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user