import { useCallback, useEffect, useMemo, useState } from 'react' import { useDropzone } from 'react-dropzone' import Image from 'next/image' import { InferenceEngine } from '@janhq/core' import { Button, ScrollArea } from '@janhq/joi' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { ChevronDownIcon, ChevronUpIcon, UploadCloudIcon, UploadIcon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' import BlankState from '@/containers/BlankState' import ModelSearch from '@/containers/ModelSearch' import SetupRemoteModel from '@/containers/SetupRemoteModel' import useDropModelBinaries from '@/hooks/useDropModelBinaries' import { useGetEngines } from '@/hooks/useEngineManagement' import { setImportModelStageAtom } from '@/hooks/useImportModel' import { getLogoEngine, getTitleByEngine, priorityEngine, } from '@/utils/modelEngine' import MyModelList from './MyModelList' import { downloadedModelsAtom, showEngineListModelAtom, } from '@/helpers/atoms/Model.atom' const MyModels = () => { const downloadedModels = useAtomValue(downloadedModelsAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom) const { onDropModels } = useDropModelBinaries() const [searchText, setSearchText] = useState('') const [showEngineListModel, setShowEngineListModel] = useAtom( showEngineListModelAtom ) const { engines } = useGetEngines() const isLocalEngine = useCallback( (engine: string) => Object.values(engines ?? {}) .flat() .find((e) => e.name === engine)?.type === 'local' || false, [engines] ) const isConfigured = useCallback( (engine: string) => (Object.values(engines ?? {}) .flat() .find((e) => e.engine === engine)?.api_key?.length ?? 0) > 0, [engines] ) const filteredDownloadedModels = useMemo( () => downloadedModels .filter((e) => e.name.toLowerCase().includes(searchText.toLowerCase().trim()) ) .sort((a, b) => a.name.localeCompare(b.name)), [downloadedModels, searchText] ) const { getRootProps, isDragActive } = useDropzone({ noClick: true, multiple: true, onDrop: onDropModels, }) const onImportModelClick = useCallback(() => { setImportModelStage('SELECTING_MODEL') }, [setImportModelStage]) const onSearchChange = useCallback((input: string) => { setSearchText(input) }, []) const findByEngine = filteredDownloadedModels.map((x) => { // Legacy engine support - they will be grouped under Cortex LlamaCPP if (x.engine === InferenceEngine.nitro) return InferenceEngine.cortex_llamacpp return x.engine }) const groupByEngine = [...new Set(findByEngine)].sort((a, b) => { const aPriority = priorityEngine.indexOf(a) const bPriority = priorityEngine.indexOf(b) if (aPriority !== -1 && bPriority !== -1) return aPriority - bPriority if (aPriority !== -1) return -1 if (bPriority !== -1) return 1 return 0 }) const getEngineStatusReady: InferenceEngine[] = Object.entries(engines ?? {}) // eslint-disable-next-line @typescript-eslint/no-unused-vars ?.filter(([_, value]) => (value?.[0]?.api_key?.length ?? 0) > 0) // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(([key, _]) => key as InferenceEngine) useEffect(() => { setShowEngineListModel((prev) => [ ...prev, ...(getEngineStatusReady as InferenceEngine[]), ]) // eslint-disable-next-line react-hooks/exhaustive-deps }, [setShowEngineListModel, engines]) return (
{isDragActive && (
Drop file here

File (GGUF) or folder

)}
{!groupByEngine.length ? (
) : ( groupByEngine.map((engine, i) => { const engineLogo = getLogoEngine(engine as InferenceEngine) const showModel = showEngineListModel.includes(engine) const onClickChevron = () => { if (showModel) { setShowEngineListModel((prev) => prev.filter((item) => item !== engine) ) } else { setShowEngineListModel((prev) => [...prev, engine]) } } return (
{engineLogo && ( logo )}
{getTitleByEngine(engine)}
{!isLocalEngine(engine) && ( )} {!showModel ? ( ) : ( )}
{filteredDownloadedModels ? filteredDownloadedModels .filter( (x) => x.engine === engine || (x.engine === InferenceEngine.nitro && engine === InferenceEngine.cortex_llamacpp) ) .map((model) => { if (!showModel) return null return ( ) }) : null}
) }) )}
) } export default MyModels