copywriter and blog metadata updates

This commit is contained in:
ajaysi
2025-04-30 16:06:33 +05:30
parent ad9f401b60
commit e19f933a19
6 changed files with 2061 additions and 312 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,9 @@ import importlib
import sys
import os
from pathlib import Path
import time
import json
from typing import Dict, List, Callable, Optional, Tuple
# Add the parent directory to the path to allow importing from lib
current_dir = Path(__file__).parent
@@ -28,132 +31,449 @@ copywriter_modules = [
"4r_copywriter"
]
# Dynamically import all copywriter modules
for module_name in copywriter_modules:
# Define formula categories for better organization
formula_categories = {
"Emotional Appeal": ["ai_emotional_copywriter", "oath_copywriter"],
"Structured Framework": ["acca_copywriter", "app_copywriter", "star_copywriter", "quest_copywriter"],
"Sales Funnel": ["aidppc_copywriter", "aida_copywriter"],
"Problem-Solution": ["pas_copywriter"],
"Feature-Benefit": ["fab_copywriter"],
"Messaging Framework": ["4c_copywriter", "4r_copywriter"]
}
# Define formula metadata for better display and filtering
formula_metadata = {
"ai_emotional_copywriter": {
"name": "Emotional Copywriter",
"icon": "🎭",
"description": "Create copy that resonates with your audience's emotions and drives action.",
"color": "#FF6B6B",
"difficulty": "Intermediate",
"best_for": ["Landing Pages", "Email", "Social Media"],
"tags": ["emotional", "persuasive", "engagement"]
},
"acca_copywriter": {
"name": "ACCA Copywriter",
"icon": "🎯",
"description": "Use the ACCA (Attention, Context, Content, Action) framework to create compelling copy.",
"color": "#4ECDC4",
"difficulty": "Beginner",
"best_for": ["Ads", "Email", "Landing Pages"],
"tags": ["structured", "conversion", "clear"]
},
"app_copywriter": {
"name": "APP Copywriter",
"icon": "🤝",
"description": "Implement the APP (Agree, Promise, Preview) formula to create persuasive copy.",
"color": "#45B7D1",
"difficulty": "Beginner",
"best_for": ["Blog Posts", "Sales Pages", "Email"],
"tags": ["persuasive", "agreement", "preview"]
},
"star_copywriter": {
"name": "STAR Copywriter",
"icon": "",
"description": "Use the STAR (Situation, Task, Action, Result) framework to tell compelling stories.",
"color": "#FFD166",
"difficulty": "Intermediate",
"best_for": ["Case Studies", "Testimonials", "About Pages"],
"tags": ["storytelling", "results", "case-study"]
},
"oath_copywriter": {
"name": "OATH Copywriter",
"icon": "📜",
"description": "Apply the OATH (Oblivious, Apathetic, Thinking, Hurting) framework to target specific audience mindsets.",
"color": "#06D6A0",
"difficulty": "Advanced",
"best_for": ["Ads", "Landing Pages", "Email Sequences"],
"tags": ["audience", "mindset", "targeting"]
},
"quest_copywriter": {
"name": "QUEST Copywriter",
"icon": "🔍",
"description": "Use the QUEST (Question, Unpack, Emphasize, Solution, Transform) framework for narrative-driven copy.",
"color": "#118AB2",
"difficulty": "Intermediate",
"best_for": ["Long-form Content", "Sales Pages", "Video Scripts"],
"tags": ["narrative", "transformation", "solution"]
},
"aidppc_copywriter": {
"name": "AIDPPC Copywriter",
"icon": "💰",
"description": "Implement the AIDPPC (Attention, Interest, Desire, Proof, Persuasion, Call to Action) framework for PPC ads.",
"color": "#073B4C",
"difficulty": "Advanced",
"best_for": ["PPC Ads", "Social Ads", "Display Ads"],
"tags": ["advertising", "ppc", "conversion"]
},
"aida_copywriter": {
"name": "AIDA Copywriter",
"icon": "🎬",
"description": "Use the AIDA (Attention, Interest, Desire, Action) framework to guide customers through the sales funnel.",
"color": "#EF476F",
"difficulty": "Beginner",
"best_for": ["Sales Pages", "Email", "Product Descriptions"],
"tags": ["sales", "funnel", "conversion"]
},
"pas_copywriter": {
"name": "PAS Copywriter",
"icon": "🔧",
"description": "Apply the PAS (Problem, Agitate, Solution) formula to address pain points and offer solutions.",
"color": "#7209B7",
"difficulty": "Beginner",
"best_for": ["Ads", "Email", "Landing Pages"],
"tags": ["problem-solving", "pain-points", "solutions"]
},
"fab_copywriter": {
"name": "FAB Copywriter",
"icon": "💎",
"description": "Use the FAB (Features, Advantages, Benefits) framework to highlight product value.",
"color": "#3A0CA3",
"difficulty": "Beginner",
"best_for": ["Product Descriptions", "Sales Pages", "Brochures"],
"tags": ["product", "features", "benefits"]
},
"4c_copywriter": {
"name": "4C Copywriter",
"icon": "📝",
"description": "Implement the 4C (Clear, Concise, Credible, Compelling) framework for effective messaging.",
"color": "#4361EE",
"difficulty": "Intermediate",
"best_for": ["Brand Messaging", "Mission Statements", "Value Propositions"],
"tags": ["clarity", "concise", "credibility"]
},
"4r_copywriter": {
"name": "4R Copywriter",
"icon": "🔄",
"description": "Use the 4R (Relevance, Resonance, Response, Results) framework to connect with your audience.",
"color": "#F72585",
"difficulty": "Intermediate",
"best_for": ["Content Marketing", "Email", "Social Media"],
"tags": ["relevance", "resonance", "results"]
}
}
def load_user_preferences() -> Dict:
"""Load user preferences from session state or initialize if not present."""
if "copywriter_preferences" not in st.session_state:
st.session_state.copywriter_preferences = {
"recent_formulas": [],
"favorite_formulas": [],
"comparison_formulas": [],
"view_mode": "grid" # or "list"
}
return st.session_state.copywriter_preferences
def save_user_preferences(preferences: Dict) -> None:
"""Save user preferences to session state."""
st.session_state.copywriter_preferences = preferences
def add_recent_formula(module_name: str) -> None:
"""Add a formula to the recent formulas list."""
preferences = load_user_preferences()
# Remove if already exists
if module_name in preferences["recent_formulas"]:
preferences["recent_formulas"].remove(module_name)
# Add to the beginning of the list
preferences["recent_formulas"].insert(0, module_name)
# Keep only the 5 most recent
preferences["recent_formulas"] = preferences["recent_formulas"][:5]
save_user_preferences(preferences)
def toggle_favorite_formula(module_name: str) -> bool:
"""Toggle a formula as favorite and return the new state."""
preferences = load_user_preferences()
if module_name in preferences["favorite_formulas"]:
preferences["favorite_formulas"].remove(module_name)
is_favorite = False
else:
preferences["favorite_formulas"].append(module_name)
is_favorite = True
save_user_preferences(preferences)
return is_favorite
def is_favorite_formula(module_name: str) -> bool:
"""Check if a formula is in the favorites list."""
preferences = load_user_preferences()
return module_name in preferences["favorite_formulas"]
def add_to_comparison(module_name: str) -> None:
"""Add a formula to the comparison list."""
preferences = load_user_preferences()
if module_name not in preferences["comparison_formulas"]:
preferences["comparison_formulas"].append(module_name)
# Keep only up to 3 formulas for comparison
preferences["comparison_formulas"] = preferences["comparison_formulas"][:3]
save_user_preferences(preferences)
def remove_from_comparison(module_name: str) -> None:
"""Remove a formula from the comparison list."""
preferences = load_user_preferences()
if module_name in preferences["comparison_formulas"]:
preferences["comparison_formulas"].remove(module_name)
save_user_preferences(preferences)
def clear_comparison() -> None:
"""Clear the comparison list."""
preferences = load_user_preferences()
preferences["comparison_formulas"] = []
save_user_preferences(preferences)
def lazy_load_module(module_name: str) -> Optional[Callable]:
"""Lazily load a module and return its input_section function."""
if module_name in input_sections:
return input_sections[module_name]
try:
module_path = f"lib.ai_writers.ai_copywriter.{module_name}"
module = importlib.import_module(module_path)
if hasattr(module, "input_section"):
input_sections[module_name] = module.input_section
return module.input_section
else:
st.warning(f"Module {module_name} does not have an input_section function.")
return None
except Exception as e:
st.write(f"Debug: Error importing {module_name}: {str(e)}")
st.error(f"Error loading module {module_name}: {str(e)}")
return None
def render_formula_card(module_name: str, index: int, view_mode: str = "grid") -> None:
"""Render a formula card with its details."""
metadata = formula_metadata.get(module_name, {})
if not metadata:
return
is_favorite = is_favorite_formula(module_name)
favorite_icon = "" if is_favorite else ""
favorite_tooltip = "Remove from favorites" if is_favorite else "Add to favorites"
if view_mode == "grid":
with st.container():
st.markdown(f"""
<div style='background-color: {metadata["color"]}; padding: 20px; border-radius: 10px; margin-bottom: 20px; color: white; position: relative;'>
<div style='position: absolute; top: 10px; right: 10px; font-size: 1.5em;'>{favorite_icon}</div>
<h2 style='color: white;'>{metadata["icon"]} {metadata["name"]}</h2>
<p>{metadata["description"]}</p>
<div style='margin-top: 10px;'>
<span style='background-color: rgba(255,255,255,0.2); padding: 3px 8px; border-radius: 10px; margin-right: 5px; font-size: 0.8em;'>
{metadata["difficulty"]}
</span>
</div>
</div>
""", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
if st.button(f"Use {metadata['name']}", key=f"use_btn_{index}", use_container_width=True):
add_recent_formula(module_name)
st.session_state.selected_formula = {
"module": module_name,
"name": metadata["name"],
"icon": metadata["icon"],
"function": lazy_load_module(module_name)
}
st.rerun()
with col2:
if st.button(f"{favorite_icon} Favorite", key=f"fav_btn_{index}", help=favorite_tooltip, use_container_width=True):
toggle_favorite_formula(module_name)
st.rerun()
with col3:
if module_name in load_user_preferences()["comparison_formulas"]:
if st.button("Remove from Compare", key=f"comp_btn_{index}", use_container_width=True):
remove_from_comparison(module_name)
st.rerun()
else:
if st.button("Add to Compare", key=f"comp_btn_{index}", use_container_width=True):
add_to_comparison(module_name)
st.rerun()
else: # list view
with st.container():
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"""
<div style='padding: 10px; border-left: 5px solid {metadata["color"]}; margin-bottom: 10px;'>
<h3>{metadata["icon"]} {metadata["name"]} {favorite_icon}</h3>
<p>{metadata["description"]}</p>
<div>
<span style='background-color: #f0f2f6; padding: 3px 8px; border-radius: 10px; margin-right: 5px; font-size: 0.8em;'>
{metadata["difficulty"]}
</span>
<span style='font-size: 0.8em;'>Best for: {", ".join(metadata["best_for"][:2])}</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
if st.button(f"Use", key=f"use_list_btn_{index}", use_container_width=True):
add_recent_formula(module_name)
st.session_state.selected_formula = {
"module": module_name,
"name": metadata["name"],
"icon": metadata["icon"],
"function": lazy_load_module(module_name)
}
st.rerun()
if st.button(f"{favorite_icon}", key=f"fav_list_btn_{index}", help=favorite_tooltip):
toggle_favorite_formula(module_name)
st.rerun()
if module_name in load_user_preferences()["comparison_formulas"]:
if st.button("- Compare", key=f"comp_list_btn_{index}"):
remove_from_comparison(module_name)
st.rerun()
else:
if st.button("+ Compare", key=f"comp_list_btn_{index}"):
add_to_comparison(module_name)
st.rerun()
def render_formula_comparison() -> None:
"""Render a comparison of selected formulas."""
preferences = load_user_preferences()
comparison_formulas = preferences["comparison_formulas"]
if not comparison_formulas:
st.info("Add formulas to compare them side by side.")
return
# Create a table for comparison
comparison_data = []
for module_name in comparison_formulas:
metadata = formula_metadata.get(module_name, {})
if metadata:
comparison_data.append({
"Name": f"{metadata['icon']} {metadata['name']}",
"Description": metadata["description"],
"Difficulty": metadata["difficulty"],
"Best For": ", ".join(metadata["best_for"][:3]),
"Tags": ", ".join(metadata["tags"])
})
# Display the comparison table
st.markdown("### Formula Comparison")
# Create columns for each formula
cols = st.columns(len(comparison_data))
# Display headers
for i, col in enumerate(cols):
with col:
st.markdown(f"#### {comparison_data[i]['Name']}")
# Display description
st.markdown("##### Description")
for i, col in enumerate(cols):
with col:
st.write(comparison_data[i]["Description"])
# Display difficulty
st.markdown("##### Difficulty")
for i, col in enumerate(cols):
with col:
st.write(comparison_data[i]["Difficulty"])
# Display best for
st.markdown("##### Best For")
for i, col in enumerate(cols):
with col:
st.write(comparison_data[i]["Best For"])
# Display tags
st.markdown("##### Tags")
for i, col in enumerate(cols):
with col:
st.write(comparison_data[i]["Tags"])
# Add buttons to use each formula
st.markdown("##### Actions")
for i, col in enumerate(cols):
with col:
module_name = comparison_formulas[i]
if st.button(f"Use {formula_metadata[module_name]['name']}", key=f"use_comp_btn_{i}"):
add_recent_formula(module_name)
st.session_state.selected_formula = {
"module": module_name,
"name": formula_metadata[module_name]["name"],
"icon": formula_metadata[module_name]["icon"],
"function": lazy_load_module(module_name)
}
st.rerun()
# Add a button to clear the comparison
if st.button("Clear Comparison", key="clear_comparison"):
clear_comparison()
st.rerun()
def filter_formulas(formulas: List[str], search_term: str, category: str, difficulty: str) -> List[str]:
"""Filter formulas based on search term, category, and difficulty."""
filtered_formulas = []
for module_name in formulas:
metadata = formula_metadata.get(module_name, {})
if not metadata:
continue
# Check if the formula matches the search term
name_match = search_term.lower() in metadata["name"].lower()
desc_match = search_term.lower() in metadata["description"].lower()
tags_match = any(search_term.lower() in tag.lower() for tag in metadata.get("tags", []))
# Check if the formula matches the category
category_match = True
if category != "All Categories":
category_match = module_name in formula_categories.get(category, [])
# Check if the formula matches the difficulty
difficulty_match = True
if difficulty != "All Difficulties":
difficulty_match = metadata.get("difficulty", "") == difficulty
# Add the formula if it matches all criteria
if (name_match or desc_match or tags_match) and category_match and difficulty_match:
filtered_formulas.append(module_name)
return filtered_formulas
def copywriter_dashboard():
"""
Main function to display the copywriting dashboard.
This function can be called from content_generator.py when the user selects "AI Copywriter".
"""
# Define the copywriting formulas with their details
copywriting_formulas = [
{
"name": "Emotional Copywriter",
"icon": "🎭",
"description": "Create copy that resonates with your audience's emotions and drives action.",
"color": "#FF6B6B",
"module": "ai_emotional_copywriter",
"function": input_sections.get("ai_emotional_copywriter")
},
{
"name": "ACCA Copywriter",
"icon": "🎯",
"description": "Use the ACCA (Attention, Context, Content, Action) framework to create compelling copy.",
"color": "#4ECDC4",
"module": "acca_copywriter",
"function": input_sections.get("acca_copywriter")
},
{
"name": "APP Copywriter",
"icon": "🤝",
"description": "Implement the APP (Agree, Promise, Preview) formula to create persuasive copy.",
"color": "#45B7D1",
"module": "app_copywriter",
"function": input_sections.get("app_copywriter")
},
{
"name": "STAR Copywriter",
"icon": "",
"description": "Use the STAR (Situation, Task, Action, Result) framework to tell compelling stories.",
"color": "#FFD166",
"module": "star_copywriter",
"function": input_sections.get("star_copywriter")
},
{
"name": "OATH Copywriter",
"icon": "📜",
"description": "Apply the OATH (Oblivious, Apathetic, Thinking, Hurting) framework to target specific audience mindsets.",
"color": "#06D6A0",
"module": "oath_copywriter",
"function": input_sections.get("oath_copywriter")
},
{
"name": "QUEST Copywriter",
"icon": "🔍",
"description": "Use the QUEST (Question, Unpack, Emphasize, Solution, Transform) framework for narrative-driven copy.",
"color": "#118AB2",
"module": "quest_copywriter",
"function": input_sections.get("quest_copywriter")
},
{
"name": "AIDPPC Copywriter",
"icon": "💰",
"description": "Implement the AIDPPC (Attention, Interest, Desire, Proof, Persuasion, Call to Action) framework for PPC ads.",
"color": "#073B4C",
"module": "aidppc_copywriter",
"function": input_sections.get("aidppc_copywriter")
},
{
"name": "AIDA Copywriter",
"icon": "🎬",
"description": "Use the AIDA (Attention, Interest, Desire, Action) framework to guide customers through the sales funnel.",
"color": "#EF476F",
"module": "aida_copywriter",
"function": input_sections.get("aida_copywriter")
},
{
"name": "PAS Copywriter",
"icon": "🔧",
"description": "Apply the PAS (Problem, Agitate, Solution) formula to address pain points and offer solutions.",
"color": "#7209B7",
"module": "pas_copywriter",
"function": input_sections.get("pas_copywriter")
},
{
"name": "FAB Copywriter",
"icon": "💎",
"description": "Use the FAB (Features, Advantages, Benefits) framework to highlight product value.",
"color": "#3A0CA3",
"module": "fab_copywriter",
"function": input_sections.get("fab_copywriter")
},
{
"name": "4C Copywriter",
"icon": "📝",
"description": "Implement the 4C (Clear, Concise, Credible, Compelling) framework for effective messaging.",
"color": "#4361EE",
"module": "four_c_copywriter",
"function": input_sections.get("four_c_copywriter")
},
{
"name": "4R Copywriter",
"icon": "🔄",
"description": "Use the 4R (Relevance, Resonance, Response, Results) framework to connect with your audience.",
"color": "#F72585",
"module": "four_r_copywriter",
"function": input_sections.get("four_r_copywriter")
}
]
# Create a container for the dashboard
dashboard_container = st.container()
# Create a container for the formula input section
formula_container = st.container()
# Load user preferences
preferences = load_user_preferences()
# Initialize session state for selected formula if it doesn't exist
if "selected_formula" not in st.session_state:
st.session_state.selected_formula = None
# Initialize session state for search and filter options
if "search_term" not in st.session_state:
st.session_state.search_term = ""
if "selected_category" not in st.session_state:
st.session_state.selected_category = "All Categories"
if "selected_difficulty" not in st.session_state:
st.session_state.selected_difficulty = "All Difficulties"
if "view_mode" not in st.session_state:
st.session_state.view_mode = preferences["view_mode"]
# Create a container for the formula input section
formula_container = st.container()
# If a formula is selected, show its input section
if st.session_state.selected_formula is not None:
with formula_container:
@@ -173,6 +493,9 @@ def copywriter_dashboard():
else:
st.error(f"The {st.session_state.selected_formula['name']} module is not available.")
else:
# Create a container for the dashboard
dashboard_container = st.container()
with dashboard_container:
# Display the dashboard
# Header
@@ -183,47 +506,162 @@ def copywriter_dashboard():
</div>
""", unsafe_allow_html=True)
# Introduction
st.markdown("""
## Welcome to the AI Copywriting Suite
# Create tabs for different sections
tab1, tab2, tab3, tab4 = st.tabs(["All Formulas", "Recent & Favorites", "Compare Formulas", "Help & Guide"])
This dashboard provides access to a variety of copywriting formulas, each designed for specific marketing needs.
Select a formula below to get started with creating compelling copy for your brand.
### How to Use This Dashboard
1. Browse the available copywriting formulas below
2. Click on a formula card to access its specific tool
3. Fill in the required information
4. Generate high-quality copy tailored to your needs
""")
# Create a 3-column layout for the formula cards
col1, col2, col3 = st.columns(3)
# Display the formula cards
for i, formula in enumerate(copywriting_formulas):
# Skip formulas that don't have a function
if formula["function"] is None:
continue
# Determine which column to use
col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3
with tab1:
# Search and filter options
col1, col2, col3, col4 = st.columns([3, 2, 2, 1])
with col:
# Create a card for each formula
st.markdown(f"""
<div style='background-color: {formula["color"]}; padding: 20px; border-radius: 10px; margin-bottom: 20px; color: white;'>
<h2 style='color: white;'>{formula["icon"]} {formula["name"]}</h2>
<p>{formula["description"]}</p>
</div>
""", unsafe_allow_html=True)
with col1:
search_term = st.text_input("🔍 Search formulas", value=st.session_state.search_term)
if search_term != st.session_state.search_term:
st.session_state.search_term = search_term
with col2:
categories = ["All Categories"] + list(formula_categories.keys())
selected_category = st.selectbox("Category", categories, index=categories.index(st.session_state.selected_category))
if selected_category != st.session_state.selected_category:
st.session_state.selected_category = selected_category
with col3:
difficulties = ["All Difficulties", "Beginner", "Intermediate", "Advanced"]
selected_difficulty = st.selectbox("Difficulty", difficulties, index=difficulties.index(st.session_state.selected_difficulty))
if selected_difficulty != st.session_state.selected_difficulty:
st.session_state.selected_difficulty = selected_difficulty
with col4:
view_options = {"Grid": "grid", "List": "list"}
view_mode = st.selectbox("View", list(view_options.keys()), index=list(view_options.values()).index(st.session_state.view_mode))
st.session_state.view_mode = view_options[view_mode]
preferences["view_mode"] = st.session_state.view_mode
save_user_preferences(preferences)
# Filter formulas based on search and filter options
filtered_formulas = filter_formulas(
copywriter_modules,
st.session_state.search_term,
st.session_state.selected_category,
st.session_state.selected_difficulty
)
if not filtered_formulas:
st.info("No formulas match your search criteria. Try adjusting your filters.")
else:
# Display the formula cards
if st.session_state.view_mode == "grid":
# Create a 3-column layout for the formula cards
col1, col2, col3 = st.columns(3)
# Display the formula cards
for i, module_name in enumerate(filtered_formulas):
# Determine which column to use
col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3
with col:
render_formula_card(module_name, i, st.session_state.view_mode)
else: # list view
for i, module_name in enumerate(filtered_formulas):
render_formula_card(module_name, i, st.session_state.view_mode)
with tab2:
# Recent formulas
st.subheader("Recently Used Formulas")
recent_formulas = preferences["recent_formulas"]
if not recent_formulas:
st.info("You haven't used any formulas yet. Start by selecting a formula from the 'All Formulas' tab.")
else:
# Create a 3-column layout for the recent formula cards
col1, col2, col3 = st.columns(3)
# Add a button to access the formula
if st.button(f"Use {formula['name']}", key=f"btn_{i}"):
# Store the selected formula in session state
st.session_state.selected_formula = formula
st.rerun()
# Display the recent formula cards
for i, module_name in enumerate(recent_formulas):
# Determine which column to use
col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3
with col:
render_formula_card(module_name, i + 100, "grid") # Use a different index to avoid key conflicts
# Favorite formulas
st.subheader("Favorite Formulas")
favorite_formulas = preferences["favorite_formulas"]
if not favorite_formulas:
st.info("You haven't added any formulas to your favorites yet. Click the star icon on a formula card to add it to your favorites.")
else:
# Create a 3-column layout for the favorite formula cards
col1, col2, col3 = st.columns(3)
# Display the favorite formula cards
for i, module_name in enumerate(favorite_formulas):
# Determine which column to use
col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3
with col:
render_formula_card(module_name, i + 200, "grid") # Use a different index to avoid key conflicts
with tab3:
# Formula comparison
render_formula_comparison()
with tab4:
# Help and guide
st.subheader("Copywriting Formula Guide")
st.write("""
This dashboard provides access to a variety of copywriting formulas, each designed for specific marketing needs.
Here's how to make the most of these powerful tools:
""")
st.markdown("""
#### How to Use This Dashboard
1. **Browse Formulas**: Explore the available copywriting formulas in the "All Formulas" tab
2. **Search & Filter**: Use the search box and filters to find the perfect formula for your needs
3. **Compare Formulas**: Add up to 3 formulas to the comparison tab to see them side by side
4. **Save Favorites**: Click the star icon to save formulas you use frequently
5. **Access Recent**: Quickly access your recently used formulas in the "Recent & Favorites" tab
#### Choosing the Right Formula
Different formulas work best for different marketing goals:
- **Emotional Appeal**: Use when you want to connect with your audience on an emotional level
- **Structured Framework**: Great for organizing complex information in a compelling way
- **Sales Funnel**: Designed to guide prospects through the buying journey
- **Problem-Solution**: Effective for highlighting pain points and positioning your solution
- **Feature-Benefit**: Perfect for product descriptions and technical offerings
- **Messaging Framework**: Helps create clear, consistent messaging across channels
#### Formula Difficulty Levels
- **Beginner**: Easy to use with minimal copywriting experience
- **Intermediate**: Requires some understanding of copywriting principles
- **Advanced**: Most effective when used by experienced copywriters
""")
# Add a section about how to use the generated copy
st.subheader("Using Your Generated Copy")
st.write("""
After generating copy with your chosen formula:
1. **Review & Edit**: Always review and personalize the generated content
2. **Test Different Versions**: Try multiple formulas for the same product/service
3. **A/B Test**: Use different versions in your marketing to see which performs best
4. **Adapt for Channels**: Modify the copy as needed for different marketing channels
""")
# Add a feedback section
st.subheader("Feedback & Suggestions")
st.write("We're constantly improving our copywriting tools. If you have feedback or suggestions, please let us know!")
feedback = st.text_area("Your feedback", placeholder="Share your thoughts, suggestions, or report any issues...")
if st.button("Submit Feedback"):
if feedback:
st.success("Thank you for your feedback! We'll use it to improve our tools.")
# In a real implementation, you would save this feedback somewhere
else:
st.warning("Please enter your feedback before submitting.")
# For standalone execution
if __name__ == "__main__":

