Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 10 additions & 18 deletions web-app/src/hooks/__tests__/useModelSources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('useModelSources', () => {
// Get the mocked function
const { fetchModelCatalog } = await import('@/services/models')
mockFetchModelCatalog = fetchModelCatalog as any

// Reset store state to defaults
useModelSources.setState({
sources: [],
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('useModelSources', () => {
expect(result.current.sources).toEqual([])
})

it('should merge new sources with existing ones', async () => {
it('should not merge new sources with existing ones', async () => {
const existingSources: CatalogModel[] = [
{
model_name: 'existing-model',
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('useModelSources', () => {
await result.current.fetchSources()
})

expect(result.current.sources).toEqual([...newSources, ...existingSources])
expect(result.current.sources).toEqual(newSources)
})

it('should not duplicate models with same model_name', async () => {
Expand Down Expand Up @@ -175,15 +175,7 @@ describe('useModelSources', () => {
await result.current.fetchSources()
})

expect(result.current.sources).toEqual([
...newSources,
{
model_name: 'unique-model',
provider: 'provider',
description: 'Unique model',
version: '1.0.0',
},
])
expect(result.current.sources).toEqual(newSources)
})

it('should handle empty sources response', async () => {
Expand Down Expand Up @@ -236,7 +228,7 @@ describe('useModelSources', () => {
describe('addSource', () => {
it('should add a new source to the store', () => {
const { result } = renderHook(() => useModelSources())

const testModel: CatalogModel = {
model_name: 'test-model',
description: 'Test model description',
Expand All @@ -263,7 +255,7 @@ describe('useModelSources', () => {

it('should replace existing source with same model_name', () => {
const { result } = renderHook(() => useModelSources())

const originalModel: CatalogModel = {
model_name: 'duplicate-model',
description: 'Original description',
Expand Down Expand Up @@ -306,7 +298,7 @@ describe('useModelSources', () => {

it('should handle multiple different sources', () => {
const { result } = renderHook(() => useModelSources())

const model1: CatalogModel = {
model_name: 'model-1',
description: 'First model',
Expand Down Expand Up @@ -342,7 +334,7 @@ describe('useModelSources', () => {

it('should handle CatalogModel with complete quants data', () => {
const { result } = renderHook(() => useModelSources())

const modelWithQuants: CatalogModel = {
model_name: 'model-with-quants',
description: 'Model with quantizations',
Expand Down Expand Up @@ -475,7 +467,7 @@ describe('useModelSources', () => {
await result.current.fetchSources()
})

expect(result.current.sources).toEqual([...sources2, ...sources1])
expect(result.current.sources).toEqual(sources2)
})

it('should handle fetch after error', async () => {
Expand Down Expand Up @@ -510,4 +502,4 @@ describe('useModelSources', () => {
expect(result.current.sources).toEqual(mockSources)
})
})
})
})
8 changes: 1 addition & 7 deletions web-app/src/hooks/useModelSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,9 @@ export const useModelSources = create<ModelSourcesState>()(
set({ loading: true, error: null })
try {
const newSources = await fetchModelCatalog()
const currentSources = get().sources

set({
sources: [
...newSources,
...currentSources.filter(
(e) => !newSources.some((s) => s.model_name === e.model_name)
),
],
sources: newSources.length ? newSources : get().sources,
loading: false,
})
} catch (error) {
Expand Down
18 changes: 13 additions & 5 deletions web-app/src/routes/hub/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useVirtualizer } from '@tanstack/react-virtual'
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { route } from '@/constants/routes'
import { useModelSources } from '@/hooks/useModelSources'
import { cn, fuzzySearch } from '@/lib/utils'
import {

Check warning on line 7 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

2-7 lines are not covered with tests
useState,
useMemo,
useEffect,
Expand All @@ -12,32 +12,32 @@
useCallback,
useRef,
} from 'react'
import { Button } from '@/components/ui/button'
import { useModelProvider } from '@/hooks/useModelProvider'
import { Card, CardItem } from '@/containers/Card'
import { RenderMarkdown } from '@/containers/RenderMarkdown'
import { extractModelName, extractDescription } from '@/lib/models'
import { IconDownload, IconFileCode, IconSearch } from '@tabler/icons-react'
import { Switch } from '@/components/ui/switch'
import Joyride, { CallBackProps, STATUS } from 'react-joyride'
import { CustomTooltipJoyRide } from '@/containers/CustomeTooltipJoyRide'
import {

Check warning on line 24 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

15-24 lines are not covered with tests
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {

Check warning on line 30 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

30 line is not covered with tests
CatalogModel,
pullModel,
fetchHuggingFaceRepo,
HuggingFaceRepo,
} from '@/services/models'
import { useDownloadStore } from '@/hooks/useDownloadStore'
import { Progress } from '@/components/ui/progress'
import HeaderPage from '@/containers/HeaderPage'
import { Loader } from 'lucide-react'
import { useTranslation } from '@/i18n/react-i18next-compat'

Check warning on line 40 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

36-40 lines are not covered with tests

type ModelProps = {
model: CatalogModel
Expand All @@ -45,49 +45,49 @@
type SearchParams = {
repo: string
}
const defaultModelQuantizations = ['iq4_xs', 'q4_k_m']

Check warning on line 48 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

48 line is not covered with tests

export const Route = createFileRoute(route.hub.index as any)({
component: Hub,
validateSearch: (search: Record<string, unknown>): SearchParams => ({
repo: search.repo as SearchParams['repo'],
}),
})

Check warning on line 55 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

50-55 lines are not covered with tests

function Hub() {
const parentRef = useRef(null)

Check warning on line 58 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

57-58 lines are not covered with tests

const { t } = useTranslation()
const sortOptions = [
{ value: 'newest', name: t('hub:sortNewest') },
{ value: 'most-downloaded', name: t('hub:sortMostDownloaded') },
]
const { sources, addSource, fetchSources, loading } = useModelSources()
const search = useSearch({ from: route.hub.index as any })
const [searchValue, setSearchValue] = useState('')
const [sortSelected, setSortSelected] = useState('newest')
const [expandedModels, setExpandedModels] = useState<Record<string, boolean>>(
{}
)
const [isSearching, setIsSearching] = useState(false)
const [showOnlyDownloaded, setShowOnlyDownloaded] = useState(false)
const [huggingFaceRepo, setHuggingFaceRepo] = useState<CatalogModel | null>(
null
)
const [joyrideReady, setJoyrideReady] = useState(false)
const [currentStepIndex, setCurrentStepIndex] = useState(0)
const addModelSourceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
null
)
const downloadButtonRef = useRef<HTMLButtonElement>(null)
const hasTriggeredDownload = useRef(false)

Check warning on line 83 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

60-83 lines are not covered with tests

const { getProviderByName } = useModelProvider()
const llamaProvider = getProviderByName('llamacpp')

Check warning on line 86 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

85-86 lines are not covered with tests

// Convert HuggingFace repository to CatalogModel format
const convertHfRepoToCatalogModel = useCallback(
(repo: HuggingFaceRepo): CatalogModel => {

Check warning on line 90 in web-app/src/routes/hub/index.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

89-90 lines are not covered with tests
// Extract GGUF files from the repository siblings
const ggufFiles =
repo.siblings?.filter((file) =>
Expand Down Expand Up @@ -145,8 +145,12 @@
const repoInfo = await fetchHuggingFaceRepo(search.repo)
if (repoInfo) {
const catalogModel = convertHfRepoToCatalogModel(repoInfo)
setHuggingFaceRepo(catalogModel)
addSource(catalogModel)
if (
!sources.some((s) => s.model_name === catalogModel.model_name)
) {
setHuggingFaceRepo(catalogModel)
addSource(catalogModel)
}
}

await fetchSources()
Expand All @@ -157,7 +161,7 @@
}
}, 500)
}
}, [convertHfRepoToCatalogModel, fetchSources, addSource, search])
}, [convertHfRepoToCatalogModel, fetchSources, addSource, search, sources])

// Sorting functionality
const sortedModels = useMemo(() => {
Expand Down Expand Up @@ -247,8 +251,12 @@
const repoInfo = await fetchHuggingFaceRepo(e.target.value)
if (repoInfo) {
const catalogModel = convertHfRepoToCatalogModel(repoInfo)
setHuggingFaceRepo(catalogModel)
addSource(catalogModel)
if (
!sources.some((s) => s.model_name === catalogModel.model_name)
) {
setHuggingFaceRepo(catalogModel)
addSource(catalogModel)
}
}

// Original addSource logic (if needed)
Expand Down
Loading