feat(feedback): Add content improvement feedback system

Frontend (frontend/app.js):

- Add textarea for improvement feedback

- Add submit button with loading state

- Handle API response and display improved content

Backend (backend/copywriter.py):

- Add improve_copy() method using Cohere API

- Integrate retry mechanism for API calls

Backend (backend/main.py):

- Add /improve-content POST endpoint

- Implement error handling and return improved content with metadata

Testing:

- Verified feedback submission flow

- Confirmed improved content generation

- Tested error scenarios and loading states
This commit is contained in:
Michael Ikehi
2025-04-18 04:39:06 +01:00
parent 6fd7213076
commit af8f99dea3
13 changed files with 550 additions and 167 deletions
+137 -47
View File
@@ -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()
brand_style_manager = BrandStyleManager()
+24 -20
View File
@@ -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)
(BASE_DIR / "logs").mkdir(exist_ok=True)
+47 -44
View File
@@ -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()
copywriter = Copywriter()
View File
Binary file not shown.
Binary file not shown.
-24
View File
@@ -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
},