sushilideaclan01 commited on
Commit
8ffe335
·
1 Parent(s): 5106651
frontend/app/browse/angles/page.tsx CHANGED
@@ -7,6 +7,7 @@ import { Select } from "@/components/ui/Select";
7
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllAngles } from "@/lib/api/endpoints";
9
  import type { AnglesResponse } from "@/types/api";
 
10
 
11
  export default function AnglesPage() {
12
  const [angles, setAngles] = useState<AnglesResponse | null>(null);
@@ -79,9 +80,20 @@ export default function AnglesPage() {
79
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
80
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
81
  <div className="text-center animate-fade-in">
82
- <h1 className="text-4xl md:text-5xl font-extrabold mb-4">
83
- <span className="gradient-text">Angles</span>
84
- </h1>
 
 
 
 
 
 
 
 
 
 
 
85
  <p className="text-lg text-gray-600">
86
  Browse all {angles.total_angles} available angles
87
  </p>
 
7
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllAngles } from "@/lib/api/endpoints";
9
  import type { AnglesResponse } from "@/types/api";
10
+ import { InfoButton } from "@/components/ui/InfoButton";
11
 
12
  export default function AnglesPage() {
13
  const [angles, setAngles] = useState<AnglesResponse | null>(null);
 
80
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
81
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
82
  <div className="text-center animate-fade-in">
83
+ <div className="flex items-center justify-center gap-3 mb-4">
84
+ <h1 className="text-4xl md:text-5xl font-extrabold">
85
+ <span className="gradient-text">Angles</span>
86
+ </h1>
87
+ <InfoButton
88
+ title="What are Angles?"
89
+ content="Angles are the reason someone should care right now. They're the specific hooks or approaches that make your ad relevant and compelling to your target audience.
90
+
91
+ Each angle targets a specific emotional trigger or pain point. The same product can have multiple angles - for example, insurance could use 'Save money' or 'Protect your family' as different angles.
92
+
93
+ Browse through all available angles to find ones that resonate with your target audience. You can use these in the Matrix flow to create targeted ads."
94
+ position="bottom"
95
+ />
96
+ </div>
97
  <p className="text-lg text-gray-600">
98
  Browse all {angles.total_angles} available angles
99
  </p>
frontend/app/browse/concepts/page.tsx CHANGED
@@ -7,6 +7,7 @@ import { Select } from "@/components/ui/Select";
7
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllConcepts } from "@/lib/api/endpoints";
9
  import type { ConceptsResponse } from "@/types/api";
 
10
 
11
  export default function ConceptsPage() {
12
  const [concepts, setConcepts] = useState<ConceptsResponse | null>(null);
@@ -79,9 +80,26 @@ export default function ConceptsPage() {
79
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
80
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
81
  <div className="text-center animate-fade-in">
82
- <h1 className="text-4xl md:text-5xl font-extrabold mb-4">
83
- <span className="gradient-text">Concepts</span>
84
- </h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  <p className="text-lg text-gray-600">
86
  Browse all {concepts.total_concepts} available concepts
87
  </p>
 
7
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllConcepts } from "@/lib/api/endpoints";
9
  import type { ConceptsResponse } from "@/types/api";
10
+ import { InfoButton } from "@/components/ui/InfoButton";
11
 
12
  export default function ConceptsPage() {
13
  const [concepts, setConcepts] = useState<ConceptsResponse | null>(null);
 
80
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
81
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
82
  <div className="text-center animate-fade-in">
83
+ <div className="flex items-center justify-center gap-3 mb-4">
84
+ <h1 className="text-4xl md:text-5xl font-extrabold">
85
+ <span className="gradient-text">Concepts</span>
86
+ </h1>
87
+ <InfoButton
88
+ title="What are Concepts?"
89
+ content="Concepts are the creative execution styles or storylines you use to deliver your angle. They define how your ad will look and feel visually.
90
+
91
+ Examples include:
92
+ - Before/After comparisons
93
+ - Testimonials and reviews
94
+ - Problem/Solution narratives
95
+ - Visual metaphors
96
+ - Lifestyle imagery
97
+ - UGC (User Generated Content) style
98
+
99
+ Each concept has a specific structure and visual direction. When combined with an angle in the Matrix flow, they create powerful ads that both hook attention and drive action. Browse through all available concepts to find ones that match your creative vision."
100
+ position="bottom"
101
+ />
102
+ </div>
103
  <p className="text-lg text-gray-600">
104
  Browse all {concepts.total_concepts} available concepts
105
  </p>
frontend/app/creative/modify/page.tsx CHANGED
@@ -18,6 +18,7 @@ import type {
18
  ModifiedImageResult,
19
  ImageCorrectResponse,
20
  } from "@/types/api";
 
21
 
22
  type WorkflowStep = "upload" | "analysis" | "modify" | "result";
23
 
@@ -213,9 +214,33 @@ export default function CreativeModifyPage() {
213
  <div className="container mx-auto px-4 py-8">
214
  {/* Header */}
215
  <div className="text-center mb-8">
216
- <h1 className="text-3xl font-bold text-gray-900 mb-2">
217
- Creative Modifier
218
- </h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  <p className="text-gray-600 max-w-2xl mx-auto">
220
  Upload your existing creative, let AI analyze it, then apply new
221
  angles or concepts to generate variations
 
18
  ModifiedImageResult,
19
  ImageCorrectResponse,
20
  } from "@/types/api";
21
+ import { InfoButton } from "@/components/ui/InfoButton";
22
 
23
  type WorkflowStep = "upload" | "analysis" | "modify" | "result";
24
 
 
214
  <div className="container mx-auto px-4 py-8">
215
  {/* Header */}
216
  <div className="text-center mb-8">
217
+ <div className="flex items-center justify-center gap-3 mb-2">
218
+ <h1 className="text-3xl font-bold text-gray-900">
219
+ Creative Modifier
220
+ </h1>
221
+ <InfoButton
222
+ title="Creative Modification Flow"
223
+ content="This flow allows you to modify existing ad creatives:
224
+
225
+ 1. UPLOAD: Upload an existing ad image (from URL or file)
226
+
227
+ 2. ANALYSIS: AI analyzes your creative to understand:
228
+ - Current angle and concept
229
+ - Visual elements and composition
230
+ - Copy elements and messaging
231
+ - Overall strategy
232
+
233
+ 3. MODIFY: Apply new angles or concepts to create variations:
234
+ - Modify: Change angle/concept while keeping similar structure
235
+ - Regenerate: Create completely new version
236
+ - Custom: Use your own prompt for specific changes
237
+
238
+ 4. RESULT: Get your modified creative with new image and updated copy
239
+
240
+ Perfect for iterating on winning ads or testing new angles with proven visuals."
241
+ position="bottom"
242
+ />
243
+ </div>
244
  <p className="text-gray-600 max-w-2xl mx-auto">
245
  Upload your existing creative, let AI analyze it, then apply new
246
  angles or concepts to generate variations
frontend/app/generate/page.tsx CHANGED
@@ -727,6 +727,7 @@ export default function GeneratePage() {
727
  </p>
728
 
729
  {/* Mode Toggle */}
 
730
  <div className="flex items-center justify-center gap-3 flex-wrap">
731
  <button
732
  onClick={() => setMode("standard")}
@@ -773,6 +774,7 @@ export default function GeneratePage() {
773
  Batch
774
  </button>
775
  </div>
 
776
  </div>
777
  </div>
778
  </div>
 
727
  </p>
728
 
729
  {/* Mode Toggle */}
730
+ <div className="flex flex-col items-center gap-3">
731
  <div className="flex items-center justify-center gap-3 flex-wrap">
732
  <button
733
  onClick={() => setMode("standard")}
 
774
  Batch
775
  </button>
776
  </div>
777
+ </div>
778
  </div>
779
  </div>
780
  </div>
frontend/app/globals.css CHANGED
@@ -135,6 +135,21 @@ body {
135
  animation: scaleIn 0.3s ease-out;
136
  }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  .animate-pulse-glow {
139
  animation: pulse-glow 2s ease-in-out infinite;
140
  }
 
135
  animation: scaleIn 0.3s ease-out;
136
  }
137
 
138
+ @keyframes infoTooltipEnter {
139
+ from {
140
+ opacity: 0;
141
+ transform: scale(0.96);
142
+ }
143
+ to {
144
+ opacity: 1;
145
+ transform: scale(1);
146
+ }
147
+ }
148
+
149
+ .info-tooltip-enter {
150
+ animation: infoTooltipEnter 0.15s ease-out;
151
+ }
152
+
153
  .animate-pulse-glow {
154
  animation: pulse-glow 2s ease-in-out infinite;
155
  }
frontend/app/matrix/page.tsx CHANGED
@@ -5,6 +5,7 @@ import Link from "next/link";
5
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
6
  import { Button } from "@/components/ui/Button";
7
  import { Search, TestTube } from "lucide-react";
 
8
 
9
  export default function MatrixPage() {
10
  return (
@@ -14,10 +15,29 @@ export default function MatrixPage() {
14
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
15
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
16
  <div className="text-center animate-fade-in">
17
- <h1 className="text-4xl md:text-5xl font-extrabold mb-4">
18
- <span className="gradient-text">Matrix</span>
19
- <span className="text-gray-900"> System</span>
20
- </h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  <p className="text-lg text-gray-600 max-w-2xl mx-auto">
22
  Explore the Angle × Concept matrix for systematic ad generation
23
  </p>
 
5
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
6
  import { Button } from "@/components/ui/Button";
7
  import { Search, TestTube } from "lucide-react";
8
+ import { InfoButton } from "@/components/ui/InfoButton";
9
 
10
  export default function MatrixPage() {
11
  return (
 
15
  <div className="absolute inset-0 bg-grid-pattern opacity-5"></div>
16
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
17
  <div className="text-center animate-fade-in">
18
+ <div className="flex items-center justify-center gap-3 mb-4">
19
+ <h1 className="text-4xl md:text-5xl font-extrabold">
20
+ <span className="gradient-text">Matrix</span>
21
+ <span className="text-gray-900"> System</span>
22
+ </h1>
23
+ <InfoButton
24
+ title="Matrix System Explained"
25
+ content="The Matrix System is a systematic approach to ad generation that combines Angles and Concepts.
26
+
27
+ ANGLES: The reason someone should care right now. They target specific emotional triggers or pain points.
28
+
29
+ CONCEPTS: The creative execution style - how your ad looks and feels visually.
30
+
31
+ By combining different angles with different concepts, you create a matrix of possibilities. This systematic approach helps you:
32
+ - Test multiple combinations efficiently
33
+ - Find winning angle/concept pairs
34
+ - Scale successful patterns
35
+ - Avoid creative fatigue
36
+
37
+ Use the Matrix flow in Generate to select specific combinations, or use the Testing Matrix Builder to create systematic test plans."
38
+ position="bottom"
39
+ />
40
+ </div>
41
  <p className="text-lg text-gray-600 max-w-2xl mx-auto">
42
  Explore the Angle × Concept matrix for systematic ad generation
43
  </p>
frontend/components/generation/BatchForm.tsx CHANGED
@@ -10,6 +10,7 @@ import { Button } from "@/components/ui/Button";
10
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
11
  import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
12
  import type { Niche } from "@/types/api";
 
13
 
14
  interface BatchFormProps {
15
  onSubmit: (data: { niche: Niche; count: number; images_per_ad: number; image_model?: string | null; target_audience?: string | null; offer?: string | null }) => Promise<void>;
@@ -44,7 +45,14 @@ export const BatchForm: React.FC<BatchFormProps> = ({
44
  return (
45
  <Card variant="glass">
46
  <CardHeader>
47
- <CardTitle>Batch Generation</CardTitle>
 
 
 
 
 
 
 
48
  <CardDescription>
49
  Generate multiple ads at once for testing and variety
50
  </CardDescription>
 
10
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
11
  import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
12
  import type { Niche } from "@/types/api";
13
+ import { InfoButton } from "@/components/ui/InfoButton";
14
 
15
  interface BatchFormProps {
16
  onSubmit: (data: { niche: Niche; count: number; images_per_ad: number; image_model?: string | null; target_audience?: string | null; offer?: string | null }) => Promise<void>;
 
45
  return (
46
  <Card variant="glass">
47
  <CardHeader>
48
+ <div className="flex items-center gap-2">
49
+ <CardTitle>Batch Generation</CardTitle>
50
+ <InfoButton
51
+ title="Batch Generation Flow"
52
+ 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 with 1-3 variations per ad. Perfect for finding winning combinations through volume testing."
53
+ position="bottom"
54
+ />
55
+ </div>
56
  <CardDescription>
57
  Generate multiple ads at once for testing and variety
58
  </CardDescription>
frontend/components/generation/ExtensiveForm.tsx CHANGED
@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
8
  import { Select } from "@/components/ui/Select";
9
  import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
10
  import type { Niche } from "@/types/api";
 
11
 
12
  const extensiveSchema = z.object({
13
  niche: z.enum(["home_insurance", "glp1", "auto_insurance", "others"]),
@@ -57,7 +58,7 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
57
  } = useForm<ExtensiveFormData>({
58
  resolver: zodResolver(extensiveSchema),
59
  defaultValues: {
60
- niche: "home_insurance" as const,
61
  custom_niche: "",
62
  target_audience: "",
63
  offer: "",
@@ -75,7 +76,24 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
75
  return (
76
  <Card variant="glass">
77
  <CardHeader>
78
- <CardTitle>Extensive Generation</CardTitle>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  <CardDescription>
80
  Researcher → Creative Director → Designer → Copywriter flow
81
  </CardDescription>
@@ -133,7 +151,7 @@ export const ExtensiveForm: React.FC<ExtensiveFormProps> = ({
133
  <input
134
  type="text"
135
  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"
136
- placeholder="e.g., Don't overpay your insurance"
137
  {...register("offer")}
138
  />
139
  {errors.offer && (
 
8
  import { Select } from "@/components/ui/Select";
9
  import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
10
  import type { Niche } from "@/types/api";
11
+ import { InfoButton } from "@/components/ui/InfoButton";
12
 
13
  const extensiveSchema = z.object({
14
  niche: z.enum(["home_insurance", "glp1", "auto_insurance", "others"]),
 
58
  } = useForm<ExtensiveFormData>({
59
  resolver: zodResolver(extensiveSchema),
60
  defaultValues: {
61
+ niche: "home_insurance" as const, // first option; flow works for any niche
62
  custom_niche: "",
63
  target_audience: "",
64
  offer: "",
 
76
  return (
77
  <Card variant="glass">
78
  <CardHeader>
79
+ <div className="flex items-center gap-2">
80
+ <CardTitle>Extensive Generation</CardTitle>
81
+ <InfoButton
82
+ title="Extensive Generation Flow"
83
+ content="This flow works for any niche (insurance, GLP-1, auto, or custom). It uses a 4-stage process:
84
+
85
+ 1. RESEARCHER: Analyzes your inputs and researches psychology triggers, angles, and concepts that work best for your niche and audience.
86
+
87
+ 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.
88
+
89
+ 3. DESIGNER: Converts each creative strategy into detailed image generation prompts optimized for affiliate marketing (authentic, low-production style).
90
+
91
+ 4. COPYWRITER: Writes compelling ad copy (title, body, description) that matches each strategy's emotional tone and psychology trigger.
92
+
93
+ You can generate multiple strategies (1-10) and multiple variations per strategy (1-3) for comprehensive testing."
94
+ position="bottom"
95
+ />
96
+ </div>
97
  <CardDescription>
98
  Researcher → Creative Director → Designer → Copywriter flow
99
  </CardDescription>
 
151
  <input
152
  type="text"
153
  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"
154
+ placeholder="e.g., Free quote, Save $500/year, Limited-time offer"
155
  {...register("offer")}
156
  />
157
  {errors.offer && (
frontend/components/generation/GenerationForm.tsx CHANGED
@@ -12,6 +12,7 @@ import { IMAGE_MODELS, getModelCost, formatCost } from "@/lib/constants/models";
12
  import type { Niche } from "@/types/api";
13
  import { Loader2, TrendingUp, Check } from "lucide-react";
14
  import apiClient from "@/lib/api/client";
 
15
 
16
  interface GenerationFormProps {
17
  onSubmit: (data: {
@@ -110,7 +111,14 @@ export const GenerationForm: React.FC<GenerationFormProps> = ({
110
  return (
111
  <Card variant="glass">
112
  <CardHeader>
113
- <CardTitle>Generate Ad</CardTitle>
 
 
 
 
 
 
 
114
  <CardDescription>
115
  Create a new ad creative using randomized strategies
116
  </CardDescription>
 
12
  import type { Niche } from "@/types/api";
13
  import { Loader2, TrendingUp, Check } from "lucide-react";
14
  import apiClient from "@/lib/api/client";
15
+ import { InfoButton } from "@/components/ui/InfoButton";
16
 
17
  interface GenerationFormProps {
18
  onSubmit: (data: {
 
111
  return (
112
  <Card variant="glass">
113
  <CardHeader>
114
+ <div className="flex items-center gap-2">
115
+ <CardTitle>Generate Ad</CardTitle>
116
+ <InfoButton
117
+ title="Standard Generation Flow"
118
+ content="This flow generates ads using randomized strategies from predefined angles and concepts. It's the fastest way to create ads with minimal configuration. The system automatically selects the best combinations based on your niche, target audience, and offer."
119
+ position="bottom"
120
+ />
121
+ </div>
122
  <CardDescription>
123
  Create a new ad creative using randomized strategies
124
  </CardDescription>
frontend/components/matrix/AngleSelector.tsx CHANGED
@@ -8,6 +8,7 @@ import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllAngles } from "@/lib/api/endpoints";
9
  import { useMatrixStore } from "@/store/matrixStore";
10
  import type { AngleInfo, AnglesResponse } from "@/types/api";
 
11
 
12
  interface AngleSelectorProps {
13
  onSelect?: (angle: AngleInfo) => void;
@@ -86,9 +87,20 @@ export const AngleSelector: React.FC<AngleSelectorProps> = ({
86
  return (
87
  <Card variant="glass" className="border-2 border-transparent hover:border-blue-200/50 transition-all duration-300">
88
  <CardHeader>
89
- <CardTitle className="bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
90
- Select Angle
91
- </CardTitle>
 
 
 
 
 
 
 
 
 
 
 
92
  </CardHeader>
93
  <CardContent className="space-y-4">
94
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 
8
  import { getAllAngles } from "@/lib/api/endpoints";
9
  import { useMatrixStore } from "@/store/matrixStore";
10
  import type { AngleInfo, AnglesResponse } from "@/types/api";
11
+ import { InfoButton } from "@/components/ui/InfoButton";
12
 
13
  interface AngleSelectorProps {
14
  onSelect?: (angle: AngleInfo) => void;
 
87
  return (
88
  <Card variant="glass" className="border-2 border-transparent hover:border-blue-200/50 transition-all duration-300">
89
  <CardHeader>
90
+ <div className="flex items-center gap-2">
91
+ <CardTitle className="bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
92
+ Select Angle
93
+ </CardTitle>
94
+ <InfoButton
95
+ title="What is an Angle?"
96
+ content="An angle is the reason someone should care right now. It's the specific hook or approach that makes your ad relevant and compelling to your target audience.
97
+
98
+ The same product can have multiple angles - each targeting different emotional triggers or pain points. For example, 'Save money' vs 'Protect your family' are different angles for insurance.
99
+
100
+ Selecting the right angle helps ensure your ad resonates with your audience and drives action."
101
+ position="bottom"
102
+ />
103
+ </div>
104
  </CardHeader>
105
  <CardContent className="space-y-4">
106
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
frontend/components/matrix/ConceptSelector.tsx CHANGED
@@ -8,6 +8,7 @@ import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
8
  import { getAllConcepts, getCompatibleConcepts } from "@/lib/api/endpoints";
9
  import { useMatrixStore } from "@/store/matrixStore";
10
  import type { ConceptInfo, ConceptsResponse } from "@/types/api";
 
11
 
12
  interface ConceptSelectorProps {
13
  onSelect?: (concept: ConceptInfo) => void;
@@ -148,9 +149,25 @@ export const ConceptSelector: React.FC<ConceptSelectorProps> = ({
148
  return (
149
  <Card variant="glass" className="border-2 border-transparent hover:border-cyan-200/50 transition-all duration-300">
150
  <CardHeader>
151
- <CardTitle className="bg-gradient-to-r from-cyan-600 to-pink-600 bg-clip-text text-transparent">
152
- Select Concept
153
- </CardTitle>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  {angleKey && (
155
  <div className="mt-2">
156
  <label className="flex items-center space-x-2">
 
8
  import { getAllConcepts, getCompatibleConcepts } from "@/lib/api/endpoints";
9
  import { useMatrixStore } from "@/store/matrixStore";
10
  import type { ConceptInfo, ConceptsResponse } from "@/types/api";
11
+ import { InfoButton } from "@/components/ui/InfoButton";
12
 
13
  interface ConceptSelectorProps {
14
  onSelect?: (concept: ConceptInfo) => void;
 
149
  return (
150
  <Card variant="glass" className="border-2 border-transparent hover:border-cyan-200/50 transition-all duration-300">
151
  <CardHeader>
152
+ <div className="flex items-center gap-2">
153
+ <CardTitle className="bg-gradient-to-r from-cyan-600 to-pink-600 bg-clip-text text-transparent">
154
+ Select Concept
155
+ </CardTitle>
156
+ <InfoButton
157
+ title="What is a Concept?"
158
+ content="A concept is the creative execution style or storyline you use to deliver your angle. It defines how your ad will look and feel visually.
159
+
160
+ Concepts include things like:
161
+ - Before/After comparisons
162
+ - Testimonials
163
+ - Problem/Solution narratives
164
+ - Visual metaphors
165
+ - Lifestyle imagery
166
+
167
+ Each concept has a specific structure and visual direction. When combined with an angle, they create a powerful ad that both hooks attention and drives action. Some concepts work better with certain angles - use the 'Show compatible concepts only' option to see recommended pairings."
168
+ position="bottom"
169
+ />
170
+ </div>
171
  {angleKey && (
172
  <div className="mt-2">
173
  <label className="flex items-center space-x-2">
frontend/components/ui/InfoButton.tsx ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useRef, useEffect } from "react";
4
+ import { Info } from "lucide-react";
5
+ import { cn } from "@/lib/utils/cn";
6
+
7
+ interface InfoButtonProps {
8
+ content: string | React.ReactNode;
9
+ title?: string;
10
+ position?: "top" | "bottom" | "left" | "right";
11
+ className?: string;
12
+ /** Smaller, more subtle variant for inline use next to labels */
13
+ variant?: "default" | "subtle";
14
+ }
15
+
16
+ export const InfoButton: React.FC<InfoButtonProps> = ({
17
+ content,
18
+ title,
19
+ position = "top",
20
+ className = "",
21
+ variant = "subtle",
22
+ }) => {
23
+ const [isOpen, setIsOpen] = useState(false);
24
+ const buttonRef = useRef<HTMLButtonElement>(null);
25
+ const tooltipRef = useRef<HTMLDivElement>(null);
26
+
27
+ useEffect(() => {
28
+ const handleClickOutside = (event: MouseEvent) => {
29
+ if (
30
+ tooltipRef.current &&
31
+ buttonRef.current &&
32
+ !tooltipRef.current.contains(event.target as Node) &&
33
+ !buttonRef.current.contains(event.target as Node)
34
+ ) {
35
+ setIsOpen(false);
36
+ }
37
+ };
38
+
39
+ if (isOpen) {
40
+ document.addEventListener("mousedown", handleClickOutside);
41
+ }
42
+
43
+ return () => {
44
+ document.removeEventListener("mousedown", handleClickOutside);
45
+ };
46
+ }, [isOpen]);
47
+
48
+ const positionClasses = {
49
+ top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
50
+ bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
51
+ left: "right-full top-1/2 -translate-y-1/2 mr-2",
52
+ right: "left-full top-1/2 -translate-y-1/2 ml-2",
53
+ };
54
+
55
+ const isBottom = position === "bottom";
56
+ const isTop = position === "top";
57
+
58
+ return (
59
+ <span className={cn("relative inline-flex", className)}>
60
+ <button
61
+ ref={buttonRef}
62
+ type="button"
63
+ onClick={(e) => {
64
+ e.preventDefault();
65
+ e.stopPropagation();
66
+ setIsOpen(!isOpen);
67
+ }}
68
+ className={cn(
69
+ "inline-flex items-center justify-center rounded-full transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-400/50 focus:ring-offset-1",
70
+ variant === "subtle"
71
+ ? "w-4 h-4 text-gray-400 hover:text-gray-600 hover:bg-gray-100"
72
+ : "w-5 h-5 bg-gray-100 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
73
+ )}
74
+ aria-label="Show information"
75
+ >
76
+ <Info className="w-3 h-3" strokeWidth={2.25} />
77
+ </button>
78
+
79
+ {isOpen && (
80
+ <div
81
+ ref={tooltipRef}
82
+ className={cn(
83
+ "absolute z-[100] w-72 sm:w-80 max-w-[calc(100vw-2rem)]",
84
+ positionClasses[position]
85
+ )}
86
+ >
87
+ <div
88
+ className={cn(
89
+ "relative rounded-xl border border-gray-200/90 bg-white/95 shadow-lg backdrop-blur-sm",
90
+ "info-tooltip-enter"
91
+ )}
92
+ >
93
+ <div className="max-h-[70vh] overflow-y-auto rounded-xl p-4">
94
+ {title && (
95
+ <h3 className="text-sm font-semibold text-gray-900 mb-2 pr-6">
96
+ {title}
97
+ </h3>
98
+ )}
99
+ <div className="text-sm leading-relaxed text-gray-600">
100
+ {typeof content === "string" ? (
101
+ <p className="whitespace-pre-line">{content}</p>
102
+ ) : (
103
+ content
104
+ )}
105
+ </div>
106
+ </div>
107
+ {/* Arrow */}
108
+ <div
109
+ className={cn(
110
+ "absolute w-2 h-2 rotate-45 border border-gray-200/90 bg-white/95",
111
+ isBottom && "top-0 left-1/2 -translate-x-1/2 -translate-y-px border-t-transparent border-l-transparent",
112
+ isTop && "bottom-0 left-1/2 -translate-x-1/2 translate-y-px border-b-transparent border-r-transparent",
113
+ position === "left" && "right-0 top-1/2 -translate-y-1/2 translate-x-px border-r-transparent border-b-transparent",
114
+ position === "right" && "left-0 top-1/2 -translate-y-1/2 -translate-x-px border-l-transparent border-t-transparent"
115
+ )}
116
+ />
117
+ </div>
118
+ </div>
119
+ )}
120
+ </span>
121
+ );
122
+ }
main.py CHANGED
@@ -1816,7 +1816,7 @@ async def motivator_generate_endpoint(
1816
  class ExtensiveGenerateRequest(BaseModel):
1817
  """Request for extensive generation."""
1818
  niche: str = Field(
1819
- description="Target niche: home_insurance, glp1, or others"
1820
  )
1821
  custom_niche: Optional[str] = Field(
1822
  default=None,
 
1816
  class ExtensiveGenerateRequest(BaseModel):
1817
  """Request for extensive generation."""
1818
  niche: str = Field(
1819
+ description="Target niche: home_insurance, glp1, auto_insurance, or others (use custom_niche when others)"
1820
  )
1821
  custom_niche: Optional[str] = Field(
1822
  default=None,
services/generator.py CHANGED
@@ -2087,9 +2087,10 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
2087
  ) -> Dict[str, Any]:
2088
  """
2089
  Generate ad using extensive: researcher → creative director → designer → copywriter.
 
2090
 
2091
  Args:
2092
- niche: Target niche (home_insurance or glp1)
2093
  target_audience: Optional target audience description
2094
  offer: Optional offer to run
2095
  num_images: Number of images to generate per strategy
@@ -2102,12 +2103,13 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
2102
  if not third_flow_available:
2103
  raise ValueError("Extensive service not available")
2104
 
2105
- # Map niche names
2106
  niche_map = {
2107
  "home_insurance": "Home Insurance",
2108
  "glp1": "GLP-1",
 
2109
  }
2110
- niche_display = niche_map.get(niche, niche.title())
2111
 
2112
  # Provide defaults if target_audience or offer are not provided
2113
  if not target_audience:
 
2087
  ) -> Dict[str, Any]:
2088
  """
2089
  Generate ad using extensive: researcher → creative director → designer → copywriter.
2090
+ Works for any niche: home_insurance, glp1, auto_insurance, or custom (e.g. from 'others').
2091
 
2092
  Args:
2093
+ niche: Target niche (home_insurance, glp1, auto_insurance, or custom display name when 'others')
2094
  target_audience: Optional target audience description
2095
  offer: Optional offer to run
2096
  num_images: Number of images to generate per strategy
 
2103
  if not third_flow_available:
2104
  raise ValueError("Extensive service not available")
2105
 
2106
+ # Map known niche keys to display names; custom niches (e.g. from 'others') pass through as-is
2107
  niche_map = {
2108
  "home_insurance": "Home Insurance",
2109
  "glp1": "GLP-1",
2110
+ "auto_insurance": "Auto Insurance",
2111
  }
2112
+ niche_display = niche_map.get(niche, niche.replace("_", " ").title())
2113
 
2114
  # Provide defaults if target_audience or offer are not provided
2115
  if not target_audience:
services/third_flow.py CHANGED
@@ -13,6 +13,7 @@ from pydantic import BaseModel
13
  # Add parent directory to path for imports
14
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15
 
 
16
  from openai import OpenAI
17
  from config import settings
18
 
@@ -44,6 +45,7 @@ class CreativeStrategies(BaseModel):
44
  titleIdeas: str
45
  captionIdeas: str
46
  bodyIdeas: str
 
47
 
48
 
49
  class CreativeStrategiesOutput(BaseModel):
@@ -78,12 +80,12 @@ class ThirdFlowService:
78
  ) -> List[ImageAdEssentials]:
79
  """
80
  Research psychology triggers, angles, and concepts.
81
-
82
  Args:
83
  target_audience: Target audience description
84
  offer: Offer to run
85
  niche: Niche category
86
-
87
  Returns:
88
  List of ImageAdEssentials with psychology triggers, angles, and concepts
89
  """
@@ -94,14 +96,14 @@ class ThirdFlowService:
94
  {
95
  "type": "text",
96
  "text": """You are the researcher for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
97
- Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
98
- A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
99
- An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
100
- An ad concept is the creative execution style or storyline you use to deliver an angle.
101
- In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.
102
-
103
- Keeping in mind all this, make sure you provide different angles and concepts we can try based on the psychology triggers for the image ads for the given input based on affiliate marketing.
104
- User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience."""
105
  }
106
  ]
107
  },
@@ -111,23 +113,23 @@ class ThirdFlowService:
111
  {
112
  "type": "text",
113
  "text": f"""Following are the inputs:
114
- Niche: {niche}
115
- Offer to run: {offer}
116
- Target Audience: {target_audience}
117
-
118
- Provide the different psychology triggers, angles and concept based on the given input."""
119
  }
120
  ]
121
  }
122
  ]
123
-
124
  try:
125
  completion = self.client.beta.chat.completions.parse(
126
  model=self.gpt_model,
127
  messages=messages,
128
  response_format=ImageAdEssentialsOutput,
129
  )
130
-
131
  response = completion.choices[0].message
132
  if response.parsed:
133
  return response.parsed.output
@@ -138,7 +140,41 @@ class ThirdFlowService:
138
  except Exception as e:
139
  print(f"Error in researcher: {e}")
140
  return []
141
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  def retrieve_search(
143
  self,
144
  target_audience: str,
@@ -446,8 +482,8 @@ class ThirdFlowService:
446
  except Exception as e:
447
  print(f"Error in creative_director: {e}")
448
  return []
449
-
450
- def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "Home Insurance") -> str:
451
  """
452
  Generate image prompt from creative strategy.
453
 
@@ -474,37 +510,26 @@ PROPS TO INCLUDE: {', '.join(niche_guidance_data.get('props', []))}
474
  AVOID: {', '.join(niche_guidance_data.get('avoid', []))}
475
  COLOR PREFERENCE: {niche_guidance_data.get('color_preference', 'balanced')}
476
 
477
- CRITICAL: The image MUST be appropriate for {niche} niche. Ensure all visual elements match this niche category."""
478
- elif niche_lower == "home_insurance":
479
- niche_guidance = """
480
- NICHE-SPECIFIC REQUIREMENTS (Home Insurance):
481
- SUBJECTS TO INCLUDE: family in front of home, house exterior, homeowner looking confident, couple reviewing papers
482
- PROPS TO INCLUDE: insurance documents, house keys, tablet showing coverage, family photos
483
- AVOID: disasters, fire or floods, stressed expressions, dark settings
484
- COLOR PREFERENCE: trust
485
-
486
- CRITICAL: The image MUST show home insurance-related content. Show REAL American suburban homes, homeowners, and insurance-related elements."""
487
- elif niche_lower == "glp1":
488
- niche_guidance = """
489
- NICHE-SPECIFIC REQUIREMENTS (GLP-1):
490
- SUBJECTS TO INCLUDE: confident person smiling (age 30-50), active lifestyle scenes with adults, healthy meal preparation, doctor consultation
491
- PROPS TO INCLUDE: fitness equipment, healthy food, comfortable clothing
492
- AVOID: before/after weight comparisons, measuring tapes, scales prominently, needle close-ups, elderly people over 65, senior citizens, very old looking people, gray-haired elderly groups
493
- AGE GUIDANCE: Show people aged 30-50 primarily. DO NOT default to elderly/senior citizens. The target audience is middle-aged adults in their 30s-40s, NOT seniors or elderly people.
494
- COLOR PREFERENCE: health
495
-
496
- CRITICAL: The image MUST be appropriate for GLP-1/weight loss niche. Show lifestyle, health, and confidence-related content. People in images should look 30-50 years old, NOT elderly."""
497
  else:
498
  niche_guidance = f"""
499
- NICHE-SPECIFIC REQUIREMENTS ({niche}):
500
- CRITICAL: The image MUST be appropriate for {niche} niche."""
501
-
 
 
 
 
 
 
 
502
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
503
  Angle: {creative_strategy.angle}
504
  Concept: {creative_strategy.concept}
505
- Text: {creative_strategy.text.textToBeWrittern if creative_strategy.text.textToBeWrittern not in [None, 'None', 'NA'] else 'No text overlay'}
506
  CTA: {creative_strategy.cta}
507
  Visual Direction: {creative_strategy.visualDirection}
 
508
  """
509
 
510
  messages = [
@@ -614,8 +639,8 @@ CRITICAL: The image MUST be appropriate for {niche} niche."""
614
  prompt += " Authentic, relatable style - not overly polished or commercial."
615
 
616
  return prompt.strip()
617
-
618
- def copy_writer(self, creative_strategy: CreativeStrategies) -> CopyWriterOutput:
619
  """
620
  Generate ad copy from creative strategy.
621
 
@@ -625,13 +650,18 @@ CRITICAL: The image MUST be appropriate for {niche} niche."""
625
  Returns:
626
  CopyWriterOutput with title, body, and description
627
  """
 
 
 
 
 
 
 
628
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
629
  Angle: {creative_strategy.angle}
630
  Concept: {creative_strategy.concept}
631
  CTA: {creative_strategy.cta}
632
- Title Ideas: {creative_strategy.titleIdeas}
633
- Caption Ideas: {creative_strategy.captionIdeas}
634
- Body Ideas: {creative_strategy.bodyIdeas}
635
  """
636
 
637
  messages = [
@@ -710,7 +740,8 @@ CRITICAL: The image MUST be appropriate for {niche} niche."""
710
  def process_strategy(
711
  self,
712
  creative_strategy: CreativeStrategies,
713
- niche: str = "Home Insurance"
 
714
  ) -> tuple[str, str, str, str]:
715
  """
716
  Process a single creative strategy to generate prompt and copy.
@@ -722,8 +753,9 @@ CRITICAL: The image MUST be appropriate for {niche} niche."""
722
  Returns:
723
  Tuple of (prompt, title, body, description)
724
  """
725
- prompt = self.creative_designer(creative_strategy, niche=niche)
726
- ad_copy = self.copy_writer(creative_strategy)
 
727
  return (
728
  prompt,
729
  ad_copy.title,
@@ -734,3 +766,58 @@ CRITICAL: The image MUST be appropriate for {niche} niche."""
734
 
735
  # Global service instance
736
  third_flow_service = ThirdFlowService()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # Add parent directory to path for imports
14
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15
 
16
+ from services.motivator import generate_motivators
17
  from openai import OpenAI
18
  from config import settings
19
 
 
45
  titleIdeas: str
46
  captionIdeas: str
47
  bodyIdeas: str
48
+ motivators: list[str] | None = None
49
 
50
 
51
  class CreativeStrategiesOutput(BaseModel):
 
80
  ) -> List[ImageAdEssentials]:
81
  """
82
  Research psychology triggers, angles, and concepts.
83
+
84
  Args:
85
  target_audience: Target audience description
86
  offer: Offer to run
87
  niche: Niche category
88
+
89
  Returns:
90
  List of ImageAdEssentials with psychology triggers, angles, and concepts
91
  """
 
96
  {
97
  "type": "text",
98
  "text": """You are the researcher for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
99
+ Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
100
+ A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
101
+ An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
102
+ An ad concept is the creative execution style or storyline you use to deliver an angle.
103
+ In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.
104
+
105
+ Keeping in mind all this, make sure you provide different angles and concepts we can try based on the psychology triggers for the image ads for the given input based on affiliate marketing.
106
+ User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience."""
107
  }
108
  ]
109
  },
 
113
  {
114
  "type": "text",
115
  "text": f"""Following are the inputs:
116
+ Niche: {niche}
117
+ Offer to run: {offer}
118
+ Target Audience: {target_audience}
119
+
120
+ Provide the different psychology triggers, angles and concept based on the given input."""
121
  }
122
  ]
123
  }
124
  ]
125
+
126
  try:
127
  completion = self.client.beta.chat.completions.parse(
128
  model=self.gpt_model,
129
  messages=messages,
130
  response_format=ImageAdEssentialsOutput,
131
  )
132
+
133
  response = completion.choices[0].message
134
  if response.parsed:
135
  return response.parsed.output
 
140
  except Exception as e:
141
  print(f"Error in researcher: {e}")
142
  return []
143
+
144
+ async def generate_motivators_for_strategy(
145
+ self,
146
+ strategy: CreativeStrategies,
147
+ niche: str,
148
+ target_audience: str | None,
149
+ offer: str | None,
150
+ count: int = 6,
151
+ ) -> list[str]:
152
+ angle_ctx = {
153
+ "name": strategy.angle,
154
+ "trigger": strategy.phsychologyTrigger,
155
+ "example": strategy.titleIdeas,
156
+ }
157
+ print(f"Generating angle {angle_ctx}")
158
+
159
+ concept_ctx = {
160
+ "name": strategy.concept,
161
+ "structure": strategy.visualDirection,
162
+ "visual": strategy.visualDirection,
163
+ }
164
+ print(f"Generating concept {concept_ctx}")
165
+
166
+ motivators = await generate_motivators(
167
+ niche=niche.lower().replace(" ", "_"),
168
+ angle=angle_ctx,
169
+ concept=concept_ctx,
170
+ target_audience=target_audience,
171
+ offer=offer,
172
+ count=count,
173
+ )
174
+ print(f"Generated motivators: {motivators}")
175
+
176
+ return motivators
177
+
178
  def retrieve_search(
179
  self,
180
  target_audience: str,
 
482
  except Exception as e:
483
  print(f"Error in creative_director: {e}")
484
  return []
485
+
486
+ def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "Home Insurance",selected_motivator: str | None = None) -> str:
487
  """
488
  Generate image prompt from creative strategy.
489
 
 
510
  AVOID: {', '.join(niche_guidance_data.get('avoid', []))}
511
  COLOR PREFERENCE: {niche_guidance_data.get('color_preference', 'balanced')}
512
 
513
+ CRITICAL: The image MUST be appropriate for {niche} niche.
514
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  else:
516
  niche_guidance = f"""
517
+ NICHE-SPECIFIC REQUIREMENTS ({niche}):
518
+ CRITICAL: The image MUST be appropriate for {niche} niche.
519
+ """
520
+
521
+ motivator_block = (
522
+ f"\nCORE MOTIVATOR (emotional driver): {selected_motivator}\n"
523
+ if selected_motivator
524
+ else ""
525
+ )
526
+
527
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
528
  Angle: {creative_strategy.angle}
529
  Concept: {creative_strategy.concept}
 
530
  CTA: {creative_strategy.cta}
531
  Visual Direction: {creative_strategy.visualDirection}
532
+ {motivator_block}
533
  """
534
 
535
  messages = [
 
639
  prompt += " Authentic, relatable style - not overly polished or commercial."
640
 
641
  return prompt.strip()
642
+
643
+ def copy_writer(self, creative_strategy: CreativeStrategies,selected_motivator: str | None = None) -> CopyWriterOutput:
644
  """
645
  Generate ad copy from creative strategy.
646
 
 
650
  Returns:
651
  CopyWriterOutput with title, body, and description
652
  """
653
+
654
+ motivator_block = (
655
+ f"\nCORE MOTIVATOR (customer’s internal voice): {selected_motivator}\n"
656
+ if selected_motivator
657
+ else ""
658
+ )
659
+
660
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
661
  Angle: {creative_strategy.angle}
662
  Concept: {creative_strategy.concept}
663
  CTA: {creative_strategy.cta}
664
+ {motivator_block}
 
 
665
  """
666
 
667
  messages = [
 
740
  def process_strategy(
741
  self,
742
  creative_strategy: CreativeStrategies,
743
+ niche: str = "Home Insurance",
744
+ selected_motivator: str | None = None,
745
  ) -> tuple[str, str, str, str]:
746
  """
747
  Process a single creative strategy to generate prompt and copy.
 
753
  Returns:
754
  Tuple of (prompt, title, body, description)
755
  """
756
+ prompt = self.creative_designer(creative_strategy,niche=niche,selected_motivator=selected_motivator)
757
+ ad_copy = self.copy_writer(creative_strategy,selected_motivator=selected_motivator)
758
+
759
  return (
760
  prompt,
761
  ad_copy.title,
 
766
 
767
  # Global service instance
768
  third_flow_service = ThirdFlowService()
769
+
770
+ if __name__ == "__main__":
771
+ import asyncio
772
+
773
+ # ---- MOCK STRATEGY FOR TESTING ----
774
+ test_strategy = CreativeStrategies(
775
+ phsychologyTrigger="Fear of unexpected financial loss",
776
+ angle="Your home could cost you thousands tomorrow",
777
+ concept="Before vs After comparison",
778
+ text=Text(
779
+ textToBeWrittern="One small issue can turn into a massive repair bill.",
780
+ color="white",
781
+ placement="top"
782
+ ),
783
+ cta="Check your coverage now",
784
+ visualDirection="Split image: damaged home vs peaceful protected home",
785
+ titleIdeas="This mistake costs homeowners $12,000",
786
+ captionIdeas="Most homeowners don’t realize this until it’s too late",
787
+ bodyIdeas="A real homeowner story about unexpected damage",
788
+ )
789
+
790
+ async def run_test():
791
+ print("\n========== TEST: GENERATE MOTIVATORS ==========\n")
792
+ motivators = await third_flow_service.generate_motivators_for_strategy(
793
+ strategy=test_strategy,
794
+ niche="home_insurance",
795
+ target_audience="US homeowners 35–65",
796
+ offer="Free home insurance quote",
797
+ )
798
+
799
+ print(motivators)
800
+
801
+ selected_motivator = motivators[0] if motivators else None
802
+
803
+ print("\n========== TEST: PROCESS STRATEGY ==========\n")
804
+ prompt, title, body, description = third_flow_service.process_strategy(
805
+ creative_strategy=test_strategy,
806
+ niche="Home Insurance",
807
+ selected_motivator=selected_motivator,
808
+ )
809
+
810
+ print("\n--- IMAGE PROMPT ---\n")
811
+ print(prompt)
812
+
813
+ print("\n--- TITLE ---\n")
814
+ print(title)
815
+
816
+ print("\n--- BODY ---\n")
817
+ print(body)
818
+
819
+ print("\n--- DESCRIPTION ---\n")
820
+ print(description)
821
+
822
+ asyncio.run(run_test())
823
+