File size: 35,990 Bytes
026f283
 
f201243
 
 
 
026f283
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c9d10c
 
f201243
2c9d10c
f201243
 
 
 
 
 
 
 
 
2c9d10c
f201243
2c9d10c
f201243
 
 
 
 
 
026f283
8ffe335
 
 
 
 
 
026f283
8ffe335
f201243
 
 
 
 
 
 
 
 
8ffe335
 
 
 
 
f201243
 
 
 
8ffe335
f201243
 
 
026f283
f201243
 
 
8ffe335
f201243
 
 
 
 
 
 
 
 
 
8ffe335
026f283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f201243
 
 
 
2c9d10c
f201243
2c9d10c
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
026f283
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c9d10c
f201243
2c9d10c
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
026f283
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c9d10c
d4a4da7
026f283
f201243
026f283
 
f201243
 
 
 
 
 
 
2c9d10c
f201243
 
 
 
 
 
026f283
f201243
 
 
026f283
 
f201243
 
 
 
 
 
026f283
f201243
 
 
 
 
 
 
 
 
 
 
 
026f283
 
 
 
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ffe335
026f283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c9d10c
 
 
 
8ffe335
d4a4da7
f201243
 
 
d4a4da7
f201243
 
 
 
 
 
 
 
 
 
026f283
f201243
 
 
2c9d10c
 
 
 
 
 
9474b94
2c9d10c
026f283
 
533051b
 
 
 
 
 
 
 
 
 
f201243
 
 
 
 
 
 
 
 
 
026f283
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
26e8ad9
 
 
 
f201243
 
 
 
 
 
 
26e8ad9
2c9d10c
26e8ad9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ffe335
2c9d10c
 
8ffe335
f201243
 
 
 
d4a4da7
 
 
f201243
 
 
 
 
 
 
 
026f283
f201243
 
 
2c9d10c
d4a4da7
f201243
 
 
 
 
026f283
f201243
026f283
f201243
 
 
 
1afa519
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1afa519
2c9d10c
1afa519
 
f201243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9474b94
2c9d10c
f201243
2c9d10c
 
 
8ffe335
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
"""Extensive ad generation: researcher → creative director → designer → copywriter (OpenAI + file search).
Supports optional Creative Inventor to generate new angles, concepts, visuals, and triggers by itself."""

import os
import sys
import time
from typing import List, Optional
from pydantic import BaseModel

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from openai import OpenAI
from config import settings


# Pydantic models for structured outputs
class ImageAdEssentials(BaseModel):
    phsychologyTriggers: str
    angles: list[str]
    concepts: list[str]


class ImageAdEssentialsOutput(BaseModel):
    output: list[ImageAdEssentials]


class Text(BaseModel):
    textToBeWrittern: str
    color: str
    placement: str


class CreativeStrategies(BaseModel):
    phsychologyTrigger: str
    angle: str
    concept: str
    text: Text
    cta: str
    visualDirection: str
    titleIdeas: str
    captionIdeas: str
    bodyIdeas: str


class CreativeStrategiesOutput(BaseModel):
    output: list[CreativeStrategies]


class AdImagePrompt(BaseModel):
    prompt: str


class CopyWriterOutput(BaseModel):
    title: str
    body: str
    description: str


