diff --git a/src/components/home/DynamicTagline.tsx b/src/components/home/DynamicTagline.tsx index 9925fc6b..2192cfdd 100644 --- a/src/components/home/DynamicTagline.tsx +++ b/src/components/home/DynamicTagline.tsx @@ -1,7 +1,7 @@ export function DynamicTagline() { return ( -

- Seamlessly connect your notes, content, and AI +

+ Docs, notes, and videos in one workspace

); } diff --git a/src/components/home/HomePromptInput.tsx b/src/components/home/HomePromptInput.tsx index dd14130d..9e76a5fa 100644 --- a/src/components/home/HomePromptInput.tsx +++ b/src/components/home/HomePromptInput.tsx @@ -6,18 +6,14 @@ import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { useCreateWorkspaceFromPrompt } from "@/hooks/workspace/use-create-workspace"; import { usePdfUpload } from "@/hooks/workspace/use-pdf-upload"; -import { ArrowUp, FileText, Loader2, Plus, Upload, X, Link as LinkIcon } from "lucide-react"; +// import { useImageUpload } from "@/hooks/workspace/use-image-upload"; +import { ArrowUp, FileText, Loader2, Upload, X, Link as LinkIcon } from "lucide-react"; +// import { ImageIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import TypingText from "@/components/ui/typing-text"; import { useDropzone } from "react-dropzone"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, @@ -27,42 +23,29 @@ import { DialogClose, } from "@/components/ui/dialog"; import type { PdfData } from "@/lib/workspace-state/types"; +// import type { ImageData } from "@/lib/workspace-state/types"; const PLACEHOLDER_OPTIONS = [ - "Calc 3 double integrals", - "planning a 2 week trip to Japan", - "APUSH Native American history", - "building a home workout routine", - "research on Pablo Picasso's paintings", - "starting a dropshipping business", - "learning React hooks and state", - "meal prepping for the week", - "organic chemistry reaction mechanisms", - "training for my first marathon", - "To Kill a Mockingbird analysis", - "planning my wedding budget", - "basic algebra word problems", - "redecorating my living room", - "learning Spanish verb conjugations", - "learning to invest in index funds", - "World War II European theater", - "planning a surprise birthday party", - "intro to Python programming", - "starting a YouTube channel", - "AP Bio cellular respiration", - "building my personal portfolio site", - "high school geometry proofs", - "tracking my monthly expenses", - "solar system planets and moons", - "planning a camping trip to Yosemite", - "learning guitar chord progressions", - "organizing my home office", - "French Revolution causes and effects", - "beginner photography composition", - "statistics hypothesis testing", - "comparing Monet and Van Gogh", - "US Presidents and their policies", - "physics kinematics problems", + "help me study organic chemistry", + "make a study guide for AP Biology", + "break down calc 3 double integrals", + "quiz me on the French Revolution", + "create flashcards for anatomy terms", + "help me prep for my physics exam", + "summarize my lecture on cellular respiration", + "research the causes of World War II", + "compare Monet and Van Gogh's techniques", + "organize my sources on climate change", + "help me outline my thesis on AI ethics", + "brainstorm ideas for my essay", + "help me learn React hooks", + "plan a 2-week trip to Japan", + "help me build a workout routine", + "break down how React hooks work", + "plan my monthly budget", + "help me learn Spanish conjugations", + "draft a project proposal for my team", + "help me prep for my presentation", ]; const baseText = "Create a workspace on "; @@ -76,15 +59,23 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { const { resolvedTheme } = useTheme(); const [value, setValue] = useState(""); const [isExpanded, setIsExpanded] = useState(false); + const [introComplete, setIntroComplete] = useState(false); + const [prefixTyped, setPrefixTyped] = useState(false); + const [typedPrefix, setTypedPrefix] = useState(""); const [isUrlDialogOpen, setIsUrlDialogOpen] = useState(false); const [urlInput, setUrlInput] = useState(""); - const [textIndent, setTextIndent] = useState(180); // Default fallback const inputRef = useRef(null); - const prefixRef = useRef(null); const typingKeyRef = useRef(0); const createFromPrompt = useCreateWorkspaceFromPrompt(); const { uploadFiles, uploadedFiles, isUploading, removeFile, clearFiles } = usePdfUpload(); + // const { + // uploadFiles: uploadImages, + // uploadedFiles: uploadedImages, + // isUploading: isUploadingImages, + // removeFile: removeImage, + // clearFiles: clearImages, + // } = useImageUpload(); // Setup drop zone for PDF files const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ @@ -95,11 +86,18 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { noClick: true, // Don't open file dialog on click (only on drag) onDrop: async (acceptedFiles) => { if (acceptedFiles.length > 0) { - // Auto-populate input immediately - const totalFiles = uploadedFiles.length + acceptedFiles.length; - if (totalFiles === 1) { + // Auto-populate input based on total uploads + const totalPdfs = uploadedFiles.length + acceptedFiles.length; + // const totalImages = uploadedImages.length; + // if (totalImages > 0) { + // const parts = []; + // parts.push(totalPdfs === 1 ? 'this pdf' : 'these pdfs'); + // parts.push(totalImages === 1 ? 'this image' : 'these images'); + // setValue(parts.join(' and ')); + // } else + if (totalPdfs === 1) { setValue("this pdf"); - } else if (totalFiles > 1) { + } else { setValue("these pdfs"); } @@ -113,6 +111,40 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { }, }); + // // Setup file picker for images (button-click only, no drag) + // const { open: openImagePicker, getInputProps: getImageInputProps } = useDropzone({ + // accept: { + // 'image/*': ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'], + // }, + // multiple: true, + // noClick: true, + // noDrag: true, + // onDrop: async (acceptedFiles) => { + // if (acceptedFiles.length > 0) { + // // Auto-populate input based on total uploads + // const totalImages = uploadedImages.length + acceptedFiles.length; + // const totalPdfs = uploadedFiles.length; + // if (totalPdfs > 0) { + // const parts = []; + // parts.push(totalPdfs === 1 ? 'this pdf' : 'these pdfs'); + // parts.push(totalImages === 1 ? 'this image' : 'these images'); + // setValue(parts.join(' and ')); + // } else if (totalImages === 1) { + // setValue("this image"); + // } else { + // setValue("these images"); + // } + + // try { + // await uploadImages(acceptedFiles); + // toast.success(`Uploaded ${acceptedFiles.length} image${acceptedFiles.length > 1 ? 's' : ''}`); + // } catch (error) { + // toast.error("Failed to upload images"); + // } + // } + // }, + // }); + // Shuffle options with random start for variety const shuffledOptions = useMemo(() => { const start = Math.floor(Math.random() * PLACEHOLDER_OPTIONS.length); @@ -122,6 +154,25 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { ]; }, []); + // Type "Ask ThinkEx to " character-by-character after intro completes + useEffect(() => { + if (!introComplete || prefixTyped) return; + + const prefix = "Ask ThinkEx to\u00a0"; + let index = 0; + const intervalId = setInterval(() => { + index++; + if (index <= prefix.length) { + setTypedPrefix(prefix.slice(0, index)); + } else { + clearInterval(intervalId); + setPrefixTyped(true); + } + }, 35); + + return () => clearInterval(intervalId); + }, [introComplete, prefixTyped]); + // Focus input when hero section becomes visible useEffect(() => { if (shouldFocus && inputRef.current) { @@ -132,17 +183,6 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { } }, [shouldFocus]); - // Dynamic width measurement for perfect alignment - // using useLayoutEffect to prevent layout shift flash if possible, or useEffect - useEffect(() => { - if (prefixRef.current) { - // Measure width of static text - const width = prefixRef.current.offsetWidth; - // Add roughly one space width (approx 4-5px for typical font, but let's say 6px to be safe) - setTextIndent(width + 6); - } - }, []); - // Handle user typing const handleInput = (e: React.ChangeEvent) => { setValue(e.target.value); @@ -158,23 +198,17 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { } }, [value]); - const handleScroll = (e: React.UIEvent) => { - if (prefixRef.current) { - prefixRef.current.style.transform = `translateY(-${e.currentTarget.scrollTop}px)`; - } - }; - const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const prompt = value.trim(); if (!prompt || createFromPrompt.isLoading || isUploading) return; - // Construct initial state with PDF cards AND empty placeholder cards if files were uploaded + const hasUploads = uploadedFiles.length > 0; + + // Construct initial state with file cards AND empty placeholder cards if files were uploaded let initialState = undefined; - if (uploadedFiles.length > 0) { - // Calculate total PDF height (each PDF takes 10 rows) - const pdfHeight = 10; - const totalPdfY = uploadedFiles.length * pdfHeight; + if (hasUploads) { + const fileHeight = 10; // Create PDF card items from uploaded files (stacked vertically at top) const pdfItems = uploadedFiles.map((file, index) => ({ @@ -183,7 +217,7 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { name: file.name, subtitle: '', color: '#6366F1' as const, // Indigo for PDFs - layout: { x: 0, y: index * pdfHeight, w: 4, h: pdfHeight }, + layout: { x: 0, y: index * fileHeight, w: 4, h: fileHeight }, lastSource: 'user' as const, data: { fileUrl: file.fileUrl, @@ -192,55 +226,66 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { } as PdfData, })); - // Create empty placeholder cards with fixed layout and colors - const noteId = crypto.randomUUID(); - const quizId = crypto.randomUUID(); - const flashcardId = crypto.randomUUID(); + const totalUploadY = uploadedFiles.length * fileHeight; + + // const pdfEndY = uploadedFiles.length * fileHeight; + + // // Create Image card items (stacked below PDFs) + // const imageItems = uploadedImages.map((file, index) => ({ + // id: crypto.randomUUID(), + // type: 'image' as const, + // name: file.name, + // subtitle: '', + // color: '#8B5CF6' as const, // Violet for Images + // layout: { x: 0, y: pdfEndY + index * fileHeight, w: 4, h: fileHeight }, + // lastSource: 'user' as const, + // data: { + // fileUrl: file.fileUrl, + // filename: file.filename, + // fileSize: file.fileSize, + // } as ImageData, + // })); + + // const totalUploadY = pdfEndY + uploadedImages.length * fileHeight; + // Create empty placeholder cards with fixed layout and colors const emptyNote = { - id: noteId, + id: crypto.randomUUID(), type: 'note' as const, name: 'Update me', subtitle: '', - color: '#10B981' as const, // Emerald for Note - layout: { x: 0, y: totalPdfY, w: 4, h: 13 }, + color: '#10B981' as const, + layout: { x: 0, y: totalUploadY, w: 4, h: 13 }, lastSource: 'user' as const, - data: { - blockContent: [], - field1: '', - }, + data: { blockContent: [], field1: '' }, }; const emptyQuiz = { - id: quizId, + id: crypto.randomUUID(), type: 'quiz' as const, name: 'Update me', subtitle: '', - color: '#F59E0B' as const, // Amber for Quiz - layout: { x: 0, y: totalPdfY + 13, w: 2, h: 13 }, + color: '#F59E0B' as const, + layout: { x: 0, y: totalUploadY + 13, w: 2, h: 13 }, lastSource: 'user' as const, - data: { - questions: [], - }, + data: { questions: [] }, }; const emptyFlashcard = { - id: flashcardId, + id: crypto.randomUUID(), type: 'flashcard' as const, name: 'Update me', subtitle: '', - color: '#EC4899' as const, // Pink for Flashcards - layout: { x: 2, y: totalPdfY + 13, w: 2, h: 8 }, + color: '#EC4899' as const, + layout: { x: 2, y: totalUploadY + 13, w: 2, h: 8 }, lastSource: 'user' as const, - data: { - cards: [], - }, + data: { cards: [] }, }; const allItems = [...pdfItems, emptyNote, emptyQuiz, emptyFlashcard]; initialState = { - workspaceId: '', // Will be set by backend + workspaceId: '', globalTitle: '', globalDescription: '', items: allItems, @@ -249,17 +294,16 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) { } createFromPrompt.mutate(prompt, { - template: uploadedFiles.length > 0 ? "blank" : "getting_started", // Use blank template if PDFs provided + template: hasUploads ? "blank" : "getting_started", initialState, onSuccess: (workspace) => { - // Reset typing animation by changing key typingKeyRef.current += 1; - // Clear uploaded files clearFiles(); + // clearImages(); const url = `/workspace/${workspace.slug}`; const params = new URLSearchParams(); - if (uploadedFiles.length > 0) { + if (hasUploads) { params.set('action', 'generate_study_materials'); } else { params.set('createFrom', prompt); @@ -292,6 +336,7 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) {
+ {/* */} {/* Drag overlay */} {isDragActive && ( @@ -332,154 +377,195 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) {
)} + {/* // Uploaded images display */} + {/* uploadedImages.length > 0 && ( +
+ {uploadedImages.map((file) => ( +
+ + {file.name} + +
+ ))} +
+ ) */} + {/* Input container styled to look like one input */}
inputRef.current?.focus()} className={cn( - "relative w-full min-h-[96px]", + "relative w-full", isExpanded ? "rounded-[24px]" : "rounded-[32px]", "border", "bg-sidebar backdrop-blur-xl", "px-4 py-2 md:px-6 md:py-3", + "pr-14 md:pr-16", "shadow-[0_24px_90px_-40px_rgba(0,0,0,0.85)]", "focus-within:border-foreground/40", "transition-[border-radius,height] duration-300 ease-in-out", "cursor-text" )} > -
- {/* Static Prefix - positioned absolutely but matched with text-indent */} - {/* Static Prefix - positioned absolutely but matched with text-indent */} - - Create a workspace on - - -