import { Fragment, useContext, useEffect, useRef, useState } from 'react' import { Model } from '@janhq/core/lib/types' import { Button, Badge, Textarea } from '@janhq/uikit' import { useAtom, useAtomValue } from 'jotai' import { Trash2Icon, Paintbrush } from 'lucide-react' import { twMerge } from 'tailwind-merge' import { currentPromptAtom } from '@/containers/Providers/Jotai' import ShortCut from '@/containers/Shortcut' import { toaster } from '@/containers/Toast' import { FeatureToggleContext } from '@/context/FeatureToggle' import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useCreateConversation } from '@/hooks/useCreateConversation' import useDeleteConversation from '@/hooks/useDeleteConversation' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import useGetUserConversations from '@/hooks/useGetUserConversations' import { useMainViewState } from '@/hooks/useMainViewState' import useSendChatMessage from '@/hooks/useSendChatMessage' import ChatBody from '@/screens/Chat/ChatBody' import HistoryList from '@/screens/Chat/HistoryList' import { currentConversationAtom, getActiveConvoIdAtom, userConversationsAtom, waitingToSendMessage, } from '@/helpers/atoms/Conversation.atom' import { currentConvoStateAtom } from '@/helpers/atoms/Conversation.atom' const ChatScreen = () => { const currentConvo = useAtomValue(currentConversationAtom) const { downloadedModels } = useGetDownloadedModels() const { deleteConvo, cleanConvo } = useDeleteConversation() const { activeModel, stateModel } = useActiveModel() const { setMainViewState } = useMainViewState() const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom) const currentConvoState = useAtomValue(currentConvoStateAtom) const { sendChatMessage } = useSendChatMessage() const isWaitingForResponse = currentConvoState?.waitingForResponse ?? false const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse const activeConversationId = useAtomValue(getActiveConvoIdAtom) const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage) const { requestCreateConvo } = useCreateConversation() const { getUserConversations } = useGetUserConversations() const conversations = useAtomValue(userConversationsAtom) const isEnableChat = (currentConvo && activeModel) || conversations.length > 0 const [isModelAvailable, setIsModelAvailable] = useState( downloadedModels.some((x) => x.id === currentConvo?.modelId) ) const { experimentalFeatureEnabed } = useContext(FeatureToggleContext) const textareaRef = useRef(null) const { startModel } = useActiveModel() const modelRef = useRef(activeModel) useEffect(() => { modelRef.current = activeModel }, [activeModel]) useEffect(() => { getUserConversations() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const handleMessageChange = (e: React.ChangeEvent) => { setCurrentPrompt(e.target.value) } useEffect(() => { setIsModelAvailable( downloadedModels.some((x) => x.id === currentConvo?.modelId) ) }, [currentConvo, downloadedModels]) const handleSendMessage = async () => { if (!activeModel || activeModel.id !== currentConvo?.modelId) { const model = downloadedModels.find((e) => e.id === currentConvo?.modelId) // Model is available to start if (model != null) { toaster({ title: 'Message queued.', description: 'It will be sent once the model is done loading.', }) startModel(model.id).then(() => { setTimeout(() => { if (modelRef?.current?.id === currentConvo?.modelId) sendChatMessage() }, 300) }) } return } if (activeConversationId) { sendChatMessage() } else { setIsWaitingToSend(true) await requestCreateConvo(activeModel as Model) } } useEffect(() => { if (isWaitingToSend && activeConversationId) { setIsWaitingToSend(false) sendChatMessage() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [waitingToSendMessage, activeConversationId]) useEffect(() => { if (textareaRef.current !== null) { const scrollHeight = textareaRef.current.scrollHeight if (currentPrompt.length === 0) { textareaRef.current.style.height = '40px' } else { textareaRef.current.style.height = `${ scrollHeight < 40 ? 40 : scrollHeight }px` } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentPrompt]) const handleKeyDown = async (e: React.KeyboardEvent) => { if (e.key === 'Enter') { if (!e.shiftKey) { e.preventDefault() handleSendMessage() } } } return (
{isEnableChat && currentConvo && (
{currentConvo?.summary ?? ''}
{!isModelAvailable && ( )} {experimentalFeatureEnabed && ( cleanConvo()} /> )} { deleteConvo()} /> }
)} {isEnableChat ? (
) : (
{downloadedModels.length === 0 && (

{`Ups, you don't have a Model`}

{`let’s download your first model.`}

)} {!activeModel && downloadedModels.length > 0 && (

{`You don’t have any actively running models`}

{`Please start a downloaded model in My Models page to use this feature.`}

  to show your model
)}
)}