fix: distinguish between hub and models search (#4989)

* fix: distinguish between hub and models search

* chore: refresh models hub when going to hub screen
This commit is contained in:
Louis 2025-05-14 12:08:33 +07:00 committed by GitHub
parent db43008813
commit b64749b4bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 7 deletions

View File

@ -40,4 +40,9 @@ export abstract class ModelExtension
* Delete a model source
*/
abstract deleteSource(source: string): Promise<void>
/**
* Fetch models hub
*/
abstract fetchModelsHub(): Promise<void>
}

View File

@ -67,7 +67,7 @@ export default class JanModelExtension extends ModelExtension {
}
// Sync with cortexsohub
this.fetchCortexsoModels()
this.fetchModelsHub()
}
/**
@ -450,7 +450,7 @@ export default class JanModelExtension extends ModelExtension {
/**
* Fetch models from cortex.so
*/
private fetchCortexsoModels = async () => {
fetchModelsHub = async () => {
const models = await this.fetchModels()
return this.queue.add(() =>

View File

@ -5,12 +5,23 @@ import { SearchIcon } from 'lucide-react'
import { useDebouncedCallback } from 'use-debounce'
import {
useGetModelSources,
useModelSourcesMutation,
} from '@/hooks/useModelSource'
import Spinner from '../Loader/Spinner'
type Props = {
supportModelImport?: boolean
onSearchLocal?: (searchText: string) => void
}
const ModelSearch = ({ onSearchLocal }: Props) => {
const ModelSearch = ({ supportModelImport, onSearchLocal }: Props) => {
const [searchText, setSearchText] = useState('')
const [isSearching, setSearching] = useState(false)
const { mutate } = useGetModelSources()
const { addModelSource } = useModelSourcesMutation()
const inputRef = useRef<HTMLInputElement | null>(null)
const debounced = useDebouncedCallback(async () => {
if (searchText.indexOf('/') === -1) {
@ -20,6 +31,15 @@ const ModelSearch = ({ onSearchLocal }: Props) => {
}
// Attempt to search local
onSearchLocal?.(searchText)
setSearching(true)
// Attempt to search model source
if (supportModelImport)
addModelSource(searchText)
.then(() => mutate())
.then(() => onSearchLocal?.(searchText))
.catch(console.debug)
.finally(() => setSearching(false))
}, 300)
const onSearchChanged = useCallback(
@ -50,8 +70,18 @@ const ModelSearch = ({ onSearchLocal }: Props) => {
return (
<Input
ref={inputRef}
prefixIcon={<SearchIcon size={16} />}
placeholder="Search models..."
prefixIcon={
isSearching ? (
<Spinner size={16} strokeWidth={2} />
) : (
<SearchIcon size={16} />
)
}
placeholder={
supportModelImport
? 'Search or enter Hugging Face URL'
: 'Search models'
}
onChange={onSearchChanged}
onKeyDown={onKeyDown}
value={searchText}

View File

@ -12,6 +12,7 @@ import {
ModelEvent,
ModelSource,
ModelSibling,
ModelExtension,
} from '@janhq/core'
import { useAtom, useAtomValue } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
@ -497,3 +498,21 @@ export const useRefreshModelList = (engine: string) => {
return { refreshingModels, refreshModels }
}
export const useFetchModelsHub = () => {
const extension = useMemo(
() => extensionManager.get<ModelExtension>(ExtensionTypeEnum.Model) ?? null,
[]
)
const { data, error, mutate } = useSWR(
extension ? 'fetchModelsHub' : null,
() => extension?.fetchModelsHub(),
{
revalidateOnFocus: false,
revalidateOnReconnect: true,
}
)
return { modelsHub: data, error, mutate }
}

View File

@ -25,7 +25,10 @@ import { twMerge } from 'tailwind-merge'
import CenterPanelContainer from '@/containers/CenterPanelContainer'
import ModelSearch from '@/containers/ModelSearch'
import { useGetEngineModelSources } from '@/hooks/useEngineManagement'
import {
useFetchModelsHub,
useGetEngineModelSources,
} from '@/hooks/useEngineManagement'
import { setImportModelStageAtom } from '@/hooks/useImportModel'
import {
@ -85,6 +88,7 @@ const hubCompatibleAtom = atom(false)
const HubScreen = () => {
const { sources } = useGetModelSources()
const { sources: remoteModelSources } = useGetEngineModelSources()
const { mutate: fetchModelsHub } = useFetchModelsHub()
const { addModelSource } = useModelSourcesMutation()
const [searchValue, setSearchValue] = useState('')
const [sortSelected, setSortSelected] = useState('newest')
@ -268,6 +272,10 @@ const HubScreen = () => {
},
})
useEffect(() => {
fetchModelsHub()
}, [])
return (
<CenterPanelContainer>
<m.div
@ -422,7 +430,10 @@ const HubScreen = () => {
<div className="absolute left-1/2 top-1/2 z-10 mx-auto w-4/5 -translate-x-1/2 -translate-y-1/2 rounded-xl sm:w-2/6">
<div className="flex flex-col items-center justify-between gap-2 sm:flex-row">
<div className="w-full" ref={dropdownRef}>
<ModelSearch onSearchLocal={onSearchUpdate} />
<ModelSearch
onSearchLocal={onSearchUpdate}
supportModelImport
/>
<div
className={twMerge(
'invisible absolute mt-2 max-h-[400px] w-full overflow-y-auto rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] shadow-lg',