import { AnimatePresence, motion } from 'framer-motion'; import { Loader2, MessageSquare, Sparkles } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { useMatchRoute } from '@tanstack/react-router'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { LANGUAGE_OPTIONS } from '@/lib/constants/languages'; import { useGenerationForm } from '@/lib/hooks/useGenerationForm'; import { useProfile, useProfiles } from '@/lib/hooks/useProfiles'; import { useStory, useAddStoryItem } from '@/lib/hooks/useStories'; import { useUIStore } from '@/stores/uiStore'; import { useStoryStore } from '@/stores/storyStore'; import { useToast } from '@/components/ui/use-toast'; import { cn } from '@/lib/utils/cn'; interface FloatingGenerateBoxProps { isPlayerOpen?: boolean; showVoiceSelector?: boolean; } export function FloatingGenerateBox({ isPlayerOpen = false, showVoiceSelector = false }: FloatingGenerateBoxProps) { const selectedProfileId = useUIStore((state) => state.selectedProfileId); const setSelectedProfileId = useUIStore((state) => state.setSelectedProfileId); const { data: selectedProfile } = useProfile(selectedProfileId || ''); const { data: profiles } = useProfiles(); const [isExpanded, setIsExpanded] = useState(false); const [isInstructMode, setIsInstructMode] = useState(false); const containerRef = useRef(null); const matchRoute = useMatchRoute(); const isStoriesRoute = matchRoute({ to: '/stories' }); const selectedStoryId = useStoryStore((state) => state.selectedStoryId); const trackEditorHeight = useStoryStore((state) => state.trackEditorHeight); const { data: currentStory } = useStory(selectedStoryId); const addStoryItem = useAddStoryItem(); const { toast } = useToast(); // Calculate if track editor is visible (on stories route with items) const hasTrackEditor = isStoriesRoute && currentStory && currentStory.items.length > 0; const { form, handleSubmit, isPending } = useGenerationForm({ onSuccess: async (generationId) => { setIsExpanded(false); // If on stories route and a story is selected, add generation to story if (isStoriesRoute && selectedStoryId && generationId) { try { await addStoryItem.mutateAsync({ storyId: selectedStoryId, data: { generation_id: generationId }, }); toast({ title: 'Added to story', description: `Generation added to "${currentStory?.name || 'story'}"`, }); } catch (error) { toast({ title: 'Failed to add to story', description: error instanceof Error ? error.message : 'Could not add generation to story', variant: 'destructive', }); } } }, }); // Click away handler to collapse the box useEffect(() => { function handleClickOutside(event: MouseEvent) { const target = event.target as HTMLElement; // Don't collapse if clicking inside the container if (containerRef.current?.contains(target)) { return; } // Don't collapse if clicking on a Select dropdown (which renders in a portal) if ( target.closest('[role="listbox"]') || target.closest('[data-radix-popper-content-wrapper]') ) { return; } setIsExpanded(false); } if (isExpanded) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isExpanded]); async function onSubmit(data: Parameters[0]) { await handleSubmit(data, selectedProfileId); } return (
{isInstructMode && ( Delivery instructions: )} (