Skip to content

Commit 31c78ad

Browse files
committed
Implement all available filtering methods
1 parent f35dcac commit 31c78ad

File tree

4 files changed

+186
-22
lines changed

4 files changed

+186
-22
lines changed

src/lib/common.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const defaultTooltipOptions: Options = {
2323
delay: [1000, 0]
2424
};
2525

26-
export enum FilterModes {
26+
export enum SortingModes {
2727
TIME_ASC = 'TIME_ASC',
2828
TIME_DESC = 'TIME_DESC',
2929
LIKES_ASC = 'LIKES_ASC',
@@ -41,7 +41,7 @@ export interface LocalStorageValue {
4141
allowedLicences: string[];
4242
allowedModelSizes: string[];
4343
allowedModelTypes: ModelTypes[];
44-
filterMode: FilterModes;
44+
filterMode: SortingModes;
4545
};
4646
}
4747

@@ -77,11 +77,10 @@ export const getModelType = (model: Model): ModelTypes => {
7777
};
7878

7979
export const getModelSize = (model: Model): string => {
80-
const nameParts = model.id.split('-');
80+
const nameParts = model.id.toUpperCase().split(/[-_/]/);
8181

82-
// The part with /^\d+B$/ is the model size
8382
for (const part of nameParts) {
84-
if (/^\d+B$/.test(part)) {
83+
if (/^\d+(\.\d)?B$/.test(part)) {
8584
return part;
8685
}
8786
}
Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,117 @@
11
<script lang="ts">
2-
import {sidePanelRight} from '$lib/stores';
2+
import { availableLicences, availableModelSizes, forceRefresh, sidePanelRight, state } from '$lib/stores';
3+
import { ModelTypes, SortingModes } from '$lib/common';
4+
import PhUserBold from '~icons/ph/user-bold';
5+
import PhSortAscendingBold from '~icons/ph/sort-ascending-bold';
6+
import PhSortDescendingBold from '~icons/ph/sort-descending-bold';
7+
import { ListBox, ListBoxItem } from '@skeletonlabs/skeleton';
8+
9+
const toggleModelSize = (size: string) => {
10+
if ($state.filters.allowedModelSizes.includes(size)) {
11+
$state.filters.allowedModelSizes = $state.filters.allowedModelSizes.filter(
12+
(s: String) => s !== size
13+
);
14+
} else {
15+
$state.filters.allowedModelSizes = [...$state.filters.allowedModelSizes, size];
16+
}
17+
};
18+
19+
const toggleLicense = (license: string) => {
20+
if ($state.filters.allowedLicenses.includes(license)) {
21+
$state.filters.allowedLicenses = $state.filters.allowedLicenses.filter(
22+
(s: String) => s !== license
23+
);
24+
} else {
25+
$state.filters.allowedLicenses = [...$state.filters.allowedLicenses, license];
26+
}
27+
};
28+
29+
const sortingModeStrings: string[] = Object.values(SortingModes);
30+
331
</script>
432

533
<div
634
bind:clientWidth={$sidePanelRight.width}
735
class="grid grid-cols-[auto_1fr] h-full border-l border-surface-500/30 lg:grid w-[300px] overflow-hidden"
836
style="margin-right: -{$sidePanelRight.offset}px;"
937
>
10-
<section class="p-4 space-y-4 overflow-y-auto">
11-
<p class="font-bold pl-4 text-2xl">Filter Parameters</p>
38+
<section class="p-4 space-y-4 overflow-y-auto w-[300px]">
39+
<p class="font-bold pl-4 text-xl">Model Type</p>
40+
<select
41+
bind:value={$state.filters.allowedModelTypes}
42+
class="select h-[38px] overflow-hidden text-center px-0 py-2 hide-scrollbar space-x-1"
43+
multiple
44+
size="1"
45+
>
46+
{#each Object.values(ModelTypes) as type}
47+
<option class="inline !px-2 my-auto" value={type}>{type}</option>
48+
{/each}
49+
</select>
50+
<p class="font-bold pl-4 text-xl">Allowed Model Sizes</p>
51+
<div class="flex flex-wrap gap-1">
52+
{#each $availableModelSizes as size, i}
53+
<span
54+
class="chip uppercase {$state.filters.allowedModelSizes.includes(size)
55+
? 'variant-filled'
56+
: 'variant-soft'}"
57+
on:click={() => {
58+
toggleModelSize(size);
59+
}}
60+
on:keypress
61+
aria-label={size}
62+
role="button"
63+
tabindex={i}
64+
>
65+
{size}
66+
</span>
67+
{/each}
68+
</div>
69+
<p class="font-bold pl-4 text-xl">Allowed Licenses</p>
70+
<div class="flex flex-wrap gap-1">
71+
{#each $availableLicences as license, i}
72+
<span
73+
class="chip uppercase {$state.filters.allowedLicences.includes(license)
74+
? 'variant-filled'
75+
: 'variant-soft'}"
76+
on:click={() => {
77+
toggleModelSize(license);
78+
}}
79+
on:keypress
80+
aria-label={license}
81+
role="button"
82+
tabindex={i}
83+
>
84+
{license}
85+
</span>
86+
{/each}
87+
</div>
88+
<p class="font-bold pl-4 text-xl">Utilized Namespace</p>
89+
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
90+
<div class="input-group-shim"><PhUserBold/></div>
91+
<input bind:value={$state.author} placeholder="Username or Organisation" type="text" />
92+
</div>
93+
<p class="font-bold pl-4 text-xl">Sort results by:</p>
94+
95+
<ListBox class='grid grid-cols-2 gap-2' spacing=''>
96+
{#each sortingModeStrings as type}
97+
<ListBoxItem bind:group={$state.filters.filterMode} value={type} name='sorting' padding='px-2 py-2'>
98+
<span class='capitalize text-center'>
99+
{#if type.split("_")[1] == "ASC"}
100+
<PhSortAscendingBold class='inline'/>
101+
{:else}
102+
<PhSortDescendingBold class='inline'/>
103+
{/if}
104+
{type.split("_")[0].toLowerCase()}
105+
</span>
106+
107+
</ListBoxItem>
108+
{/each}
109+
</ListBox>
110+
111+
<div class='flex items-center'>
112+
<button class="btn variant-filled-primary text-center mx-auto" disabled='{$forceRefresh}'
113+
on:click={() => {$forceRefresh = true}} type="button">Force refresh</button>
114+
</div>
115+
12116
</section>
13117
</div>

src/lib/stores.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { derived, type Writable, writable } from 'svelte/store';
22
import type { LocalStorageValue, SidePanel } from './common';
3-
import { FilterModes, getModelSize } from './common';
3+
import { getModelSize, getModelType, SortingModes } from './common';
44

55
import { localStorageStore } from '@skeletonlabs/skeleton';
66
import * as devalue from 'devalue';
@@ -23,7 +23,7 @@ export const state: Writable<LocalStorageValue> = localStorageStore<LocalStorage
2323
allowedLicences: [],
2424
allowedModelSizes: [],
2525
allowedModelTypes: [],
26-
filterMode: FilterModes.TIME_DESC
26+
filterMode: SortingModes.TIME_DESC
2727
}
2828
},
2929
{
@@ -43,7 +43,10 @@ export const availableLicences = derived(state, ($state) => {
4343
}
4444
});
4545
});
46-
return Array.from(licences);
46+
// Sort the array based on string length (shortest first)
47+
return Array.from(licences).sort((a, b) => {
48+
return a.length - b.length;
49+
});
4750
});
4851

