import { ChatbotUIContext } from "@/context/context" import useHotkey from "@/lib/hooks/use-hotkey" import { LLM_LIST } from "@/lib/models/llm/llm-list" import { cn } from "@/lib/utils" import { IconBolt, IconCirclePlus, IconPlayerStopFilled, IconSend } from "@tabler/icons-react" import Image from "next/image" import { FC, useContext, useEffect, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" import { Input } from "../ui/input" import { TextareaAutosize } from "../ui/textarea-autosize" import { ChatCommandInput } from "./chat-command-input" import { ChatFilesDisplay } from "./chat-files-display" import { useChatHandler } from "./chat-hooks/use-chat-handler" import { useChatHistoryHandler } from "./chat-hooks/use-chat-history" import { usePromptAndCommand } from "./chat-hooks/use-prompt-and-command" import { useSelectFileHandler } from "./chat-hooks/use-select-file-handler" interface ChatInputProps {} export const ChatInput: FC = ({}) => { const { t } = useTranslation() useHotkey("l", () => { handleFocusChatInput() }) const [isTyping, setIsTyping] = useState(false) const { isAssistantPickerOpen, focusAssistant, setFocusAssistant, userInput, chatMessages, isGenerating, selectedPreset, selectedAssistant, focusPrompt, setFocusPrompt, focusFile, focusTool, setFocusTool, isToolPickerOpen, isPromptPickerOpen, setIsPromptPickerOpen, isFilePickerOpen, setFocusFile, chatSettings, selectedTools, setSelectedTools, assistantImages } = useContext(ChatbotUIContext) const { chatInputRef, handleSendMessage, handleStopMessage, handleFocusChatInput } = useChatHandler() const { handleInputChange } = usePromptAndCommand() const { filesToAccept, handleSelectDeviceFile } = useSelectFileHandler() const { setNewMessageContentToNextUserMessage, setNewMessageContentToPreviousUserMessage } = useChatHistoryHandler() const fileInputRef = useRef(null) useEffect(() => { setTimeout(() => { handleFocusChatInput() }, 200) // FIX: hacky }, [selectedPreset, selectedAssistant]) const handleKeyDown = (event: React.KeyboardEvent) => { if (!isTyping && event.key === "Enter" && !event.shiftKey) { event.preventDefault() setIsPromptPickerOpen(false) handleSendMessage(userInput, chatMessages, false) } // Consolidate conditions to avoid TypeScript error if ( isPromptPickerOpen || isFilePickerOpen || isToolPickerOpen || isAssistantPickerOpen ) { if ( event.key === "Tab" || event.key === "ArrowUp" || event.key === "ArrowDown" ) { event.preventDefault() // Toggle focus based on picker type if (isPromptPickerOpen) setFocusPrompt(!focusPrompt) if (isFilePickerOpen) setFocusFile(!focusFile) if (isToolPickerOpen) setFocusTool(!focusTool) if (isAssistantPickerOpen) setFocusAssistant(!focusAssistant) } } if (event.key === "ArrowUp" && event.shiftKey && event.ctrlKey) { event.preventDefault() setNewMessageContentToPreviousUserMessage() } if (event.key === "ArrowDown" && event.shiftKey && event.ctrlKey) { event.preventDefault() setNewMessageContentToNextUserMessage() } //use shift+ctrl+up and shift+ctrl+down to navigate through chat history if (event.key === "ArrowUp" && event.shiftKey && event.ctrlKey) { event.preventDefault() setNewMessageContentToPreviousUserMessage() } if (event.key === "ArrowDown" && event.shiftKey && event.ctrlKey) { event.preventDefault() setNewMessageContentToNextUserMessage() } if ( isAssistantPickerOpen && (event.key === "Tab" || event.key === "ArrowUp" || event.key === "ArrowDown") ) { event.preventDefault() setFocusAssistant(!focusAssistant) } } const handlePaste = (event: React.ClipboardEvent) => { const imagesAllowed = LLM_LIST.find( llm => llm.modelId === chatSettings?.model )?.imageInput const items = event.clipboardData.items for (const item of items) { if (item.type.indexOf("image") === 0) { if (!imagesAllowed) { toast.error( `Images are not supported for this model. Use models like GPT-4 Vision instead.` ) return } const file = item.getAsFile() if (!file) return handleSelectDeviceFile(file) } } } return ( <>
{selectedTools && selectedTools.map((tool, index) => (
setSelectedTools( selectedTools.filter( selectedTool => selectedTool.id !== tool.id ) ) } >
{tool.name}
))} {selectedAssistant && (
{selectedAssistant.image_path && ( img.path === selectedAssistant.image_path )?.base64 } width={28} height={28} alt={selectedAssistant.name} /> )}
Talking to {selectedAssistant.name}
)}
<> fileInputRef.current?.click()} /> {/* Hidden input to select files from device */} { if (!e.target.files) return handleSelectDeviceFile(e.target.files[0]) }} accept={filesToAccept} /> setIsTyping(true)} onCompositionEnd={() => setIsTyping(false)} />
{isGenerating ? ( ) : ( { if (!userInput) return handleSendMessage(userInput, chatMessages, false) }} size={30} /> )}
) }