File size: 8,802 Bytes
f201243
 
4a56a0b
f201243
 
 
 
 
da4a2eb
f201243
8ffe335
f201243
 
bf8e54b
c5771b6
 
 
f201243
 
 
29066e0
 
 
 
 
 
 
 
 
 
 
 
f201243
 
 
 
 
 
c5771b6
 
 
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a56a0b
f201243
 
 
8ffe335
29066e0
f201243
 
 
 
 
 
 
 
4a56a0b
f201243
29066e0
da4a2eb
f201243
4a56a0b
 
 
 
 
 
 
 
f201243
 
 
8ffe335
 
 
 
 
 
 
 
 
 
 
 
 
 
4a56a0b
8ffe335
 
 
f201243
 
 
 
 
4a56a0b
 
f201243
 
 
 
 
b3adf58
 
f201243
 
 
 
 
29066e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f201243
 
c5771b6
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
c5771b6
f201243
 
 
 
8ffe335
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
"use client";

import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
import { Select } from "@/components/ui/Select";
import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
import type { Niche } from "@/types/api";
import { InfoButton } from "@/components/ui/InfoButton";

const extensiveSchema = z.object({
  niche: z.enum(["home_insurance", "glp1", "auto_insurance", "others"]),
  custom_niche: z.string().optional().nullable(),
  target_audience: z.string().optional().nullable(),
  offer: z.string().optional().nullable(),
  num_images: z.number().min(1).max(3),
  num_strategies: z.number().min(1).max(10),
  image_model: z.string().nullable().optional(),
}).refine(
  (data) => {
    if (data.niche === "others") {
      return data.custom_niche && data.custom_niche.trim().length > 0;
    }
    return true;
  },
  {
    message: "Please enter your custom niche",
    path: ["custom_niche"],
  }
);

type ExtensiveFormData = z.infer<typeof extensiveSchema>;

interface ExtensiveFormProps {
  onSubmit: (data: {
    niche: Niche;
    custom_niche?: string | null;
    target_audience?: string | null;
    offer?: string | null;
    num_images: number;
    num_strategies: number;
    image_model?: string | null;
  }) => Promise<void>;
  isLoading: boolean;
}

export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
  onSubmit,
  isLoading,
}) => {
  const {
    register,
    handleSubmit,
    formState: { errors },
    watch,
    setValue,
  } = useForm<ExtensiveFormData>({
    resolver: zodResolver(extensiveSchema),
    defaultValues: {
      niche: "home_insurance" as const, // first option; flow works for any niche
      custom_niche: "",
      target_audience: "",
      offer: "",
      num_images: 1,
      num_strategies: 5,
      image_model: null,
    },
  });

  const numImages = 1;
  const numStrategies = watch("num_strategies");
  const selectedNiche = watch("niche");
  const selectedModel = watch("image_model");

  useEffect(() => {
    setValue("num_images", numImages, { shouldDirty: false, shouldValidate: false });
  }, [numImages, setValue]);

  const onFormSubmit = handleSubmit(({ num_images, ...rest }) =>
    onSubmit({ ...rest, num_images: num_images ?? 1 })
  );

  return (
    <Card variant="glass">
      <CardHeader>
        <div className="flex items-center gap-2">
          <CardTitle>Extensive Generation</CardTitle>
          <InfoButton
            title="Extensive Generation Flow"
            content="This flow works for any niche (insurance, GLP-1, auto, or custom). It uses a 4-stage process:

1. RESEARCHER: Analyzes your inputs and researches psychology triggers, angles, and concepts that work best for your niche and audience.

2. CREATIVE DIRECTOR: Uses marketing knowledge and successful ad patterns to create multiple creative strategies. Each strategy includes visual direction, text placement, CTA, and copy ideas.

3. DESIGNER: Converts each creative strategy into detailed image generation prompts optimized for affiliate marketing (authentic, low-production style).

4. COPYWRITER: Writes compelling ad copy (title, body, description) that matches each strategy's emotional tone and psychology trigger.

You can generate multiple strategies (1-10). Each strategy includes one image and a full ad package for comprehensive testing."
            position="bottom"
          />
        </div>
        <CardDescription>
          Researcher → Creative Director → Designer → Copywriter flow
        </CardDescription>
      </CardHeader>
      <CardContent>
        <form onSubmit={onFormSubmit} className="space-y-6">
          <input type="hidden" value={numImages} {...register("num_images", { valueAsNumber: true })} />
          <Select
            label="Niche"
            options={[
              { value: "home_insurance", label: "Home Insurance" },
              { value: "glp1", label: "GLP-1" },
              { value: "auto_insurance", label: "Auto Insurance" },
              { value: "others", label: "Others (Custom)" },
            ]}
            error={errors.niche?.message}
            {...register("niche")}
          />

          {selectedNiche === "others" && (
            <div>
              <label className="block text-sm font-semibold text-gray-700 mb-2">
                Custom Niche <span className="text-red-500">*</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., Pet Insurance, Solar Panels, Fitness Supplements"
                {...register("custom_niche")}
              />
              {errors.custom_niche && (
                <p className="text-red-500 text-xs mt-1">{errors.custom_niche.message}</p>
              )}
            </div>
          )}

          <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., Free quote, Save $500/year, Limited-time offer"
              {...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-semibold text-gray-700 mb-2">
              Number of Strategies: <span className="text-blue-600 font-bold">{numStrategies}</span>
            </label>
            <input
              type="range"
              min="1"
              max="10"
              step="1"
              className="w-full accent-blue-500"
              {...register("num_strategies", { valueAsNumber: true })}
            />
            <div className="flex justify-between text-xs text-gray-500 mt-1 font-medium">
              <span>1</span>
              <span>10</span>
            </div>
            <p className="text-xs text-gray-500 mt-1">
              More strategies = more variety, but longer generation time
            </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 || "", numStrategies))}
            </p>
            <p className="text-xs text-gray-600 mt-1">
              {numStrategies} total image{numStrategies > 1 ? 's' : ''} × {IMAGE_MODELS.find(m => m.value === (selectedModel || ""))?.label.split(' - ')[0] || "Default model"}
            </p>
          </div>

          <button
            type="submit"
            disabled={isLoading}
            className="w-full bg-gradient-to-r from-blue-500 to-cyan-500 text-white font-bold py-4 px-6 rounded-xl hover:from-blue-600 hover:to-cyan-600 transition-all duration-300 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {isLoading ? "Generating..." : "Generate with Extensive"}
          </button>
        </form>
      </CardContent>
    </Card>
  );
};