6fd7213076
- 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.
175 lines
7.0 KiB
Python
175 lines
7.0 KiB
Python
"""
|
|
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() |