/* eslint-disable @next/next/no-img-element */ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { ChatRequestOptions, CreateMessage, Message } from 'ai'; import { toast } from 'sonner'; import { Button } from '../ui/button'; import { Textarea } from '../ui/textarea'; import { cn } from '@/lib/utils'; import useWindowSize from '@/hooks/use-window-size'; import { X } from 'lucide-react'; interface Attachment { name: string; contentType: string; url: string; size: number; } const ArrowUpIcon = ({ size = 16 }: { size?: number }) => { return ( ); }; const StopIcon = ({ size = 16 }: { size?: number }) => { return ( ); }; const PaperclipIcon = ({ size = 16 }: { size?: number }) => { return ( ); }; const MAX_IMAGES = 3; interface FormComponentProps { input: string; setInput: (input: string) => void; attachments: Array; setAttachments: React.Dispatch>>; hasSubmitted: boolean; setHasSubmitted: (value: boolean) => void; isLoading: boolean; handleSubmit: ( event?: { preventDefault?: () => void; }, chatRequestOptions?: ChatRequestOptions, ) => void; fileInputRef: React.RefObject; inputRef: React.RefObject; stop: () => void; messages: Array; append: ( message: Message | CreateMessage, chatRequestOptions?: ChatRequestOptions, ) => Promise; } const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment, onRemove: () => void, isUploading: boolean }> = ({ attachment, onRemove, isUploading }) => { const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + ' bytes'; else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; else return (bytes / 1048576).toFixed(1) + ' MB'; }; const isUploadingAttachment = (attachment: Attachment | UploadingAttachment): attachment is UploadingAttachment => { return 'progress' in attachment; }; return ( {isUploading ? ( ) : isUploadingAttachment(attachment) ? ( {Math.round(attachment.progress * 100)}% ) : ( )} {!isUploadingAttachment(attachment) && ( {attachment.name} )} {isUploadingAttachment(attachment) ? 'Uploading...' : formatFileSize((attachment as Attachment).size)} { e.stopPropagation(); onRemove(); }} className="absolute -top-2 -right-2 p-0.5 m-0 rounded-full bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 shadow-sm hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors z-20" > ); }; interface UploadingAttachment { file: File; progress: number; } const FormComponent: React.FC = ({ input, setInput, attachments, setAttachments, hasSubmitted, setHasSubmitted, isLoading, handleSubmit, fileInputRef, inputRef, stop }) => { const [uploadingAttachments, setUploadingAttachments] = useState([]); const [uploadQueue, setUploadQueue] = useState>([]); const { width } = useWindowSize(); const postSubmitFileInputRef = useRef(null); const adjustHeight = useCallback(() => { if (inputRef.current) { inputRef.current.style.height = "auto"; inputRef.current.style.height = `${inputRef.current.scrollHeight + 2}px`; } }, [inputRef]); useEffect(() => { if (inputRef.current) { adjustHeight(); } }, [adjustHeight, input, inputRef]); const handleInput = (event: React.ChangeEvent) => { setInput(event.target.value); adjustHeight(); }; const uploadFile = async (file: File): Promise => { const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/api/upload', { method: 'POST', body: formData, }); if (response.ok) { const data = await response.json(); return data; } else { throw new Error('Failed to upload file'); } } catch (error) { console.error("Error uploading file:", error); toast.error("Failed to upload file, please try again!"); throw error; } }; const handleFileChange = useCallback(async (event: React.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); setAttachments((currentAttachments) => [ ...currentAttachments, ...uploadedAttachments, ]); } catch (error) { console.error("Error uploading files!", error); } finally { setUploadQueue([]); event.target.value = ''; } }, [setAttachments]); const removeAttachment = (index: number) => { setAttachments(prev => prev.filter((_, i) => i !== index)); }; const onSubmit = useCallback((event: React.FormEvent) => { event.preventDefault(); event.stopPropagation(); if (input.trim() || attachments.length > 0) { setHasSubmitted(true); handleSubmit(event, { experimental_attachments: attachments, }); setAttachments([]); setUploadingAttachments([]); if (fileInputRef.current) { fileInputRef.current.value = ''; } } else { toast.error("Please enter a search query or attach an image."); } }, [input, attachments, setHasSubmitted, handleSubmit, setAttachments, setUploadingAttachments, fileInputRef]); const submitForm = useCallback(() => { onSubmit({ preventDefault: () => { }, stopPropagation: () => { } } as React.FormEvent); if (width && width > 768) { inputRef.current?.focus(); } }, [onSubmit, width, inputRef]); const triggerFileInput = useCallback(() => { if (hasSubmitted) { postSubmitFileInputRef.current?.click(); } else { fileInputRef.current?.click(); } }, [hasSubmitted, fileInputRef]); return ( {(attachments.length > 0 || uploadQueue.length > 0) && ( {attachments.map((attachment, index) => ( removeAttachment(index)} isUploading={false} /> ))} {uploadQueue.map((filename) => ( { }} isUploading={true} /> ))} )} { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); if (isLoading) { toast.error("Please wait for the model to finish its response!"); } else { submitForm(); } } }} /> {isLoading ? ( { event.preventDefault(); stop(); }} > ) : ( { event.preventDefault(); submitForm(); }} disabled={input.length === 0 && attachments.length === 0 || uploadQueue.length > 0} > )} { event.preventDefault(); triggerFileInput(); }} variant="outline" disabled={isLoading} > ); }; export default FormComponent;
{attachment.name}
{isUploadingAttachment(attachment) ? 'Uploading...' : formatFileSize((attachment as Attachment).size)}