import { useCallback, useEffect, useState } from 'react' import { InferenceEngine, Thread } from '@janhq/core' import { Button } from '@janhq/joi' import { useAtomValue, useSetAtom } from 'jotai' import { GalleryHorizontalEndIcon, MoreHorizontalIcon, Paintbrush, PencilIcon, Trash2Icon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' import LeftPanelContainer from '@/containers/LeftPanelContainer' import { useCreateNewThread } from '@/hooks/useCreateNewThread' import useRecommendedModel from '@/hooks/useRecommendedModel' import useSetActiveThread from '@/hooks/useSetActiveThread' import { activeAssistantAtom, assistantsAtom, } from '@/helpers/atoms/Assistant.atom' import { editMessageAtom } from '@/helpers/atoms/ChatMessage.atom' import { getActiveThreadIdAtom, modalActionThreadAtom, threadDataReadyAtom, ThreadModalAction, threadsAtom, } from '@/helpers/atoms/Thread.atom' const ThreadLeftPanel = () => { const threads = useAtomValue(threadsAtom) const activeThreadId = useAtomValue(getActiveThreadIdAtom) const activeAssistant = useAtomValue(activeAssistantAtom) const { setActiveThread } = useSetActiveThread() const assistants = useAtomValue(assistantsAtom) const threadDataReady = useAtomValue(threadDataReadyAtom) const { requestCreateNewThread } = useCreateNewThread() const setEditMessage = useSetAtom(editMessageAtom) const { recommendedModel, downloadedModels } = useRecommendedModel() const setModalActionThread = useSetAtom(modalActionThreadAtom) const [contextMenu, setContextMenu] = useState<{ visible: boolean thread?: Thread }>({ visible: false, thread: undefined, }) const onThreadClick = useCallback( (thread: Thread) => { setActiveThread(thread) setEditMessage('') }, [setActiveThread, setEditMessage] ) /** * Auto create thread * This will create a new thread if there are assistants available * and there are no threads available */ useEffect(() => { if ( threadDataReady && assistants.length > 0 && threads.length === 0 && downloadedModels.length > 0 ) { const model = downloadedModels.filter( (model) => model.engine === InferenceEngine.cortex_llamacpp ) const selectedModel = model[0] || recommendedModel requestCreateNewThread( activeAssistant ? { ...assistants[0], ...activeAssistant } : assistants[0], selectedModel ) } else if (threadDataReady && !activeThreadId) { setActiveThread(threads[0]) } }, [ assistants, threads, threadDataReady, requestCreateNewThread, activeThreadId, setActiveThread, recommendedModel, downloadedModels, activeAssistant, ]) const onContextMenu = (event: React.MouseEvent, thread: Thread) => { event.preventDefault() setContextMenu({ visible: true, thread, }) } const closeContextMenu = () => { setContextMenu({ visible: false, thread: undefined, }) } return ( {threads.length === 0 ? (

No Thread History

) : (
{threads.map((thread) => (
{ onThreadClick(thread) }} onContextMenu={(e) => onContextMenu(e, thread)} onMouseLeave={closeContextMenu} >

{thread.title ?? thread.metadata?.title}

{ setModalActionThread({ showModal: ThreadModalAction.EditTitle, thread, }) e.stopPropagation() }} > Edit title
{ setModalActionThread({ showModal: ThreadModalAction.Clean, thread, }) e.stopPropagation() }} > Clean thread
{ setModalActionThread({ showModal: ThreadModalAction.Delete, thread, }) e.stopPropagation() }} > Delete thread
))}
)}
) } export default ThreadLeftPanel