import { ChangeEvent, Fragment, KeyboardEvent, useEffect, useRef } from 'react' import { EventName, MessageStatus, events } from '@janhq/core' import { Button, Textarea } from '@janhq/uikit' import { useAtom, useAtomValue } from 'jotai' import { debounce } from 'lodash' import { StopCircle } from 'lucide-react' import { twMerge } from 'tailwind-merge' import LogoMark from '@/containers/Brand/Logo/Mark' import ModelReload from '@/containers/Loader/ModelReload' import ModelStart from '@/containers/Loader/ModelStart' import { currentPromptAtom } from '@/containers/Providers/Jotai' import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' import useSendChatMessage from '@/hooks/useSendChatMessage' import ChatBody from '@/screens/Chat/ChatBody' import ThreadList from '@/screens/Chat/ThreadList' import Sidebar, { showRightSideBarAtom } from './Sidebar' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { activeThreadAtom, engineParamsUpdateAtom, getActiveThreadIdAtom, waitingToSendMessage, } from '@/helpers/atoms/Thread.atom' import { activeThreadStateAtom } from '@/helpers/atoms/Thread.atom' const ChatScreen = () => { const activeThread = useAtomValue(activeThreadAtom) const { downloadedModels } = useGetDownloadedModels() const { activeModel, stateModel } = useActiveModel() const { setMainViewState } = useMainViewState() const messages = useAtomValue(getCurrentChatMessagesAtom) const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom) const activeThreadState = useAtomValue(activeThreadStateAtom) const { sendChatMessage, queuedMessage, reloadModel } = useSendChatMessage() const isWaitingForResponse = activeThreadState?.waitingForResponse ?? false const isDisabledChatbox = currentPrompt.trim().length === 0 || isWaitingForResponse const activeThreadId = useAtomValue(getActiveThreadIdAtom) const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage) const showing = useAtomValue(showRightSideBarAtom) const textareaRef = useRef(null) const modelRef = useRef(activeModel) const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom) useEffect(() => { modelRef.current = activeModel }, [activeModel]) const onPromptChange = (e: React.ChangeEvent) => { setCurrentPrompt(e.target.value) } useEffect(() => { if (isWaitingToSend && activeThreadId) { setIsWaitingToSend(false) sendChatMessage() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [waitingToSendMessage, activeThreadId]) useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = '40px' textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px' } }, [currentPrompt]) const onKeyDown = debounce( async (e: React.KeyboardEvent) => { if (e.key === 'Enter') { if (!e.shiftKey) { e.preventDefault() if (messages[messages.length - 1]?.status !== MessageStatus.Pending) sendChatMessage() else onStopInferenceClick() } } }, 50, { leading: false, trailing: true } ) const onStopInferenceClick = async () => { events.emit(EventName.OnInferenceStopped, {}) } return (
{activeThread ? (
) : (
{downloadedModels.length === 0 && (

Welcome!

You need to download your first model

)}
)} {!engineParamsUpdate && } {reloadModel && ( <>
Model is reloading to apply new changes.
)} {queuedMessage && !reloadModel && (
Message queued. It can be sent once the model has started
)}