copywriter and blog metadata updates
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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__":
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user