Spaces:
Sleeping
Sleeping
Update blog_generator.py
Browse files- blog_generator.py +1 -214
blog_generator.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1 |
import requests
|
2 |
-
import aiohttp
|
3 |
from typing import Dict, List
|
4 |
import re
|
5 |
import json
|
6 |
-
from web_scraper import get_cover_image
|
7 |
|
8 |
class BlogGenerator:
|
9 |
def __init__(self, openai_key: str, openrouter_key: str, serpapi_key: str = None):
|
@@ -25,20 +24,6 @@ class BlogGenerator:
|
|
25 |
print(f"Error in get_cover_image: {e}")
|
26 |
return None
|
27 |
|
28 |
-
async def get_cover_image_async(self, title: str) -> str:
|
29 |
-
"""Get a cover image URL for the blog post asynchronously"""
|
30 |
-
try:
|
31 |
-
# Use our custom image search function
|
32 |
-
image_url = await get_cover_image_async(title)
|
33 |
-
if not image_url:
|
34 |
-
print("No image found, trying with modified query...")
|
35 |
-
# Try again with a more generic query if specific title fails
|
36 |
-
image_url = await get_cover_image_async(title + " high quality cover image")
|
37 |
-
return image_url
|
38 |
-
except Exception as e:
|
39 |
-
print(f"Error in get_cover_image_async: {e}")
|
40 |
-
return None
|
41 |
-
|
42 |
def create_detailed_plan(self, cluster_data: Dict, preliminary_plan: str, research: str) -> str:
|
43 |
try:
|
44 |
response = requests.post(
|
@@ -107,46 +92,6 @@ Create the detailed plan."""
|
|
107 |
print(f"Error in create_detailed_plan: {e}")
|
108 |
raise
|
109 |
|
110 |
-
async def create_detailed_plan_async(self, cluster_data: Dict, plan: str, research: str) -> str:
|
111 |
-
"""Create a detailed plan for the blog post asynchronously"""
|
112 |
-
try:
|
113 |
-
async with aiohttp.ClientSession() as session:
|
114 |
-
async with session.post(
|
115 |
-
'https://openrouter.ai/api/v1/chat/completions',
|
116 |
-
headers={
|
117 |
-
'Authorization': f'Bearer {self.openrouter_key}',
|
118 |
-
'HTTP-Referer': 'http://localhost:5001',
|
119 |
-
'X-Title': 'Blog Generator'
|
120 |
-
},
|
121 |
-
json={
|
122 |
-
'model': 'google/gemini-2.0-flash-thinking-exp:free',
|
123 |
-
'messages': [{
|
124 |
-
'role': 'user',
|
125 |
-
'content': f"""Based on the preliminary plan and research, create a detailed outline for the blog post.
|
126 |
-
|
127 |
-
Keywords: {cluster_data['Keywords']}
|
128 |
-
Primary Keyword: {cluster_data['Primary Keyword']}
|
129 |
-
Search Intent: {cluster_data['Intent']}
|
130 |
-
|
131 |
-
Preliminary Plan:
|
132 |
-
{plan}
|
133 |
-
|
134 |
-
Research:
|
135 |
-
{research}
|
136 |
-
|
137 |
-
Create a detailed outline that incorporates the research findings and ensures all keywords are used naturally."""
|
138 |
-
}]
|
139 |
-
}
|
140 |
-
) as response:
|
141 |
-
if response.status != 200:
|
142 |
-
raise Exception(f"OpenRouter API error: {await response.text()}")
|
143 |
-
|
144 |
-
response_data = await response.json()
|
145 |
-
return response_data['choices'][0]['message']['content']
|
146 |
-
except Exception as e:
|
147 |
-
print(f"Error in create_detailed_plan_async: {e}")
|
148 |
-
raise
|
149 |
-
|
150 |
def write_blog_post(self, detailed_plan: str, cluster_data: Dict) -> str:
|
151 |
try:
|
152 |
response = requests.post(
|
@@ -209,43 +154,6 @@ Write the blog post."""
|
|
209 |
print(f"Error in write_blog_post: {e}")
|
210 |
raise
|
211 |
|
212 |
-
async def write_blog_post_async(self, detailed_plan: str, cluster_data: Dict) -> str:
|
213 |
-
"""Write the blog post content asynchronously"""
|
214 |
-
try:
|
215 |
-
async with aiohttp.ClientSession() as session:
|
216 |
-
async with session.post(
|
217 |
-
'https://openrouter.ai/api/v1/chat/completions',
|
218 |
-
headers={
|
219 |
-
'Authorization': f'Bearer {self.openrouter_key}',
|
220 |
-
'HTTP-Referer': 'http://localhost:5001',
|
221 |
-
'X-Title': 'Blog Generator'
|
222 |
-
},
|
223 |
-
json={
|
224 |
-
'model': 'google/gemini-2.0-flash-thinking-exp:free',
|
225 |
-
'messages': [{
|
226 |
-
'role': 'user',
|
227 |
-
'content': f"""Write a comprehensive blog post based on the detailed plan.
|
228 |
-
|
229 |
-
Keywords: {cluster_data['Keywords']}
|
230 |
-
Primary Keyword: {cluster_data['Primary Keyword']}
|
231 |
-
Search Intent: {cluster_data['Intent']}
|
232 |
-
|
233 |
-
Detailed Plan:
|
234 |
-
{detailed_plan}
|
235 |
-
|
236 |
-
Write a high-quality blog post that naturally incorporates all keywords and satisfies the search intent."""
|
237 |
-
}]
|
238 |
-
}
|
239 |
-
) as response:
|
240 |
-
if response.status != 200:
|
241 |
-
raise Exception(f"OpenRouter API error: {await response.text()}")
|
242 |
-
|
243 |
-
response_data = await response.json()
|
244 |
-
return response_data['choices'][0]['message']['content']
|
245 |
-
except Exception as e:
|
246 |
-
print(f"Error in write_blog_post_async: {e}")
|
247 |
-
raise
|
248 |
-
|
249 |
def add_internal_links(self, blog_content: str, previous_posts: List[Dict]) -> str:
|
250 |
try:
|
251 |
response = requests.post(
|
@@ -312,42 +220,6 @@ Your output must be the complete blog post with new internal links added. Don't
|
|
312 |
# If there's an error, return the original content
|
313 |
return blog_content
|
314 |
|
315 |
-
async def add_internal_links_async(self, content: str, previous_posts: List[Dict]) -> str:
|
316 |
-
"""Add internal links to the blog post content asynchronously"""
|
317 |
-
try:
|
318 |
-
async with aiohttp.ClientSession() as session:
|
319 |
-
async with session.post(
|
320 |
-
'https://openrouter.ai/api/v1/chat/completions',
|
321 |
-
headers={
|
322 |
-
'Authorization': f'Bearer {self.openrouter_key}',
|
323 |
-
'HTTP-Referer': 'http://localhost:5001',
|
324 |
-
'X-Title': 'Blog Generator'
|
325 |
-
},
|
326 |
-
json={
|
327 |
-
'model': 'google/gemini-2.0-flash-thinking-exp:free',
|
328 |
-
'messages': [{
|
329 |
-
'role': 'user',
|
330 |
-
'content': f"""Add relevant internal links to the blog post content.
|
331 |
-
|
332 |
-
Current Content:
|
333 |
-
{content}
|
334 |
-
|
335 |
-
Previous Posts:
|
336 |
-
{json.dumps(previous_posts, indent=2)}
|
337 |
-
|
338 |
-
Add internal links where relevant, maintaining natural flow and readability."""
|
339 |
-
}]
|
340 |
-
}
|
341 |
-
) as response:
|
342 |
-
if response.status != 200:
|
343 |
-
raise Exception(f"OpenRouter API error: {await response.text()}")
|
344 |
-
|
345 |
-
response_data = await response.json()
|
346 |
-
return response_data['choices'][0]['message']['content']
|
347 |
-
except Exception as e:
|
348 |
-
print(f"Error in add_internal_links_async: {e}")
|
349 |
-
raise
|
350 |
-
|
351 |
def convert_to_html(self, blog_content: str, image_url: str = None) -> str:
|
352 |
try:
|
353 |
# First replace newlines with <br> tags
|
@@ -418,41 +290,6 @@ Blog post content:
|
|
418 |
# If there's an error, return the original content
|
419 |
return blog_content
|
420 |
|
421 |
-
async def convert_to_html_async(self, content: str, cover_image_url: str) -> str:
|
422 |
-
"""Convert markdown content to HTML asynchronously"""
|
423 |
-
try:
|
424 |
-
async with aiohttp.ClientSession() as session:
|
425 |
-
async with session.post(
|
426 |
-
'https://openrouter.ai/api/v1/chat/completions',
|
427 |
-
headers={
|
428 |
-
'Authorization': f'Bearer {self.openrouter_key}',
|
429 |
-
'HTTP-Referer': 'http://localhost:5001',
|
430 |
-
'X-Title': 'Blog Generator'
|
431 |
-
},
|
432 |
-
json={
|
433 |
-
'model': 'google/gemini-2.0-flash-thinking-exp:free',
|
434 |
-
'messages': [{
|
435 |
-
'role': 'user',
|
436 |
-
'content': f"""Convert this markdown content to clean, semantic HTML.
|
437 |
-
|
438 |
-
Markdown Content:
|
439 |
-
{content}
|
440 |
-
|
441 |
-
Cover Image URL: {cover_image_url}
|
442 |
-
|
443 |
-
Convert to HTML with proper semantic structure and include the cover image."""
|
444 |
-
}]
|
445 |
-
}
|
446 |
-
) as response:
|
447 |
-
if response.status != 200:
|
448 |
-
raise Exception(f"OpenRouter API error: {await response.text()}")
|
449 |
-
|
450 |
-
response_data = await response.json()
|
451 |
-
return response_data['choices'][0]['message']['content']
|
452 |
-
except Exception as e:
|
453 |
-
print(f"Error in convert_to_html_async: {e}")
|
454 |
-
raise
|
455 |
-
|
456 |
def generate_metadata(self, blog_content: str, primary_keyword: str, cluster_data: Dict) -> Dict:
|
457 |
try:
|
458 |
# Generate slug
|
@@ -600,54 +437,4 @@ Your output must only be the meta description and nothing else."""
|
|
600 |
}
|
601 |
except Exception as e:
|
602 |
print(f"Error in generate_metadata: {e}")
|
603 |
-
raise
|
604 |
-
|
605 |
-
async def generate_metadata_async(self, content: str, primary_keyword: str, cluster_data: Dict) -> Dict:
|
606 |
-
"""Generate metadata for the blog post asynchronously"""
|
607 |
-
try:
|
608 |
-
async with aiohttp.ClientSession() as session:
|
609 |
-
async with session.post(
|
610 |
-
'https://openrouter.ai/api/v1/chat/completions',
|
611 |
-
headers={
|
612 |
-
'Authorization': f'Bearer {self.openrouter_key}',
|
613 |
-
'HTTP-Referer': 'http://localhost:5001',
|
614 |
-
'X-Title': 'Blog Generator'
|
615 |
-
},
|
616 |
-
json={
|
617 |
-
'model': 'google/gemini-2.0-flash-thinking-exp:free',
|
618 |
-
'messages': [{
|
619 |
-
'role': 'user',
|
620 |
-
'content': f"""Generate SEO metadata for this blog post.
|
621 |
-
|
622 |
-
Content:
|
623 |
-
{content}
|
624 |
-
|
625 |
-
Primary Keyword: {primary_keyword}
|
626 |
-
Keywords: {cluster_data['Keywords']}
|
627 |
-
Search Intent: {cluster_data['Intent']}
|
628 |
-
|
629 |
-
Generate a title, slug, and meta description optimized for SEO."""
|
630 |
-
}]
|
631 |
-
}
|
632 |
-
) as response:
|
633 |
-
if response.status != 200:
|
634 |
-
raise Exception(f"OpenRouter API error: {await response.text()}")
|
635 |
-
|
636 |
-
response_data = await response.json()
|
637 |
-
metadata_text = response_data['choices'][0]['message']['content']
|
638 |
-
|
639 |
-
# Parse the metadata text into a dictionary
|
640 |
-
metadata = {}
|
641 |
-
for line in metadata_text.split('\n'):
|
642 |
-
if ':' in line:
|
643 |
-
key, value = line.split(':', 1)
|
644 |
-
metadata[key.strip().lower()] = value.strip()
|
645 |
-
|
646 |
-
return {
|
647 |
-
'title': metadata.get('title', ''),
|
648 |
-
'slug': metadata.get('slug', ''),
|
649 |
-
'meta_description': metadata.get('meta description', '')
|
650 |
-
}
|
651 |
-
except Exception as e:
|
652 |
-
print(f"Error in generate_metadata_async: {e}")
|
653 |
raise
|
|
|
1 |
import requests
|
|
|
2 |
from typing import Dict, List
|
3 |
import re
|
4 |
import json
|
5 |
+
from web_scraper import get_cover_image
|
6 |
|
7 |
class BlogGenerator:
|
8 |
def __init__(self, openai_key: str, openrouter_key: str, serpapi_key: str = None):
|
|
|
24 |
print(f"Error in get_cover_image: {e}")
|
25 |
return None
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
def create_detailed_plan(self, cluster_data: Dict, preliminary_plan: str, research: str) -> str:
|
28 |
try:
|
29 |
response = requests.post(
|
|
|
92 |
print(f"Error in create_detailed_plan: {e}")
|
93 |
raise
|
94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
def write_blog_post(self, detailed_plan: str, cluster_data: Dict) -> str:
|
96 |
try:
|
97 |
response = requests.post(
|
|
|
154 |
print(f"Error in write_blog_post: {e}")
|
155 |
raise
|
156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
def add_internal_links(self, blog_content: str, previous_posts: List[Dict]) -> str:
|
158 |
try:
|
159 |
response = requests.post(
|
|
|
220 |
# If there's an error, return the original content
|
221 |
return blog_content
|
222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
def convert_to_html(self, blog_content: str, image_url: str = None) -> str:
|
224 |
try:
|
225 |
# First replace newlines with <br> tags
|
|
|
290 |
# If there's an error, return the original content
|
291 |
return blog_content
|
292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
def generate_metadata(self, blog_content: str, primary_keyword: str, cluster_data: Dict) -> Dict:
|
294 |
try:
|
295 |
# Generate slug
|
|
|
437 |
}
|
438 |
except Exception as e:
|
439 |
print(f"Error in generate_metadata: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
raise
|