AI blog writer & blog metadata updates & improvements
This commit is contained in:
@@ -2,8 +2,8 @@ import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
from ..gpt_providers.openai_chat_completion import openai_chatgpt
|
||||
from ..gpt_providers.gemini_pro_text import gemini_text_response
|
||||
from ..gpt_providers.text_generation.openai_text_gen import openai_text_generation
|
||||
from ..gpt_providers.text_generation.gemini_pro_text import gemini_text_generation
|
||||
|
||||
from loguru import logger
|
||||
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_metaphor_ai_research, do_google_pytrends_analysis)
|
||||
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_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
|
||||
|
||||
|
||||
# Function to convert text to speech and save as an audio file
|
||||
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>'
|
||||
from ..ai_seo_tools.content_title_generator import generate_blog_titles
|
||||
from ..ai_seo_tools.meta_desc_generator import generate_blog_metadesc
|
||||
from ..ai_seo_tools.seo_structured_data import ai_structured_data
|
||||
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
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")
|
||||
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:
|
||||
st.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")
|
||||
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.
|
||||
|
||||
@@ -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_markdown_str (str): Blog content
|
||||
status: Streamlit status object
|
||||
blog_tags (list, optional): Blog tags to use for image prompt enhancement
|
||||
|
||||
Returns:
|
||||
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}")
|
||||
status.update(label=f"🖼️ Creating image with prompt: \"{text_to_image[:50]}...\"")
|
||||
|
||||
# Attempt image generation
|
||||
generated_image_filepath = generate_image(text_to_image)
|
||||
# Extract blog tags if available
|
||||
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 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
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -371,6 +375,7 @@ def regenerate_blog_image(blog_title, blog_meta_desc, blog_markdown_str):
|
||||
blog_title (str): Blog title
|
||||
blog_meta_desc (str): Blog meta description
|
||||
blog_markdown_str (str): Blog content
|
||||
blog_tags (list, optional): Blog tags to use for image prompt enhancement
|
||||
|
||||
Returns:
|
||||
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}\"")
|
||||
|
||||
# Generate the image
|
||||
generated_image_filepath = generate_image(prompt)
|
||||
# Extract any tags if available - will be passed as empty list otherwise
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -419,6 +433,8 @@ def save_blog_content(blog_markdown_str, blog_title, blog_meta_desc, blog_tags,
|
||||
blog_categories (list): Blog categories
|
||||
generated_image_filepath (str): Path to the generated image
|
||||
status: Streamlit status object
|
||||
blog_hashtags (str, optional): Social media hashtags
|
||||
blog_slug (str, optional): SEO-friendly URL slug
|
||||
|
||||
Returns:
|
||||
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:
|
||||
# Generate metadata
|
||||
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
|
||||
)
|
||||
|
||||
# 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:
|
||||
status.update(label=f"✅ Generated metadata: \"{blog_title}\"")
|
||||
|
||||
# Generate featured image
|
||||
status.update(label="🖼️ Creating featured 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
|
||||
status.update(label="💾 Saving blog content...")
|
||||
saved_blog_to_file = save_blog_content(
|
||||
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")
|
||||
else:
|
||||
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:
|
||||
st.markdown(f"**Tags:** {', '.join(blog_tags)}")
|
||||
# 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)
|
||||
|
||||
if blog_categories:
|
||||
st.markdown(f"**Categories:** {', '.join(blog_categories)}")
|
||||
# Add a row for structured data
|
||||
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
|
||||
st.subheader("🖼️ Featured Image")
|
||||
image_container = st.container()
|
||||
@@ -688,14 +825,14 @@ def write_blog_from_keywords(search_keywords, url=None, search_params=None, blog
|
||||
|
||||
# Add regenerate button
|
||||
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:
|
||||
generated_image_filepath = new_image_path
|
||||
st.rerun() # Refresh the page to show the new image
|
||||
else:
|
||||
st.info("No featured image was generated. Click below to generate one.")
|
||||
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:
|
||||
generated_image_filepath = new_image_path
|
||||
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:
|
||||
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
|
||||
update_progress(5, 5, "Blog generation complete!")
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@ async def blog_metadata(blog_article):
|
||||
|
||||
# Present the result in a table format
|
||||
status_container.success("✅ Metadata generation complete")
|
||||
st.table({
|
||||
"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]
|
||||
})
|
||||
#st.table({
|
||||
# "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]
|
||||
#})
|
||||
|
||||
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]}...'")
|
||||
|
||||
# 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
|
||||
if enhance_prompt and keywords:
|
||||
prompt_generator = AIPromptGenerator()
|
||||
@@ -209,7 +217,7 @@ def generate_gemini_image(prompt, keywords=None, style=None, focus=None, enhance
|
||||
|
||||
while retry_count <= max_retries:
|
||||
try:
|
||||
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
|
||||
client = genai.Client(api_key=api_key)
|
||||
contents = (prompt)
|
||||
|
||||
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 .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.
|
||||
|
||||
@@ -49,7 +49,8 @@ def generate_image(user_prompt):
|
||||
|
||||
if user_prompt:
|
||||
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:
|
||||
logger.info(f"Calling Dalle3 text-to-image with prompt: {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)
|
||||
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)
|
||||
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_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:
|
||||
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:
|
||||
logger.error(f"Failed to get generated image content: {e}")
|
||||
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}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user