File size: 5,705 Bytes
f201243 da4a2eb f201243 8ffe335 f201243 4a56a0b f201243 f103b85 f201243 addcf34 f201243 da4a2eb f201243 8ffe335 4a56a0b 8ffe335 f201243 b3adf58 f201243 addcf34 c5771b6 addcf34 c5771b6 addcf34 f201243 365a483 f201243 365a483 f201243 da4a2eb 4a56a0b da4a2eb 4a56a0b da4a2eb f201243 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | "use client";
import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { generateBatchSchema } 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 { InfoButton } from "@/components/ui/InfoButton";
interface BatchFormProps {
onSubmit: (data: { niche: Niche; count: number; image_model?: string | null; target_audience?: string | null; offer?: string | null }) => Promise<void>;
isLoading: boolean;
}
export const BatchForm: React.FC<BatchFormProps> = ({
onSubmit,
isLoading,
}) => {
const {
register,
handleSubmit,
formState: { errors },
watch,
} = useForm({
resolver: zodResolver(generateBatchSchema),
defaultValues: {
niche: "home_insurance" as const,
count: 5,
image_model: null,
target_audience: "",
offer: "",
},
});
const count = watch("count");
const selectedModel = watch("image_model");
return (
<Card variant="glass">
<CardHeader>
<div className="flex items-center gap-2">
<CardTitle>Batch Generation</CardTitle>
<InfoButton
title="Batch Generation Flow"
content="Generate multiple ads simultaneously for A/B testing and variety. Each ad is created with randomized strategies, giving you diverse options to test. You can generate up to 100 ads in a single run to quickly find winning combinations."
position="bottom"
/>
</div>
<CardDescription>
Generate multiple ads at once for testing and variety
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<Select
label="Niche"
options={[
{ value: "home_insurance", label: "Home Insurance" },
{ value: "glp1", label: "GLP-1" },
{ value: "auto_insurance", label: "Auto Insurance" },
]}
error={errors.niche?.message}
{...register("niche")}
/>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Target Audience <span className="text-gray-400 font-normal">(Optional)</span>
</label>
<input
type="text"
className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 bg-white/80 backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-250"
placeholder="e.g., US people over 50+ age"
{...register("target_audience")}
/>
{errors.target_audience && (
<p className="text-red-500 text-xs mt-1">{errors.target_audience.message}</p>
)}
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Offer <span className="text-gray-400 font-normal">(Optional)</span>
</label>
<input
type="text"
className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 bg-white/80 backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-250"
placeholder="e.g., Don't overpay your insurance"
{...register("offer")}
/>
{errors.offer && (
<p className="text-red-500 text-xs mt-1">{errors.offer.message}</p>
)}
</div>
<Select
label="Image Model"
options={IMAGE_MODELS.map(model => ({ value: model.value, label: model.label }))}
error={errors.image_model?.message}
{...register("image_model")}
/>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Number of Ads: {count}
</label>
<input
type="range"
min="1"
max="100"
step="1"
className="w-full"
{...register("count", { valueAsNumber: true })}
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>1</span>
<span>100</span>
</div>
{errors.count && (
<p className="mt-1 text-sm text-red-600">
{errors.count.message}
</p>
)}
</div>
{/* Cost Estimator */}
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-4">
<p className="text-sm font-semibold text-gray-800">
💰 <strong>Estimated Cost:</strong> {formatCost(getModelCost(selectedModel || "", count))}
</p>
<p className="text-xs text-gray-600 mt-1">
{count} total image{count > 1 ? 's' : ''} × {IMAGE_MODELS.find(m => m.value === (selectedModel || ""))?.label.split(' - ')[0] || "Default model"}
</p>
</div>
<Button
type="submit"
variant="primary"
size="lg"
isLoading={isLoading}
className="w-full"
>
Generate Batch
</Button>
</form>
</CardContent>
</Card>
);
};
|