4952
// Available Model Sizes
@@ -52,48 +55,70 @@ export const availableModelSizes = derived(state, ($state) => {
5255
$state.models.forEach((model) => {
5356
modelSizes.add(getModelSize(model));
5457
});
55-
return Array.from(modelSizes);
58+
59+
// Order the array by size (extract the number from the string)
60+
return Array.from(modelSizes).sort((a, b) => {
61+
// If it is not a number, assume infinity
62+
const aSize = parseInt(a.substring(0, a.length - 1)) || Infinity;
63+
const bSize = parseInt(b.substring(0, b.length - 1)) || Infinity;
64+
return aSize - bSize;
65+
});
5666
});
5767

5868
export const filteredModels = derived(state, ($state) => {
5969
let filtered = $state.models.filter((model) => {
70+
// Check the name of the model
6071
if (
6172
$state.filters.name !== '' &&
6273
!model.id.toLowerCase().includes($state.filters.name.toLowerCase())
6374
) {
6475
return false;
6576
}
6677

78+
// Check the model type
79+
if ($state.filters.allowedModelTypes.length > 0) {
80+
if (!$state.filters.allowedModelTypes.includes(getModelType(model))) {
81+
return false;
82+
}
83+
}
84+
85+
// Check the model size
86+
if ($state.filters.allowedModelSizes.length > 0) {
87+
if (!$state.filters.allowedModelSizes.includes(getModelSize(model))) {
88+
return false;
89+
}
90+
}
91+
6792
return true;
6893
});
6994

7095
switch ($state.filters.filterMode) {
71-
case FilterModes.TIME_ASC:
96+
case SortingModes.TIME_ASC:
7297
filtered = filtered.sort((a, b) => {
7398
return a.lastModified.getTime() - b.lastModified.getTime();
7499
});
75100
break;
76-
case FilterModes.TIME_DESC:
101+
case SortingModes.TIME_DESC:
77102
filtered = filtered.sort((a, b) => {
78103
return b.lastModified.getTime() - a.lastModified.getTime();
79104
});
80105
break;
81-
case FilterModes.LIKES_ASC:
106+
case SortingModes.LIKES_ASC:
82107
filtered = filtered.sort((a, b) => {
83108
return a.likes - b.likes;
84109
});
85110
break;
86-
case FilterModes.LIKES_DESC:
111+
case SortingModes.LIKES_DESC:
87112
filtered = filtered.sort((a, b) => {
88113
return b.likes - a.likes;
89114
});
90115
break;
91-
case FilterModes.DOWNLOADS_ASC:
116+
case SortingModes.DOWNLOADS_ASC:
92117
filtered = filtered.sort((a, b) => {
93118
return a.downloads - b.downloads;
94119
});
95120
break;
96-
case FilterModes.DOWNLOADS_DESC:
121+
case SortingModes.DOWNLOADS_DESC:
97122
filtered = filtered.sort((a, b) => {
98123
return b.downloads - a.downloads;
99124
});
@@ -102,3 +127,5 @@ export const filteredModels = derived(state, ($state) => {
102127

103128
return filtered;
104129
});
130+
131+
export const forceRefresh = writable<boolean>(false);

src/routes/+page.svelte

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
<script lang="ts">
2-
import { availableLicences, availableModelSizes, filteredModels, state } from '$lib/stores';
2+
import { availableLicences, availableModelSizes, filteredModels, forceRefresh, state } from '$lib/stores';
33
import { onMount } from 'svelte';
44
import type { Model } from '$lib/huggingfaceAPI';
55
import { ModelSchema } from '$lib/huggingfaceAPI';
66
import ModelCard from '$lib/components/ModelCard.svelte';
77
import { ProgressRadial } from '@skeletonlabs/skeleton';
88
import InfiniteLoading, { type StateChanger } from 'svelte-infinite-loading';
9+
import type { LocalStorageValue } from '$lib/common';
10+
11+
12+
let usedName = $state.author;
13+
let isLoading = true;
914
1015
const updateModels = async () => {
11-
const res = await fetch('https://huggingface.co/api/models?full=1&author=' + $state.author);
16+
const res = await fetch('https://huggingface.co/api/models?full=1&author=' + usedName);
1217
const data = (await res.json()) as any[];
1318
1419
let models: Model[] = [];
@@ -32,13 +37,18 @@
3237
});
3338
3439
$state.models = models;
40+
41+
isLoading = false;
42+
3543
$state.lastUpdate = new Date();
3644
};
3745
3846
onMount(async () => {
3947
// Check if our state is newer than 30 minutes
4048
if (Date.now() - $state.lastUpdate.getTime() > 1000 * 60 * 30) {
4149
await updateModels();
50+
} else {
51+
isLoading = false;
4252
}
4353
4454
console.log($state);
@@ -50,11 +60,35 @@
5060
let shownModelCount = 50;
5161
let loadingIdentifier = 0;
5262
63+
let updateTimeout: NodeJS.Timeout | undefined;
64+
65+
66+
// Check if the author changes, if so debounce the change and then update the models
67+
state.subscribe((newState: LocalStorageValue) => {
68+
if (newState.author !== usedName) {
69+
isLoading = true;
70+
usedName = newState.author;
71+
if (updateTimeout) {
72+
clearTimeout(updateTimeout);
73+
}
74+
updateTimeout = setTimeout(updateModels, 1000);
75+
}
76+
});
77+
5378
filteredModels.subscribe(() => {
5479
shownModelCount = 50;
5580
loadingIdentifier++;
5681
});
5782
83+
forceRefresh.subscribe(async (state: boolean) => {
84+
if (state){
85+
isLoading = true;
86+
await updateModels();
87+
$forceRefresh = false;
88+
}
89+
});
90+
91+
5892
$: currentShownModels = $filteredModels.slice(0, shownModelCount);
5993
6094
$: canShowMore = $filteredModels.length > shownModelCount;
@@ -85,7 +119,7 @@
85119
</svelte:head>
86120

87121
<div class="py-2 md:p-4 h-full mx-auto justify-center items-center">
88-
{#if $state.models.length === 0}
122+
{#if $state.models.length === 0 || isLoading}
89123
<div class="flex flex-col items-center justify-center w-full m-6 space-y-2">
90124
<h1 class="text-2xl font-bold">Loading...</h1>
91125
<p class="text-gray-500">This might take a while</p>
@@ -104,7 +138,7 @@
104138
</div>
105139
{/if}
106140

107-
{#if canShowMore}
141+
{#if canShowMore && !isLoading}
108142
<InfiniteLoading on:infinite={loadMore} distance={400} identifier={loadingIdentifier}>
109143
<div slot="noMore"></div>
110144
</InfiniteLoading>

0 commit comments

Comments
 (0)