Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,376 +1,120 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import google.generativeai as genai
|
| 3 |
-
import
|
| 4 |
-
import re
|
| 5 |
-
import requests
|
| 6 |
-
from urllib.parse import urlparse
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
def
|
| 10 |
-
return text.replace('{', '{{').replace('}', '}}')
|
| 11 |
-
|
| 12 |
-
# --- Semantic SEO Enhancement ---
|
| 13 |
-
def generate_semantic_keywords(keyword):
|
| 14 |
-
"""Generate semantic variations of the primary keyword"""
|
| 15 |
-
words = keyword.lower().split()
|
| 16 |
-
variations = []
|
| 17 |
-
|
| 18 |
-
# Add common semantic variations
|
| 19 |
-
semantic_map = {
|
| 20 |
-
'benefits': ['advantages', 'pros', 'positive effects', 'value', 'perks'],
|
| 21 |
-
'tips': ['advice', 'strategies', 'recommendations', 'guidelines', 'hacks'],
|
| 22 |
-
'guide': ['tutorial', 'handbook', 'instructions', 'walkthrough', 'manual'],
|
| 23 |
-
'best': ['top', 'excellent', 'outstanding', 'premier', 'finest'],
|
| 24 |
-
'how to': ['ways to', 'methods to', 'steps to', 'process of'],
|
| 25 |
-
'review': ['analysis', 'evaluation', 'assessment', 'examination'],
|
| 26 |
-
'comparison': ['versus', 'difference', 'contrast', 'alternative']
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
for word in words:
|
| 30 |
-
if word in semantic_map:
|
| 31 |
-
variations.extend(semantic_map[word])
|
| 32 |
-
|
| 33 |
-
# Add plurals and singulars
|
| 34 |
-
for word in words:
|
| 35 |
-
if word.endswith('s') and len(word) > 3:
|
| 36 |
-
variations.append(word[:-1]) # Remove 's' for singular
|
| 37 |
-
elif not word.endswith('s'):
|
| 38 |
-
variations.append(word + 's') # Add 's' for plural
|
| 39 |
-
|
| 40 |
-
return list(set(variations))
|
| 41 |
-
|
| 42 |
-
# --- Enhanced Readability Analysis ---
|
| 43 |
-
def analyze_text_advanced(text, keyword):
|
| 44 |
-
"""Enhanced readability analysis with interpretation"""
|
| 45 |
-
flesch_score = textstat.flesch_reading_ease(text)
|
| 46 |
-
gunning_fog = textstat.gunning_fog(text)
|
| 47 |
-
word_count = len(text.split())
|
| 48 |
-
sentence_count = textstat.sentence_count(text)
|
| 49 |
-
avg_sentence_length = word_count / sentence_count if sentence_count > 0 else 0
|
| 50 |
-
|
| 51 |
-
# Keyword analysis
|
| 52 |
-
keyword_count = text.lower().count(keyword.lower())
|
| 53 |
-
density = round((keyword_count / word_count) * 100, 2) if word_count > 0 else 0
|
| 54 |
-
|
| 55 |
-
# Readability interpretation
|
| 56 |
-
if flesch_score >= 90:
|
| 57 |
-
readability_level = "Very Easy (5th grade)"
|
| 58 |
-
elif flesch_score >= 80:
|
| 59 |
-
readability_level = "Easy (6th grade)"
|
| 60 |
-
elif flesch_score >= 70:
|
| 61 |
-
readability_level = "Fairly Easy (7th grade)"
|
| 62 |
-
elif flesch_score >= 60:
|
| 63 |
-
readability_level = "Standard (8th-9th grade)"
|
| 64 |
-
elif flesch_score >= 50:
|
| 65 |
-
readability_level = "Fairly Difficult (10th-12th grade)"
|
| 66 |
-
elif flesch_score >= 30:
|
| 67 |
-
readability_level = "Difficult (College level)"
|
| 68 |
-
else:
|
| 69 |
-
readability_level = "Very Difficult (Graduate level)"
|
| 70 |
-
|
| 71 |
-
return {
|
| 72 |
-
'flesch_score': flesch_score,
|
| 73 |
-
'gunning_fog': gunning_fog,
|
| 74 |
-
'readability_level': readability_level,
|
| 75 |
-
'word_count': word_count,
|
| 76 |
-
'sentence_count': sentence_count,
|
| 77 |
-
'avg_sentence_length': round(avg_sentence_length, 1),
|
| 78 |
-
'keyword_count': keyword_count,
|
| 79 |
-
'keyword_density': density
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
# --- Image Alt Text Generator ---
|
| 83 |
-
def generate_image_alt_texts(keyword, num_sections=5):
|
| 84 |
-
"""Generate alt text suggestions for images based on keyword"""
|
| 85 |
-
alt_texts = [
|
| 86 |
-
f"Infographic showing {keyword} overview",
|
| 87 |
-
f"Step-by-step guide for {keyword}",
|
| 88 |
-
f"Visual representation of {keyword} benefits",
|
| 89 |
-
f"Comparison chart related to {keyword}",
|
| 90 |
-
f"Real-world example of {keyword} in action"
|
| 91 |
-
]
|
| 92 |
-
return alt_texts[:num_sections]
|
| 93 |
-
|
| 94 |
-
# --- CTA Personalization ---
|
| 95 |
-
def generate_personalized_cta(emotion, tone, keyword):
|
| 96 |
-
"""Generate personalized CTAs based on emotion and tone"""
|
| 97 |
-
cta_templates = {
|
| 98 |
-
'Trust': {
|
| 99 |
-
'Friendly': f"Ready to explore {keyword}? Share your thoughts below!",
|
| 100 |
-
'Professional': f"Implement these {keyword} strategies and let us know your results.",
|
| 101 |
-
'Motivational': f"Take action on {keyword} today - your success story starts now!"
|
| 102 |
-
},
|
| 103 |
-
'Excitement': {
|
| 104 |
-
'Friendly': f"Excited about {keyword}? Tell us what you're planning to try first!",
|
| 105 |
-
'Professional': f"Ready to leverage {keyword}? Connect with our team for expert guidance.",
|
| 106 |
-
'Motivational': f"Don't wait - start your {keyword} journey today!"
|
| 107 |
-
},
|
| 108 |
-
'Curiosity': {
|
| 109 |
-
'Friendly': f"What's your experience with {keyword}? We'd love to hear from you!",
|
| 110 |
-
'Professional': f"Have questions about {keyword}? Our experts are here to help.",
|
| 111 |
-
'Motivational': f"Discover more about {keyword} - your next breakthrough awaits!"
|
| 112 |
-
},
|
| 113 |
-
'Confidence': {
|
| 114 |
-
'Friendly': f"Feel confident about {keyword} now? Share your success story!",
|
| 115 |
-
'Professional': f"Ready to implement {keyword} with confidence? Get started today.",
|
| 116 |
-
'Motivational': f"You have everything you need for {keyword} success - take the first step!"
|
| 117 |
-
},
|
| 118 |
-
'Inspiration': {
|
| 119 |
-
'Friendly': f"Feeling inspired about {keyword}? Let's continue the conversation!",
|
| 120 |
-
'Professional': f"Transform your approach to {keyword} with our proven methods.",
|
| 121 |
-
'Motivational': f"Your {keyword} transformation starts now - make it happen!"
|
| 122 |
-
}
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
return cta_templates.get(emotion, {}).get(tone, f"What are your thoughts on {keyword}? Share below!")
|
| 126 |
-
|
| 127 |
-
# --- Human Tone Enforcement ---
|
| 128 |
-
def create_human_tone_prompt(keyword, tone, emotion, pov):
|
| 129 |
-
"""Create prompts that enforce human-like writing"""
|
| 130 |
-
anti_ai_phrases = [
|
| 131 |
-
"Avoid starting with 'In conclusion', 'In summary', or 'To summarize'",
|
| 132 |
-
"Don't use phrases like 'delve into', 'dive deep', 'it's worth noting'",
|
| 133 |
-
"Skip generic openings like 'In today's digital world' or 'In this comprehensive guide'",
|
| 134 |
-
"Use contractions naturally (don't, won't, can't, you'll)",
|
| 135 |
-
"Write with personal experience and specific examples",
|
| 136 |
-
"Vary sentence length - mix short punchy sentences with longer explanatory ones"
|
| 137 |
-
]
|
| 138 |
-
|
| 139 |
-
human_elements = [
|
| 140 |
-
"Include personal anecdotes or relatable scenarios",
|
| 141 |
-
"Use conversational transitions like 'Here's the thing' or 'But wait, there's more'",
|
| 142 |
-
"Add rhetorical questions to engage readers",
|
| 143 |
-
"Include specific numbers, dates, or statistics when relevant",
|
| 144 |
-
"Use storytelling elements to make points memorable"
|
| 145 |
-
]
|
| 146 |
-
|
| 147 |
return f"""
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
if not api_key or not keyword or not plan:
|
| 179 |
-
return "Please provide API key, keyword, and SEO plan to generate content."
|
| 180 |
-
|
| 181 |
-
try:
|
| 182 |
-
genai.configure(api_key=api_key)
|
| 183 |
-
model = genai.GenerativeModel('gemini-1.5-pro')
|
| 184 |
-
except Exception as e:
|
| 185 |
-
return f"Error configuring API: {str(e)}"
|
| 186 |
-
|
| 187 |
-
# Generate semantic keywords
|
| 188 |
-
semantic_keywords = generate_semantic_keywords(keyword)
|
| 189 |
-
all_keywords = [keyword] + (target_keywords.split(',') if target_keywords else []) + semantic_keywords[:5]
|
| 190 |
-
|
| 191 |
-
# Auto-detect internal link suggestions
|
| 192 |
-
internal_links = ""
|
| 193 |
-
if links_input.strip():
|
| 194 |
-
for line in links_input.strip().splitlines():
|
| 195 |
-
if ":" in line:
|
| 196 |
-
anchor, url = line.split(":", 1)
|
| 197 |
-
internal_links += f'<p><strong>Related:</strong> <a href="{url.strip()}">{anchor.strip()}</a></p>\n'
|
| 198 |
-
|
| 199 |
-
# Generate personalized CTA if not provided
|
| 200 |
-
if not cta_text:
|
| 201 |
-
cta_text = generate_personalized_cta(emotion, tone, keyword)
|
| 202 |
-
|
| 203 |
-
# Human tone enforcement
|
| 204 |
-
human_tone_guide = create_human_tone_prompt(keyword, tone, emotion, pov)
|
| 205 |
-
|
| 206 |
-
# Compose the enhanced prompt
|
| 207 |
-
prompt = f"""
|
| 208 |
-
{human_tone_guide}
|
| 209 |
-
|
| 210 |
-
Generate a comprehensive, WordPress-optimized blog post with proper SEO structure.
|
| 211 |
-
|
| 212 |
-
ARTICLE REQUIREMENTS:
|
| 213 |
-
- Primary keyword: "{keyword}"
|
| 214 |
-
- Use these related keywords naturally: {', '.join(all_keywords)}
|
| 215 |
-
- Tone: {tone}, POV: {pov}, Emotion: {emotion}
|
| 216 |
-
- Include proper HTML structure for WordPress
|
| 217 |
-
- Length target: {length}
|
| 218 |
-
|
| 219 |
-
SEO STRUCTURE REQUIRED:
|
| 220 |
-
1. Compelling introduction (hook + keyword in first 100 words)
|
| 221 |
-
2. Multiple H2 sections covering different aspects
|
| 222 |
-
3. {"FAQ section with 3-5 questions" if include_faqs else ""}
|
| 223 |
-
4. Strong conclusion with the CTA
|
| 224 |
-
|
| 225 |
-
SEO PLAN TO FOLLOW:
|
| 226 |
-
{plan}
|
| 227 |
-
|
| 228 |
-
INTERNAL LINKS TO INCLUDE:
|
| 229 |
-
{links_input if links_input else "No internal links provided"}
|
| 230 |
-
|
| 231 |
-
FINAL CTA TO USE:
|
| 232 |
-
{cta_text}
|
| 233 |
-
|
| 234 |
-
OUTPUT FORMAT:
|
| 235 |
-
Use clean HTML suitable for WordPress. Include <h1>, <h2>, <h3>, <p>, <ul>, <li> tags as needed.
|
| 236 |
-
Make it engaging, informative, and naturally optimized for "{keyword}".
|
| 237 |
"""
|
| 238 |
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
return article
|
| 243 |
|
| 244 |
-
|
|
|
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
""
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
f"<p><strong>🎯 SEO Metrics:</strong></p>",
|
| 318 |
-
f"<p>• Keyword '{keyword}' appears {analysis['keyword_count']} times</p>",
|
| 319 |
-
f"<p>• Keyword Density: {analysis['keyword_density']}% (optimal: 1-2%)</p>",
|
| 320 |
-
f"<p>• Total Words: {analysis['word_count']}</p>",
|
| 321 |
-
f"<p>• Total Sentences: {analysis['sentence_count']}</p>",
|
| 322 |
-
f"</div>",
|
| 323 |
-
""
|
| 324 |
-
])
|
| 325 |
-
|
| 326 |
-
result_parts.extend([
|
| 327 |
-
"<!-- ✅ Semantic Keywords Used -->",
|
| 328 |
-
f"<p><strong>🔑 Keywords Integrated:</strong> {', '.join(all_keywords[:10])}</p>"
|
| 329 |
-
])
|
| 330 |
-
|
| 331 |
-
return '\n'.join(result_parts)
|
| 332 |
-
|
| 333 |
-
# --- Gradio UI ---
|
| 334 |
-
with gr.Blocks(css="#generate_button { background: #10b981 !important; color: white !important; }") as demo:
|
| 335 |
-
|
| 336 |
-
gr.Markdown("## 🧠 SeoPlan2Article v3 — Enhanced SEO-Optimized Blog Generator")
|
| 337 |
-
gr.Markdown("*Features: Human Tone Enforcement, Semantic SEO, Advanced Readability, Image Alt Texts, Personalized CTAs*")
|
| 338 |
-
|
| 339 |
-
with gr.Accordion("🔑 API & SEO Plan", open=True):
|
| 340 |
-
api_key_input_ui = gr.Textbox(label="Gemini API Key", type="password", placeholder="Paste your Gemini API key here...")
|
| 341 |
-
primary_keyword_ui = gr.Textbox(label="Primary Keyword", placeholder="e.g., fermented garlic benefits")
|
| 342 |
-
seo_plan_ui = gr.Textbox(label="SEO Plan or Outline", lines=10, placeholder="Paste detailed outline here...")
|
| 343 |
-
|
| 344 |
-
with gr.Row():
|
| 345 |
-
pov_ui = gr.Dropdown(label="📖 Point of View", choices=["First Person (I/We)", "Second Person (You/Your)", "Third Person (He/She/It/They)"], value="Second Person (You/Your)")
|
| 346 |
-
tone_ui = gr.Dropdown(label="🎨 Tone", choices=["Friendly", "Professional", "Witty", "Motivational", "Reassuring", "Curious"], value="Friendly")
|
| 347 |
-
length_ui = gr.Dropdown(label="🧾 Length", choices=["Short", "Standard", "Long", "Very Long"], value="Standard")
|
| 348 |
-
emotion_ui = gr.Dropdown(label="💬 Emotion", choices=["Trust", "Excitement", "Curiosity", "Confidence", "Inspiration"], value="Confidence")
|
| 349 |
-
|
| 350 |
-
with gr.Accordion("🔗 Keywords, Links, CTA", open=False):
|
| 351 |
-
target_keywords_ui = gr.Textbox(label="Extra Keywords (optional)", placeholder="Comma-separated")
|
| 352 |
-
links_to_include_ui = gr.Textbox(label="Internal/External Links (Optional)", placeholder="Format: Anchor: URL", lines=4)
|
| 353 |
-
cta_text_ui = gr.Textbox(label="Custom Call to Action (Leave blank for auto-generated)", placeholder="e.g., Try it yourself and share your tips!")
|
| 354 |
-
|
| 355 |
-
with gr.Accordion("⚙️ Enhanced Features", open=False):
|
| 356 |
-
include_titles_ui = gr.Checkbox(label="Generate 3 SEO Title Variations", value=True)
|
| 357 |
-
include_readability_ui = gr.Checkbox(label="Include Advanced Readability Analysis", value=True)
|
| 358 |
-
include_alt_texts_ui = gr.Checkbox(label="Generate Image Alt Text Suggestions", value=True)
|
| 359 |
-
include_faqs_ui = gr.Checkbox(label="Include FAQ Section", value=True)
|
| 360 |
-
|
| 361 |
-
submit_btn = gr.Button("✨ Generate Enhanced Article", elem_id="generate_button")
|
| 362 |
-
output_ui = gr.HTML("Your generated content will appear here...")
|
| 363 |
-
|
| 364 |
-
submit_btn.click(
|
| 365 |
-
fn=generate_article,
|
| 366 |
-
inputs=[
|
| 367 |
-
api_key_input_ui, primary_keyword_ui, seo_plan_ui, pov_ui, tone_ui,
|
| 368 |
-
length_ui, emotion_ui, include_titles_ui, include_readability_ui,
|
| 369 |
-
target_keywords_ui, links_to_include_ui, cta_text_ui, include_alt_texts_ui, include_faqs_ui
|
| 370 |
-
],
|
| 371 |
-
outputs=output_ui,
|
| 372 |
-
)
|
| 373 |
|
| 374 |
-
# --- Launch ---
|
| 375 |
if __name__ == "__main__":
|
| 376 |
-
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import google.generativeai as genai
|
| 3 |
+
import os
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
# ========== Gemini Prompt Builder ==========
|
| 6 |
+
def build_gemini_prompt(keyword, tone, pov, voice_style, additional_instructions):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
return f"""
|
| 8 |
+
Write a full-length SEO blog article in English optimized for the keyword: "{keyword}".
|
| 9 |
+
|
| 10 |
+
Tone: {tone}
|
| 11 |
+
Point of View: {pov}
|
| 12 |
+
Voice Style: {voice_style}
|
| 13 |
+
{f"Additional Instructions: {additional_instructions}" if additional_instructions else ""}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
- Include a compelling <title> and <meta description>.
|
| 17 |
+
- Use HTML tags: <h1>, <h2>, <p>, <ul>, <li>, <meta>, and <title>.
|
| 18 |
+
- Use short paragraphs (max 2-3 sentences).
|
| 19 |
+
- Include variations of the keyword (LSI).
|
| 20 |
+
- The article should be at least 700 words.
|
| 21 |
+
- Keep keyword density natural.
|
| 22 |
+
|
| 23 |
+
Structure:
|
| 24 |
+
- Title (inside <title>)
|
| 25 |
+
- Meta Description (max 160 characters)
|
| 26 |
+
- <h1> Main heading
|
| 27 |
+
- <h2> Subheadings with rich content
|
| 28 |
+
- <ul> Bullet points if applicable
|
| 29 |
+
- Natural conclusion encouraging engagement
|
| 30 |
+
|
| 31 |
+
Example format:
|
| 32 |
+
<title>Keyword Mastery in 2025</title>
|
| 33 |
+
<meta name=\"description\" content=\"Learn how to master keywords in 2025 for optimal SEO.\">
|
| 34 |
+
<h1>How to Master Keywords</h1>
|
| 35 |
+
<p>Keyword usage is evolving rapidly...</p>
|
| 36 |
+
|
| 37 |
+
Ready? Begin:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
"""
|
| 39 |
|
| 40 |
+
# ========== Async Blog Generator ==========
|
| 41 |
+
async def generate_blog(api_key, keyword, tone, pov, voice_style, instructions):
|
| 42 |
+
prompt = build_gemini_prompt(keyword, tone, pov, voice_style, instructions)
|
|
|
|
| 43 |
|
| 44 |
+
genai.configure(api_key=api_key)
|
| 45 |
+
model = genai.GenerativeModel("gemini-pro")
|
| 46 |
|
| 47 |
+
try:
|
| 48 |
+
response = await model.generate_content_async(prompt)
|
| 49 |
+
return response.text
|
| 50 |
+
except Exception as e:
|
| 51 |
+
return f"Error: {str(e)}"
|
| 52 |
+
|
| 53 |
+
# ========== Gradio UI ==========
|
| 54 |
+
def main():
|
| 55 |
+
with gr.Blocks(css=".gr-button {margin-top: 10px}") as demo:
|
| 56 |
+
gr.Markdown("""# 📝 Gemini SEO Blog Writer
|
| 57 |
+
Generate full HTML blog posts optimized for SEO, powered by Gemini.
|
| 58 |
+
""")
|
| 59 |
+
|
| 60 |
+
with gr.Row():
|
| 61 |
+
api_key = gr.Textbox(label="Gemini API Key", type="password")
|
| 62 |
+
keyword = gr.Textbox(label="Primary Keyword")
|
| 63 |
+
|
| 64 |
+
with gr.Row():
|
| 65 |
+
tone = gr.Dropdown(label="Tone", choices=["Neutral", "Professional", "Friendly", "Conversational"], value="Professional")
|
| 66 |
+
pov = gr.Dropdown(label="Point of View", choices=["First Person", "Second Person", "Third Person"], value="Third Person")
|
| 67 |
+
voice_style = gr.Dropdown(label="Voice Style", choices=["Formal", "Informal", "Storytelling"], value="Formal")
|
| 68 |
+
|
| 69 |
+
additional_instructions = gr.Textbox(label="Additional Instructions (optional)")
|
| 70 |
+
error_box = gr.Textbox(visible=False, label="Error", interactive=False)
|
| 71 |
+
|
| 72 |
+
generate_btn = gr.Button("✨ Generate Blog")
|
| 73 |
+
|
| 74 |
+
with gr.Accordion("📄 HTML Blog Output", open=True):
|
| 75 |
+
blog_output = gr.Textbox(label="Generated HTML", lines=20)
|
| 76 |
+
|
| 77 |
+
with gr.Accordion("🔍 Live HTML Preview", open=False):
|
| 78 |
+
blog_preview = gr.HTML()
|
| 79 |
+
|
| 80 |
+
download_btn = gr.Button("⬇️ Download HTML File")
|
| 81 |
+
file_output = gr.File()
|
| 82 |
+
|
| 83 |
+
# Generate blog content
|
| 84 |
+
def validate_inputs(api_key, keyword):
|
| 85 |
+
if not api_key:
|
| 86 |
+
return gr.Textbox.update(visible=True, value="API key is required"), None
|
| 87 |
+
if not keyword:
|
| 88 |
+
return gr.Textbox.update(visible=True, value="Keyword is required"), None
|
| 89 |
+
return gr.Textbox.update(visible=False), keyword
|
| 90 |
+
|
| 91 |
+
def sync_blog_preview(blog_text):
|
| 92 |
+
return blog_text
|
| 93 |
+
|
| 94 |
+
generate_btn.click(
|
| 95 |
+
fn=validate_inputs,
|
| 96 |
+
inputs=[api_key, keyword],
|
| 97 |
+
outputs=[error_box, keyword],
|
| 98 |
+
).then(
|
| 99 |
+
fn=generate_blog,
|
| 100 |
+
inputs=[api_key, keyword, tone, pov, voice_style, additional_instructions],
|
| 101 |
+
outputs=[blog_output],
|
| 102 |
+
).then(
|
| 103 |
+
fn=sync_blog_preview,
|
| 104 |
+
inputs=[blog_output],
|
| 105 |
+
outputs=[blog_preview],
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
# Export button logic
|
| 109 |
+
def export_html(content):
|
| 110 |
+
path = "/mnt/data/generated_blog.html"
|
| 111 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 112 |
+
f.write(content)
|
| 113 |
+
return path
|
| 114 |
+
|
| 115 |
+
download_btn.click(fn=export_html, inputs=[blog_output], outputs=[file_output])
|
| 116 |
+
|
| 117 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
|
|
|
| 119 |
if __name__ == "__main__":
|
| 120 |
+
main()
|