import { useCallback, useEffect, useState } from 'react' import { InferenceEngine, Model } from '@janhq/core' import { Button, Select, SelectContent, SelectGroup, SelectPortal, SelectItem, SelectTrigger, SelectValue, } from '@janhq/uikit' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { MonitorIcon, LayoutGridIcon, FoldersIcon, GlobeIcon, CheckIcon, CopyIcon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useClipboard } from '@/hooks/useClipboard' import useRecommendedModel from '@/hooks/useRecommendedModel' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' import { toGibibytes } from '@/utils/converter' import ModelLabel from '../ModelLabel' import OpenAiKeyInput from '../OpenAiKeyInput' import { mainViewStateAtom } from '@/helpers/atoms/App.atom' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' import { activeThreadAtom, setThreadModelParamsAtom, } from '@/helpers/atoms/Thread.atom' export const selectedModelAtom = atom(undefined) const engineOptions = ['Local', 'Remote'] // TODO: Move all of the unscoped logics outside of the component const DropdownListSidebar = ({ strictedThread = true, }: { strictedThread?: boolean }) => { const activeThread = useAtomValue(activeThreadAtom) const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) const [isTabActive, setIsTabActive] = useState(0) const { stateModel } = useActiveModel() const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom) const setMainViewState = useSetAtom(mainViewStateAtom) const [loader, setLoader] = useState(0) const { recommendedModel, downloadedModels } = useRecommendedModel() const { updateModelParameter } = useUpdateModelParameters() const clipboard = useClipboard({ timeout: 1000 }) const [copyId, setCopyId] = useState('') const localModel = downloadedModels.filter( (model) => model.engine === InferenceEngine.nitro ) const remoteModel = downloadedModels.filter( (model) => model.engine === InferenceEngine.openai ) const modelOptions = isTabActive === 0 ? localModel : remoteModel useEffect(() => { if (!activeThread) return let model = downloadedModels.find( (model) => model.id === activeThread.assistants[0].model.id ) if (!model) { model = recommendedModel } setSelectedModel(model) }, [recommendedModel, activeThread, downloadedModels, setSelectedModel]) // This is fake loader please fix this when we have realtime percentage when load model useEffect(() => { if (stateModel.model === selectedModel?.id && stateModel.loading) { if (loader === 24) { setTimeout(() => { setLoader(loader + 1) }, 250) } else if (loader === 50) { setTimeout(() => { setLoader(loader + 1) }, 250) } else if (loader === 78) { setTimeout(() => { setLoader(loader + 1) }, 250) } else if (loader === 85) { setLoader(85) } else { setLoader(loader + 1) } } else { setLoader(0) } }, [stateModel.loading, loader, selectedModel, stateModel.model]) const onValueSelected = useCallback( async (modelId: string) => { const model = downloadedModels.find((m) => m.id === modelId) setSelectedModel(model) if (serverEnabled) { window.core?.api?.stopServer() setServerEnabled(false) } if (activeThread) { const modelParams = { ...model?.parameters, ...model?.settings, } // Update model paramter to the thread state setThreadModelParams(activeThread.id, modelParams) // Update model parameter to the thread file if (model) updateModelParameter(activeThread.id, { params: modelParams, modelId: model.id, engine: model.engine, }) } }, [ downloadedModels, serverEnabled, activeThread, setSelectedModel, setServerEnabled, setThreadModelParams, updateModelParameter, ] ) if (strictedThread && !activeThread) { return null } const selectedModelLoading = stateModel.model === selectedModel?.id && stateModel.loading return ( <>
) } export default DropdownListSidebar