Skip to content

Commit 2a63d95

Browse files
committed
feat(Models): paginate available models endpoint
1 parent 76fcbe4 commit 2a63d95

File tree

7 files changed

+68
-23
lines changed

7 files changed

+68
-23
lines changed

admin/app/controllers/ollama_controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default class OllamaController {
2121
sort: reqData.sort,
2222
recommendedOnly: reqData.recommendedOnly,
2323
query: reqData.query || null,
24+
limit: reqData.limit || 15,
2425
})
2526
}
2627

admin/app/controllers/settings_controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ export default class SettingsController {
5151
}
5252

5353
async models({ inertia }: HttpContext) {
54-
const availableModels = await this.ollamaService.getAvailableModels({ sort: 'pulls', recommendedOnly: false, query: null });
54+
const availableModels = await this.ollamaService.getAvailableModels({ sort: 'pulls', recommendedOnly: false, query: null, limit: 15 });
5555
const installedModels = await this.ollamaService.getModels();
5656
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
5757
return inertia.render('settings/models', {
5858
models: {
59-
availableModels: availableModels || [],
59+
availableModels: availableModels?.models || [],
6060
installedModels: installedModels || [],
6161
settings: {
6262
chatSuggestionsEnabled: parseBoolean(chatSuggestionsEnabled)

admin/app/services/ollama_service.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,25 +183,32 @@ export class OllamaService {
183183
}
184184

185185
async getAvailableModels(
186-
{ sort, recommendedOnly, query }: { sort?: 'pulls' | 'name'; recommendedOnly?: boolean, query: string | null } = {
186+
{ sort, recommendedOnly, query, limit }: { sort?: 'pulls' | 'name'; recommendedOnly?: boolean, query: string | null, limit?: number } = {
187187
sort: 'pulls',
188188
recommendedOnly: false,
189189
query: null,
190+
limit: 15,
190191
}
191-
): Promise<NomadOllamaModel[] | null> {
192+
): Promise<{ models: NomadOllamaModel[], hasMore: boolean } | null> {
192193
try {
193194
const models = await this.retrieveAndRefreshModels(sort)
194195
if (!models) {
195196
// If we fail to get models from the API, return the fallback recommended models
196197
logger.warn(
197198
'[OllamaService] Returning fallback recommended models due to failure in fetching available models'
198199
)
199-
return FALLBACK_RECOMMENDED_OLLAMA_MODELS
200+
return {
201+
models: FALLBACK_RECOMMENDED_OLLAMA_MODELS,
202+
hasMore: false
203+
}
200204
}
201205

202206
if (!recommendedOnly) {
203207
const filteredModels = query ? this.fuseSearchModels(models, query) : models
204-
return filteredModels
208+
return {
209+
models: filteredModels.slice(0, limit || 15),
210+
hasMore: filteredModels.length > (limit || 15)
211+
}
205212
}
206213

207214
// If recommendedOnly is true, only return the first three models (if sorted by pulls, these will be the top 3)
@@ -217,10 +224,17 @@ export class OllamaService {
217224
})
218225

219226
if (query) {
220-
return this.fuseSearchModels(recommendedModels, query)
227+
const filteredRecommendedModels = this.fuseSearchModels(recommendedModels, query)
228+
return {
229+
models: filteredRecommendedModels,
230+
hasMore: filteredRecommendedModels.length > (limit || 15)
231+
}
221232
}
222233

223-
return recommendedModels
234+
return {
235+
models: recommendedModels,
236+
hasMore: recommendedModels.length > (limit || 15)
237+
}
224238
} catch (error) {
225239
logger.error(
226240
`[OllamaService] Failed to get available models: ${error instanceof Error ? error.message : error}`
@@ -253,7 +267,7 @@ export class OllamaService {
253267
}
254268

255269
const rawModels = response.data.models as NomadOllamaModel[]
256-
270+
257271
// Filter out tags where cloud is truthy, then remove models with no remaining tags
258272
const noCloud = rawModels
259273
.map((model) => ({

admin/app/validators/ollama.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ export const getAvailableModelsSchema = vine.compile(
1818
sort: vine.enum(['pulls', 'name'] as const).optional(),
1919
recommendedOnly: vine.boolean().optional(),
2020
query: vine.string().trim().optional(),
21+
limit: vine.number().positive().optional(),
2122
})
2223
)

admin/inertia/lib/api.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,13 @@ class API {
196196
})()
197197
}
198198

199-
async getAvailableModels(query: string | null, recommendedOnly: boolean): Promise<NomadOllamaModel[] | undefined> {
199+
async getAvailableModels(params: { query?: string; recommendedOnly?: boolean; limit?: number }) {
200200
return catchInternal(async () => {
201-
const response = await this.client.get<NomadOllamaModel[]>('/ollama/models', {
202-
params: { sort: 'pulls', recommendedOnly, query },
201+
const response = await this.client.get<{
202+
models: NomadOllamaModel[]
203+
hasMore: boolean
204+
}>('/ollama/models', {
205+
params: { sort: 'pulls', ...params },
203206
})
204207
return response.data
205208
})()
@@ -506,7 +509,7 @@ class API {
506509
// For 409 Conflict errors, throw a specific error that the UI can handle
507510
if (error.response?.status === 409) {
508511
const err = new Error(error.response?.data?.error || 'This benchmark has already been submitted to the repository')
509-
;(err as any).status = 409
512+
; (err as any).status = 409
510513
throw err
511514
}
512515
// For other errors, extract the message and throw

admin/inertia/pages/easy-setup/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,13 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
152152

153153
const { data: recommendedModels, isLoading: isLoadingRecommendedModels } = useQuery({
154154
queryKey: ['recommended-ollama-models'],
155-
queryFn: () => api.getAvailableModels(null, true),
155+
queryFn: async () => {
156+
const res = await api.getAvailableModels({ recommendedOnly: true })
157+
if (!res) {
158+
return []
159+
}
160+
return res.models
161+
},
156162
refetchOnWindowFocus: false,
157163
})
158164

@@ -736,7 +742,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
736742
className={classNames(
737743
'relative',
738744
selectedMapCollections.includes(collection.slug) &&
739-
'ring-4 ring-desert-green rounded-lg',
745+
'ring-4 ring-desert-green rounded-lg',
740746
collection.all_installed && 'opacity-75',
741747
!isOnline && 'opacity-50 cursor-not-allowed'
742748
)}
@@ -760,7 +766,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
760766

761767
const renderStep3 = () => {
762768
// Check if AI or Information capabilities are selected OR already installed
763-
const isAiSelected = selectedServices.includes(SERVICE_NAMES.OLLAMA) ||
769+
const isAiSelected = selectedServices.includes(SERVICE_NAMES.OLLAMA) ||
764770
installedServices.some((s) => s.service_name === SERVICE_NAMES.OLLAMA)
765771
const isInformationSelected = selectedServices.includes(SERVICE_NAMES.KIWIX) ||
766772
installedServices.some((s) => s.service_name === SERVICE_NAMES.KIWIX)

admin/inertia/pages/settings/models.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,29 @@ export default function ModelsPage(props: {
3737

3838
const [query, setQuery] = useState('')
3939
const [queryUI, setQueryUI] = useState('')
40+
const [limit, setLimit] = useState(15)
4041

4142
const debouncedSetQuery = debounce((val: string) => {
4243
setQuery(val)
4344
}, 300)
4445

45-
const { data: availableModels, isLoading } = useQuery({
46-
queryKey: ['ollama', 'availableModels', query],
46+
const { data: availableModelData, isFetching } = useQuery({
47+
queryKey: ['ollama', 'availableModels', query, limit],
4748
queryFn: async () => {
48-
const res = await api.getAvailableModels(query, false)
49+
const res = await api.getAvailableModels({
50+
query,
51+
recommendedOnly: false,
52+
limit,
53+
})
4954
if (!res) {
50-
return []
55+
return {
56+
models: [],
57+
hasMore: false,
58+
}
5159
}
5260
return res
5361
},
54-
initialData: props.models.availableModels,
62+
initialData: { models: props.models.availableModels, hasMore: false },
5563
})
5664

5765
async function handleInstallModel(modelName: string) {
@@ -209,8 +217,8 @@ export default function ModelsPage(props: {
209217
title: 'Last Updated',
210218
},
211219
]}
212-
data={availableModels || []}
213-
loading={isLoading}
220+
data={availableModelData?.models || []}
221+
loading={isFetching}
214222
expandable={{
215223
expandedRowRender: (record) => (
216224
<div className="pl-14">
@@ -283,6 +291,18 @@ export default function ModelsPage(props: {
283291
),
284292
}}
285293
/>
294+
<div className="flex justify-center mt-6">
295+
{availableModelData?.hasMore && (
296+
<StyledButton
297+
variant="primary"
298+
onClick={() => {
299+
setLimit((prev) => prev + 15)
300+
}}
301+
>
302+
Load More
303+
</StyledButton>
304+
)}
305+
</div>
286306
</main>
287307
</div>
288308
</SettingsLayout>

0 commit comments

Comments
 (0)