|
|
import gradio as gr
|
|
|
import google.generativeai as genai
|
|
|
from bs4 import BeautifulSoup, NavigableString
|
|
|
import re
|
|
|
import json
|
|
|
import random
|
|
|
import os
|
|
|
|
|
|
|
|
|
BLACKLIST_WORDS = [
|
|
|
"landscape", "realm", "navigate", "unveil", "explore", "transformative",
|
|
|
"encompass", "examine", "crucial", "discover", "dive", "delve",
|
|
|
"uncover", "unlock", "elevate", "unleash", "harness"
|
|
|
]
|
|
|
|
|
|
BRITISH_MAPPINGS = {
|
|
|
"color": "colour", "flavor": "flavour", "humor": "humour", "labor": "labour",
|
|
|
"neighbor": "neighbour", "favor": "favour", "honor": "honour", "behavior": "behaviour",
|
|
|
"center": "centre", "fiber": "fibre", "liter": "litre", "theater": "theatre",
|
|
|
"meter": "metre", "analyze": "analyse", "breathalyze": "breathalyse", "paralyze": "paralyse",
|
|
|
"catalyze": "catalyse", "organization": "organisation", "realize": "realise",
|
|
|
"recognize": "recognise", "standardize": "standardise", "appetizer": "appetiser",
|
|
|
"leukemia": "leukaemia", "maneuver": "manoeuvre", "estrogen": "oestrogen",
|
|
|
"pediatric": "paediatric", "defense": "defence", "license": "licence",
|
|
|
"offense": "offence", "pretense": "pretence", "traveler": "traveller", "modeling": "modelling",
|
|
|
"cancelled": "cancelled",
|
|
|
"program": "programme",
|
|
|
}
|
|
|
|
|
|
SOCIAL_PROOF_TEMPLATES = [
|
|
|
"We recently hired {KEYWORD} for our project, and the results were outstanding. The team was professional, efficient, and delivered exactly what we needed. I highly recommend their services to anyone looking for reliable {KEYWORD_LOWER}.",
|
|
|
"I was struggling to find trustworthy {KEYWORD_LOWER} until I found this company. They exceeded my expectations with their attention to detail and timely completion. It was a refreshing experience to work with such dedicated professionals.",
|
|
|
"If you need {KEYWORD_LOWER}, look no further. Their expertise is evident in the quality of their work, and the customer service is top-notch. I am completely satisfied with the outcome and will definitely use them again.",
|
|
|
"Finding a dependable {KEYWORD} can be difficult, but this team made it easy. They communicated clearly throughout the process and finished the job to a high standard. I'm very impressed with their workmanship."
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def capitalize(s):
|
|
|
if not s: return ""
|
|
|
return s[0].upper() + s[1:]
|
|
|
|
|
|
def parse_growmatic_data(text):
|
|
|
term_map = {}
|
|
|
if not text: return term_map
|
|
|
|
|
|
regex = r'["\']?([\w\s]+)["\']?\s*[:=]\s*(\d+)%?'
|
|
|
matches = re.findall(regex, text)
|
|
|
for term, score in matches:
|
|
|
term_lower = term.strip().lower()
|
|
|
if term_lower:
|
|
|
term_map[term_lower] = int(score)
|
|
|
return term_map
|
|
|
|
|
|
def generate_titles(main_keyword, term_map):
|
|
|
titles = []
|
|
|
|
|
|
templates = [
|
|
|
"{KEYWORD} in [location] - {TERM_A} [zip]",
|
|
|
"{KEYWORD} in [location] - {TERM_B} Services [zip]",
|
|
|
"Expert {KEYWORD} in [location] - {TERM_C} [zip]",
|
|
|
"{KEYWORD} Services in [location] - {TERM_A} [zip]",
|
|
|
"Leading {KEYWORD} in [location] - {TERM_B} [zip]",
|
|
|
"{KEYWORD} Specialists in [location] - {TERM_C} [zip]",
|
|
|
"Best {KEYWORD} in [location] - {TERM_A} Solutions [zip]"
|
|
|
]
|
|
|
|
|
|
|
|
|
sorted_terms = sorted(term_map.keys(), key=lambda k: term_map[k], reverse=True)
|
|
|
|
|
|
term_a = sorted_terms[0] if len(sorted_terms) > 0 else "Projects"
|
|
|
term_b = sorted_terms[1] if len(sorted_terms) > 1 else "Installations"
|
|
|
term_c = sorted_terms[2] if len(sorted_terms) > 2 else "Solutions"
|
|
|
|
|
|
for tmpl in templates:
|
|
|
t = tmpl.replace("{KEYWORD}", main_keyword)
|
|
|
t = t.replace("{TERM_A}", capitalize(term_a))
|
|
|
t = t.replace("{TERM_B}", capitalize(term_b))
|
|
|
t = t.replace("{TERM_C}", capitalize(term_c))
|
|
|
titles.append(t)
|
|
|
|
|
|
|
|
|
variations = [
|
|
|
f"{main_keyword} {capitalize(term_a)}",
|
|
|
f"{main_keyword} {capitalize(term_b)} Services",
|
|
|
f"{capitalize(term_a)} & {main_keyword}"
|
|
|
]
|
|
|
return titles + variations
|
|
|
|
|
|
def calculate_score(title, term_map):
|
|
|
title_lower = title.lower()
|
|
|
|
|
|
|
|
|
for bad_word in BLACKLIST_WORDS:
|
|
|
if bad_word in title_lower:
|
|
|
return {"title": title, "score": 0, "terms": "BLACKLISTED"}
|
|
|
|
|
|
total_score = 0
|
|
|
matched_terms = []
|
|
|
|
|
|
for term, weight in term_map.items():
|
|
|
if term in title_lower:
|
|
|
total_score += weight
|
|
|
matched_terms.append(f"{term} ({weight}%)")
|
|
|
|
|
|
|
|
|
final_score = round(total_score / 30, 1)
|
|
|
if final_score > 10: final_score = 10
|
|
|
|
|
|
return {
|
|
|
"title": title,
|
|
|
"score": final_score,
|
|
|
"terms": ", ".join(matched_terms)
|
|
|
}
|
|
|
|
|
|
def process_text_nodes(html_content, callback):
|
|
|
if not html_content: return ""
|
|
|
soup = BeautifulSoup(html_content, 'html.parser')
|
|
|
|
|
|
|
|
|
def walk(node):
|
|
|
if isinstance(node, NavigableString):
|
|
|
if node.parent.name not in ['script', 'style']:
|
|
|
new_text = callback(str(node))
|
|
|
if new_text != str(node):
|
|
|
node.replace_with(new_text)
|
|
|
elif hasattr(node, 'children'):
|
|
|
for child in node.children:
|
|
|
walk(child)
|
|
|
|
|
|
walk(soup)
|
|
|
return str(soup)
|
|
|
|
|
|
def convert_to_british(html_content):
|
|
|
if not html_content: return ""
|
|
|
|
|
|
def replacer(text):
|
|
|
processed = text
|
|
|
for us, uk in BRITISH_MAPPINGS.items():
|
|
|
|
|
|
pattern = re.compile(r'\b' + re.escape(us) + r'\b', re.IGNORECASE)
|
|
|
|
|
|
def match_handler(m):
|
|
|
|
|
|
word = m.group(0)
|
|
|
if word[0].isupper():
|
|
|
return capitalize(uk)
|
|
|
return uk
|
|
|
|
|
|
processed = pattern.sub(match_handler, processed)
|
|
|
return processed
|
|
|
|
|
|
return process_text_nodes(html_content, replacer)
|
|
|
|
|
|
def clean_homepage_content(html_content):
|
|
|
if not html_content: return ""
|
|
|
|
|
|
def replacer(text):
|
|
|
clean = text
|
|
|
|
|
|
|
|
|
phrases_to_remove = [
|
|
|
r'\s+in\s+\[location\]', r'in\s+\[location\]',
|
|
|
r'\s+across\s+the\s+\[location\]', r'across\s+the\s+\[location\]',
|
|
|
r'\s+across\s+\[location\]', r'across\s+\[location\]',
|
|
|
r'\s+around\s+the\s+\[location\]', r'around\s+the\s+\[location\]',
|
|
|
r'\s+nearby\s+\[location\]', r'nearby\s+\[location\]',
|
|
|
r'\s+throughout\s+\[location\]', r'throughout\s+\[location\]'
|
|
|
]
|
|
|
for phrase in phrases_to_remove:
|
|
|
clean = re.sub(phrase, '', clean, flags=re.IGNORECASE)
|
|
|
|
|
|
|
|
|
tags_to_remove = [
|
|
|
r'\[location\]', r'\[county\]', r'\[region\]', r'\[zip\]'
|
|
|
]
|
|
|
for tag in tags_to_remove:
|
|
|
clean = re.sub(tag, '', clean, flags=re.IGNORECASE)
|
|
|
|
|
|
|
|
|
footer_regex = r'in\s*\[region\]\.?\s*Here\s*are\s*some\s*towns\s*we\s*cover\s*near\s*\[location\]\s*\[zip\]\s*\[cities[^\]]*\]'
|
|
|
clean = re.sub(footer_regex, '', clean, flags=re.IGNORECASE | re.DOTALL)
|
|
|
|
|
|
|
|
|
clean = re.sub(r'\s{2,}', ' ', clean)
|
|
|
clean = re.sub(r'\s+\.', '.', clean)
|
|
|
clean = re.sub(r'\s+\?', '?', clean)
|
|
|
clean = re.sub(r'\s+\,', ',', clean)
|
|
|
|
|
|
return clean.strip()
|
|
|
|
|
|
return process_text_nodes(html_content, replacer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_gemini(prompt, api_key, model_name="gemini-1.5-flash"):
|
|
|
if not api_key: return None
|
|
|
try:
|
|
|
genai.configure(api_key=api_key)
|
|
|
model = genai.GenerativeModel(model_name)
|
|
|
response = model.generate_content(prompt)
|
|
|
return response.text
|
|
|
except Exception as e:
|
|
|
return f"Error: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
def run_automation(main_keyword, site_link, growmatic_data, api_key, article_content, model_selection):
|
|
|
if not main_keyword:
|
|
|
return "Error: Main Keyword is required.", ""
|
|
|
|
|
|
term_map = parse_growmatic_data(growmatic_data)
|
|
|
|
|
|
|
|
|
magic_output_html = ""
|
|
|
|
|
|
|
|
|
if api_key:
|
|
|
|
|
|
terms_str = ", ".join([f"{k} ({v}%)" for k, v in term_map.items()])
|
|
|
prompt = f"""Act as an SEO expert.
|
|
|
Main Keyword: "{main_keyword}"
|
|
|
Semantic Terms (Growmatic Data): {terms_str}
|
|
|
|
|
|
Task:
|
|
|
1. Generate 3 highly optimized Meta Titles for a page targeting "{main_keyword}". Use the semantic terms to increase relevance.
|
|
|
2. Generate a list of 5-8 Meta Keywords (comma separated).
|
|
|
3. Select the "Best" Title from the 3 options based on SEO scoring principles.
|
|
|
|
|
|
Output JSON format ONLY (no markdown):
|
|
|
{{
|
|
|
"metaTitles": ["Title 1", "Title 2", "Title 3"],
|
|
|
"bestTitle": "The Best Title",
|
|
|
"metaKeywords": "keyword1, keyword2, keyword3"
|
|
|
}}"""
|
|
|
|
|
|
llm_resp = call_gemini(prompt, api_key, model_selection)
|
|
|
|
|
|
try:
|
|
|
|
|
|
clean_json = llm_resp.replace('```json', '').replace('```', '').strip()
|
|
|
data = json.loads(clean_json)
|
|
|
|
|
|
magic_output_html += "<h3>--- GENERATED SEO TITLES (LLM) ---</h3>"
|
|
|
for t in data.get("metaTitles", []):
|
|
|
is_best = t == data.get("bestTitle")
|
|
|
style = "color: blue; font-weight: bold;" if is_best else ""
|
|
|
suffix = "(Best Match)" if is_best else ""
|
|
|
magic_output_html += f'<p style="{style}">• {t} {suffix}</p>'
|
|
|
|
|
|
magic_output_html += f"<p><strong>Meta Keywords:</strong> {data.get('metaKeywords', '')}</p><br>"
|
|
|
|
|
|
except:
|
|
|
magic_output_html += f"<p style='color:red'>Error parsing LLM response: {llm_resp}</p>"
|
|
|
|
|
|
else:
|
|
|
|
|
|
titles = generate_titles(main_keyword, term_map)
|
|
|
scored = [calculate_score(t, term_map) for t in titles]
|
|
|
scored.sort(key=lambda x: x['score'], reverse=True)
|
|
|
|
|
|
magic_output_html += "<h3>--- GENERATED SEO TITLES (Template) ---</h3>"
|
|
|
for item in scored[:5]:
|
|
|
magic_output_html += f"<p>• [Score: {item['score']}] {item['title']}</p>"
|
|
|
magic_output_html += "<br>"
|
|
|
|
|
|
|
|
|
social_proof_text = ""
|
|
|
if api_key:
|
|
|
sp_prompt = f"""Write 2 positive testimonials for a service provider offering "{main_keyword}".
|
|
|
Create two very non-generic names including last names.
|
|
|
Each testimonial should be max 3-4 sentences.
|
|
|
Focus on professionalism, result quality, and ease of working with them."""
|
|
|
social_proof_text = call_gemini(sp_prompt, api_key, model_selection)
|
|
|
else:
|
|
|
tmpl = random.choice(SOCIAL_PROOF_TEMPLATES)
|
|
|
social_proof_text = tmpl.replace("{KEYWORD}", main_keyword).replace("{KEYWORD_LOWER}", main_keyword.lower())
|
|
|
|
|
|
magic_output_html += f"<h3>--- MAGIC PAGE METADATA ---</h3>"
|
|
|
magic_output_html += f"<p><strong>Target Keyword:</strong> {main_keyword}</p>"
|
|
|
magic_output_html += f"<p><strong>Site URL:</strong> {site_link}</p><br>"
|
|
|
|
|
|
magic_output_html += f"<h3>--- SOCIAL PROOF ---</h3>"
|
|
|
magic_output_html += f"<p>{social_proof_text.replace(chr(10), '<br>')}</p>"
|
|
|
|
|
|
|
|
|
clean_html = clean_homepage_content(article_content)
|
|
|
british_html = convert_to_british(clean_html)
|
|
|
|
|
|
return magic_output_html, british_html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Content Automation Tool") as app:
|
|
|
gr.Markdown("# Content Automation Tool (Gradio Edition)")
|
|
|
gr.Markdown("Generate Magic Page & Optimized Homepage Content Instantly")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
main_keyword = gr.Textbox(label="Main Keyword", placeholder="e.g. Suspended Ceiling Contractors")
|
|
|
site_link = gr.Textbox(label="Site Link", placeholder="e.g. https://example.com")
|
|
|
growmatic_data = gr.TextArea(label="Growmatic Data", placeholder='"suspended": 100%, "ceiling": 73%')
|
|
|
|
|
|
with gr.Row():
|
|
|
api_key = gr.Textbox(label="Gemini API Key", type="password", placeholder="AIza...")
|
|
|
model_selection = gr.Dropdown(
|
|
|
choices=["gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.0-pro"],
|
|
|
value="gemini-1.5-flash",
|
|
|
label="Gemini Model"
|
|
|
)
|
|
|
|
|
|
with gr.Column():
|
|
|
article_content = gr.Textbox(label="Article Content (HTML/Text)", lines=15, placeholder="Paste content with [tags] here...")
|
|
|
|
|
|
generate_btn = gr.Button("Generate Output ✨", variant="primary")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
gr.Markdown("### Magic Page Output")
|
|
|
magic_output = gr.HTML(label="Magic Page Result")
|
|
|
|
|
|
with gr.Column():
|
|
|
gr.Markdown("### Homepage Output")
|
|
|
home_output = gr.HTML(label="Homepage Result")
|
|
|
|
|
|
generate_btn.click(
|
|
|
fn=run_automation,
|
|
|
inputs=[main_keyword, site_link, growmatic_data, api_key, article_content, model_selection],
|
|
|
outputs=[magic_output, home_output]
|
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
app.launch()
|
|
|
|