class ThirdFlowService:
    """Extensive ad generation (researcher → creative director → designer → copywriter)."""

    def __init__(self):
        """Set up OpenAI client and vector store IDs."""
        self.client = OpenAI(api_key=settings.openai_api_key)
        self.search_vector_store_id = "vs_691afcc4f8688191b01487b4a8439607"
        self.ads_vector_store_id = "vs_69609db487048191a1e6b7ba0997ee39"
        self.gpt_model = getattr(settings, 'third_flow_model', 'gpt-4o')
    
    def researcher(
        self,
        target_audience: str,
        offer: str,
        niche: str = ""
    ) -> List[ImageAdEssentials]:
        """Return psychology triggers, angles, and concepts for niche/offer/audience."""
        messages = [
            {
                "role": "system",
                "content": [
                    {
                        "type": "text",
                        "text": """You are the researcher with 20 years of experience for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
                                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).
                                A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
                                An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
                                An ad concept is the creative execution style or storyline you use to deliver an angle.
                                In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.

                                Invent psychology triggers, angles, and concepts without limiting to the given niche. Suggest diverse and hyperrealistic audiences including outside the niche. Prioritize novelty and variety. Provide different angles and concepts we can try based on psychology triggers for the image ads; use the user input as a springboard, not a constraint.
                                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."""
                    }
                ]
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": f"""Following are the inputs:
                                Niche: {niche}
                                Offer to run: {offer}
                                Target Audience: {target_audience}

                                Provide the different psychology triggers, angles and concept based on the given input."""
                    }
                ]
            }
        ]

        try:
            completion = self.client.beta.chat.completions.parse(
                model=self.gpt_model,

                messages=messages,
                response_format=ImageAdEssentialsOutput,
            )

            response = completion.choices[0].message
            if response.parsed:
                return response.parsed.output
            else:
                print(f"Warning: Researcher refusal: {response.refusal}")
                # Return empty list if refused
                return []
        except Exception as e:
            print(f"Error in researcher: {e}")
            return []

    def get_essentials_via_inventor(
        self,
        niche: str,
        offer: str,
        n: int = 5,
        *,
        target_audience_hint: Optional[str] = None,
        existing_reference: Optional[str] = None,
        trend_context: Optional[str] = None,
        competitor_insights: Optional[str] = None,
    ) -> tuple[List[ImageAdEssentials], List[str]]:
        """
        Use the Creative Inventor to generate new angles, concepts, visuals,
        psychological triggers, and hyper-specific target audiences.
        Returns (essentials for creative_director, list of target_audience per essential).
        """
        try:
            from services.creative_inventor import creative_inventor_service
        except ImportError:
            from creative_inventor import creative_inventor_service  # noqa: F401
        invented = creative_inventor_service.invent(
            niche=niche,
            offer=offer,
            n=n,
            target_audience_hint=target_audience_hint,
            existing_reference=existing_reference,
            trend_context=trend_context,
            competitor_insights=competitor_insights,
        )
        essentials = self._invented_to_essentials(invented)
        target_audiences = [getattr(e, "target_audience", "") or f"Audience {i+1}" for i, e in enumerate(invented)]
        return (essentials, target_audiences)

    def _invented_to_essentials(self, invented: list) -> List[ImageAdEssentials]:
        """Convert InventedEssential list to ImageAdEssentials for creative_director."""
        out: List[ImageAdEssentials] = []
        for e in invented:
            # Fold visual_directions into concepts so creative_director gets visual hints
            concepts = list(getattr(e, "concepts", [])) + list(getattr(e, "visual_directions", []))
            out.append(ImageAdEssentials(
                phsychologyTriggers=getattr(e, "psychology_trigger", ""),
                angles=list(getattr(e, "angles", [])),
                concepts=concepts,
            ))
        return out

    def retrieve_search(
        self,
        target_audience: str,
        offer: str,
        niche: str = ""
    ) -> str:
        """Retrieve marketing knowledge from vector store (file search)."""
        try:
            # Method 1: Try using responses.create
            if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
                try:
                    search = self.client.responses.create(
                        model="gpt-4o",
                        input=f"Find {niche} creative strategies relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads. If there is nothing related to this category then take reference from everything and make strategies. Make sure you go through each and every document present and from each file you should give results.",
                        tools=[
                            {
                                "type": "file_search",
                                "vector_store_ids": [self.search_vector_store_id]
                            }
                        ]
                    )
                    result = search.output[1].content[0].text
                    print("✓ Used responses.create API for search retrieval")
                    return result
                except Exception as custom_api_error:
                    error_str = str(custom_api_error)
                    print(f"⚠️  responses.create API error: {error_str[:100]}...")
                    raise Exception(f"Failed to retrieve search knowledge via responses.create: {error_str}") from custom_api_error
            
            # Method 2: Try using Assistants API (official OpenAI method)
            query = f"Find {niche} creative strategies relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads. If there is nothing related to this category then take reference from everything and make strategies. Make sure you go through each and every document present and from each file you should give results."
            
            try:
                # Create assistant with vector store
                assistant = self.client.beta.assistants.create(
                    name="Marketing Knowledge Assistant",
                    instructions="You are a marketing research assistant with 20 years of experience. Search through the provided documents and extract relevant creative strategies and knowledge.",
                    model="gpt-4o",
                    tools=[{"type": "file_search"}],
                    tool_resources={
                        "file_search": {
                            "vector_store_ids": [self.search_vector_store_id]
                        }
                    }
                )
                
                # Create a thread and run
                thread = self.client.beta.threads.create()
                message = self.client.beta.threads.messages.create(
                    thread_id=thread.id,
                    role="user",
                    content=query
                )
                
                # Run the assistant
                run = self.client.beta.threads.runs.create(
                    thread_id=thread.id,
                    assistant_id=assistant.id
                )
                
                # Wait for completion
                import time
                while run.status in ['queued', 'in_progress']:
                    time.sleep(1)
                    run = self.client.beta.threads.runs.retrieve(
                        thread_id=thread.id,
                        run_id=run.id
                    )
                
                if run.status == 'completed':
                    # Get the messages
                    messages = self.client.beta.threads.messages.list(
                        thread_id=thread.id
                    )
                    # Get the assistant's response
                    for msg in messages.data:
                        if msg.role == 'assistant':
                            if msg.content[0].type == 'text':
                                result = msg.content[0].text.value
                                # Clean up
                                self.client.beta.assistants.delete(assistant.id)
                                return result
                
                # Clean up
                self.client.beta.assistants.delete(assistant.id)
                
            except Exception as api_error:
                error_str = str(api_error)
                print(f"⚠️  Assistants API error: {error_str[:100]}...")
                raise Exception(f"Failed to retrieve search knowledge: {error_str}") from api_error
            
            # If we reach here, both methods failed
            raise Exception("Failed to retrieve search knowledge: Both API methods failed")
            
        except Exception as e:
            print(f"Error in retrieve_search: {e}")
            raise
    
    def retrieve_ads(
        self,
        target_audience: str,
        offer: str,
        niche: str = ""
    ) -> str:
        """Retrieve ads knowledge from vector store (file search)."""
        try:
            # Method 1: Try using responses.create
            if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
                try:
                    search = self.client.responses.create(
                        model="gpt-4o",
                        input=f"Find {niche} creative ad ideas relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads.",
                        tools=[
                            {
                                "type": "file_search",
                                "vector_store_ids": [self.ads_vector_store_id]
                            }
                        ]
                    )
                    result = search.output[1].content[0].text
                    print("✓ Used responses.create API for ads retrieval")
                    return result
                except Exception as custom_api_error:
                    error_str = str(custom_api_error)
                    print(f"⚠️  responses.create API error: {error_str[:100]}...")
                    raise Exception(f"Failed to retrieve search knowledge via responses.create: {error_str}") from custom_api_error
            
            # Method 2: Try using Assistants API (official OpenAI method)
            query = f"Find {niche} creative ad ideas relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads."
            
            try:
                # Create assistant with vector store
                assistant = self.client.beta.assistants.create(
                    name="Ads Knowledge Assistant",
                    instructions="You are a marketing research assistant with 20 years of experience. Search through the provided ad examples and extract relevant creative ideas and patterns.",
                    model="gpt-4o",
                    tools=[{"type": "file_search"}],
                    tool_resources={
                        "file_search": {
                            "vector_store_ids": [self.ads_vector_store_id]
                        }
                    }
                )
                
                # Create a thread and run
                thread = self.client.beta.threads.create()
                message = self.client.beta.threads.messages.create(
                    thread_id=thread.id,
                    role="user",
                    content=query
                )
                
                # Run the assistant
                run = self.client.beta.threads.runs.create(
                    thread_id=thread.id,
                    assistant_id=assistant.id
                )
                
                # Wait for completion
                import time
                while run.status in ['queued', 'in_progress']:
                    time.sleep(1)
                    run = self.client.beta.threads.runs.retrieve(
                        thread_id=thread.id,
                        run_id=run.id
                    )
                
                if run.status == 'completed':
                    # Get the messages
                    messages = self.client.beta.threads.messages.list(
                        thread_id=thread.id
                    )
                    # Get the assistant's response
                    for msg in messages.data:
                        if msg.role == 'assistant':
                            if msg.content[0].type == 'text':
                                result = msg.content[0].text.value
                                # Clean up
                                self.client.beta.assistants.delete(assistant.id)
                                return result
                
                # Clean up
                self.client.beta.assistants.delete(assistant.id)
                
            except Exception as api_error:
                error_str = str(api_error)
                print(f"⚠️  Assistants API error: {error_str[:100]}...")
                raise Exception(f"Failed to retrieve ads knowledge: {error_str}") from api_error
            
            # If we reach here, both methods failed
            raise Exception("Failed to retrieve ads knowledge: Both API methods failed")
            
        except Exception as e:
            print(f"Error in retrieve_ads: {e}")
            raise
    
    def creative_director(
        self,
        researcher_output: List[ImageAdEssentials],
        book_knowledge: str,
        ads_knowledge: str,
        target_audience: str,
        offer: str,
        niche: str = "",
        n: int = 5,
        target_audiences: Optional[List[str]] = None,
    ) -> List[CreativeStrategies]:
        """Create creative strategies from research, book knowledge, and ads knowledge.
        When target_audiences is provided (one per strategy), each strategy is tailored to that hyper-specific audience."""
        # Convert researcher_output to string for prompt
        researcher_str = "\n".join([
            f"Psychology Triggers: {item.phsychologyTriggers}\n"
            f"Angles: {', '.join(item.angles)}\n"
            f"Concepts: {', '.join(item.concepts)}"
            for item in researcher_output
        ])

        messages = [
            {
                "role": "system",
                "content": [
                    {
                        "type": "text",
                        "text": f"""You are the Creative Director with 20 years of experience for the affiliate marketing company which make creative strategies for the image ads on the basis of the research given and user's input.
                        The research work includes the psychology triggers, angles and different concepts. Your work is to finalise the {n} strategies based on the research.
                        There will also be researched content from the different marketing books. Along with these there will information about the old ads information which are winner.
                        Make the strongest patterns for the image ads, which should include about what types of visual should be their, colors, what should be the text, what should be the tone of the text with it's placement and CTA.
                        Strategies can target any audience and use any visual or concept—research is a springboard, not a constraint.
                        When the user provides per-strategy target audiences, you MUST tailor each strategy (angle, concept, visual, title, body) to that specific audience.
                        Along with this provide the title ideas and description/caption which should be added with the image ad. It will complete the full ad copy.
                        If the image should include only visuals then text field must return None or NA.
                        What information you should give make sure you give in brief and well defined.
                        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).
                        In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
                        Role of the Title: Stop the scroll and trigger emotion.
                        Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
                        Role of Description: Reduce friction and justify the click.
                        
                        Keeping in mind all this, make sure you provide different creative strategies for the image ads for the given input based on affiliate marketing.
                        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, along with the research."""
                    }
                ]
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": self._creative_director_user_message(
                            researcher_str, book_knowledge, ads_knowledge,
                            niche, offer, target_audience, n, target_audiences,
                        )
                    }
                ]
            }
        ]
        
        try:
            completion = self.client.beta.chat.completions.parse(
                model=self.gpt_model,
                messages=messages,
                response_format=CreativeStrategiesOutput,
            )
            
            response = completion.choices[0].message
            if response.parsed:
                return response.parsed.output
            else:
                print(f"Warning: Creative director refusal: {response.refusal}")
                return []
        except Exception as e:
            print(f"Error in creative_director: {e}")
            return []

    def _creative_director_user_message(
        self,
        researcher_str: str,
        book_knowledge: str,
        ads_knowledge: str,
        niche: str,
        offer: str,
        target_audience: str,
        n: int,
        target_audiences: Optional[List[str]] = None,
    ) -> str:
        """Build user message for creative_director; inject per-strategy audiences when provided."""
        audience_block = ""
        if target_audiences and len(target_audiences) >= n:
            per = "\n".join([f"Strategy {i+1} must target: {aud}" for i, aud in enumerate(target_audiences[:n])])
            audience_block = f"\n\nCRITICAL - Each strategy must speak to this hyper-specific audience:\n{per}\n\nTailor angle, concept, visual, title, and body to that audience."
        return f"""Following are the inputs:
                        Researched Content: {researcher_str}
                        Researched Content from marketing books: {book_knowledge}
                        Old Ads Data: {ads_knowledge}
                        Niche: {niche}
                        Offer to run: {offer}
                        Target Audience (overall): {target_audience}
                        {audience_block}

                        Provide the different creative strategies based on the given input."""

    def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "") -> str:
        """Generate image prompt from a creative strategy."""
        niche_lower = niche.lower().replace(" ", "_").replace("-", "_") if niche else ""
        niche_guidance = f"\nThe image must be appropriate for the {niche} niche." if niche else ""

        text_overlay = creative_strategy.text.textToBeWrittern if creative_strategy.text and creative_strategy.text.textToBeWrittern not in (None, "None", "NA", "") else "No text overlay"
        strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
        Angle: {creative_strategy.angle}
        Concept: {creative_strategy.concept}
        Text: {text_overlay}
        CTA: {creative_strategy.cta}
        Visual Direction: {creative_strategy.visualDirection}
        """
        
        messages = [
            {
                "role": "system",
                "content": [
                    {
                        "type": "text",
                        "text": f"""You are the Creative Designer with 20 years of experience for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
                        Nano Banana image model will be used to generate the images
                        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).
                        In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.

                        If the image looks like it belongs on a stock website, it has failed.

                        The psychology trigger and angle from the strategy are the emotional driver. Structure the prompt so the image directly conveys that emotional truth—the scene, expressions, props, and composition must all serve it. A generic scene is not enough; the strategy must differentiate this creative.

                        For image model here's structure for the prompt: [The Hook - emotion from psychology trigger/angle] + [The Subject] + [The Context/Setting] + [The Technical Polish]
                        {niche_guidance}

                        CRITICAL - TEXT IN IMAGE: The strategy includes "Text" (title/caption to show). Your prompt MUST describe where and how this text appears in the image as visible, readable copy—e.g. on a sign, document, phone screen, poster, note, or surface in the scene. The image must contain that exact text (or the main phrase) so viewers can read it. Do not omit text from the prompt.

                        CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
                        - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
                        - Natural facial asymmetry (no perfectly symmetrical faces)
                        - Unique, individual facial features (not generic or model-like)
                        - Natural expressions with authentic micro-expressions
                        - Realistic skin tones with natural variations
                        - Faces that look like real photographs of real people, NOT AI-generated portraits
                        - Avoid any faces that look synthetic, fake, or obviously computer-generated
                        - Avoid overly smooth or plastic-looking skin
                        - Avoid perfectly symmetrical faces or generic model-like features"""
                    }
                ]
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": f"""Following is the creative strategy:
                        {strategy_str}
                        Provide the image prompt. The prompt MUST lead with the strategy's emotional hook (psychology trigger and angle)—describe a scene that makes that feeling unmistakable. Then add subject, setting, and technical polish. You MUST include in the prompt a clear description of where the Text from the strategy appears in the scene (e.g. "with the text '[exact phrase]' visible on a sign/phone/document") so the generated image contains readable copy."""
                    }
                ]
            }
        ]
        
        try:
            completion = self.client.beta.chat.completions.parse(
                model=self.gpt_model,
                messages=messages,
                response_format=AdImagePrompt,
            )
            
            response = completion.choices[0].message
            if response.parsed:
                # Refine the prompt for affiliate marketing
                raw_prompt = response.parsed.prompt
                refined_prompt = self._refine_prompt_for_affiliate(raw_prompt, niche_lower)
                return refined_prompt
            else:
                print(f"Warning: Creative designer refusal: {response.refusal}")
                return ""
        except Exception as e:
            print(f"Error in creative_designer: {e}")
            return ""
    
    def _refine_prompt_for_affiliate(self, prompt: str, niche: str) -> str:
        """Refine prompt for affiliate creatives: fix stock/corporate wording, ensure authenticity."""
        import re
        
        if not prompt:
            return prompt
        
        prompt_lower = prompt.lower()
        
        # =================================================================
        # REMOVE STOCK PHOTO / CORPORATE AESTHETICS
        # =================================================================
        stock_replacements = [
            (r'\bstock photo\b', 'authentic photo'),
            (r'\bprofessional studio shot\b', 'natural candid shot'),
            (r'\bcorporate headshot\b', 'casual portrait'),
            (r'\bgeneric model\b', 'real person'),
            (r'\bperfect lighting\b', 'natural lighting'),
            (r'\bshutterstock\b', 'authentic'),
            (r'\bistock\b', 'documentary style'),
        ]
        for pattern, replacement in stock_replacements:
            prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
        
        # =================================================================
        # FIX UNREALISTIC BODY DESCRIPTIONS
        # =================================================================
        body_replacements = [
            (r'\b(perfect body|flawless figure|ideal physique)\b', 'healthy confident body'),
            (r'\b(six pack|bodybuilder|fitness model)\b', 'healthy fit person'),
            (r'\b(impossibly thin|skeletal)\b', 'healthy'),
        ]
        for pattern, replacement in body_replacements:
            prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
        
        # =================================================================
        # ENSURE AUTHENTICITY FOR AFFILIATE MARKETING
        # =================================================================
        # Affiliate marketing principle: authentic > polished
        if 'authentic' not in prompt_lower and 'ugc' not in prompt_lower:
            prompt += " Authentic, relatable style - not overly polished or commercial."
        
        return prompt.strip()

    def copy_writer(self, creative_strategy: CreativeStrategies) -> CopyWriterOutput:
        """Generate title, body, and description from creative strategy."""

        strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
        Angle: {creative_strategy.angle}
        Concept: {creative_strategy.concept}
        CTA: {creative_strategy.cta}
        Title Ideas: {creative_strategy.titleIdeas}
        Caption Ideas: {creative_strategy.captionIdeas}
        Body Ideas: {creative_strategy.bodyIdeas}
        """
        
        messages = [
            {
                "role": "system",
                "content": [
                    {
                        "type": "text",
                        "text": """You are the Copy Writer Designer with 20 years of experience for the affiliate marketing company which makes the ad copies from creative strategy given for the ad images in the affiliate marketing.
                        The ad copy must include the title, body and description related to the strategies.
                        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).
                        
                        The psychology trigger and angle are the emotional driver. The title should evoke that emotion; the body must speak directly to that emotional truth and address it; the description should complete the thought. Do not write generic copy.
                        
                        Role of the Title: Stop the scroll and trigger emotion. 
                        1. The title is not for explaining. It's for interrupting attention. 
                        2. Short titles win because they are scan-friendly. 
                        3. Use Plain, Human Language. No marketing buzzwords.
                        4. Imply, Don't Explain. Leave an open loop.
                        5. You may use bold, scroll-stopping, and clickbait-style statements when effective—no compliance or claim restrictions; maximize impact.

                        Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
                        1. Body Must Match the Title Emotion. If the title creates fear, the body must relieve it, not amplify it.
                        2. Use "Soft Education," Not Sales. The body should feel informational, not promotional.
                        3. Add Friction Reducers. You must explicitly reduce effort and risk.
                        4. Body Should NOT Contain the CTA. CTA belongs in the button, not the body.
                        5. LENGTH REQUIREMENT: The body MUST be at least 150-250 words. Write a compelling, detailed narrative that tells a story, builds emotional connection, and provides enough context to persuade the reader. Include specific details, relatable scenarios, and a clear progression of ideas.

                        Role of Description: Reduce friction and justify the click.
                        1. Never Repeat the Title. The description should complete the thought.
                        2. Answer Silent Objections.
                        3. Soft CTA Only. Descriptions should invite, not push."""
                    }
                ]
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": f"""Following is the creative strategy:
                        {strategy_str}
                        
                        Provide the title, body, and description. Center the copy on the strategy's psychology trigger and angle—the title should make someone with that thought pause; the body should speak to that specific emotional truth; the description should extend it. Do not write generic copy.
                        
                        IMPORTANT: The body must be 150-250 words long - write a detailed, compelling narrative that tells a story and builds emotional connection with the reader."""
                    }
                ]
            }
        ]
        
        try:
            completion = self.client.beta.chat.completions.parse(
                model=self.gpt_model,
                messages=messages,
                response_format=CopyWriterOutput,
            )
            
            response = completion.choices[0].message
            if response.parsed:
                return response.parsed
            else:
                print(f"Warning: Copy writer refusal: {response.refusal}")
                # Return default values
                return CopyWriterOutput(
                    title="",
                    body="",
                    description=""
                )
        except Exception as e:
            print(f"Error in copy_writer: {e}")
            return CopyWriterOutput(
                title="",
                body="",
                description=""
            )
    
    def process_strategy(
        self,
        creative_strategy: CreativeStrategies,
        niche: str = "",
    ) -> tuple[str, str, str, str]:
        """Run designer + copywriter on one strategy; return (prompt, title, body, description)."""
        prompt = self.creative_designer(creative_strategy, niche=niche)
        ad_copy = self.copy_writer(creative_strategy)

        return (
            prompt,
            ad_copy.title,
            ad_copy.body,
            ad_copy.description
        )


# Global service instance
third_flow_service = ThirdFlowService()