Spaces:
Running
Running
| import gradio as gr | |
| import google.generativeai as genai | |
| from bs4 import BeautifulSoup, NavigableString | |
| import re | |
| import json | |
| import random | |
| import os | |
| # --- Constants & Config --- | |
| 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." | |
| ] | |
| # --- Logic Ports --- | |
| 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 to match: "term": number% OR term: number% | |
| 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 | |
| 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]" | |
| ] | |
| # Sort terms by score descending | |
| 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 | |
| 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() | |
| # Blacklist check | |
| 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}%)") | |
| # Scale score (approx 0-10) | |
| 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') | |
| # Recursive function specifically for NavigableStrings | |
| def walk(node): | |
| if isinstance(node, NavigableString): | |
| if node.parent.name not in ['script', 'style']: # Skip script/style tags | |
| 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(): | |
| # Regex for whole word match, case insensitive | |
| pattern = re.compile(r'\b' + re.escape(us) + r'\b', re.IGNORECASE) | |
| def match_handler(m): | |
| # Preserve case | |
| 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 | |
| # 1. Remove phrases | |
| 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) | |
| # 2. Remove tags | |
| 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) | |
| # 3. Footer text | |
| 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) | |
| # 4. Whitespace cleanup | |
| 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) | |
| # --- Gemini Integration --- | |
| def call_gemini(prompt, api_key, model_name="gemini-2.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)}" | |
| # --- Main Automation Logic --- | |
| 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) | |
| # 1. Magic Page Logic | |
| magic_output_html = "" | |
| # SEO Titles | |
| if api_key: | |
| # LLM Title Gen | |
| 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 block if present | |
| 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: | |
| # Template Gen | |
| 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 | |
| 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>" | |
| # 2. Homepage Logic | |
| clean_html = clean_homepage_content(article_content) | |
| british_html = convert_to_british(clean_html) | |
| return magic_output_html, british_html | |
| # --- Gradio UI --- | |
| 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-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.5-pro"], | |
| value="gemini-2.5-pro", | |
| 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() | |