Gemini2Content / app.py
mroccuper's picture
Update app.py
7284427 verified
# seo_blog_writer_app.py
import gradio as gr
import google.generativeai as genai
import json
from datetime import datetime
import re
import os
# Example system prompt for Gemini
SYSTEM_PROMPT = '''
You are an expert SEO blog writer for a fermentation-focused website.
Write a long-form SEO blog post targeting the keyword: "{KEYWORD}".
Use the following writing guidelines:
- Tone: {TONE} (e.g., Informative, Conversational, Friendly)
- Point of View: {POV} (e.g., Third Person, Second Person)
- Audience: Beginners interested in fermentation
Content structure:
- Start with a compelling introduction (hook)
- Use <h2> and <h3> tags for subheadings
- Include a bullet-point section with beginner-friendly tips
- Add a step-by-step guide (if applicable)
- Include an FAQ section with at least 3 relevant questions and answers
- Add a strong conclusion with a CTA (Call to Action)
- Add meta title and meta description at the beginning
- End with <script type="application/ld+json"> containing valid FAQ schema
Formatting:
- Output only valid WordPress-ready HTML (no markdown)
- Use readable paragraphs and short sentences
- Emphasize clarity and structure
### πŸ“Œ INTERNAL LINK MAP
The following keywords should be hyperlinked when they appear naturally in the content:
{LINKS_JSON}
Use this format for links:
<a href="[URL]">[Keyword]</a>
'''
# Helper to parse keyword:url text into JSON
def parse_links_to_json(raw_text):
try:
link_map = {}
if raw_text.strip():
lines = raw_text.strip().splitlines()
for line in lines:
if ":" in line:
parts = line.split(":", 1)
keyword = parts[0].strip()
url = parts[1].strip()
if keyword and url:
link_map[keyword] = url
return json.dumps(link_map, indent=2)
except Exception as e:
return "{}"
# Function to insert internal links from user-defined JSON
def insert_internal_links(text, internal_links_json):
try:
if not internal_links_json or internal_links_json == "{}":
return text
links = json.loads(internal_links_json)
for keyword, url in links.items():
# Only replace the first occurrence to avoid duplicate links
if keyword in text and f'href="{url}"' not in text:
text = text.replace(keyword, f'<a href="{url}">{keyword}</a>', 1)
except Exception as e:
return text + f"\n<!-- Error in internal linking: {str(e)} -->"
return text
def generate_blog(api_key, keyword, tone, pov, internal_links_raw):
try:
# Validate inputs
if not api_key.strip():
return "Error: Please provide a Gemini API key.", "<p>Please provide a Gemini API key.</p>", gr.File(visible=False)
if not keyword.strip():
return "Error: Please provide a keyword.", "<p>Please provide a keyword.</p>", gr.File(visible=False)
# Configure Gemini
genai.configure(api_key=api_key)
model = genai.GenerativeModel("gemini-1.5-pro")
# Parse internal links
internal_links_json = parse_links_to_json(internal_links_raw)
# Format links for the prompt
formatted_links = "No internal links provided."
if internal_links_json != "{}":
try:
links_dict = json.loads(internal_links_json)
formatted_links = "\n".join([f"- {k}: {v}" for k, v in links_dict.items()])
except:
formatted_links = "Error parsing internal links."
# Create the prompt
prompt = SYSTEM_PROMPT.format(
KEYWORD=keyword,
TONE=tone,
POV=pov,
LINKS_JSON=formatted_links
)
# Generate content
response = model.generate_content(prompt)
raw_html = response.text
# Insert internal links
linked_html = insert_internal_links(raw_html, internal_links_json)
# Create clean preview HTML without custom styling
preview = f"""
<div style="max-height: 500px; overflow-y: auto; padding: 10px;">
<p><strong>Keyword:</strong> {keyword} | <strong>Tone:</strong> {tone} | <strong>POV:</strong> {pov}</p>
<hr>
{linked_html}
</div>
"""
# Create full HTML file for download (clean version)
full_html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>{keyword.title()}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>SEO Blog Post: {keyword.title()}</h1>
<p><em>Tone: {tone} | Point of View: {pov}</em></p>
<hr>
{linked_html}
</body>
</html>
"""
# Create filename and save file
filename = f"seo_post_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
# Save to current directory
with open(filename, "w", encoding="utf-8") as f:
f.write(full_html)
return linked_html, preview, gr.File(value=filename, visible=True)
except Exception as e:
error_msg = f"Error generating blog post: {str(e)}"
return error_msg, f"<p>{error_msg}</p>", gr.File(visible=False)
def test_faq_schema(blog_html):
if not blog_html or "Error" in blog_html[:100]:
return "<p>No content to analyze.</p>"
try:
if "application/ld+json" in blog_html:
# Extract schema
start_tag = "<script type=\"application/ld+json\">"
end_tag = "</script>"
start_idx = blog_html.find(start_tag)
if start_idx != -1:
start_idx += len(start_tag)
end_idx = blog_html.find(end_tag, start_idx)
if end_idx != -1:
schema_content = blog_html[start_idx:end_idx].strip()
return f"""
<div>
<h4>βœ… FAQ Schema Detected</h4>
<details>
<summary>View Schema Code</summary>
<pre>{schema_content}</pre>
</details>
</div>
"""
return "<div><h4>⚠️ No FAQ Schema Found</h4><p>The generated content doesn't include FAQ structured data.</p></div>"
except Exception as e:
return f"<p>Error analyzing schema: {str(e)}</p>"
# Create the Gradio interface
with gr.Blocks(theme=gr.themes.Soft(), title="SEO Blog Generator") as demo:
gr.Markdown("""
# 🧠 SEO Blog Article Generator
Generate WordPress-ready blog content with metadata, FAQ, internal links, and schema using Gemini 1.5 Pro.
**Quick Start:**
1. πŸ”‘ Enter your Gemini API key
2. 🎯 Specify your target keyword
3. πŸ“ Choose tone and point of view
4. πŸ”— Add internal links (optional)
5. πŸš€ Click Generate!
""")
# API Key Section
gr.Markdown("### πŸ” API Configuration")
api_key_input = gr.Textbox(
label="Gemini API Key",
type="password",
placeholder="Enter your Google Gemini API key...",
info="Get your API key from Google AI Studio"
)
# Content Settings
gr.Markdown("### βš™οΈ Content Settings")
with gr.Row():
keyword_input = gr.Textbox(
label="Target SEO Keyword",
placeholder="e.g., how to ferment carrots at home",
scale=2
)
tone_input = gr.Dropdown(
["Informative", "Conversational", "Friendly", "Professional"],
value="Friendly",
label="Writing Tone",
scale=1
)
pov_input = gr.Dropdown(
["First Person", "Second Person", "Third Person"],
value="Second Person",
label="Point of View",
scale=1
)
# Internal Links Section
gr.Markdown("### πŸ”— Internal Links (Optional)")
internal_links_box = gr.Textbox(
label="Internal Links",
lines=3,
placeholder="kefir:https://example.com/kefir-guide\nkombucha:https://example.com/kombucha-basics",
info="Format: keyword:url (one per line)"
)
# Generate Button
generate_btn = gr.Button("πŸš€ Generate Blog Post", variant="primary", size="lg")
# Results Section
gr.Markdown("### πŸ“Š Generated Content")
with gr.Row():
with gr.Column():
result_html = gr.Textbox(
label="Generated HTML Code",
lines=12,
info="Copy this HTML to use in WordPress",
max_lines=20,
show_copy_button=True
)
with gr.Column():
preview_html = gr.HTML(label="Live Preview")
# Analysis & Download
gr.Markdown("### πŸ” Analysis & Download")
with gr.Row():
with gr.Column():
faq_test = gr.HTML(label="Schema Analysis")
with gr.Column():
download_file = gr.File(
label="Download HTML File",
interactive=False,
visible=True
)
# Event handlers
generate_btn.click(
fn=generate_blog,
inputs=[api_key_input, keyword_input, tone_input, pov_input, internal_links_box],
outputs=[result_html, preview_html, download_file]
)
result_html.change(
fn=test_faq_schema,
inputs=[result_html],
outputs=[faq_test]
)
# Add examples
gr.Markdown("### πŸ’‘ Example Inputs")
gr.Examples(
examples=[
["how to ferment vegetables at home", "Conversational", "Second Person", "fermentation:https://example.com/fermentation-guide\nvegetables:https://example.com/vegetable-guide"],
["benefits of drinking kefir daily", "Informative", "Third Person", "kefir:https://example.com/kefir-guide\nprobiotics:https://example.com/probiotics-benefits"],
["making kombucha for beginners", "Friendly", "Second Person", "kombucha:https://example.com/kombucha-basics\nSCOBY:https://example.com/what-is-scoby"]
],
inputs=[keyword_input, tone_input, pov_input, internal_links_box]
)
if __name__ == "__main__":
demo.launch(
share=True,
server_name="0.0.0.0",
inbrowser=False,
show_error=True,
quiet=False
)