Commit
·
29066e0
1
Parent(s):
254d33c
Enhance extensive ad generation with custom niche support
Browse files- Updated the ExtensiveGenerateRequest model to include an optional custom_niche field for enhanced flexibility in ad generation.
- Modified the generate_extensive function to validate custom niche input when 'others' is selected as the niche.
- Adjusted frontend components to accommodate the new custom_niche field, including validation in the ExtensiveForm and updates to the GeneratePage.
- Updated API endpoints to handle the new custom_niche parameter, ensuring seamless integration across the application.
- frontend/app/generate/page.tsx +1 -0
- frontend/components/generation/ExtensiveForm.tsx +35 -2
- frontend/lib/api/endpoints.ts +2 -0
- frontend/types/api.ts +1 -1
- main.py +23 -3
frontend/app/generate/page.tsx
CHANGED
|
@@ -365,6 +365,7 @@ export default function GeneratePage() {
|
|
| 365 |
|
| 366 |
const handleExtensiveGenerate = async (data: {
|
| 367 |
niche: Niche;
|
|
|
|
| 368 |
target_audience?: string;
|
| 369 |
offer?: string;
|
| 370 |
num_images: number;
|
|
|
|
| 365 |
|
| 366 |
const handleExtensiveGenerate = async (data: {
|
| 367 |
niche: Niche;
|
| 368 |
+
custom_niche?: string;
|
| 369 |
target_audience?: string;
|
| 370 |
offer?: string;
|
| 371 |
num_images: number;
|
frontend/components/generation/ExtensiveForm.tsx
CHANGED
|
@@ -10,19 +10,32 @@ import { IMAGE_MODELS } from "@/lib/constants/models";
|
|
| 10 |
import type { Niche } from "@/types/api";
|
| 11 |
|
| 12 |
const extensiveSchema = z.object({
|
| 13 |
-
niche: z.enum(["home_insurance", "glp1"]),
|
|
|
|
| 14 |
target_audience: z.string().optional(),
|
| 15 |
offer: z.string().optional(),
|
| 16 |
num_images: z.number().min(1).max(3),
|
| 17 |
num_strategies: z.number().min(1).max(10),
|
| 18 |
image_model: z.string().nullable().optional(),
|
| 19 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
type ExtensiveFormData = z.infer<typeof extensiveSchema>;
|
| 22 |
|
| 23 |
interface ExtensiveFormProps {
|
| 24 |
onSubmit: (data: {
|
| 25 |
niche: Niche;
|
|
|
|
| 26 |
target_audience?: string;
|
| 27 |
offer?: string;
|
| 28 |
num_images: number;
|
|
@@ -45,6 +58,7 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
|
|
| 45 |
resolver: zodResolver(extensiveSchema),
|
| 46 |
defaultValues: {
|
| 47 |
niche: "home_insurance" as Niche,
|
|
|
|
| 48 |
target_audience: "",
|
| 49 |
offer: "",
|
| 50 |
num_images: 1,
|
|
@@ -55,6 +69,7 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
|
|
| 55 |
|
| 56 |
const numImages = watch("num_images");
|
| 57 |
const numStrategies = watch("num_strategies");
|
|
|
|
| 58 |
|
| 59 |
return (
|
| 60 |
<Card variant="glass">
|
|
@@ -71,11 +86,29 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
|
|
| 71 |
options={[
|
| 72 |
{ value: "home_insurance", label: "Home Insurance" },
|
| 73 |
{ value: "glp1", label: "GLP-1" },
|
|
|
|
| 74 |
]}
|
| 75 |
error={errors.niche?.message}
|
| 76 |
{...register("niche")}
|
| 77 |
/>
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
<div>
|
| 80 |
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
| 81 |
Target Audience
|
|
|
|
| 10 |
import type { Niche } from "@/types/api";
|
| 11 |
|
| 12 |
const extensiveSchema = z.object({
|
| 13 |
+
niche: z.enum(["home_insurance", "glp1", "others"]),
|
| 14 |
+
custom_niche: z.string().optional(),
|
| 15 |
target_audience: z.string().optional(),
|
| 16 |
offer: z.string().optional(),
|
| 17 |
num_images: z.number().min(1).max(3),
|
| 18 |
num_strategies: z.number().min(1).max(10),
|
| 19 |
image_model: z.string().nullable().optional(),
|
| 20 |
+
}).refine(
|
| 21 |
+
(data) => {
|
| 22 |
+
if (data.niche === "others") {
|
| 23 |
+
return data.custom_niche && data.custom_niche.trim().length > 0;
|
| 24 |
+
}
|
| 25 |
+
return true;
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
message: "Please enter your custom niche",
|
| 29 |
+
path: ["custom_niche"],
|
| 30 |
+
}
|
| 31 |
+
);
|
| 32 |
|
| 33 |
type ExtensiveFormData = z.infer<typeof extensiveSchema>;
|
| 34 |
|
| 35 |
interface ExtensiveFormProps {
|
| 36 |
onSubmit: (data: {
|
| 37 |
niche: Niche;
|
| 38 |
+
custom_niche?: string;
|
| 39 |
target_audience?: string;
|
| 40 |
offer?: string;
|
| 41 |
num_images: number;
|
|
|
|
| 58 |
resolver: zodResolver(extensiveSchema),
|
| 59 |
defaultValues: {
|
| 60 |
niche: "home_insurance" as Niche,
|
| 61 |
+
custom_niche: "",
|
| 62 |
target_audience: "",
|
| 63 |
offer: "",
|
| 64 |
num_images: 1,
|
|
|
|
| 69 |
|
| 70 |
const numImages = watch("num_images");
|
| 71 |
const numStrategies = watch("num_strategies");
|
| 72 |
+
const selectedNiche = watch("niche");
|
| 73 |
|
| 74 |
return (
|
| 75 |
<Card variant="glass">
|
|
|
|
| 86 |
options={[
|
| 87 |
{ value: "home_insurance", label: "Home Insurance" },
|
| 88 |
{ value: "glp1", label: "GLP-1" },
|
| 89 |
+
{ value: "others", label: "Others" },
|
| 90 |
]}
|
| 91 |
error={errors.niche?.message}
|
| 92 |
{...register("niche")}
|
| 93 |
/>
|
| 94 |
|
| 95 |
+
{selectedNiche === "others" && (
|
| 96 |
+
<div>
|
| 97 |
+
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
| 98 |
+
Custom Niche <span className="text-red-500">*</span>
|
| 99 |
+
</label>
|
| 100 |
+
<input
|
| 101 |
+
type="text"
|
| 102 |
+
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"
|
| 103 |
+
placeholder="e.g., Pet Insurance, Solar Panels, Fitness Supplements"
|
| 104 |
+
{...register("custom_niche")}
|
| 105 |
+
/>
|
| 106 |
+
{errors.custom_niche && (
|
| 107 |
+
<p className="text-red-500 text-xs mt-1">{errors.custom_niche.message}</p>
|
| 108 |
+
)}
|
| 109 |
+
</div>
|
| 110 |
+
)}
|
| 111 |
+
|
| 112 |
<div>
|
| 113 |
<label className="block text-sm font-semibold text-gray-700 mb-2">
|
| 114 |
Target Audience
|
frontend/lib/api/endpoints.ts
CHANGED
|
@@ -72,6 +72,7 @@ export const generateMatrixAd = async (params: {
|
|
| 72 |
// Extensive Endpoint
|
| 73 |
export const generateExtensiveAd = async (params: {
|
| 74 |
niche: Niche;
|
|
|
|
| 75 |
target_audience?: string;
|
| 76 |
offer?: string;
|
| 77 |
num_images: number;
|
|
@@ -83,6 +84,7 @@ export const generateExtensiveAd = async (params: {
|
|
| 83 |
niche: params.niche,
|
| 84 |
num_images: params.num_images || 1,
|
| 85 |
num_strategies: params.num_strategies || 1,
|
|
|
|
| 86 |
...(params.target_audience && { target_audience: params.target_audience }),
|
| 87 |
...(params.offer && { offer: params.offer }),
|
| 88 |
...(params.image_model && { image_model: params.image_model }),
|
|
|
|
| 72 |
// Extensive Endpoint
|
| 73 |
export const generateExtensiveAd = async (params: {
|
| 74 |
niche: Niche;
|
| 75 |
+
custom_niche?: string;
|
| 76 |
target_audience?: string;
|
| 77 |
offer?: string;
|
| 78 |
num_images: number;
|
|
|
|
| 84 |
niche: params.niche,
|
| 85 |
num_images: params.num_images || 1,
|
| 86 |
num_strategies: params.num_strategies || 1,
|
| 87 |
+
...(params.custom_niche && { custom_niche: params.custom_niche }),
|
| 88 |
...(params.target_audience && { target_audience: params.target_audience }),
|
| 89 |
...(params.offer && { offer: params.offer }),
|
| 90 |
...(params.image_model && { image_model: params.image_model }),
|
frontend/types/api.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
// API Response Types based on FastAPI backend
|
| 2 |
|
| 3 |
-
export type Niche = "home_insurance" | "glp1";
|
| 4 |
|
| 5 |
export interface ImageResult {
|
| 6 |
filename?: string | null;
|
|
|
|
| 1 |
// API Response Types based on FastAPI backend
|
| 2 |
|
| 3 |
+
export type Niche = "home_insurance" | "glp1" | "others";
|
| 4 |
|
| 5 |
export interface ImageResult {
|
| 6 |
filename?: string | null;
|
main.py
CHANGED
|
@@ -1104,8 +1104,12 @@ async def get_compatible_concepts(angle_key: str):
|
|
| 1104 |
|
| 1105 |
class ExtensiveGenerateRequest(BaseModel):
|
| 1106 |
"""Request for extensive generation."""
|
| 1107 |
-
niche:
|
| 1108 |
-
description="Target niche"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
)
|
| 1110 |
target_audience: Optional[str] = Field(
|
| 1111 |
default=None,
|
|
@@ -1151,10 +1155,24 @@ async def generate_extensive(
|
|
| 1151 |
5. Generates images for each strategy
|
| 1152 |
|
| 1153 |
Returns all generated ads from all strategies (like batch generation).
|
|
|
|
|
|
|
|
|
|
| 1154 |
"""
|
| 1155 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1156 |
results = await ad_generator.generate_ad_extensive(
|
| 1157 |
-
niche=
|
| 1158 |
target_audience=request.target_audience,
|
| 1159 |
offer=request.offer,
|
| 1160 |
num_images=request.num_images,
|
|
@@ -1167,6 +1185,8 @@ async def generate_extensive(
|
|
| 1167 |
count=len(results),
|
| 1168 |
ads=results
|
| 1169 |
)
|
|
|
|
|
|
|
| 1170 |
except Exception as e:
|
| 1171 |
raise HTTPException(status_code=500, detail=str(e))
|
| 1172 |
|
|
|
|
| 1104 |
|
| 1105 |
class ExtensiveGenerateRequest(BaseModel):
|
| 1106 |
"""Request for extensive generation."""
|
| 1107 |
+
niche: str = Field(
|
| 1108 |
+
description="Target niche: home_insurance, glp1, or others"
|
| 1109 |
+
)
|
| 1110 |
+
custom_niche: Optional[str] = Field(
|
| 1111 |
+
default=None,
|
| 1112 |
+
description="Custom niche name when 'others' is selected"
|
| 1113 |
)
|
| 1114 |
target_audience: Optional[str] = Field(
|
| 1115 |
default=None,
|
|
|
|
| 1155 |
5. Generates images for each strategy
|
| 1156 |
|
| 1157 |
Returns all generated ads from all strategies (like batch generation).
|
| 1158 |
+
|
| 1159 |
+
Supports custom niches via the 'others' option - when niche is 'others',
|
| 1160 |
+
custom_niche field must be provided with the custom niche name.
|
| 1161 |
"""
|
| 1162 |
try:
|
| 1163 |
+
# Determine effective niche
|
| 1164 |
+
if request.niche == "others":
|
| 1165 |
+
if not request.custom_niche or not request.custom_niche.strip():
|
| 1166 |
+
raise HTTPException(
|
| 1167 |
+
status_code=400,
|
| 1168 |
+
detail="custom_niche is required when niche is 'others'"
|
| 1169 |
+
)
|
| 1170 |
+
effective_niche = request.custom_niche.strip()
|
| 1171 |
+
else:
|
| 1172 |
+
effective_niche = request.niche
|
| 1173 |
+
|
| 1174 |
results = await ad_generator.generate_ad_extensive(
|
| 1175 |
+
niche=effective_niche,
|
| 1176 |
target_audience=request.target_audience,
|
| 1177 |
offer=request.offer,
|
| 1178 |
num_images=request.num_images,
|
|
|
|
| 1185 |
count=len(results),
|
| 1186 |
ads=results
|
| 1187 |
)
|
| 1188 |
+
except HTTPException:
|
| 1189 |
+
raise
|
| 1190 |
except Exception as e:
|
| 1191 |
raise HTTPException(status_code=500, detail=str(e))
|
| 1192 |
|