View File

@@ -6,6 +6,7 @@ import streamlit as st
from loguru import logger
import random
import asyncio
import re
logger.remove()
logger.add(sys.stdout,
@@ -17,110 +18,367 @@ from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
async def blog_metadata(blog_article):
""" Common function to get blog metadata """
logger.info(f"Generating Content MetaData\n")
"""
Generate comprehensive SEO metadata for a blog article.
Args:
blog_article (str): The content of the blog article
Returns:
tuple: (blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug)
"""
logger.info("Generating comprehensive blog metadata")
progress_bar = st.progress(0)
total_steps = 4
total_steps = 6 # Increased steps for new metadata types
status_container = st.empty()
# Step 1: Generate blog title
await asyncio.sleep(random.uniform(1, 3))
blog_title = generate_blog_title(blog_article)
progress_bar.progress(1 / total_steps)
try:
# Step 1: Generate blog title
status_container.info("Generating SEO-optimized blog title...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_title = generate_blog_title(blog_article)
progress_bar.progress(1 / total_steps)
# Step 2: Generate blog meta description
await asyncio.sleep(random.uniform(1, 3))
blog_meta_desc = generate_blog_description(blog_article)
progress_bar.progress(2 / total_steps)
# Step 2: Generate blog meta description
status_container.info("Creating compelling meta description...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_meta_desc = generate_blog_description(blog_article)
progress_bar.progress(2 / total_steps)
# Step 3: Generate blog tags
await asyncio.sleep(random.uniform(1, 3))
blog_tags = get_blog_tags(blog_article)
progress_bar.progress(3 / total_steps)
# Step 3: Generate blog tags
status_container.info("Extracting relevant blog tags...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_tags = get_blog_tags(blog_article)
progress_bar.progress(3 / total_steps)
# Step 4: Generate blog categories
await asyncio.sleep(random.uniform(1, 3))
blog_categories = get_blog_categories(blog_article)
progress_bar.progress(4 / total_steps)
# Step 4: Generate blog categories
status_container.info("Identifying primary blog categories...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_categories = get_blog_categories(blog_article)
progress_bar.progress(4 / total_steps)
# Step 5: Generate social media hashtags
status_container.info("Creating social media hashtags...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_hashtags = generate_blog_hashtags(blog_article)
progress_bar.progress(5 / total_steps)
# Step 6: Generate SEO URL slug
status_container.info("Generating SEO-friendly URL slug...")
await asyncio.sleep(random.uniform(0.5, 1.5))
blog_slug = generate_blog_slug(blog_title)
progress_bar.progress(6 / total_steps)
# Present the result in a table format
st.table({
"Metadata": ["Blog Title", "Meta Description", "Tags", "Categories"],
"Value": [blog_title, blog_meta_desc, blog_tags, blog_categories]
})
# Present the result in a table format
status_container.success("✅ Metadata generation complete")
st.table({
"Metadata": ["Blog Title", "Meta Description", "Tags", "Categories", "Social Hashtags", "URL Slug"],
"Value": [blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug]
})
return blog_title, blog_meta_desc, blog_tags, blog_categories
return blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug
except Exception as e:
status_container.error(f"Error generating metadata: {str(e)}")
logger.error(f"Failed to generate metadata: {str(e)}")
# Return default values to ensure the blog generation process can continue
return f"Blog Article", "An informative blog post", "content, blog", "General, Information", "#content #blog", "blog-article"
def generate_blog_title(blog_article):
"""
Given a blog title generate an outline for it
Generate an SEO-optimized and engaging title for a blog article.
Args:
blog_article (str): The content of the blog article
Returns:
str: An SEO-optimized title
"""
logger.info("Generating blog title.")
prompt = f"""As a SEO expert, I will provide you with a blog content.
Your task is write a SEO optimized and call to action, blog title for given blog content.
Follow SEO best practises to suggest the blog title.
Please keep the titles concise, not exceeding 60 words.
Respond with only the title and no explanations.
Negative Keywords: Unvieling, unleash, power of. Dont use such words in your title.
\nGenerate blog title for this given blog content:\n '{blog_article}' """
logger.info("Generating SEO-optimized blog title")
# Extract the first 3000 characters for title generation
snippet = blog_article[:3000] if len(blog_article) > 3000 else blog_article
prompt = f"""As an expert SEO copywriter, create the perfect blog title based on this content.
REQUIREMENTS:
1. Make it compelling, specific, and actionable
2. Include primary keywords naturally near the beginning
3. Keep it between 50-60 characters (10-12 words maximum)
4. Make it promise clear value to the reader
5. Use power words that evoke emotion where appropriate
AVOID:
- Clickbait tactics or false promises
- Generic titles that could apply to any article
- Using words like "unveiling", "unleash", "power of", "ultimate guide", or "complete"
- ALL CAPS or excessive punctuation!!!!
EXAMPLES OF GREAT TITLES:
- "7 Proven Strategies to Improve Your Email Marketing ROI"
- "Why Remote Work Improves Productivity: New Research Findings"
- "How to Build a Personal Budget That Actually Works"
CONTENT TO ANALYZE:
"{snippet}"
Reply with ONLY the title and no other text or explanation.
"""
try:
response = llm_text_gen(prompt)
return response
title = llm_text_gen(prompt)
# Clean up any quotes or extra spaces
title = title.strip('"\'').strip()
logger.info(f"Generated title: {title}")
return title
except Exception as err:
logger.error(f"Failed to get response from LLM: {err}")
raise err
logger.error(f"Failed to generate blog title: {err}")
return "Blog Article" # Fallback title
def generate_blog_description(blog_content):
"""
Prompt designed to give SEO optimized blog descripton
Generate an SEO-optimized meta description for the blog.
Args:
blog_content (str): The content of the blog article
Returns:
str: An SEO-optimized meta description
"""
logger.info("Generating Blog Meta Description for the given blog.")
prompt = f"""As an expert SEO and blog writer, Compose a compelling meta description for the given blog content,
adhering to SEO best practices. Keep it between 150-160 characters.
Provide a glimpse of the content's value to entice readers.
Respond with only one of your best effort and do not include your explanations.
Blog Content: '{blog_content}'"""
logger.info("Generating SEO-optimized meta description")
# Extract the first 2000 characters for description generation
snippet = blog_content[:2000] if len(blog_content) > 2000 else blog_content
prompt = f"""As an SEO expert, write the perfect meta description for this blog content.
try:
response = llm_text_gen(prompt)
return response
except Exception as err:
logger.error(f"Failed to get response from LLM:{err}")
raise err
REQUIREMENTS:
1. Exactly 150-160 characters (this is critical for SEO)
2. Include primary keywords naturally
3. Compelling value proposition that makes readers want to click
4. Clear indication of what the reader will learn/gain
5. End with an implicit call-to-action when possible
def get_blog_categories(blog_article):
"""
Function to generate blog categories for given blog content.
"""
prompt = f"""As an expert SEO and content writer, I will provide you with blog content.
Suggest only 2 blog categories which are most relevant to provided blog content,
by identifying the main topic. Also consider the target audience and the
blog's category taxonomy. Only reply with comma separated values.
The blog content is: '{blog_article}'"
"""
logger.info("Generating blog categories for the given blog.")
EXAMPLES OF EXCELLENT META DESCRIPTIONS:
- "Learn how to increase email open rates by 43% with these 5 proven strategies from industry experts. Implement today for immediate results."
- "Discover why 67% of professionals struggle with work-life balance and explore research-backed techniques to reclaim your time and energy."
CONTENT TO SUMMARIZE:
"{snippet}"
Reply with ONLY the meta description and no other text. Keep it between 150-160 characters exactly.
"""
try:
response = llm_text_gen(prompt)
return response
description = llm_text_gen(prompt)
# Clean up any quotes or extra spaces
description = description.strip('"\'').strip()
logger.info(f"Generated meta description: {description}")
return description
except Exception as err:
logger.error(f"get_blog_categories:Failed to get response from LLM: {err}")
logger.error(f"Failed to generate blog description: {err}")
return "An informative blog post about this topic." # Fallback description
def get_blog_tags(blog_article):
"""
Function to suggest tags for the given blog content
Generate relevant SEO tags for a blog article.
Args:
blog_article (str): The content of the blog article
Returns:
str: Comma-separated list of relevant tags
"""
prompt = f"""As an expert SEO and blog writer, suggest only 2 relevant and specific blog tags
for the given blog content. Only reply with comma separated values.
Blog content: {blog_article}."""
logger.info("Generating Blog tags for the given blog post.")
logger.info("Generating SEO-optimized blog tags")
# Extract the first 3000 characters for tag generation
snippet = blog_article[:3000] if len(blog_article) > 3000 else blog_article
prompt = f"""As an SEO specialist, extract the 4-6 most relevant tags for this blog post.
REQUIREMENTS:
1. Choose specific, targeted keywords that accurately represent the content
2. Include a mix of broad and specific tags
3. Focus on terms users would actually search for
4. Include at least one long-tail keyword phrase
5. Ensure all tags are directly addressed in the content
CONTENT TO ANALYZE:
"{snippet}"
Reply with ONLY the tags as a comma-separated list (e.g., "keyword1, keyword2, keyword3, keyword phrase"). Provide 4-6 tags total.
"""
try:
response = llm_text_gen(prompt)
return response
tags = llm_text_gen(prompt)
# Clean up any quotes or extra commas
tags = tags.strip('"\'').strip()
if tags.endswith(','):
tags = tags[:-1]
logger.info(f"Generated tags: {tags}")
return tags
except Exception as err:
logger.error(f"Failed to get response from LLM: {err}")
raise err
logger.error(f"Failed to generate blog tags: {err}")
return "content, blog" # Fallback tags
def get_blog_categories(blog_article):
"""
Identify the most appropriate blog categories for the article.
Args:
blog_article (str): The content of the blog article
Returns:
str: Comma-separated list of relevant categories
"""
logger.info("Generating blog categories")
# Extract the first 2000 characters for category generation
snippet = blog_article[:2000] if len(blog_article) > 2000 else blog_article
prompt = f"""As a content strategist, identify the 2-3 most appropriate high-level categories for this blog.
REQUIREMENTS:
1. Choose broad, established categories used in content organization
2. Select categories that best represent the main themes of the article
3. Consider the target audience and their interests
4. Focus on categories that would help with site navigation
5. Aim for a primary category and 1-2 supporting categories
EXAMPLES OF GOOD CATEGORIES:
- Marketing, Social Media, Strategy
- Finance, Personal Budgeting, Money Management
- Productivity, Remote Work, Business
CONTENT TO ANALYZE:
"{snippet}"
Reply with ONLY the categories as a comma-separated list (e.g., "Category1, Category2, Category3"). Provide 2-3 categories total.
"""
try:
categories = llm_text_gen(prompt)
# Clean up any quotes or extra commas
categories = categories.strip('"\'').strip()
if categories.endswith(','):
categories = categories[:-1]
logger.info(f"Generated categories: {categories}")
return categories
except Exception as err:
logger.error(f"Failed to generate blog categories: {err}")
return "General, Information" # Fallback categories
def generate_blog_hashtags(blog_article):
"""
Generate social media hashtags for promoting the blog article.
Args:
blog_article (str): The content of the blog article
Returns:
str: Space-separated list of hashtags starting with #
"""
logger.info("Generating social media hashtags")
# Extract the first 2000 characters for hashtag generation
snippet = blog_article[:2000] if len(blog_article) > 2000 else blog_article
prompt = f"""As a social media strategist, create 5-7 effective hashtags for this blog content.
REQUIREMENTS:
1. Mix of popular and niche hashtags for better visibility
2. Include industry-specific and trending hashtags where relevant
3. Avoid overly generic hashtags (like #content or #blog)
4. Format each hashtag with # symbol and camelCase or separate words
5. Include at least one branded or campaign-style hashtag
EXAMPLES OF EFFECTIVE HASHTAG SETS:
- #EmailMarketing #ROITips #DigitalStrategy #MarketingTips #GrowthHacking #EmailROI
- #RemoteWork #ProductivityTips #FutureOfWork #WorkFromHome #RemoteProductivity #HRInsights
CONTENT TO ANALYZE:
"{snippet}"
Reply with ONLY the hashtags, each starting with # and separated by spaces. Provide 5-7 hashtags total.
"""
try:
hashtags = llm_text_gen(prompt)
# Clean up any quotes or extra spaces
hashtags = hashtags.strip('"\'').strip()
# Ensure all hashtags start with #
if not hashtags.startswith('#'):
hashtags = ' '.join([f"#{tag.strip('#')}" for tag in hashtags.split()])
logger.info(f"Generated hashtags: {hashtags}")
return hashtags
except Exception as err:
logger.error(f"Failed to generate blog hashtags: {err}")
return "#content #blog" # Fallback hashtags
def generate_blog_slug(blog_title):
"""
Generate an SEO-friendly URL slug from the blog title.
Args:
blog_title (str): The title of the blog article
Returns:
str: An SEO-friendly URL slug
"""
logger.info("Generating SEO-friendly URL slug")
try:
# Use a prompt to generate a customized slug
prompt = f"""As an SEO specialist, create an SEO-friendly URL slug for this blog title: "{blog_title}"
REQUIREMENTS:
1. Keep it under 60 characters
2. Use only lowercase letters, numbers, and hyphens
3. Include primary keywords near the beginning
4. Remove all unnecessary words (a, the, and, or, but, etc.)
5. Ensure it's human-readable and descriptive
EXAMPLES:
- Title: "10 Effective Ways to Improve Your Email Marketing ROI This Quarter"
Slug: "improve-email-marketing-roi"
- Title: "Why Most Remote Workers Are More Productive According to New Research"
Slug: "remote-workers-productivity-research"
Reply with ONLY the slug and no other text or explanation.
"""
slug = llm_text_gen(prompt)
# Clean up and normalize the slug
slug = slug.strip('"\'').strip()
# If the LLM didn't create a proper slug, do it programmatically
if not re.match(r'^[a-z0-9-]+$', slug):
# Fallback to simple programmatic slug creation
slug = blog_title.lower()
# Remove special characters
slug = re.sub(r'[^a-z0-9\s-]', '', slug)
# Replace spaces with hyphens
slug = re.sub(r'\s+', '-', slug)
# Remove redundant hyphens
slug = re.sub(r'-+', '-', slug)
# Limit length to 60 characters
slug = slug[:60].strip('-')
logger.info(f"Generated slug: {slug}")
return slug
except Exception as err:
logger.error(f"Failed to generate blog slug: {err}")
# Create a simple slug programmatically as fallback
slug = blog_title.lower()
slug = re.sub(r'[^a-z0-9\s-]', '', slug)
slug = re.sub(r'\s+', '-', slug)
slug = re.sub(r'-+', '-', slug)
slug = slug[:60].strip('-')
return slug
# Helper function to run the asyncio event loop within Streamlit
def run_async(coro):

View File

@@ -42,6 +42,8 @@ def llm_text_gen(prompt, system_prompt=None, json_struct=None):
top_p = 0.9
n = 1
fp = 16
frequency_penalty = 0.0
presence_penalty = 0.0
# Default blog characteristics
blog_tone = "Professional"
@@ -60,7 +62,16 @@ def llm_text_gen(prompt, system_prompt=None, json_struct=None):
model = llm_config[1] if llm_config[1] else model
temperature = llm_config[2] if llm_config[2] else temperature
max_tokens = llm_config[3] if llm_config[3] else max_tokens
# Use default values for top_p, n, fp if they're not in the config
# Handle additional parameters with defaults if they're missing
if len(llm_config) > 4:
top_p = llm_config[4] if llm_config[4] else top_p
if len(llm_config) > 5:
# Try to get n parameter (could be either 'N' or 'n' in config)
n = llm_config[5] if llm_config[5] else n
if len(llm_config) > 6:
frequency_penalty = llm_config[6] if llm_config[6] else frequency_penalty
logger.debug(f"[llm_text_gen] LLM Config loaded: Provider={gpt_provider}, Model={model}, Temp={temperature}")
except Exception as err:
logger.warning(f"[llm_text_gen] Couldn't load LLM config completely, using defaults where needed: {err}")

View File

@@ -144,7 +144,7 @@ class APIKeyManager:
'ANTHROPIC_API_KEY',
'MISTRAL_API_KEY',
# Research Providers
'SERPAPI_KEY',
'SERPER_API_KEY',
'TAVILY_API_KEY',
'METAPHOR_API_KEY',
'FIRECRAWL_API_KEY'

View File

@@ -8,18 +8,21 @@
"Blog Output Format": "markdown"
},
"Blog Images Details": {
"Image Generation Model": "stable-diffusion",
"Image Generation Model": "Gemini-AI",
"Number of Blog Images": 1,
"Image Style": "Realistic"
},
"LLM Options": {
"GPT Provider": "google",
"Model": "gemini-1.5-flash-latest",
"Model": "gemini-2.0-flash",
"Temperature": 0.7,
"Max Tokens": 4000,
"Top-p": 0.9,
"N": 1,
"n": 1,
"fp": 16
"fp": 16,
"Frequency Penalty": 0.0,
"Presence Penalty": 0.0
},
"Search Engine Parameters": {
"Geographic Location": "us",