"use client"; import { Attachment, ChatRequestOptions, CreateMessage, Message } from "ai"; import { motion } from "framer-motion"; import React, { useRef, useEffect, useState, useCallback, Dispatch, SetStateAction, ChangeEvent, } from "react"; import { toast } from "sonner"; import { ArrowUpIcon, PaperclipIcon, StopIcon } from "./icons"; import { PreviewAttachment } from "./preview-attachment"; import { Button } from "../ui/button"; import { Textarea } from "../ui/textarea"; const suggestedActions = [ { title: "What is", label: "the meaning of life?", action: "what is the meaning of life?", }, { title: "Why do", label: "developers use Next.js?", action: "why do developers use Next.js?", }, ]; export function MultimodalInput({ input, setInput, isLoading, stop, attachments, setAttachments, messages, append, handleSubmit, }: { input: string; setInput: (value: string) => void; isLoading: boolean; stop: () => void; attachments: Array; setAttachments: Dispatch>>; messages: Array; append: ( message: Message | CreateMessage, chatRequestOptions?: ChatRequestOptions, ) => Promise; handleSubmit: ( event?: { preventDefault?: () => void; }, chatRequestOptions?: ChatRequestOptions, ) => void; }) { const textareaRef = useRef(null); useEffect(() => { if (textareaRef.current) { adjustHeight(); } }, []); const adjustHeight = () => { if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.style.height = `${textareaRef.current.scrollHeight + 2}px`; } }; const handleInput = (event: React.ChangeEvent) => { setInput(event.target.value); adjustHeight(); }; const fileInputRef = useRef(null); const [uploadQueue, setUploadQueue] = useState>([]); const submitForm = useCallback(() => { handleSubmit(undefined, { experimental_attachments: attachments, }); setAttachments([]); }, [attachments, handleSubmit, setAttachments]); const uploadFile = async (file: File) => { const formData = new FormData(); formData.append("file", file); try { const response = await fetch(`/api/files/upload`, { method: "POST", body: formData, }); if (response.ok) { const data = await response.json(); const { url, pathname, contentType } = data; return { url, name: pathname, contentType: contentType, }; } else { const { error } = await response.json(); toast.error(error); } } catch (error) { toast.error("Failed to upload file, please try again!"); } }; const handleFileChange = useCallback( async (event: ChangeEvent) => { const files = Array.from(event.target.files || []); setUploadQueue(files.map((file) => file.name)); try { const uploadPromises = files.map((file) => uploadFile(file)); const uploadedAttachments = await Promise.all(uploadPromises); const successfullyUploadedAttachments = uploadedAttachments.filter( (attachment) => attachment !== undefined, ); setAttachments((currentAttachments) => [ ...currentAttachments, ...successfullyUploadedAttachments, ]); } catch (error) { console.error("Error uploading files!", error); } finally { setUploadQueue([]); } }, [setAttachments], ); return (
{messages.length === 0 && attachments.length === 0 && uploadQueue.length === 0 && (
{suggestedActions.map((suggestedAction, index) => ( 1 ? "hidden sm:block" : "block"} > ))}
)} {(attachments.length > 0 || uploadQueue.length > 0) && (
{attachments.map((attachment) => ( ))} {uploadQueue.map((filename) => ( ))}
)}