mroccuper commited on
Commit
293fdca
·
verified ·
1 Parent(s): c637756

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +110 -366
app.py CHANGED
@@ -1,376 +1,120 @@
1
  import gradio as gr
2
  import google.generativeai as genai
3
- import textstat
4
- import re
5
- import requests
6
- from urllib.parse import urlparse
7
 
8
- # --- Sanitize for f-string ---
9
- def sanitize_for_fstring(text):
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
- Write in a genuinely human {tone.lower()} tone with {emotion.lower()} emotion using {pov}.
149
-
150
- AVOID these AI-like patterns:
151
- {chr(10).join('- ' + phrase for phrase in anti_ai_phrases)}
152
-
153
- INCLUDE these human elements:
154
- {chr(10).join('- ' + element for element in human_elements)}
155
-
156
- Write like you're having a conversation with a friend who asked about {keyword}.
157
- """
158
-
159
- # --- Error Handling ---
160
- def safe_api_call(model, prompt, max_retries=3):
161
- """Safely call the API with error handling"""
162
- for attempt in range(max_retries):
163
- try:
164
- response = model.generate_content(prompt)
165
- if response.text:
166
- return response.text
167
- else:
168
- raise Exception("Empty response from API")
169
- except Exception as e:
170
- if attempt == max_retries - 1:
171
- return f"Error generating content: {str(e)}. Please check your API key and try again."
172
- continue
173
- return "Failed to generate content after multiple attempts."
174
-
175
- # --- Main Article Generation ---
176
- def generate_article(api_key, keyword, plan, pov, tone, length, emotion, show_titles, show_readability, target_keywords, links_input, cta_text, include_alt_texts, include_faqs):
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
- # Generate main article
240
- article = safe_api_call(model, prompt)
241
- if "Error" in article:
242
- return article
243
 
244
- article = sanitize_for_fstring(article)
 
245
 
246
- # Generate meta description
247
- meta_desc_prompt = f"""
248
- Write a compelling meta description for a blog post about '{keyword}'.
249
- Requirements:
250
- - Under 160 characters
251
- - Include the primary keyword
252
- - Action-oriented and click-worthy
253
- - Match the {emotion.lower()} emotion
254
-
255
- Content summary: {article[:500]}...
256
- """
257
-
258
- meta_description = safe_api_call(model, meta_desc_prompt)
259
- meta_description = sanitize_for_fstring(meta_description.strip().replace("\n", " "))[:160]
260
-
261
- # SEO Title Variations
262
- title_prompt = f"""
263
- Create 3 SEO-optimized blog post titles for '{keyword}' that:
264
- - Include the primary keyword
265
- - Evoke {emotion.lower()} emotion
266
- - Are under 60 characters
267
- - Use power words and numbers where appropriate
268
- - Match {tone.lower()} tone
269
-
270
- Format as a numbered list.
271
- """
272
- titles = safe_api_call(model, title_prompt)
273
-
274
- # Advanced readability analysis
275
- analysis = analyze_text_advanced(article, keyword)
276
-
277
- # Generate image alt texts
278
- alt_texts = generate_image_alt_texts(keyword) if include_alt_texts else []
279
-
280
- # Build final output
281
- result_parts = [
282
- "<!-- WordPress Blog Article Output -->",
283
- f"<h1>{keyword.title()}</h1>",
284
- article,
285
- internal_links,
286
- "",
287
- "<!-- Rank Math Meta Description -->",
288
- f"<p><strong>Meta Description:</strong> {meta_description}</p>",
289
- ""
290
- ]
291
-
292
- if show_titles:
293
- result_parts.extend([
294
- "<!-- ✅ SEO Title Variations -->",
295
- f"<p><strong>Title Variations:</strong><br>{titles.replace(chr(10), '<br>')}</p>",
296
- ""
297
- ])
298
-
299
- if include_alt_texts and alt_texts:
300
- result_parts.extend([
301
- "<!-- ✅ Image Alt Text Suggestions -->",
302
- "<p><strong>Suggested Image Alt Texts:</strong></p>",
303
- "<ul>"
304
- ])
305
- for alt in alt_texts:
306
- result_parts.append(f"<li>{alt}</li>")
307
- result_parts.extend(["</ul>", ""])
308
-
309
- if show_readability:
310
- result_parts.extend([
311
- "<!-- ✅ Enhanced Readability & SEO Analysis -->",
312
- f"<div style='background:#f8f9fa;padding:15px;border-radius:5px;'>",
313
- f"<p><strong>📊 Readability Analysis:</strong></p>",
314
- f"<p>• Flesch Reading Ease: {analysis['flesch_score']:.1f} ({analysis['readability_level']})</p>",
315
- f"<p>• Gunning Fog Index: {analysis['gunning_fog']:.1f}</p>",
316
- f"<p>• Average Sentence Length: {analysis['avg_sentence_length']} words</p>",
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
- demo.launch(debug=True, show_error=True)
 
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()