diff --git a/.gitignore b/.gitignore index aa2ff0d..1984e10 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,11 @@ data/past_campaigns/* data/user_queries/* !data/past_campaigns/.gitkeep !data/user_queries/.gitkeep +backend/data/vector_store/* +!backend/data/vector_store/.gitkeep + +# Logs +logs/* # OS specific .DS_Store diff --git a/backend/brand_style.py b/backend/brand_style.py index 6232d35..3104434 100644 --- a/backend/brand_style.py +++ b/backend/brand_style.py @@ -18,6 +18,104 @@ class BrandStyleManager: """Initialize the BrandStyleManager with default or stored style guidelines.""" self.style_path = Path(config.DATA_DIR) / "style_guidelines" / "brand_style.json" self.style_guidelines = self._load_or_create_style() + self.content_formats = { + "website_copy": """ + Generate engaging website copy for a brand or business. + - Start with a strong headline and supporting subheadline + - Write in a clear, benefit-driven tone + - Use SEO-friendly keywords naturally + - Structure content with short paragraphs and bullet points + - Include a clear call-to-action at the end + """, + "email": """ + Create a marketing or sales email for a target audience. + - Start with a compelling subject line + - Use a warm, conversational tone + - Keep the message focused and value-driven + - Personalize where possible (name, context) + - End with a clear and persuasive CTA + """, + "social_media": """ + Write social media content tailored to a specific platform. + - Hook the reader within the first sentence + - Keep the message concise and engaging + - Use platform-appropriate tone and emojis (if applicable) + - Add relevant hashtags and tag accounts when needed + - Include a prompt or CTA to drive interaction + """, + "blog_post": """ + Generate a blog article on a given topic or keyword. + - Begin with a strong hook or introduction + - Organize content with subheadings and logical flow + - Use examples, data, and storytelling + - Optimize for SEO with keywords and meta description + - Conclude with a summary or actionable insight + """, + "sales_copy": """ + Write persuasive sales copy for a product or service. + - Lead with a strong value proposition + - Address specific pain points and offer solutions + - Highlight features, benefits, and outcomes + - Include social proof (testimonials, stats, etc.) + - End with a direct and compelling CTA + """, + "ad_copy": """ + Create short, punchy ad copy for digital or print campaigns. + - Capture attention in the first line + - Use emotional or benefit-driven language + - Keep it brief and persuasive + - Align copy with the target audience + - Include a CTA or promotional message + """, + "video_script": """ + Generate a short video script for a marketing video. + - Hook the viewer in the first few seconds + - Introduce the problem and present the solution + - Keep the tone conversational and natural + - Include visual cues and on-screen text ideas + - Wrap up with a strong CTA + """, + "case_study": """ + Write a case study that highlights a customer success story. + - Start with a quick summary of the results + - Describe the client and their initial problem + - Explain how the product/service helped + - Include measurable outcomes or metrics + - End with a quote and a CTA to learn more + """, + "product_description": """ + Generate a product description that drives interest and conversions. + - Begin with the most attractive benefit + - Mention key features and what makes the product unique + - Use sensory and persuasive language + - Include important specs or FAQs + - End with a micro-CTA (e.g., "Shop now", "View details") + """, + "landing_page": """ + Write copy for a focused landing page. + - Use a bold, attention-grabbing headline + - Describe the offer clearly and simply + - Include supporting details that reinforce value + - Remove distractions and focus on a single goal + - Add a CTA above the fold and at the end + """, + "press_release": """ + Create a professional press release for an announcement. + - Begin with a headline that summarizes the news + - Use a journalistic tone and structure + - Provide key facts in the first paragraph + - Add quotes from relevant leaders or stakeholders + - End with boilerplate company info and contact details + """, + "newsletter": """ + Write a newsletter update for subscribers. + - Start with a warm greeting or short intro + - Highlight the most important news or offer first + - Use engaging sub-sections or article teasers + - Maintain consistent tone with the brand + - Include CTAs to drive clicks or traffic + """ + } logger.info("BrandStyleManager initialized successfully") def _load_or_create_style(self) -> Dict[str, Any]: @@ -84,55 +182,29 @@ class BrandStyleManager: raise def format_prompt_with_brand_style(self, user_prompt: str, content_type: Optional[str] = None) -> str: - """ - Format user prompt with brand style guidelines for the LLM. + """Format user prompt to match the established writing style.""" - Args: - user_prompt: Original user prompt - content_type: Type of content being generated - - Returns: - Formatted prompt with brand style instructions - """ - style = self.style_guidelines - - # Create a formatted prompt with brand style instructions - prompt_parts = [ - f"Generate marketing content for {style['brand_name']} based on the following request:", - f"\"{user_prompt}\"", - "\nFollow these brand style guidelines:", - f"- Brand Name: {style['brand_name']}", - f"- Tone: {', '.join(style.get('tone', []))}", - f"- Voice Characteristics: {', '.join(style.get('voice_characteristics', []))}", + style_instructions = [ + "Follow these writing style guidelines:", + "- Use direct commands that empower the reader", + "- Address the reader directly using 'you' and 'your'", + "- Create rhythmic, repetitive patterns in key messages", + "- Maintain a clear, confident, and authoritative tone", + "- Use simple, practical language without jargon", + "- Acknowledge challenges while focusing on solutions", + "- Include empowering phrases that emphasize reader's control and choice" ] + + # Content type specific formatting + content_format = self._get_content_format(content_type) if content_type else "" - # Add taboo words if any - if 'taboo_words' in style and style['taboo_words']: - prompt_parts.append(f"- Avoid these words: {', '.join(style['taboo_words'])}") - - # Add preferred terms if any - if 'preferred_terms' in style and style['preferred_terms']: - terms = [f"use '{value}' instead of '{key}'" for key, value in style['preferred_terms'].items()] - prompt_parts.append(f"- Preferred terminology: {'; '.join(terms)}") - - # Add content type specific instructions - if content_type: - if content_type == "email_campaign": - prompt_parts.append("- Format as a professional email with subject line, greeting, body, and signature") - elif content_type == "social_media": - prompt_parts.append("- Format as a concise social media post with appropriate hashtags") - elif content_type == "blog_post": - prompt_parts.append("- Format as a blog post with title, introduction, body with subheadings, and conclusion") - elif content_type == "website_copy": - prompt_parts.append("- Format as website copy with clear headings and concise paragraphs") - elif content_type == "ad_copy": - prompt_parts.append("- Format as advertising copy with headline, body, and clear call to action") - - # Combine all parts - formatted_prompt = "\n".join(prompt_parts) - - logger.debug("Created formatted prompt with brand style") - return formatted_prompt + return "\n".join([ + f"Generate content based on this request:", + f"\"{user_prompt}\"", + "", + "\n".join(style_instructions), + content_format + ]) def check_content_alignment(self, content: str) -> Dict[str, Any]: """ @@ -171,5 +243,23 @@ class BrandStyleManager: 'aligned': alignment_score >= 80 # Consider aligned if score is 80% or higher } + def _get_content_format(self, content_type: str) -> str: + """ + Get formatting instructions for specific content type. + + Args: + content_type: Type of content to generate + + Returns: + Formatting instructions as string + """ + if not content_type: + return "" + + format_instructions = self.content_formats.get(content_type, "") + if format_instructions: + return f"\nContent type specific instructions:\n{format_instructions.strip()}" + return "" + # Create a singleton instance -brand_style_manager = BrandStyleManager() \ No newline at end of file +brand_style_manager = BrandStyleManager() diff --git a/backend/config.py b/backend/config.py index 214da1a..a1ef94f 100644 --- a/backend/config.py +++ b/backend/config.py @@ -38,26 +38,26 @@ BRAND_NAME = os.getenv("BRAND_NAME", "Adriana James") # Content types CONTENT_TYPES = [ - "email_campaign", + "website_copy", + "email", "social_media", "blog_post", - "website_copy", + "sales_copy", "ad_copy", - "funnel_page", + "video_script", + "case_study", "product_description", - "press_release" + "landing_page", + "press_release", + "newsletter" ] -# Tone options +# Tone options - simplified to match the core style TONE_OPTIONS = [ - "professional", - "friendly", - "excited", - "authoritative", - "casual", - "inspirational", - "empathetic", - "humorous" + "direct", + "empowering", + "confident", + "practical" ] # Content length options @@ -69,13 +69,17 @@ LENGTH_OPTIONS = [ # Default brand style guidelines DEFAULT_BRAND_STYLE = { - "brand_name": BRAND_NAME, - "tone": ["professional", "friendly", "inspirational"], - "voice_characteristics": ["clear", "direct", "empowering"], - "taboo_words": ["cheap", "discount", "bargain"], + "tone": ["direct", "empowering", "confident", "practical"], + "voice_characteristics": ["clear", "authoritative", "steady", "rhythmic"], + "writing_patterns": ["direct commands", "personal pronouns", "repetitive rhythms"], + "taboo_words": ["cheap", "discount", "bargain", "failure", "impossible", "difficult"], "preferred_terms": { - "customers": "clients", - "products": "solutions" + "problems": "challenges", + "try": "take action", + "difficult": "ready for growth", + "failure": "learning opportunity", + "hope": "know", + "maybe": "will" } } @@ -84,4 +88,4 @@ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") LOG_FILE = os.getenv("LOG_FILE", str(BASE_DIR / "logs" / "app.log")) # Create logs directory if it doesn't exist -(BASE_DIR / "logs").mkdir(exist_ok=True) \ No newline at end of file +(BASE_DIR / "logs").mkdir(exist_ok=True) diff --git a/backend/copywriter.py b/backend/copywriter.py index ede1bbc..4014c87 100644 --- a/backend/copywriter.py +++ b/backend/copywriter.py @@ -28,7 +28,6 @@ class Copywriter: self, prompt: str, content_type: Optional[str] = None, - tone: Optional[str] = None, length: Optional[str] = None, include_cta: bool = False, reference_similar_content: bool = True, @@ -36,18 +35,7 @@ class Copywriter: ) -> Dict[str, Any]: """ Generate marketing copy based on the user prompt and parameters. - - Args: - prompt: User prompt for content generation - content_type: Type of content to generate - tone: Desired tone of the content - length: Desired length of the content - include_cta: Whether to include a call to action - reference_similar_content: Whether to fetch and reference similar content - max_tokens: Maximum tokens for the generated response - - Returns: - Dictionary with generated content and metadata + Note: Removed tone parameter as we always use the established style """ try: # Step 1: Format prompt with brand style guidelines @@ -60,35 +48,19 @@ class Copywriter: if search_results: reference_content = [result['text'] for result in search_results] - # Step 3: Add additional instructions based on parameters - full_prompt = branded_prompt - - if tone: - full_prompt += f"\n- Use a {tone} tone" - + # Step 3: Add length and CTA instructions if needed if length: - length_instructions = { - "short": "Keep the content brief and to the point (under 100 words).", - "medium": "Write a moderate amount of content (100-300 words).", - "long": "Create comprehensive content with depth (over 300 words)." - } - full_prompt += f"\n- {length_instructions.get(length, '')}" - + branded_prompt += f"\n- Generate {length} content" if include_cta: - full_prompt += "\n- Include a strong call to action at the end" + branded_prompt += "\n- Include a direct, empowering call to action" # Step 4: Add reference content if available if reference_content: - full_prompt += "\n\nFor reference, here are some similar pieces of content that have performed well in the past:" - for i, content in enumerate(reference_content, 1): - # Truncate reference content if it's too long - preview = content[:300] + "..." if len(content) > 300 else content - full_prompt += f"\n\nReference {i}:\n{preview}" - - full_prompt += "\n\nUse these references for inspiration, but create original content." + branded_prompt += "\n\nReference these successful examples for tone and style:\n" + branded_prompt += "\n---\n".join(reference_content) # Step 5: Generate content using the LLM - generated_content = await self._call_llm_api(full_prompt, max_tokens) + generated_content = await self._call_llm_api(branded_prompt, max_tokens) # Step 6: Check content alignment with brand style alignment_check = brand_style_manager.check_content_alignment(generated_content) @@ -102,7 +74,7 @@ class Copywriter: "suggestions": headline_suggestions, "metadata": { "content_type": content_type, - "tone": tone, + "tone": None, # Removed tone parameter "alignment_score": alignment_check['alignment_score'], "generated_at": None # Will be added by the API } @@ -174,20 +146,51 @@ class Copywriter: Args: original_prompt: The original user prompt generated_content: The generated marketing content - + Returns: List of headline suggestions """ try: - # This would call the LLM to generate headlines - # Simplified mock response for demonstration - return [ - "Alternative Headline 1: Discover the Power of Adriana James' Solutions", - "Alternative Headline 2: Transform Your Results with Adriana James", - "Alternative Headline 3: The Adriana James Approach: Excellence Redefined" + # Create a prompt for headline generation + headline_prompt = f""" + Generate 3 alternative marketing headlines for the following content. + Make headlines compelling, concise, and aligned with the content's message. + Each headline should be unique and capture attention. + + ORIGINAL PROMPT: + {original_prompt} + + CONTENT: + {generated_content} + + Generate exactly 3 headlines, one per line, without numbering or prefixes. + """ + + # Call LLM to generate headlines + response = await self._call_llm_api( + prompt=headline_prompt, + max_tokens=100 # Shorter limit for headlines + ) + + # Process the response into a list of headlines + headlines = [ + headline.strip() + for headline in response.split('\n') + if headline.strip() and not headline.lower().startswith(('headline', 'title', '-', '*', '•')) ] + + # Ensure we have exactly 3 headlines + if len(headlines) > 3: + headlines = headlines[:3] + while len(headlines) < 3: + headlines.append(f"Headline Option {len(headlines) + 1}") + + logger.info(f"Generated {len(headlines)} headline suggestions") + return headlines + except Exception as e: logger.error(f"Error generating headline suggestions: {str(e)}") + # Return empty list instead of mock response on error return [] async def improve_copy(self, content: str, feedback: str) -> str: @@ -275,4 +278,4 @@ class Copywriter: raise # Create a singleton instance -copywriter = Copywriter() \ No newline at end of file +copywriter = Copywriter() diff --git a/backend/data/vector_store/.gitkeep b/backend/data/vector_store/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/data/vector_store/faiss_index.bin b/backend/data/vector_store/faiss_index.bin index 969a818..c9f990d 100644 Binary files a/backend/data/vector_store/faiss_index.bin and b/backend/data/vector_store/faiss_index.bin differ diff --git a/backend/data/vector_store/metadata.pkl b/backend/data/vector_store/metadata.pkl index 6d6ae08..7e07f3f 100644 Binary files a/backend/data/vector_store/metadata.pkl and b/backend/data/vector_store/metadata.pkl differ diff --git a/backend/main.py b/backend/main.py index 673aca0..57be807 100644 --- a/backend/main.py +++ b/backend/main.py @@ -43,7 +43,6 @@ app.add_middleware( class GenerateCopyRequest(BaseModel): prompt: str = Field(..., description="The main instruction for generating content") content_type: Optional[str] = Field(None, description="Type of content to generate") - tone: Optional[str] = Field(None, description="Desired tone of the content") length: Optional[str] = Field(None, description="Desired length of the content") include_cta: Optional[bool] = Field(False, description="Whether to include a call to action") reference_similar_content: Optional[bool] = Field(True, description="Whether to reference similar content") @@ -88,31 +87,10 @@ async def generate_copy(request: GenerateCopyRequest): } ) - # Validate tone if provided - if request.tone and request.tone not in config.TONE_OPTIONS: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={ - "status": "error", - "message": f"Invalid tone. Must be one of: {', '.join(config.TONE_OPTIONS)}" - } - ) - - # Validate length if provided - if request.length and request.length not in config.LENGTH_OPTIONS: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={ - "status": "error", - "message": f"Invalid length. Must be one of: {', '.join(config.LENGTH_OPTIONS)}" - } - ) - # Generate copy result = await copywriter.generate_copy( prompt=request.prompt, content_type=request.content_type, - tone=request.tone, length=request.length, include_cta=request.include_cta, reference_similar_content=request.reference_similar_content, @@ -126,7 +104,6 @@ async def generate_copy(request: GenerateCopyRequest): if result["content"]: metadata = { "content_type": request.content_type, - "tone": request.tone, "prompt": request.prompt, "generated": True } @@ -139,7 +116,6 @@ async def generate_copy(request: GenerateCopyRequest): "prompt": request.prompt, "parameters": { "content_type": request.content_type, - "tone": request.tone, "length": request.length, "include_cta": request.include_cta }, diff --git a/data/style_guidelines/brand_style.json b/data/style_guidelines/brand_style.json index 43e5c62..129d514 100644 --- a/data/style_guidelines/brand_style.json +++ b/data/style_guidelines/brand_style.json @@ -1,12 +1,9 @@ { - "brand_name": "Adriana James", "tone": [ "professional", "friendly", "inspirational", - "empowering", - "excited", - "authoritative" + "empowering" ], "voice_characteristics": [ "clear", @@ -14,9 +11,13 @@ "empowering", "confident", "authentic", - "innovative", - "visionary", - "approachable" + "innovative" + ], + "writing_patterns": [ + "direct commands", + "personal pronouns", + "repetitive rhythms", + "empowering phrases" ], "taboo_words": [ "cheap", @@ -32,5 +33,6 @@ "problems": "challenges", "services": "experiences", "training": "transformation" - } + }, + "brand_name": "Adriana James" } \ No newline at end of file diff --git a/docs/API_DOCUMENTATION.md b/docs/API_DOCUMENTATION.md index e9dbdf1..7868a6e 100644 --- a/docs/API_DOCUMENTATION.md +++ b/docs/API_DOCUMENTATION.md @@ -15,7 +15,6 @@ Generates marketing copy based on the provided prompt and optional parameters. { "prompt": "Write a social media post for our new product launch", "content_type": "social_media", - "tone": "excited", "length": "medium", "include_cta": true } @@ -24,7 +23,6 @@ Generates marketing copy based on the provided prompt and optional parameters. **Parameters**: - `prompt` (string, required): The main instruction for generating content - `content_type` (string, optional): Type of content to generate (social_media, email, blog, website, etc.) -- `tone` (string, optional): Desired tone (excited, professional, casual, etc.) - `length` (string, optional): Content length (short, medium, long) - `include_cta` (boolean, optional): Whether to include a call to action @@ -184,4 +182,4 @@ Error response format: "message": "Detailed error message", "error_code": "ERROR_CODE" } -``` \ No newline at end of file +``` diff --git a/frontend/app.js b/frontend/app.js index 2d8bf8c..99cc6fe 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -8,7 +8,6 @@ document.addEventListener('DOMContentLoaded', function() { const generateBtn = document.getElementById('generate-btn'); const promptInput = document.getElementById('prompt'); const contentTypeSelect = document.getElementById('content-type'); - const toneSelect = document.getElementById('tone'); const lengthSelect = document.getElementById('length'); const includeCTACheckbox = document.getElementById('include-cta'); const referenceSimilarCheckbox = document.getElementById('reference-similar'); @@ -86,7 +85,6 @@ document.addEventListener('DOMContentLoaded', function() { const requestData = { prompt: promptInput.value, content_type: contentTypeSelect.value || null, - tone: toneSelect.value || null, length: lengthSelect.value || null, include_cta: includeCTACheckbox.checked, reference_similar_content: referenceSimilarCheckbox.checked @@ -510,4 +508,4 @@ document.addEventListener('DOMContentLoaded', function() { // For demonstration purposes, let's create a mocked pre-filled content // In a real implementation, this would be loaded from the backend document.getElementById('prompt').value = 'Write a social media post about our new coaching program'; -}); \ No newline at end of file +}); diff --git a/frontend/index.html b/frontend/index.html index 2d8977b..67b7a8e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -56,30 +56,21 @@ -
- - -
+