import { memo, useCallback, useMemo, useState } from 'react' import { EngineStatus, LocalEngines, Model, RemoteEngine, RemoteEngines, } from '@janhq/core' import { Badge, Button, useClickOutside } from '@janhq/joi' import { useAtomValue, useSetAtom } from 'jotai' import { MoreVerticalIcon, PlayIcon, StopCircleIcon, Trash2Icon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' import { toaster } from '@/containers/Toast' import useAssistantQuery from '@/hooks/useAssistantQuery' import useEngineQuery from '@/hooks/useEngineQuery' import useModelStart from '@/hooks/useModelStart' import useModelStop from '@/hooks/useModelStop' import useModels from '@/hooks/useModels' import useThreadCreateMutation from '@/hooks/useThreadCreateMutation' import { showWarningMultipleModelModalAtom } from '@/screens/HubScreen2/components/WarningMultipleModelModal' import { MainViewState, mainViewStateAtom } from '@/helpers/atoms/App.atom' import { activeModelsAtom } from '@/helpers/atoms/Model.atom' type Props = { model: Model } // If more than this number of models are running, show a warning modal. export const concurrentModelWarningThreshold = 2 const ModelItem: React.FC = ({ model }) => { const activeModels = useAtomValue(activeModelsAtom) const startModel = useModelStart() const stopModel = useModelStop() const [more, setMore] = useState(false) const { deleteModel } = useModels() const { data: engineData } = useEngineQuery() const createThreadMutation = useThreadCreateMutation() const { data: assistants } = useAssistantQuery() const setMainViewState = useSetAtom(mainViewStateAtom) const isRemoteEngine = RemoteEngines.includes(model.engine as RemoteEngine) const isEngineReady = engineData?.find((e) => e.name === model.engine)?.status === EngineStatus.Ready const [menu, setMenu] = useState(null) const [toggle, setToggle] = useState(null) const setShowWarningMultipleModelModal = useSetAtom( showWarningMultipleModelModalAtom ) useClickOutside(() => setMore(false), null, [menu, toggle]) const isActive = useMemo( () => activeModels.map((m) => m.model).includes(model.model), [activeModels, model.model] ) const onModelActionClick = useCallback( (modelId: string) => { if (isActive) { // if model already active, stop it stopModel.mutate(modelId) return } if (activeModels.length >= concurrentModelWarningThreshold) { // if max concurrent models reached, stop the first model // display popup setShowWarningMultipleModelModal(true) } startModel.mutate(modelId) }, [ isActive, startModel, stopModel, activeModels.length, setShowWarningMultipleModelModal, ] ) const onDeleteModelClicked = useCallback( async (modelId: string) => { await stopModel.mutateAsync(modelId) await deleteModel(modelId) }, [stopModel, deleteModel] ) const isLocalModel = LocalEngines.find( (e) => model.engine != null && e === model.engine ) const onClickCloudModel = useCallback(async () => { if (!isRemoteEngine) return null if (!model || !engineData) return if (!assistants || !assistants.length) { toaster({ title: 'No assistant available.', description: `Could not create a new thread. Please add an assistant.`, type: 'error', }) return } await createThreadMutation.mutateAsync({ modelId: model.model, assistant: assistants[0], }) setMainViewState(MainViewState.Thread) }, [ assistants, createThreadMutation, engineData, isRemoteEngine, model, setMainViewState, ]) return (
{model.model}
{model.engine === 'cortex.llamacpp' && (

{model.model}

)}
{isLocalModel && (
{model.version != null ? `v${model.version}` : '-'}
{isActive ? ( Active ) : ( Inactive )}
{ setMore(!more) }} > {more && (
{ onModelActionClick(model.model) setMore(false) }} > {isActive ? ( ) : ( )} {isActive ? 'Stop' : 'Start'}  Model
onDeleteModelClicked(model.model)} > Delete Model
)}
)}
) } export default memo(ModelItem)