From 57d47dcc3303ff42e6ff08d519a3d28f5a427424 Mon Sep 17 00:00:00 2001 From: SupremeEvilGod Date: Wed, 18 Mar 2026 12:37:14 +0530 Subject: [PATCH 1/4] added the coming soon features(voice, text, image) --- index.html | 1 + src/components/AddEventDialog.tsx | 32 ++++--- src/components/ImageUploadDialog.tsx | 125 ++++++++++++++++++++++++++ src/components/QuickActions.tsx | 28 +++--- src/components/TextInputDialog.tsx | 69 +++++++++++++++ src/components/VoiceInputDialog.tsx | 126 +++++++++++++++++++++++++++ src/lib/ai.ts | 91 +++++++++++++++++++ src/pages/Index.tsx | 61 ++++++++++++- 8 files changed, 509 insertions(+), 24 deletions(-) create mode 100644 src/components/ImageUploadDialog.tsx create mode 100644 src/components/TextInputDialog.tsx create mode 100644 src/components/VoiceInputDialog.tsx create mode 100644 src/lib/ai.ts diff --git a/index.html b/index.html index 271a5fe..d5911ac 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@
+ diff --git a/src/components/AddEventDialog.tsx b/src/components/AddEventDialog.tsx index 662dec5..dd209a5 100644 --- a/src/components/AddEventDialog.tsx +++ b/src/components/AddEventDialog.tsx @@ -13,6 +13,7 @@ interface AddEventDialogProps { onOpenChange: (open: boolean) => void; onSubmit: (event: Omit) => void; selectedDate: Date | undefined; + initialData?: Partial; } const CATEGORIES = [ @@ -23,7 +24,7 @@ const CATEGORIES = [ { value: "social", label: "Social" }, ]; -const AddEventDialog = ({ open, onOpenChange, onSubmit, selectedDate }: AddEventDialogProps) => { +const AddEventDialog = ({ open, onOpenChange, onSubmit, selectedDate, initialData }: AddEventDialogProps) => { const [title, setTitle] = useState(""); const [date, setDate] = useState(""); const [time, setTime] = useState(""); @@ -32,16 +33,27 @@ const AddEventDialog = ({ open, onOpenChange, onSubmit, selectedDate }: AddEvent const [recurrence, setRecurrence] = useState("none"); useEffect(() => { - if (selectedDate) { - const localDate = new Date( - selectedDate.getFullYear(), - selectedDate.getMonth(), - selectedDate.getDate() - ); - const formattedDate = localDate.toLocaleDateString('en-CA'); - setDate(formattedDate); + if (open) { + setTitle(initialData?.title || ""); + setTime(initialData?.time || ""); + setDescription(initialData?.description || ""); + setCategory(initialData?.category || ""); + setRecurrence(initialData?.recurrence || "none"); + + if (initialData?.date) { + setDate(initialData.date); + } else if (selectedDate) { + const localDate = new Date( + selectedDate.getFullYear(), + selectedDate.getMonth(), + selectedDate.getDate() + ); + setDate(localDate.toLocaleDateString('en-CA')); + } else { + setDate(""); + } } - }, [selectedDate]); + }, [open, initialData, selectedDate]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); diff --git a/src/components/ImageUploadDialog.tsx b/src/components/ImageUploadDialog.tsx new file mode 100644 index 0000000..417d400 --- /dev/null +++ b/src/components/ImageUploadDialog.tsx @@ -0,0 +1,125 @@ +import { useState, useRef } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { toast } from "sonner"; +import { parseEventFromAI, ParsedEvent } from "@/lib/ai"; +import { Image as ImageIcon, Loader2, UploadCloud } from "lucide-react"; + +interface ImageUploadDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onParsed: (event: ParsedEvent) => void; +} + +const ImageUploadDialog = ({ open, onOpenChange, onParsed }: ImageUploadDialogProps) => { + const [isParsing, setIsParsing] = useState(false); + const [preview, setPreview] = useState(null); + const fileInputRef = useRef(null); + + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Check if image + if (!file.type.startsWith('image/')) { + toast.error("Please upload an image file."); + return; + } + + // Convert to base64 Data URL for Puter.js and preview + const reader = new FileReader(); + reader.onload = async (event) => { + const dataUrl = event.target?.result as string; + setPreview(dataUrl); + setIsParsing(true); + + try { + const parsedEvent = await parseEventFromAI(dataUrl, "image"); + if (parsedEvent) { + onParsed(parsedEvent); + onOpenChange(false); + setPreview(null); + } else { + toast.error("Could not find event details in the image."); + } + } catch (error) { + toast.error("Failed to parse image."); + } finally { + setIsParsing(false); + if (fileInputRef.current) fileInputRef.current.value = ""; + } + }; + reader.onerror = () => { + toast.error("Failed to read file."); + }; + + reader.readAsDataURL(file); + }; + + const handleReset = () => { + if (!isParsing) { + setPreview(null); + if (fileInputRef.current) fileInputRef.current.value = ""; + } + }; + + return ( + { + if (!isParsing) { + onOpenChange(val); + setPreview(null); + } + }}> + + + Scan Event from Image + + Upload a flyer, invitation, or screenshot to automatically create an event. + + + +
+ + + {!preview ? ( +
fileInputRef.current?.click()} + > + +
+

Click to upload image

+

PNG, JPG, WEBP up to 5MB

+
+
+ ) : ( +
+
+ Upload preview + {isParsing && ( +
+
+ + Analyzing Image... +
+
+ )} +
+ {!isParsing && ( + + )} +
+ )} +
+
+
+ ); +}; + +export default ImageUploadDialog; diff --git a/src/components/QuickActions.tsx b/src/components/QuickActions.tsx index 5eca571..07569a8 100644 --- a/src/components/QuickActions.tsx +++ b/src/components/QuickActions.tsx @@ -1,44 +1,46 @@ import { Button } from "@/components/ui/button"; import { Plus, Mic, Image, MessageSquare } from "lucide-react"; -import { toast } from "sonner"; interface QuickActionsProps { onAddEvent: () => void; + onVoiceClick: () => void; + onImageClick: () => void; + onTextClick: () => void; } -const QuickActions = ({ onAddEvent }: QuickActionsProps) => { +const QuickActions = ({ onAddEvent, onVoiceClick, onImageClick, onTextClick }: QuickActionsProps) => { return (
); diff --git a/src/components/TextInputDialog.tsx b/src/components/TextInputDialog.tsx new file mode 100644 index 0000000..cd31987 --- /dev/null +++ b/src/components/TextInputDialog.tsx @@ -0,0 +1,69 @@ +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { parseEventFromAI, ParsedEvent } from "@/lib/ai"; +import { toast } from "sonner"; +import { Loader2 } from "lucide-react"; + +interface TextInputDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onParsed: (event: ParsedEvent) => void; +} + +const TextInputDialog = ({ open, onOpenChange, onParsed }: TextInputDialogProps) => { + const [text, setText] = useState(""); + const [isParsing, setIsParsing] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!text.trim()) return; + + setIsParsing(true); + try { + const parsedEvent = await parseEventFromAI(text, "text"); + if (parsedEvent) { + onParsed(parsedEvent); + onOpenChange(false); + setText(""); + } else { + toast.error("Could not understand event details. True manually."); + } + } catch (error) { + toast.error("Something went wrong."); + } finally { + setIsParsing(false); + } + }; + + return ( + + + + Smart Event Creation + + Type your event details naturally. For example: "Lunch with Sarah tomorrow at 12pm for 1 hour". + + +
+