import { KeyboardEvent, useCallback, useRef, useState } from "react"; import { useFetcher, useLoaderData } from "@remix-run/react"; import { useUploadFile } from "../lib/hooks/use-upload-file"; import SpacesSelector from "./memories/SpacesSelector"; import { Button } from "./ui/button"; import { Textarea } from "./ui/textarea"; import { SpaceIcon } from "@supermemory/shared/icons"; import { cn } from "~/lib/utils"; import { loader } from "~/routes/_index"; function MemoryInputForm({ user, input, setInput, submit: externalSubmit, mini = false, fileURLs = [], setFileURLs, isLoading = false, }: { user: ReturnType>["user"]; input: string; setInput: React.Dispatch>; submit: () => void; mini?: boolean; fileURLs?: string[]; setFileURLs?: React.Dispatch>; isLoading?: boolean; }) { const [previews, setPreviews] = useState([]); const { uploadFile, isUploading } = useUploadFile(); const fetcher = useFetcher(); const fileInputRef = useRef(null); const [isDragActive, setIsDragActive] = useState(false); const [selectedSpaces, setSelectedSpaces] = useState([]); const submit = useCallback(() => { if (input.trim() || fileURLs.length > 0) { if (!isLoading) { externalSubmit(); setInput(""); setFileURLs?.([]); setPreviews([]); } } }, [externalSubmit, input, fileURLs.length, setInput, isLoading]); const handlePaste = useCallback( (e: React.ClipboardEvent) => { const items = e.clipboardData.items; for (const item of items) { if (item.type.startsWith("image/") || item.type === "application/pdf") { const file = item.getAsFile(); if (file) { if (isUploading || fileURLs.length !== previews.length) { console.log( "Cannot upload file: Upload in progress or previous upload not completed", ); return; } if (fileURLs.length >= 5) { console.log("Maximum file limit reached"); return; } handleFileUpload(file); break; // Only handle one file per paste } } } }, [isUploading, fileURLs.length, previews.length], ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); } }, [submit], ); const handleAttachClick = useCallback(() => { if (isUploading || fileURLs.length !== previews.length) { console.log("Cannot attach file: Upload in progress or previous upload not completed"); return; } if (fileInputRef.current) { fileInputRef.current.click(); // For mobile Safari, we need to focus and blur to ensure the file picker opens fileInputRef.current.focus(); fileInputRef.current.blur(); } }, [isUploading, fileURLs.length, previews.length]); const handleFileChange = useCallback( (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); const remainingSlots = 5 - fileURLs.length; files.slice(0, remainingSlots).forEach(handleFileUpload); e.target.value = ""; }, [fileURLs.length], ); const handleFileUpload = useCallback( async (file: File) => { if (fileURLs.length >= 5) { console.log("Maximum file limit reached"); return; } if ( file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "application/pdf" ) { console.error("Unsupported file type:", file.type); return; } const reader = new FileReader(); reader.onload = (ev) => { const previewURL = ev.target?.result as string; if (previews.includes(previewURL)) { console.log("Duplicate file detected. Skipping upload."); return; } setPreviews((prev) => [...prev, previewURL]); }; reader.readAsDataURL(file); try { const { url: fileURL, error } = await uploadFile(file); if (error) { console.error("File upload failed:", error); setPreviews((prev) => prev.filter((_, i) => i !== fileURLs.length)); return; } if (fileURL) { const encodedURL = encodeURIComponent(fileURL); if (fileURLs.includes(encodedURL)) { console.log("Duplicate file URL detected. Skipping."); return; } setFileURLs?.((prev) => [...prev, encodedURL]); } else { console.error("File upload failed:", fileURL); } } catch (error) { console.error("File upload failed:", error); } }, [fileURLs, previews, uploadFile], ); const removeFile = useCallback((index: number) => { setFileURLs?.((prev) => prev.filter((_, i) => i !== index)); setPreviews((prev) => prev.filter((_, i) => i !== index)); }, []); const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragActive(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const relatedTarget = e.relatedTarget as Node | null; if (!relatedTarget || !e.currentTarget.contains(relatedTarget)) { setIsDragActive(false); } }, []); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragActive(false); const files = Array.from(e.dataTransfer.files); const remainingSlots = 5 - fileURLs.length; files.slice(0, remainingSlots).forEach(handleFileUpload); }, [fileURLs.length, handleFileUpload], ); return (