22 import { onMount } from " svelte" ;
33 import ModelCard from " ./ModelCard.svelte" ;
44 import { getApiUrl } from " ../../lib/config" ;
5+ import { getModelTier } from " ../../lib/modelMetrics" ;
56
67 let models = [];
78 let modelCount = 0 ;
89 let loading = true ;
910 let error = null ;
11+
12+ let search = " " ;
13+ let activeFilter = " all" ; // "all" | "24/7" | "slurm"
14+
1015 onMount (async () => {
1116 try {
1217 const response = await fetch (` ${ getApiUrl ()} /v1/models_detailed` );
4550 loading = false ;
4651 }
4752 });
53+
54+ $: filteredModels = models .filter ((m ) => {
55+ const title = m .data .title ;
56+
57+ if (search .trim ()) {
58+ const q = search .trim ().toLowerCase ();
59+ const haystack = (title + " " + m .data .devices .join (" " )).toLowerCase ();
60+ if (! haystack .includes (q)) return false ;
61+ }
62+
63+ if (activeFilter === " 24/7" ) return getModelTier (title) === " L2" ;
64+ if (activeFilter === " slurm" ) return getModelTier (title) === " slurm" ;
65+ return true ;
66+ });
4867 </script >
4968
5069<div >
5978 Access state-of-the-art language models from leading AI research organizations
6079 </p >
6180 </div >
81+
82+ {#if ! loading && ! error }
83+ <div class =" mb-6 flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between" >
84+ <div class =" flex flex-wrap gap-2" >
85+ <button
86+ class =" pill"
87+ class:active ={activeFilter === " all" }
88+ on:click ={() => (activeFilter = " all" )}
89+ >All</button >
90+ <button
91+ class =" pill"
92+ class:active ={activeFilter === " 24/7" }
93+ on:click ={() => (activeFilter = " 24/7" )}
94+ >24/7</button >
95+ <button
96+ class =" pill"
97+ class:active ={activeFilter === " slurm" }
98+ on:click ={() => (activeFilter = " slurm" )}
99+ >Slurm</button >
100+ </div >
101+ <input
102+ type =" text"
103+ bind:value ={search }
104+ placeholder =" Search by model name or GPU..."
105+ class =" search-input"
106+ />
107+ </div >
108+ {/if }
109+
62110 {#if loading }
63111 <div class =" loading" >Loading...</div >
64112 {:else if error }
67115 </div >
68116 {:else }
69117 <div class =" model-list space-y-2" >
70- {#each models as model }
118+ {#each filteredModels as model ( model . data . title ) }
71119 <ModelCard entry ={model } />
72120 {/each }
121+ {#if filteredModels .length === 0 }
122+ <div class =" text-center text-slate-500 dark:text-slate-400 py-6" >
123+ No models match your filters.
124+ </div >
125+ {/if }
73126 </div >
74127 {/if }
75128</div >
76129
77130<style >
78- /* Optional styling */
79- </style >
131+ .search-input {
132+ width : 100% ;
133+ max-width : 420px ;
134+ padding : 0.5rem 0.75rem ;
135+ border-radius : 6px ;
136+ border : 1px solid rgba (0 , 0 , 0 , 0.15 );
137+ background-color : transparent ;
138+ color : inherit ;
139+ font-size : 0.875rem ;
140+ outline : none ;
141+ transition : border-color 0.15s ease ;
142+ }
143+ :global(.dark ) .search-input {
144+ border-color : rgba (255 , 255 , 255 , 0.2 );
145+ }
146+ .search-input :focus {
147+ border-color : #6366f1 ;
148+ }
149+
150+ .pill {
151+ padding : 0.3rem 0.75rem ;
152+ border-radius : 9999px ;
153+ font-size : 0.8rem ;
154+ font-weight : 600 ;
155+ border : 1px solid rgba (0 , 0 , 0 , 0.15 );
156+ background-color : transparent ;
157+ color : inherit ;
158+ cursor : pointer ;
159+ transition : background-color 0.15s ease , border-color 0.15s ease , color 0.15s ease ;
160+ }
161+ :global(.dark ) .pill {
162+ border-color : rgba (255 , 255 , 255 , 0.2 );
163+ }
164+ .pill :hover {
165+ background-color : rgba (0 , 0 , 0 , 0.05 );
166+ }
167+ :global(.dark ) .pill :hover {
168+ background-color : rgba (255 , 255 , 255 , 0.08 );
169+ }
170+ .pill.active {
171+ background-color : #6366f1 ;
172+ border-color : #6366f1 ;
173+ color : white ;
174+ }
175+ </style >
0 commit comments