feat: Initial implementation of Marketing Assistant AI for Adriana James
- Set up FastAPI backend with modular structure: - main.py for API routing - copywriter.py for AI-powered content generation using Cohere - embeddings.py for generating and reranking content embeddings - vector_store.py for FAISS-based similarity search - brand_style.py for managing brand tone, taboo words, and preferred terms - config.py for managing environment and application settings - Configured RESTful API endpoints: /generate-copy, /brand-style, /training-data, /improve-content, /analyze-content - Created frontend with vanilla HTML, CSS, and JS (index.html, styles.css, app.js) - Integrated brand style management for tone, voice, taboo words, and terminology - Implemented vector search for referencing similar historical content - Enabled training data input to improve future AI output - Added environment variable support for API keys and model configs - Structured data storage with local JSON and DB files - Added developer documentation, API reference, and project setup instructions This commit provides the foundation for a full-stack, AI-driven content creation platform that ensures brand consistency, speeds up marketing workflows, and supports iterative improvement over time.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Brand style module for the Marketing Assistant AI.
|
||||
Ensures generated content aligns with Adriana James' brand voice and tone.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Any, Optional
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
import config
|
||||
|
||||
class BrandStyleManager:
|
||||
"""Manages brand style guidelines and ensures content consistency."""
|
||||
|
||||
def __init__(self):
|
||||
"""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()
|
||||
logger.info("BrandStyleManager initialized successfully")
|
||||
|
||||
def _load_or_create_style(self) -> Dict[str, Any]:
|
||||
"""Load existing style guidelines or create new ones with defaults."""
|
||||
try:
|
||||
if self.style_path.exists():
|
||||
with open(self.style_path, 'r') as f:
|
||||
style = json.load(f)
|
||||
logger.info("Loaded existing brand style guidelines")
|
||||
return style
|
||||
else:
|
||||
# Create directory if it doesn't exist
|
||||
self.style_path.parent.mkdir(exist_ok=True)
|
||||
|
||||
# Use default style guidelines
|
||||
style = config.DEFAULT_BRAND_STYLE
|
||||
|
||||
# Save default style
|
||||
with open(self.style_path, 'w') as f:
|
||||
json.dump(style, f, indent=2)
|
||||
|
||||
logger.info("Created default brand style guidelines")
|
||||
return style
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading or creating style guidelines: {str(e)}")
|
||||
# Fall back to default style
|
||||
return config.DEFAULT_BRAND_STYLE
|
||||
|
||||
def get_style_guidelines(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get current brand style guidelines.
|
||||
|
||||
Returns:
|
||||
Dictionary of style guidelines
|
||||
"""
|
||||
return self.style_guidelines
|
||||
|
||||
def update_style_guidelines(self, new_style: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Update brand style guidelines.
|
||||
|
||||
Args:
|
||||
new_style: Dictionary with new style guidelines
|
||||
|
||||
Returns:
|
||||
Updated style guidelines dictionary
|
||||
"""
|
||||
try:
|
||||
# Merge new style with existing
|
||||
for key, value in new_style.items():
|
||||
self.style_guidelines[key] = value
|
||||
|
||||
# Ensure brand name is preserved
|
||||
self.style_guidelines['brand_name'] = config.BRAND_NAME
|
||||
|
||||
# Save updated style
|
||||
with open(self.style_path, 'w') as f:
|
||||
json.dump(self.style_guidelines, f, indent=2)
|
||||
|
||||
logger.info("Updated brand style guidelines")
|
||||
return self.style_guidelines
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating style guidelines: {str(e)}")
|
||||
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.
|
||||
|
||||
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', []))}",
|
||||
]
|
||||
|
||||
# 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
|
||||
|
||||
def check_content_alignment(self, content: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Check if generated content aligns with brand style guidelines.
|
||||
|
||||
Args:
|
||||
content: Generated marketing content
|
||||
|
||||
Returns:
|
||||
Dictionary with alignment metrics and suggestions
|
||||
"""
|
||||
style = self.style_guidelines
|
||||
taboo_words = style.get('taboo_words', [])
|
||||
preferred_terms = style.get('preferred_terms', {})
|
||||
|
||||
# Check for taboo words
|
||||
found_taboo_words = []
|
||||
for word in taboo_words:
|
||||
if word.lower() in content.lower():
|
||||
found_taboo_words.append(word)
|
||||
|
||||
# Check for preferred terminology
|
||||
terminology_issues = []
|
||||
for avoid, use in preferred_terms.items():
|
||||
if avoid.lower() in content.lower():
|
||||
terminology_issues.append(f"Found '{avoid}', should use '{use}' instead")
|
||||
|
||||
# Calculate an overall alignment score (simple implementation)
|
||||
issues_count = len(found_taboo_words) + len(terminology_issues)
|
||||
alignment_score = max(0, 100 - (issues_count * 10)) # Reduce score for each issue
|
||||
|
||||
return {
|
||||
'alignment_score': alignment_score,
|
||||
'taboo_words_found': found_taboo_words,
|
||||
'terminology_issues': terminology_issues,
|
||||
'aligned': alignment_score >= 80 # Consider aligned if score is 80% or higher
|
||||
}
|
||||
|
||||
# Create a singleton instance
|
||||
brand_style_manager = BrandStyleManager()
|
||||
Reference in New Issue
Block a user