import { Send, Square } from 'lucide-react' import { createContext, useContext, useRef, useState } from 'react' import { cn } from '../lib/utils' import { Button } from '../ui/button' import { Textarea } from '../ui/textarea' import { FileUploader } from '../widgets/index.js' // this import needs the file extension as it's importing the widget bundle import { useChatUI } from './chat.context' import { Message } from './chat.interface' import { v4 as uuidv4 } from 'uuid' import { MessagePart } from './message-parts' interface ChatInputProps extends React.PropsWithChildren { className?: string resetUploadedFiles?: () => void attachments?: MessagePart[] } interface ChatInputFormProps extends React.PropsWithChildren { className?: string } interface ChatInputFieldProps { className?: string placeholder?: string } interface ChatInputUploadProps { className?: string onUpload?: (file: File) => Promise | undefined allowedExtensions?: string[] multiple?: boolean } interface ChatInputSubmitProps extends React.PropsWithChildren { className?: string disabled?: boolean } interface ChatInputContext { isDisabled: boolean handleKeyDown: (e: React.KeyboardEvent) => void handleSubmit: (e: React.FormEvent) => void isComposing: boolean setIsComposing: (value: boolean) => void } const chatInputContext = createContext(null) const ChatInputProvider = chatInputContext.Provider export const useChatInput = () => { const context = useContext(chatInputContext) if (!context) { throw new Error('useChatInput must be used within a ChatInputProvider') } return context } function ChatInput(props: ChatInputProps) { const { input, setInput, sendMessage, isLoading, requestData } = useChatUI() const isDisabled = isLoading || !input.trim() const [isComposing, setIsComposing] = useState(false) const submit = async () => { const newMessage: Message = { id: uuidv4(), role: 'user', parts: [{ type: 'text', text: input }, ...(props.attachments ?? [])], } setInput('') // Clear the input props.resetUploadedFiles?.() // Reset the uploaded files await sendMessage(newMessage, { body: requestData }) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() await submit() } const handleKeyDown = async (e: React.KeyboardEvent) => { if (isDisabled) return if (e.key === 'Enter' && !e.shiftKey && !isComposing) { e.preventDefault() await submit() } } const children = props.children ?? return (
{children}
) } function ChatInputForm(props: ChatInputFormProps) { const { handleSubmit } = useChatInput() const children = props.children ?? ( <> ) return (
{children}
) } function ChatInputField(props: ChatInputFieldProps) { const { input, setInput } = useChatUI() const { handleKeyDown, setIsComposing } = useChatInput() const textareaRef = useRef(null) // auto resize the textarea based on the content const handleInputChange = (e: React.ChangeEvent) => { setInput(e.target.value) if (textareaRef.current) { textareaRef.current.style.height = 'auto' let newHeight = Math.max(textareaRef.current.scrollHeight, 100) if (textareaRef.current.scrollHeight > 80) { newHeight += 40 // offset for the textarea padding } textareaRef.current.style.height = `${newHeight}px` } } return (