From e839a5f61f0faa21ca8b4bd5767f7575d5e576ee Mon Sep 17 00:00:00 2001 From: polwex Date: Wed, 21 May 2025 14:00:28 +0700 Subject: the card flip animation is legit --- src/components/ParseForm.tsx | 222 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/components/ParseForm.tsx (limited to 'src/components/ParseForm.tsx') diff --git a/src/components/ParseForm.tsx b/src/components/ParseForm.tsx new file mode 100644 index 0000000..3e6f3e7 --- /dev/null +++ b/src/components/ParseForm.tsx @@ -0,0 +1,222 @@ +// src/components/SorlangPage.tsx +"use client"; // For Next.js App Router, if applicable + +import React, { + useState, + useRef, + useTransition, + useEffect, + useCallback, + startTransition, +} from "react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Loader2 } from "lucide-react"; // Loading spinner +import { useRouter } from "waku"; + +const SorlangPage: React.FC = () => { + const [textValue, setTextValue] = useState(""); + const [pastedImageUrl, setPastedImageUrl] = useState(null); + const [pastedImageFile, setPastedImageFile] = useState(null); // Store the file for extraction + const [isAnalyzing, setIsAnalyzing] = useState(false); + const [isExtracting, setIsExtracting] = useState(false); + + const textareaRef = useRef(null); + + // Cleanup object URL when component unmounts or image changes + useEffect(() => { + return () => { + if (pastedImageUrl) { + URL.revokeObjectURL(pastedImageUrl); + } + }; + }, [pastedImageUrl]); + + const handlePaste = useCallback( + (event: ClipboardEvent) => { + const items = event.clipboardData?.items; + console.log({ items }); + if (!items) return; + + let imageFound = false; + for (const item of items) { + if (item.kind === "file" && item.type.startsWith("image/")) { + event.preventDefault(); // Prevent pasting image data as text + const file = item.getAsFile(); + if (file) { + if (pastedImageUrl) { + URL.revokeObjectURL(pastedImageUrl); // Revoke previous if any + } + const newImageUrl = URL.createObjectURL(file); + setPastedImageUrl(newImageUrl); + setPastedImageFile(file); + imageFound = true; + } + break; // Handle first image found + } + } + + // // If no image was found, let the default text paste happen + // // Or, if you want to explicitly handle text paste: + // if (!imageFound) { + // // Let the default textarea paste handle it, or: + // // event.preventDefault(); + // // const text = event.clipboardData.getData('text/plain'); + // // setTextValue(prev => prev + text); // Or replace, depending on desired behavior + // // setPastedImageUrl(null); // Clear image if text is pasted + // // setPastedImageFile(null); + // } + }, + [pastedImageUrl], + ); + useEffect(() => { + window.addEventListener("paste", handlePaste); + return () => { + window.removeEventListener("paste", handlePaste); + }; + }, [handlePaste]); + + const router = useRouter(); + async function fetchNLP(text: string, app: "spacy" | "stanza") { + const opts = { + method: "POST", + headers: { "Content-type": "application/json" }, + body: JSON.stringify({ text, app }), + }; + const res = await fetch("/api/nlp", opts); + const j = await res.json(); + console.log("j", j); + if ("ok" in j) { + sessionStorage.setItem(`${app}res`, JSON.stringify(j.ok)); + } + } + + const handleProcessText = async () => { + setIsAnalyzing(true); + const text = textValue.trim(); + if (!text) { + alert("Text area is empty!"); + return; + } + await Promise.all([fetchNLP(text, "spacy"), fetchNLP(text, "stanza")]); + router.push("/zoom"); + setIsAnalyzing(false); + }; + + // const [isPending, startTransition] = useTransition(); + const handleExtractTextFromImage = async () => { + if (!pastedImageFile) { + alert("No image to extract text from!"); + return; + } + setIsExtracting(true); + const formData = new FormData(); + formData.append("file", pastedImageFile, pastedImageFile.name); + console.log("Extracting text from image:", pastedImageFile.name); + const res = await fetch("/api/formdata/ocr", { + method: "POST", + body: formData, + }); + const j = await res.json(); + console.log("ocr res", j); + if ("ok" in j) { + setTextValue((t) => t + j.ok.join("\n")); + } + setIsExtracting(false); + // handleClearImage(); + }; + + // setPastedImageUrl(null); + // setPastedImageFile(null); + + const handleClearImage = () => { + if (pastedImageUrl) { + URL.revokeObjectURL(pastedImageUrl); + } + setPastedImageUrl(null); + setPastedImageFile(null); + }; + + return ( +
+ + + + Sorlang + + + +