AI blog writer & blog metadata updates & improvements
This commit is contained in:
@@ -2,8 +2,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ..gpt_providers.openai_chat_completion import openai_chatgpt
|
from ..gpt_providers.text_generation.openai_text_gen import openai_text_generation
|
||||||
from ..gpt_providers.gemini_pro_text import gemini_text_response
|
from ..gpt_providers.text_generation.gemini_pro_text import gemini_text_generation
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
logger.remove()
|
logger.remove()
|
||||||
|
|||||||
@@ -25,25 +25,12 @@ from ..ai_web_researcher.gpt_online_researcher import (
|
|||||||
do_tavily_ai_search as gpt_do_tavily_ai_search,
|
do_tavily_ai_search as gpt_do_tavily_ai_search,
|
||||||
do_metaphor_ai_research, do_google_pytrends_analysis)
|
do_metaphor_ai_research, do_google_pytrends_analysis)
|
||||||
from .blog_from_google_serp import write_blog_google_serp, blog_with_research
|
from .blog_from_google_serp import write_blog_google_serp, blog_with_research
|
||||||
from ..ai_web_researcher.you_web_reseacher import get_rag_results, search_ydc_index
|
|
||||||
from ..blog_metadata.get_blog_metadata import blog_metadata
|
from ..blog_metadata.get_blog_metadata import blog_metadata
|
||||||
from ..blog_postprocessing.save_blog_to_file import save_blog_to_file
|
from ..blog_postprocessing.save_blog_to_file import save_blog_to_file
|
||||||
from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||||
|
from ..ai_seo_tools.content_title_generator import generate_blog_titles
|
||||||
|
from ..ai_seo_tools.meta_desc_generator import generate_blog_metadesc
|
||||||
# Function to convert text to speech and save as an audio file
|
from ..ai_seo_tools.seo_structured_data import ai_structured_data
|
||||||
def text_to_speech(text, lang='en'):
|
|
||||||
tts = gTTS(text=text, lang=lang)
|
|
||||||
tts.save("output.mp3")
|
|
||||||
return "output.mp3"
|
|
||||||
|
|
||||||
|
|
||||||
# Function to get audio file as a downloadable link
|
|
||||||
def get_audio_file(audio_file):
|
|
||||||
with open(audio_file, "rb") as file:
|
|
||||||
data = file.read()
|
|
||||||
b64_data = base64.b64encode(data).decode()
|
|
||||||
return f'<a href="data:audio/mp3;base64,{b64_data}" download="output.mp3">Download audio file</a>'
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_parameters(search_params=None, blog_params=None):
|
def initialize_parameters(search_params=None, blog_params=None):
|
||||||
@@ -285,21 +272,22 @@ def generate_blog_metadata(blog_markdown_str, search_keywords, status):
|
|||||||
status: Streamlit status object
|
status: Streamlit status object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (blog_title, blog_meta_desc, blog_tags, blog_categories)
|
tuple: (blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug)
|
||||||
"""
|
"""
|
||||||
status.update(label="🔍 Generating title, meta description, tags, and categories...")
|
status.update(label="🔍 Generating title, meta description, tags, categories, hashtags, and slug...")
|
||||||
try:
|
try:
|
||||||
blog_title, blog_meta_desc, blog_tags, blog_categories = asyncio.run(blog_metadata(blog_markdown_str))
|
# Get all 6 metadata values from blog_metadata
|
||||||
|
blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug = asyncio.run(blog_metadata(blog_markdown_str))
|
||||||
status.update(label="✅ Generated blog metadata successfully")
|
status.update(label="✅ Generated blog metadata successfully")
|
||||||
return blog_title, blog_meta_desc, blog_tags, blog_categories
|
return blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
st.error(f"Failed to get blog metadata: {err}")
|
st.error(f"Failed to get blog metadata: {err}")
|
||||||
logger.error(f"Failed to get blog metadata: {err}")
|
logger.error(f"Failed to get blog metadata: {err}")
|
||||||
status.update(label="❌ Failed to get blog metadata", state="error")
|
status.update(label="❌ Failed to get blog metadata", state="error")
|
||||||
return None, None, None, None
|
return None, None, None, None, None, None
|
||||||
|
|
||||||
|
|
||||||
def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status):
|
def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status, blog_tags=None):
|
||||||
"""
|
"""
|
||||||
Generate a featured image for the blog.
|
Generate a featured image for the blog.
|
||||||
|
|
||||||
@@ -308,6 +296,7 @@ def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status):
|
|||||||
blog_meta_desc (str): Blog meta description
|
blog_meta_desc (str): Blog meta description
|
||||||
blog_markdown_str (str): Blog content
|
blog_markdown_str (str): Blog content
|
||||||
status: Streamlit status object
|
status: Streamlit status object
|
||||||
|
blog_tags (list, optional): Blog tags to use for image prompt enhancement
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to the generated image or None if generation failed
|
str: Path to the generated image or None if generation failed
|
||||||
@@ -337,8 +326,17 @@ def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status):
|
|||||||
logger.info(f"Generating image with prompt: {text_to_image}")
|
logger.info(f"Generating image with prompt: {text_to_image}")
|
||||||
status.update(label=f"🖼️ Creating image with prompt: \"{text_to_image[:50]}...\"")
|
status.update(label=f"🖼️ Creating image with prompt: \"{text_to_image[:50]}...\"")
|
||||||
|
|
||||||
# Attempt image generation
|
# Extract blog tags if available
|
||||||
generated_image_filepath = generate_image(text_to_image)
|
blog_tags_list = blog_tags if isinstance(blog_tags, list) else []
|
||||||
|
|
||||||
|
# Attempt image generation with all available parameters
|
||||||
|
generated_image_filepath = generate_image(
|
||||||
|
user_prompt=text_to_image,
|
||||||
|
title=blog_title,
|
||||||
|
description=blog_meta_desc,
|
||||||
|
tags=blog_tags_list,
|
||||||
|
content=blog_markdown_str[:2000] # Limit content length to avoid too large payloads
|
||||||
|
)
|
||||||
|
|
||||||
# If first attempt failed, try with a simplified prompt
|
# If first attempt failed, try with a simplified prompt
|
||||||
if not generated_image_filepath:
|
if not generated_image_filepath:
|
||||||
@@ -347,7 +345,13 @@ def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status):
|
|||||||
|
|
||||||
# Create a simpler prompt
|
# Create a simpler prompt
|
||||||
simplified_prompt = " ".join(text_to_image.split()[:10])
|
simplified_prompt = " ".join(text_to_image.split()[:10])
|
||||||
generated_image_filepath = generate_image(simplified_prompt)
|
generated_image_filepath = generate_image(
|
||||||
|
user_prompt=simplified_prompt,
|
||||||
|
title=blog_title,
|
||||||
|
description=blog_meta_desc,
|
||||||
|
tags=blog_tags_list,
|
||||||
|
content=blog_markdown_str[:1000] # Use even shorter content for the retry
|
||||||
|
)
|
||||||
|
|
||||||
if generated_image_filepath:
|
if generated_image_filepath:
|
||||||
status.update(label="✅ Successfully generated featured image")
|
status.update(label="✅ Successfully generated featured image")
|
||||||
@@ -363,7 +367,7 @@ def generate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, status):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str):
|
def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, blog_tags=None):
|
||||||
"""
|
"""
|
||||||
Regenerate a blog image on demand.
|
Regenerate a blog image on demand.
|
||||||
|
|
||||||
@@ -371,6 +375,7 @@ def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str):
|
|||||||
blog_title (str): Blog title
|
blog_title (str): Blog title
|
||||||
blog_meta_desc (str): Blog meta description
|
blog_meta_desc (str): Blog meta description
|
||||||
blog_markdown_str (str): Blog content
|
blog_markdown_str (str): Blog content
|
||||||
|
blog_tags (list, optional): Blog tags to use for image prompt enhancement
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to the generated image or None if generation failed
|
str: Path to the generated image or None if generation failed
|
||||||
@@ -390,8 +395,17 @@ def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str):
|
|||||||
|
|
||||||
status.update(label=f"🖼️ Generating new image with prompt: \"{prompt}\"")
|
status.update(label=f"🖼️ Generating new image with prompt: \"{prompt}\"")
|
||||||
|
|
||||||
# Generate the image
|
# Extract any tags if available - will be passed as empty list otherwise
|
||||||
generated_image_filepath = generate_image(prompt)
|
blog_tags_list = blog_tags if isinstance(blog_tags, list) else []
|
||||||
|
|
||||||
|
# Generate the image with all parameters
|
||||||
|
generated_image_filepath = generate_image(
|
||||||
|
user_prompt=prompt,
|
||||||
|
title=blog_title,
|
||||||
|
description=blog_meta_desc,
|
||||||
|
tags=blog_tags_list,
|
||||||
|
content=blog_markdown_str[:2000] # Limit content length to avoid too large payloads
|
||||||
|
)
|
||||||
|
|
||||||
if generated_image_filepath:
|
if generated_image_filepath:
|
||||||
status.update(label="✅ Successfully generated new image", state="complete")
|
status.update(label="✅ Successfully generated new image", state="complete")
|
||||||
@@ -407,7 +421,7 @@ def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def save_blog_content(blog_markdown_str, blog_title, blog_meta_desc, blog_tags, blog_categories, generated_image_filepath, status):
|
def save_blog_content(blog_markdown_str, blog_title, blog_meta_desc, blog_tags, blog_categories, generated_image_filepath, status, blog_hashtags=None, blog_slug=None):
|
||||||
"""
|
"""
|
||||||
Save the blog content to a file.
|
Save the blog content to a file.
|
||||||
|
|
||||||
@@ -419,6 +433,8 @@ def save_blog_content(blog_markdown_str, blog_title, blog_meta_desc, blog_tags,
|
|||||||
blog_categories (list): Blog categories
|
blog_categories (list): Blog categories
|
||||||
generated_image_filepath (str): Path to the generated image
|
generated_image_filepath (str): Path to the generated image
|
||||||
status: Streamlit status object
|
status: Streamlit status object
|
||||||
|
blog_hashtags (str, optional): Social media hashtags
|
||||||
|
blog_slug (str, optional): SEO-friendly URL slug
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to the saved file or None if saving failed
|
str: Path to the saved file or None if saving failed
|
||||||
@@ -632,51 +648,172 @@ def write_blog_from_keywords(search_keywords, url=None, search_params=None, blog
|
|||||||
with st.status("Enhancing content...", expanded=True) as status:
|
with st.status("Enhancing content...", expanded=True) as status:
|
||||||
# Generate metadata
|
# Generate metadata
|
||||||
status.update(label="🏷️ Generating SEO metadata (title, description, tags)...")
|
status.update(label="🏷️ Generating SEO metadata (title, description, tags)...")
|
||||||
blog_title, blog_meta_desc, blog_tags, blog_categories = generate_blog_metadata(
|
blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug = generate_blog_metadata(
|
||||||
blog_markdown_str, search_keywords, status
|
blog_markdown_str, search_keywords, status
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check if there are updated values in session state
|
||||||
|
if 'blog_title' in st.session_state:
|
||||||
|
blog_title = st.session_state.blog_title
|
||||||
|
status.update(label=f"✅ Using refined title: \"{blog_title}\"")
|
||||||
|
|
||||||
|
if 'blog_meta_desc' in st.session_state:
|
||||||
|
blog_meta_desc = st.session_state.blog_meta_desc
|
||||||
|
status.update(label=f"✅ Using refined meta description")
|
||||||
|
|
||||||
if blog_title and blog_meta_desc:
|
if blog_title and blog_meta_desc:
|
||||||
status.update(label=f"✅ Generated metadata: \"{blog_title}\"")
|
status.update(label=f"✅ Generated metadata: \"{blog_title}\"")
|
||||||
|
|
||||||
# Generate featured image
|
# Generate featured image
|
||||||
status.update(label="🖼️ Creating featured image...")
|
status.update(label="🖼️ Creating featured image...")
|
||||||
generated_image_filepath = generate_blog_image(
|
generated_image_filepath = generate_blog_image(
|
||||||
blog_title, blog_meta_desc, blog_markdown_str, status
|
blog_title, blog_meta_desc, blog_markdown_str, status, blog_tags
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save blog content to file
|
# Save blog content to file
|
||||||
status.update(label="💾 Saving blog content...")
|
status.update(label="💾 Saving blog content...")
|
||||||
saved_blog_to_file = save_blog_content(
|
saved_blog_to_file = save_blog_content(
|
||||||
blog_markdown_str, blog_title, blog_meta_desc, blog_tags,
|
blog_markdown_str, blog_title, blog_meta_desc, blog_tags,
|
||||||
blog_categories, generated_image_filepath, status
|
blog_categories, generated_image_filepath, status, blog_hashtags, blog_slug
|
||||||
)
|
)
|
||||||
|
|
||||||
status.update(label="✅ Content enhancement complete", state="complete")
|
status.update(label="✅ Content enhancement complete", state="complete")
|
||||||
else:
|
else:
|
||||||
status.update(label="⚠️ Metadata generation had issues, using simplified format", state="warning")
|
status.update(label="⚠️ Metadata generation had issues, using simplified format", state="warning")
|
||||||
|
|
||||||
# STEP 4: Final presentation
|
|
||||||
update_progress(4, 5, "Preparing final blog presentation")
|
|
||||||
|
|
||||||
# Now display the final blog content
|
|
||||||
with final_content_placeholder.container():
|
|
||||||
st.markdown("---")
|
|
||||||
st.header("📝 Generated Blog Content")
|
|
||||||
|
|
||||||
# Display metadata
|
|
||||||
if blog_title:
|
|
||||||
st.title(blog_title)
|
|
||||||
|
|
||||||
if blog_meta_desc:
|
|
||||||
st.markdown(f"*{blog_meta_desc}*")
|
|
||||||
|
|
||||||
if blog_tags:
|
# Add buttons in columns for refining metadata
|
||||||
st.markdown(f"**Tags:** {', '.join(blog_tags)}")
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
refine_title_button = st.button("🔄 Refine Blog Title", use_container_width=True)
|
||||||
|
with col2:
|
||||||
|
refine_meta_button = st.button("🔄 Refine Meta Description", use_container_width=True)
|
||||||
|
|
||||||
if blog_categories:
|
# Add a row for structured data
|
||||||
st.markdown(f"**Categories:** {', '.join(blog_categories)}")
|
st.markdown("---")
|
||||||
|
structured_data_col1, structured_data_col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with structured_data_col1:
|
||||||
|
# Educational popover explaining why rich snippets are important
|
||||||
|
with st.expander("ℹ️ Why Rich Snippets Are Important for SEO"):
|
||||||
|
st.markdown("""
|
||||||
|
### Rich Snippets: Boosting Your SEO and Click-Through Rates
|
||||||
|
|
||||||
|
**What are Rich Snippets?**
|
||||||
|
|
||||||
|
Rich snippets are enhanced search results that display additional information directly in search engine results pages (SERPs). They're created using structured data markup (JSON-LD) that helps search engines understand your content better.
|
||||||
|
|
||||||
|
**Why are they important?**
|
||||||
|
|
||||||
|
1. **Increased Visibility**: Rich snippets stand out in search results with stars, images, and additional information
|
||||||
|
|
||||||
|
2. **Higher Click-Through Rates (CTR)**: Studies show rich snippets can increase CTR by 30-150%
|
||||||
|
|
||||||
|
3. **Improved SEO**: They help search engines understand your content better, potentially improving rankings
|
||||||
|
|
||||||
|
4. **Enhanced User Experience**: Users can see key information before clicking, leading to more qualified traffic
|
||||||
|
|
||||||
|
5. **Mobile-Friendly**: Rich snippets are especially effective on mobile searches
|
||||||
|
|
||||||
|
**Common types of rich snippets include:**
|
||||||
|
- Articles/Blogs (with author, date, image)
|
||||||
|
- Products (with ratings, price, availability)
|
||||||
|
- Recipes (with cooking time, ratings, calories)
|
||||||
|
- Events (with date, location, ticket info)
|
||||||
|
- Local Business (with address, hours, ratings)
|
||||||
|
|
||||||
|
Adding structured data to your content is a powerful SEO technique that requires minimal effort but provides significant benefits.
|
||||||
|
""")
|
||||||
|
|
||||||
|
with structured_data_col2:
|
||||||
|
# Button to generate rich snippet
|
||||||
|
generate_snippet_button = st.button("📊 Generate Rich Snippet", use_container_width=True)
|
||||||
|
|
||||||
|
# Dialog for generating structured data
|
||||||
|
if generate_snippet_button:
|
||||||
|
with st.expander("Structured Data Generation Tool", expanded=True):
|
||||||
|
st.subheader("Generate Structured Data (Rich Snippets)")
|
||||||
|
|
||||||
|
# Simplified blog URL input
|
||||||
|
blog_url = st.text_input(
|
||||||
|
"Blog URL:",
|
||||||
|
placeholder="https://yourblog.com/your-article",
|
||||||
|
help="Enter the URL where this blog will be published"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Auto-fill content type to "Article" since we're working with a blog
|
||||||
|
content_type = "Article"
|
||||||
|
st.info(f"Content Type: {content_type} (Auto-selected for blog content)")
|
||||||
|
|
||||||
|
# Create details dictionary with blog metadata
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# Form for additional article details
|
||||||
|
with st.form(key="structured_data_form"):
|
||||||
|
st.markdown("#### Article Details")
|
||||||
|
|
||||||
|
# Pre-fill with blog title and other metadata
|
||||||
|
article_title = st.text_input("Headline:", value=blog_title if blog_title else "")
|
||||||
|
article_author = st.text_input("Author:", value="")
|
||||||
|
article_date = st.date_input("Date Published:", value=datetime.now())
|
||||||
|
article_keywords = st.text_input("Keywords:", value=blog_tags if blog_tags else "")
|
||||||
|
|
||||||
|
submit_structured_data = st.form_submit_button("Generate JSON-LD")
|
||||||
|
|
||||||
|
if submit_structured_data:
|
||||||
|
if not blog_url:
|
||||||
|
st.error("Please enter a blog URL to generate structured data.")
|
||||||
|
else:
|
||||||
|
# Create details dictionary
|
||||||
|
details = {
|
||||||
|
"Headline": article_title,
|
||||||
|
"Author": article_author,
|
||||||
|
"Date Published": article_date,
|
||||||
|
"Keywords": article_keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the imported ai_structured_data function or recreate its functionality
|
||||||
|
with st.spinner("Generating structured data..."):
|
||||||
|
# Import and use the function from the module directly
|
||||||
|
from ..ai_seo_tools.seo_structured_data import generate_json_data
|
||||||
|
|
||||||
|
# Generate the structured data
|
||||||
|
structured_data = generate_json_data(content_type, details, blog_url)
|
||||||
|
|
||||||
|
if structured_data:
|
||||||
|
st.success("✅ Structured data generated successfully!")
|
||||||
|
st.markdown("### Generated JSON-LD Code")
|
||||||
|
st.code(structured_data, language="json")
|
||||||
|
|
||||||
|
# Download button
|
||||||
|
st.download_button(
|
||||||
|
label="📥 Download JSON-LD",
|
||||||
|
data=structured_data,
|
||||||
|
file_name=f"{content_type}_structured_data.json",
|
||||||
|
mime="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Implementation instructions
|
||||||
|
with st.expander("How to Implement This Code"):
|
||||||
|
st.markdown("""
|
||||||
|
### Adding this JSON-LD to your website:
|
||||||
|
|
||||||
|
1. **Copy the generated JSON-LD code** above
|
||||||
|
|
||||||
|
2. **Add it to the `<head>` section of your HTML** like this:
|
||||||
|
```html
|
||||||
|
<script type="application/ld+json">
|
||||||
|
[PASTE YOUR JSON-LD CODE HERE]
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify the implementation** using Google's Rich Results Test tool:
|
||||||
|
[https://search.google.com/test/rich-results](https://search.google.com/test/rich-results)
|
||||||
|
|
||||||
|
4. **Monitor your search appearance** in Google Search Console
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
st.error("Failed to generate structured data. Please check your inputs and try again.")
|
||||||
|
|
||||||
# Image section with regeneration option
|
# Image section with regeneration option
|
||||||
st.subheader("🖼️ Featured Image")
|
st.subheader("🖼️ Featured Image")
|
||||||
image_container = st.container()
|
image_container = st.container()
|
||||||
@@ -688,14 +825,14 @@ def write_blog_from_keywords(search_keywords, url=None, search_params=None, blog
|
|||||||
|
|
||||||
# Add regenerate button
|
# Add regenerate button
|
||||||
if st.button("🔄 Regenerate Image", key="regenerate_image"):
|
if st.button("🔄 Regenerate Image", key="regenerate_image"):
|
||||||
new_image_path = regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str)
|
new_image_path = regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, blog_tags)
|
||||||
if new_image_path:
|
if new_image_path:
|
||||||
generated_image_filepath = new_image_path
|
generated_image_filepath = new_image_path
|
||||||
st.rerun() # Refresh the page to show the new image
|
st.rerun() # Refresh the page to show the new image
|
||||||
else:
|
else:
|
||||||
st.info("No featured image was generated. Click below to generate one.")
|
st.info("No featured image was generated. Click below to generate one.")
|
||||||
if st.button("🖼️ Generate Image", key="generate_image"):
|
if st.button("🖼️ Generate Image", key="generate_image"):
|
||||||
new_image_path = regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str)
|
new_image_path = regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str, blog_tags)
|
||||||
if new_image_path:
|
if new_image_path:
|
||||||
generated_image_filepath = new_image_path
|
generated_image_filepath = new_image_path
|
||||||
st.rerun() # Refresh the page to show the new image
|
st.rerun() # Refresh the page to show the new image
|
||||||
@@ -718,6 +855,303 @@ def write_blog_from_keywords(search_keywords, url=None, search_params=None, blog
|
|||||||
if generate_audio_button:
|
if generate_audio_button:
|
||||||
generate_audio_version(blog_markdown_str)
|
generate_audio_version(blog_markdown_str)
|
||||||
|
|
||||||
|
# STEP 4: Final presentation
|
||||||
|
update_progress(4, 5, "Preparing final blog presentation")
|
||||||
|
|
||||||
|
# Now display the final blog content
|
||||||
|
with final_content_placeholder.container():
|
||||||
|
st.markdown("---")
|
||||||
|
# Display tabular data of metadata
|
||||||
|
st.subheader("🏷️ Metadata")
|
||||||
|
metadata_container = st.container()
|
||||||
|
with metadata_container:
|
||||||
|
st.table({
|
||||||
|
"Metadata": ["Blog Title", "Meta Description", "Tags", "Categories", "Hashtags", "Slug"],
|
||||||
|
"Value": [blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add buttons in columns for refining metadata
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
refine_title_button = st.button("🔄 Refine Blog Title", use_container_width=True)
|
||||||
|
with col2:
|
||||||
|
refine_meta_button = st.button("🔄 Refine Meta Description", use_container_width=True)
|
||||||
|
|
||||||
|
# Add a row for structured data with a "Generate Rich Snippet" button
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### Get Structured Data")
|
||||||
|
|
||||||
|
structured_data_col1, structured_data_col2 = st.columns([3, 1])
|
||||||
|
|
||||||
|
with structured_data_col1:
|
||||||
|
# Educational popover explaining why rich snippets are important
|
||||||
|
with st.expander("ℹ️ Why Rich Snippets Are Important for SEO"):
|
||||||
|
st.markdown("""
|
||||||
|
### Rich Snippets: Boosting Your SEO and Click-Through Rates
|
||||||
|
|
||||||
|
**What are Rich Snippets?**
|
||||||
|
|
||||||
|
Rich snippets are enhanced search results that display additional information directly in search engine results pages (SERPs). They're created using structured data markup (JSON-LD) that helps search engines understand your content better.
|
||||||
|
|
||||||
|
**Why are they important?**
|
||||||
|
|
||||||
|
1. **Increased Visibility**: Rich snippets stand out in search results with stars, images, and additional information
|
||||||
|
|
||||||
|
2. **Higher Click-Through Rates (CTR)**: Studies show rich snippets can increase CTR by 30-150%
|
||||||
|
|
||||||
|
3. **Improved SEO**: They help search engines understand your content better, potentially improving rankings
|
||||||
|
|
||||||
|
4. **Enhanced User Experience**: Users can see key information before clicking, leading to more qualified traffic
|
||||||
|
|
||||||
|
5. **Mobile-Friendly**: Rich snippets are especially effective on mobile searches
|
||||||
|
|
||||||
|
**Common types of rich snippets include:**
|
||||||
|
- Articles/Blogs (with author, date, image)
|
||||||
|
- Products (with ratings, price, availability)
|
||||||
|
- Recipes (with cooking time, ratings, calories)
|
||||||
|
- Events (with date, location, ticket info)
|
||||||
|
- Local Business (with address, hours, ratings)
|
||||||
|
|
||||||
|
Adding structured data to your content is a powerful SEO technique that requires minimal effort but provides significant benefits.
|
||||||
|
""")
|
||||||
|
|
||||||
|
with structured_data_col2:
|
||||||
|
# Button to generate rich snippet
|
||||||
|
generate_snippet_button = st.button("📊 Generate Rich Snippet", use_container_width=True)
|
||||||
|
|
||||||
|
# Dialog for generating structured data
|
||||||
|
if generate_snippet_button:
|
||||||
|
with st.expander("Structured Data Generation Tool", expanded=True):
|
||||||
|
st.subheader("Generate Structured Data (Rich Snippets)")
|
||||||
|
|
||||||
|
# Simplified blog URL input
|
||||||
|
blog_url = st.text_input(
|
||||||
|
"Blog URL:",
|
||||||
|
placeholder="https://yourblog.com/your-article",
|
||||||
|
help="Enter the URL where this blog will be published"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Auto-fill content type to "Article" since we're working with a blog
|
||||||
|
content_type = "Article"
|
||||||
|
st.info(f"Content Type: {content_type} (Auto-selected for blog content)")
|
||||||
|
|
||||||
|
# Form for additional article details
|
||||||
|
with st.form(key="structured_data_form"):
|
||||||
|
st.markdown("#### Article Details")
|
||||||
|
|
||||||
|
# Pre-fill with blog title and other metadata
|
||||||
|
article_title = st.text_input("Headline:", value=blog_title if blog_title else "")
|
||||||
|
article_author = st.text_input("Author:", value="")
|
||||||
|
article_date = st.date_input("Date Published:", value=datetime.now())
|
||||||
|
article_keywords = st.text_input("Keywords:", value=blog_tags if blog_tags else "")
|
||||||
|
|
||||||
|
submit_structured_data = st.form_submit_button("Generate JSON-LD")
|
||||||
|
|
||||||
|
if submit_structured_data:
|
||||||
|
if not blog_url:
|
||||||
|
st.error("Please enter a blog URL to generate structured data.")
|
||||||
|
else:
|
||||||
|
# Create details dictionary
|
||||||
|
details = {
|
||||||
|
"Headline": article_title,
|
||||||
|
"Author": article_author,
|
||||||
|
"Date Published": article_date,
|
||||||
|
"Keywords": article_keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the imported ai_structured_data function or recreate its functionality
|
||||||
|
with st.spinner("Generating structured data..."):
|
||||||
|
# Import and use the function from the module directly
|
||||||
|
from ..ai_seo_tools.seo_structured_data import generate_json_data
|
||||||
|
|
||||||
|
# Generate the structured data
|
||||||
|
structured_data = generate_json_data(content_type, details, blog_url)
|
||||||
|
|
||||||
|
if structured_data:
|
||||||
|
st.success("✅ Structured data generated successfully!")
|
||||||
|
st.markdown("### Generated JSON-LD Code")
|
||||||
|
st.code(structured_data, language="json")
|
||||||
|
|
||||||
|
# Download button
|
||||||
|
st.download_button(
|
||||||
|
label="📥 Download JSON-LD",
|
||||||
|
data=structured_data,
|
||||||
|
file_name=f"{content_type}_structured_data.json",
|
||||||
|
mime="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Implementation instructions
|
||||||
|
with st.expander("How to Implement This Code"):
|
||||||
|
st.markdown("""
|
||||||
|
### Adding this JSON-LD to your website:
|
||||||
|
|
||||||
|
1. **Copy the generated JSON-LD code** above
|
||||||
|
|
||||||
|
2. **Add it to the `<head>` section of your HTML** like this:
|
||||||
|
```html
|
||||||
|
<script type="application/ld+json">
|
||||||
|
[PASTE YOUR JSON-LD CODE HERE]
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify the implementation** using Google's Rich Results Test tool:
|
||||||
|
[https://search.google.com/test/rich-results](https://search.google.com/test/rich-results)
|
||||||
|
|
||||||
|
4. **Monitor your search appearance** in Google Search Console
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
st.error("Failed to generate structured data. Please check your inputs and try again.")
|
||||||
|
|
||||||
|
# Dialog for refining blog title
|
||||||
|
if refine_title_button:
|
||||||
|
with st.expander("Blog Title Refinement Tool", expanded=True):
|
||||||
|
st.subheader("Refine Your Blog Title")
|
||||||
|
|
||||||
|
# Store the current title in session state for later reference
|
||||||
|
if 'current_title' not in st.session_state:
|
||||||
|
st.session_state.current_title = blog_title
|
||||||
|
|
||||||
|
# Extract keywords from tags and content
|
||||||
|
keywords_from_tags = blog_tags if blog_tags else ""
|
||||||
|
blog_content_sample = blog_markdown_str[:3000] if blog_markdown_str else ""
|
||||||
|
|
||||||
|
# Title generation form
|
||||||
|
with st.form(key="title_form"):
|
||||||
|
st.markdown("#### Provide information to generate new title suggestions")
|
||||||
|
title_keywords = st.text_input(
|
||||||
|
"Main Keywords:",
|
||||||
|
value=keywords_from_tags,
|
||||||
|
help="Enter main keywords separated by commas"
|
||||||
|
)
|
||||||
|
|
||||||
|
title_type = st.selectbox(
|
||||||
|
"Blog Type:",
|
||||||
|
options=['General', 'How-to Guides', 'Tutorials', 'Listicles', 'Newsworthy Posts', 'FAQs', 'Checklists/Cheat Sheets'],
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
intent_type = st.selectbox(
|
||||||
|
"Search Intent:",
|
||||||
|
options=['Informational Intent', 'Commercial Intent', 'Transactional Intent', 'Navigational Intent'],
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
language = st.selectbox(
|
||||||
|
"Language:",
|
||||||
|
options=["English", "Spanish", "French", "German", "Chinese", "Japanese", "Other"],
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
if language == "Other":
|
||||||
|
language = st.text_input("Specify Language:", placeholder="e.g., Italian, Dutch")
|
||||||
|
|
||||||
|
submit_title = st.form_submit_button("Generate Title Suggestions")
|
||||||
|
|
||||||
|
if submit_title:
|
||||||
|
with st.spinner("Generating title suggestions..."):
|
||||||
|
# Use the imported generate_blog_titles function
|
||||||
|
title_suggestions = generate_blog_titles(
|
||||||
|
title_keywords,
|
||||||
|
blog_content_sample,
|
||||||
|
title_type,
|
||||||
|
intent_type,
|
||||||
|
language
|
||||||
|
)
|
||||||
|
|
||||||
|
if title_suggestions:
|
||||||
|
st.success("✅ Title suggestions generated!")
|
||||||
|
st.markdown("### Title Suggestions")
|
||||||
|
st.markdown(title_suggestions)
|
||||||
|
|
||||||
|
# Allow selecting a title
|
||||||
|
st.markdown("#### Select or enter a new title")
|
||||||
|
new_title = st.text_input("New Blog Title", value=st.session_state.current_title)
|
||||||
|
|
||||||
|
if st.button("Apply New Title"):
|
||||||
|
# Store the new title in the session state
|
||||||
|
st.session_state.blog_title = new_title
|
||||||
|
st.success(f"✅ Title updated to: {new_title}")
|
||||||
|
# Return to main page with updated title
|
||||||
|
st.experimental_rerun()
|
||||||
|
else:
|
||||||
|
st.error("Failed to generate title suggestions.")
|
||||||
|
|
||||||
|
# Dialog for refining meta description
|
||||||
|
if refine_meta_button:
|
||||||
|
with st.expander("Meta Description Refinement Tool", expanded=True):
|
||||||
|
st.subheader("Refine Your Meta Description")
|
||||||
|
|
||||||
|
# Store the current meta description in session state
|
||||||
|
if 'current_meta_desc' not in st.session_state:
|
||||||
|
st.session_state.current_meta_desc = blog_meta_desc
|
||||||
|
|
||||||
|
# Extract keywords from tags and content
|
||||||
|
keywords_from_tags = blog_tags if blog_tags else ""
|
||||||
|
|
||||||
|
# Meta description generation form
|
||||||
|
with st.form(key="meta_desc_form"):
|
||||||
|
st.markdown("#### Provide information to generate new meta description suggestions")
|
||||||
|
meta_keywords = st.text_input(
|
||||||
|
"Target Keywords:",
|
||||||
|
value=keywords_from_tags,
|
||||||
|
help="Enter target keywords separated by commas"
|
||||||
|
)
|
||||||
|
|
||||||
|
tone_options = ["General", "Informative", "Engaging", "Humorous", "Intriguing", "Playful"]
|
||||||
|
tone = st.selectbox(
|
||||||
|
"Desired Tone:",
|
||||||
|
options=tone_options,
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
search_type = st.selectbox(
|
||||||
|
"Search Intent:",
|
||||||
|
options=['Informational Intent', 'Commercial Intent', 'Transactional Intent', 'Navigational Intent'],
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
language_options = ["English", "Spanish", "French", "German", "Other"]
|
||||||
|
language_choice = st.selectbox(
|
||||||
|
"Preferred Language:",
|
||||||
|
options=language_options,
|
||||||
|
index=0
|
||||||
|
)
|
||||||
|
|
||||||
|
if language_choice == "Other":
|
||||||
|
language = st.text_input("Specify Language:", placeholder="e.g., Italian, Chinese")
|
||||||
|
else:
|
||||||
|
language = language_choice
|
||||||
|
|
||||||
|
submit_meta = st.form_submit_button("Generate Meta Description Suggestions")
|
||||||
|
|
||||||
|
if submit_meta:
|
||||||
|
with st.spinner("Generating meta description suggestions..."):
|
||||||
|
# Use the imported generate_blog_metadesc function
|
||||||
|
meta_suggestions = generate_blog_metadesc(
|
||||||
|
meta_keywords,
|
||||||
|
tone,
|
||||||
|
search_type,
|
||||||
|
language
|
||||||
|
)
|
||||||
|
|
||||||
|
if meta_suggestions:
|
||||||
|
st.success("✅ Meta description suggestions generated!")
|
||||||
|
st.markdown("### Meta Description Suggestions")
|
||||||
|
st.markdown(meta_suggestions)
|
||||||
|
|
||||||
|
# Allow selecting a meta description
|
||||||
|
st.markdown("#### Select or enter a new meta description")
|
||||||
|
new_meta_desc = st.text_area("New Meta Description", value=st.session_state.current_meta_desc)
|
||||||
|
|
||||||
|
if st.button("Apply New Meta Description"):
|
||||||
|
# Store the new meta description in the session state
|
||||||
|
st.session_state.blog_meta_desc = new_meta_desc
|
||||||
|
st.success(f"✅ Meta description updated!")
|
||||||
|
# Return to main page with updated meta description
|
||||||
|
st.experimental_rerun()
|
||||||
|
else:
|
||||||
|
st.error("Failed to generate meta description suggestions.")
|
||||||
|
|
||||||
# Final progress update
|
# Final progress update
|
||||||
update_progress(5, 5, "Blog generation complete!")
|
update_progress(5, 5, "Blog generation complete!")
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ async def blog_metadata(blog_article):
|
|||||||
|
|
||||||
# Present the result in a table format
|
# Present the result in a table format
|
||||||
status_container.success("✅ Metadata generation complete")
|
status_container.success("✅ Metadata generation complete")
|
||||||
st.table({
|
#st.table({
|
||||||
"Metadata": ["Blog Title", "Meta Description", "Tags", "Categories", "Social Hashtags", "URL Slug"],
|
# "Metadata": ["Blog Title", "Meta Description", "Tags", "Categories", "Social Hashtags", "URL Slug"],
|
||||||
"Value": [blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug]
|
# "Value": [blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug]
|
||||||
})
|
#})
|
||||||
|
|
||||||
return blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug
|
return blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,14 @@ def generate_gemini_image(prompt, keywords=None, style=None, focus=None, enhance
|
|||||||
"""
|
"""
|
||||||
logger.info(f"Generating image with prompt: '{prompt[:100]}...'")
|
logger.info(f"Generating image with prompt: '{prompt[:100]}...'")
|
||||||
|
|
||||||
|
# Check if the GEMINI_API_KEY is available
|
||||||
|
api_key = os.getenv("GEMINI_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
error_msg = "GEMINI_API_KEY is missing. Please set it in your environment variables."
|
||||||
|
logger.error(error_msg)
|
||||||
|
st.error(f"🔑 {error_msg}")
|
||||||
|
return None
|
||||||
|
|
||||||
# Enhance the prompt if requested
|
# Enhance the prompt if requested
|
||||||
if enhance_prompt and keywords:
|
if enhance_prompt and keywords:
|
||||||
prompt_generator = AIPromptGenerator()
|
prompt_generator = AIPromptGenerator()
|
||||||
@@ -209,7 +217,7 @@ def generate_gemini_image(prompt, keywords=None, style=None, focus=None, enhance
|
|||||||
|
|
||||||
while retry_count <= max_retries:
|
while retry_count <= max_retries:
|
||||||
try:
|
try:
|
||||||
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
|
client = genai.Client(api_key=api_key)
|
||||||
contents = (prompt)
|
contents = (prompt)
|
||||||
|
|
||||||
logger.info("Sending request to Gemini API")
|
logger.info("Sending request to Gemini API")
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from .gen_stabl_diff_img import generate_stable_diffusion_image
|
|||||||
from ..text_generation.main_text_generation import llm_text_gen
|
from ..text_generation.main_text_generation import llm_text_gen
|
||||||
from .gen_gemini_images import generate_gemini_image
|
from .gen_gemini_images import generate_gemini_image
|
||||||
|
|
||||||
def generate_image(user_prompt):
|
def generate_image(user_prompt, title=None, description=None, tags=None, content=None):
|
||||||
"""
|
"""
|
||||||
The generation API endpoint creates an image based on a text prompt.
|
The generation API endpoint creates an image based on a text prompt.
|
||||||
|
|
||||||
@@ -49,7 +49,8 @@ def generate_image(user_prompt):
|
|||||||
|
|
||||||
if user_prompt:
|
if user_prompt:
|
||||||
try:
|
try:
|
||||||
img_prompt = generate_img_prompt(user_prompt)
|
# Use enhanced prompt generator with all available parameters
|
||||||
|
img_prompt = generate_enhanced_img_prompt(user_prompt, title, description, tags, content)
|
||||||
if 'Dalle3' in image_engine:
|
if 'Dalle3' in image_engine:
|
||||||
logger.info(f"Calling Dalle3 text-to-image with prompt: {img_prompt}")
|
logger.info(f"Calling Dalle3 text-to-image with prompt: {img_prompt}")
|
||||||
image_stored_at = generate_dalle3_images(img_prompt)
|
image_stored_at = generate_dalle3_images(img_prompt)
|
||||||
@@ -85,3 +86,72 @@ def generate_img_prompt(user_prompt):
|
|||||||
|
|
||||||
response = llm_text_gen(prompt)
|
response = llm_text_gen(prompt)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def generate_enhanced_img_prompt(user_prompt, title=None, description=None, tags=None, content=None):
|
||||||
|
"""
|
||||||
|
Given user prompt and additional context (title, description, tags, content),
|
||||||
|
this function generates an enhanced prompt for better image generation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_prompt (str): Base prompt from the user
|
||||||
|
title (str, optional): Blog title or content title
|
||||||
|
description (str, optional): Blog or content description/summary
|
||||||
|
tags (list, optional): List of tags related to the content
|
||||||
|
content (str, optional): Actual content or excerpt
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Enhanced prompt for image generation
|
||||||
|
"""
|
||||||
|
# Start with the base prompt
|
||||||
|
context_parts = [user_prompt]
|
||||||
|
|
||||||
|
# Add relevant context if available
|
||||||
|
if title:
|
||||||
|
context_parts.append(f"Title: {title}")
|
||||||
|
|
||||||
|
if description:
|
||||||
|
context_parts.append(f"Description: {description}")
|
||||||
|
|
||||||
|
if tags and len(tags) > 0:
|
||||||
|
tag_text = ", ".join(tags[:5]) # Limit to 5 tags to avoid too much noise
|
||||||
|
context_parts.append(f"Tags: {tag_text}")
|
||||||
|
|
||||||
|
# Create a combined context
|
||||||
|
combined_context = "\n".join(context_parts)
|
||||||
|
|
||||||
|
# Add some content excerpt if available (limited to avoid token limits)
|
||||||
|
content_excerpt = ""
|
||||||
|
if content:
|
||||||
|
# Just use the first few hundred characters as excerpt
|
||||||
|
content_excerpt = content[:300] + "..." if len(content) > 300 else content
|
||||||
|
|
||||||
|
# Create the prompt for LLM
|
||||||
|
prompt = f"""
|
||||||
|
As an expert prompt engineer for AI image generation models, create a detailed, creative prompt
|
||||||
|
for generating a high-quality, relevant image based on the following context:
|
||||||
|
|
||||||
|
{combined_context}
|
||||||
|
|
||||||
|
Additional content excerpt:
|
||||||
|
{content_excerpt}
|
||||||
|
|
||||||
|
Your task is to:
|
||||||
|
1. Analyze the context and content to understand the main theme and subject
|
||||||
|
2. Create a rich, detailed prompt for image generation (50-75 words)
|
||||||
|
3. Include specific visual details, art style, mood, lighting, composition
|
||||||
|
4. Make sure the prompt is highly relevant to the original context
|
||||||
|
5. Avoid prohibited content or anything that violates image generation guidelines
|
||||||
|
|
||||||
|
Reply with ONLY the final prompt. No explanations or other text.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Generate the enhanced prompt
|
||||||
|
try:
|
||||||
|
enhanced_prompt = llm_text_gen(prompt)
|
||||||
|
logger.info(f"Generated enhanced image prompt: {enhanced_prompt[:100]}...")
|
||||||
|
return enhanced_prompt
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error generating enhanced prompt: {e}")
|
||||||
|
# Fall back to the simple prompt generation if enhanced fails
|
||||||
|
return generate_img_prompt(user_prompt)
|
||||||
|
|||||||
@@ -12,8 +12,16 @@ def save_generated_image(img_generation_response):
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Get image save directory with fallback to a local directory
|
||||||
|
image_save_dir = os.getenv('IMG_SAVE_DIR', 'generated_images')
|
||||||
|
|
||||||
|
# Create the directory if it doesn't exist
|
||||||
|
if not os.path.exists(image_save_dir):
|
||||||
|
logger.info(f"Creating image save directory: {image_save_dir}")
|
||||||
|
os.makedirs(image_save_dir, exist_ok=True)
|
||||||
|
|
||||||
generated_image_name = f"generated_image_{datetime.datetime.now():%Y-%m-%d-%H-%M-%S}.webp"
|
generated_image_name = f"generated_image_{datetime.datetime.now():%Y-%m-%d-%H-%M-%S}.webp"
|
||||||
generated_image_filepath = os.path.join(os.getenv('IMG_SAVE_DIR'), generated_image_name)
|
generated_image_filepath = os.path.join(image_save_dir, generated_image_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for i, image in enumerate(img_generation_response["artifacts"]):
|
for i, image in enumerate(img_generation_response["artifacts"]):
|
||||||
@@ -22,6 +30,9 @@ def save_generated_image(img_generation_response):
|
|||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.error(f"Failed to get generated image content: {e}")
|
logger.error(f"Failed to get generated image content: {e}")
|
||||||
return None
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error saving image: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
logger.info(f"Saved image at path: {generated_image_filepath}")
|
logger.info(f"Saved image at path: {generated_image_filepath}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user