/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useRef, useState } from 'react' import { InferenceEngine, MessageStatus } from '@janhq/core' import { TextArea, Button, Tooltip, useClickOutside, Badge } from '@janhq/joi' import { useAtom, useAtomValue } from 'jotai' import { FileTextIcon, ImageIcon, StopCircle, PaperclipIcon, SettingsIcon, ChevronUpIcon, Settings2Icon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' import ModelDropdown from '@/containers/ModelDropdown' import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai' import { useActiveModel } from '@/hooks/useActiveModel' import useSendChatMessage from '@/hooks/useSendChatMessage' import { isLocalEngine } from '@/utils/modelEngine' import FileUploadPreview from '../FileUploadPreview' import ImageUploadPreview from '../ImageUploadPreview' import RichTextEditor from './RichTextEditor' import { showRightPanelAtom } from '@/helpers/atoms/App.atom' import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom' import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { selectedModelAtom } from '@/helpers/atoms/Model.atom' import { spellCheckAtom } from '@/helpers/atoms/Setting.atom' import { activeSettingInputBoxAtom, activeThreadAtom, getActiveThreadIdAtom, isGeneratingResponseAtom, threadStatesAtom, } from '@/helpers/atoms/Thread.atom' import { activeTabThreadRightPanelAtom } from '@/helpers/atoms/ThreadRightPanel.atom' const ChatInput = () => { const activeThread = useAtomValue(activeThreadAtom) const { stateModel } = useActiveModel() const messages = useAtomValue(getCurrentChatMessagesAtom) const spellCheck = useAtomValue(spellCheckAtom) const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom) const [activeSettingInputBox, setActiveSettingInputBox] = useAtom( activeSettingInputBoxAtom ) const { sendChatMessage } = useSendChatMessage() const selectedModel = useAtomValue(selectedModelAtom) const activeThreadId = useAtomValue(getActiveThreadIdAtom) const [fileUpload, setFileUpload] = useAtom(fileUploadAtom) const [showAttacmentMenus, setShowAttacmentMenus] = useState(false) const textareaRef = useRef(null) const fileInputRef = useRef(null) const imageInputRef = useRef(null) const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom) const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom) const threadStates = useAtomValue(threadStatesAtom) const activeAssistant = useAtomValue(activeAssistantAtom) const { stopInference } = useActiveModel() const [activeTabThreadRightPanel, setActiveTabThreadRightPanel] = useAtom( activeTabThreadRightPanelAtom ) const isStreamingResponse = Object.values(threadStates).some( (threadState) => threadState.waitingForResponse ) const refAttachmentMenus = useClickOutside(() => setShowAttacmentMenus(false)) const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom) useEffect(() => { if (textareaRef.current) { textareaRef.current.focus() } }, [activeThreadId]) const onStopInferenceClick = async () => { stopInference() } const isModelSupportRagAndTools = selectedModel?.engine === InferenceEngine.openai || isLocalEngine(selectedModel?.engine as InferenceEngine) /** * Handles the change event of the extension file input element by setting the file name state. * Its to be used to display the extension file name of the selected file. * @param event - The change event object. */ const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return setFileUpload([{ file: file, type: 'pdf' }]) } const handleImageChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return setFileUpload([{ file: file, type: 'image' }]) } const renderPreview = (fileUpload: any) => { if (fileUpload.length > 0) { if (fileUpload[0].type === 'image') { return } else { return } } } return (
{renderPreview(fileUpload)}