"use client"; import React, { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { generateAdSchema } from "@/lib/utils/validators"; import { Input } from "@/components/ui/Input"; import { Select } from "@/components/ui/Select"; import { Button } from "@/components/ui/Button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card"; import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models"; import type { Niche } from "@/types/api"; import { Loader2, TrendingUp, Check } from "lucide-react"; import apiClient from "@/lib/api/client"; import { InfoButton } from "@/components/ui/InfoButton"; interface GenerationFormProps { onSubmit: (data: { niche: Niche; num_images: number; image_model?: string | null; target_audience?: string | null; offer?: string | null; use_trending?: boolean; trending_context?: string | null; }) => Promise; isLoading: boolean; } interface TrendItem { title: string; description: string; url?: string; keyword?: string; relevance_score?: number; } export const GenerationForm: React.FC = ({ onSubmit, isLoading, }) => { const [trends, setTrends] = useState([]); const [selectedTrend, setSelectedTrend] = useState(null); const [isFetchingTrends, setIsFetchingTrends] = useState(false); const [trendsError, setTrendsError] = useState(null); const { register, handleSubmit, formState: { errors }, watch, setValue, } = useForm({ resolver: zodResolver(generateAdSchema), defaultValues: { niche: "home_insurance" as const, num_images: 1, image_model: null, target_audience: "", offer: "", use_trending: false, trending_context: "", }, }); const numImages = watch("num_images"); const currentNiche = watch("niche"); const useTrending = watch("use_trending"); const selectedModel = watch("image_model"); // Fetch trends when toggle is enabled const handleFetchTrends = async () => { setIsFetchingTrends(true); setTrendsError(null); setTrends([]); setSelectedTrend(null); try { const response = await apiClient.get(`/api/trends/${currentNiche}`); const data = response.data; if (data.trends && data.trends.length > 0) { setTrends(data.trends); } else { setTrendsError("No relevant trends found for this niche"); } } catch (error: any) { setTrendsError(error.message || "Failed to fetch trends"); } finally { setIsFetchingTrends(false); } }; // Handle trend selection const handleSelectTrend = (trend: TrendItem) => { setSelectedTrend(trend); // Set the trending context with title and description const trendContext = `${trend.title} - ${trend.description}`; setValue("trending_context", trendContext); }; // Reset trends when toggle is turned off React.useEffect(() => { if (!useTrending) { setTrends([]); setSelectedTrend(null); setTrendsError(null); } }, [useTrending]); return (
Generate Ad
Create a new ad creative using randomized strategies
{errors.target_audience && (

{errors.target_audience.message}

)}
{errors.offer && (

{errors.offer.message}

)}
{/* Trending Topics – AI occasions + niche news; used in ad copy generation */}

Tie your ad to current occasions and niche news for timeliness

{useTrending && (
{trendsError &&

{trendsError}

} {trends.length > 0 && (

Pick one (optional – otherwise we use the top trend):

{trends.map((trend) => ( ))}
)}
)}
1 10

Generate multiple images for the same ad copy using the same method

{errors.num_images && (

{errors.num_images.message}

)} {/* Cost Estimator */}

💰 Estimated Cost: {formatCost(getModelCost(selectedModel || "", numImages))}

{numImages} image{numImages > 1 ? 's' : ''} × {IMAGE_MODELS.find(m => m.value === (selectedModel || ""))?.label.split(' - ')[0] || "Default model"}

); };