mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
feat(Models): paginate available models endpoint
This commit is contained in:
parent
a3f10dd158
commit
6874a2824f
|
|
@ -21,6 +21,7 @@ export default class OllamaController {
|
|||
sort: reqData.sort,
|
||||
recommendedOnly: reqData.recommendedOnly,
|
||||
query: reqData.query || null,
|
||||
limit: reqData.limit || 15,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ export default class SettingsController {
|
|||
}
|
||||
|
||||
async models({ inertia }: HttpContext) {
|
||||
const availableModels = await this.ollamaService.getAvailableModels({ sort: 'pulls', recommendedOnly: false, query: null });
|
||||
const availableModels = await this.ollamaService.getAvailableModels({ sort: 'pulls', recommendedOnly: false, query: null, limit: 15 });
|
||||
const installedModels = await this.ollamaService.getModels();
|
||||
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
|
||||
return inertia.render('settings/models', {
|
||||
models: {
|
||||
availableModels: availableModels || [],
|
||||
availableModels: availableModels?.models || [],
|
||||
installedModels: installedModels || [],
|
||||
settings: {
|
||||
chatSuggestionsEnabled: parseBoolean(chatSuggestionsEnabled)
|
||||
|
|
|
|||
|
|
@ -183,12 +183,13 @@ export class OllamaService {
|
|||
}
|
||||
|
||||
async getAvailableModels(
|
||||
{ sort, recommendedOnly, query }: { sort?: 'pulls' | 'name'; recommendedOnly?: boolean, query: string | null } = {
|
||||
{ sort, recommendedOnly, query, limit }: { sort?: 'pulls' | 'name'; recommendedOnly?: boolean, query: string | null, limit?: number } = {
|
||||
sort: 'pulls',
|
||||
recommendedOnly: false,
|
||||
query: null,
|
||||
limit: 15,
|
||||
}
|
||||
): Promise<NomadOllamaModel[] | null> {
|
||||
): Promise<{ models: NomadOllamaModel[], hasMore: boolean } | null> {
|
||||
try {
|
||||
const models = await this.retrieveAndRefreshModels(sort)
|
||||
if (!models) {
|
||||
|
|
@ -196,12 +197,18 @@ export class OllamaService {
|
|||
logger.warn(
|
||||
'[OllamaService] Returning fallback recommended models due to failure in fetching available models'
|
||||
)
|
||||
return FALLBACK_RECOMMENDED_OLLAMA_MODELS
|
||||
return {
|
||||
models: FALLBACK_RECOMMENDED_OLLAMA_MODELS,
|
||||
hasMore: false
|
||||
}
|
||||
}
|
||||
|
||||
if (!recommendedOnly) {
|
||||
const filteredModels = query ? this.fuseSearchModels(models, query) : models
|
||||
return filteredModels
|
||||
return {
|
||||
models: filteredModels.slice(0, limit || 15),
|
||||
hasMore: filteredModels.length > (limit || 15)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
|||
})
|
||||
|
||||
if (query) {
|
||||
return this.fuseSearchModels(recommendedModels, query)
|
||||
const filteredRecommendedModels = this.fuseSearchModels(recommendedModels, query)
|
||||
return {
|
||||
models: filteredRecommendedModels,
|
||||
hasMore: filteredRecommendedModels.length > (limit || 15)
|
||||
}
|
||||
}
|
||||
|
||||
return recommendedModels
|
||||
return {
|
||||
models: recommendedModels,
|
||||
hasMore: recommendedModels.length > (limit || 15)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[OllamaService] Failed to get available models: ${error instanceof Error ? error.message : error}`
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@ export const getAvailableModelsSchema = vine.compile(
|
|||
sort: vine.enum(['pulls', 'name'] as const).optional(),
|
||||
recommendedOnly: vine.boolean().optional(),
|
||||
query: vine.string().trim().optional(),
|
||||
limit: vine.number().positive().optional(),
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -196,10 +196,13 @@ class API {
|
|||
})()
|
||||
}
|
||||
|
||||
async getAvailableModels(query: string | null, recommendedOnly: boolean): Promise<NomadOllamaModel[] | undefined> {
|
||||
async getAvailableModels(params: { query?: string; recommendedOnly?: boolean; limit?: number }) {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.get<NomadOllamaModel[]>('/ollama/models', {
|
||||
params: { sort: 'pulls', recommendedOnly, query },
|
||||
const response = await this.client.get<{
|
||||
models: NomadOllamaModel[]
|
||||
hasMore: boolean
|
||||
}>('/ollama/models', {
|
||||
params: { sort: 'pulls', ...params },
|
||||
})
|
||||
return response.data
|
||||
})()
|
||||
|
|
@ -506,7 +509,7 @@ class API {
|
|||
// For 409 Conflict errors, throw a specific error that the UI can handle
|
||||
if (error.response?.status === 409) {
|
||||
const err = new Error(error.response?.data?.error || 'This benchmark has already been submitted to the repository')
|
||||
;(err as any).status = 409
|
||||
; (err as any).status = 409
|
||||
throw err
|
||||
}
|
||||
// For other errors, extract the message and throw
|
||||
|
|
|
|||
|
|
@ -152,7 +152,13 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
|||
|
||||
const { data: recommendedModels, isLoading: isLoadingRecommendedModels } = useQuery({
|
||||
queryKey: ['recommended-ollama-models'],
|
||||
queryFn: () => api.getAvailableModels(null, true),
|
||||
queryFn: async () => {
|
||||
const res = await api.getAvailableModels({ recommendedOnly: true })
|
||||
if (!res) {
|
||||
return []
|
||||
}
|
||||
return res.models
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -37,21 +37,29 @@ export default function ModelsPage(props: {
|
|||
|
||||
const [query, setQuery] = useState('')
|
||||
const [queryUI, setQueryUI] = useState('')
|
||||
const [limit, setLimit] = useState(15)
|
||||
|
||||
const debouncedSetQuery = debounce((val: string) => {
|
||||
setQuery(val)
|
||||
}, 300)
|
||||
|
||||
const { data: availableModels, isLoading } = useQuery({
|
||||
queryKey: ['ollama', 'availableModels', query],
|
||||
const { data: availableModelData, isFetching } = useQuery({
|
||||
queryKey: ['ollama', 'availableModels', query, limit],
|
||||
queryFn: async () => {
|
||||
const res = await api.getAvailableModels(query, false)
|
||||
const res = await api.getAvailableModels({
|
||||
query,
|
||||
recommendedOnly: false,
|
||||
limit,
|
||||
})
|
||||
if (!res) {
|
||||
return []
|
||||
return {
|
||||
models: [],
|
||||
hasMore: false,
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
initialData: props.models.availableModels,
|
||||
initialData: { models: props.models.availableModels, hasMore: false },
|
||||
})
|
||||
|
||||
async function handleInstallModel(modelName: string) {
|
||||
|
|
@ -209,8 +217,8 @@ export default function ModelsPage(props: {
|
|||
title: 'Last Updated',
|
||||
},
|
||||
]}
|
||||
data={availableModels || []}
|
||||
loading={isLoading}
|
||||
data={availableModelData?.models || []}
|
||||
loading={isFetching}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<div className="pl-14">
|
||||
|
|
@ -283,6 +291,18 @@ export default function ModelsPage(props: {
|
|||
),
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-center mt-6">
|
||||
{availableModelData?.hasMore && (
|
||||
<StyledButton
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setLimit((prev) => prev + 15)
|
||||
}}
|
||||
>
|
||||
Load More
|
||||
</StyledButton>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user