diff --git a/lib/ai_writers/gpt_blog_sections.py b/lib/ai_writers/gpt_blog_sections.py index c0f93829..a266a31c 100644 --- a/lib/ai_writers/gpt_blog_sections.py +++ b/lib/ai_writers/gpt_blog_sections.py @@ -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() diff --git a/lib/ai_writers/keywords_to_blog_streamlit.py b/lib/ai_writers/keywords_to_blog_streamlit.py index 4f286c7e..35b53fd1 100644 --- a/lib/ai_writers/keywords_to_blog_streamlit.py +++ b/lib/ai_writers/keywords_to_blog_streamlit.py @@ -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'Download audio file' +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 `
` section of your HTML** like this: + ```html + + ``` + + 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 `` section of your HTML** like this: + ```html + + ``` + + 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!") diff --git a/lib/blog_metadata/get_blog_metadata.py b/lib/blog_metadata/get_blog_metadata.py index d2751c72..b74c1b73 100644 --- a/lib/blog_metadata/get_blog_metadata.py +++ b/lib/blog_metadata/get_blog_metadata.py @@ -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 diff --git a/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py b/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py index bfb6d852..2b60daf2 100644 --- a/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py +++ b/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py @@ -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") diff --git a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py index a16d9897..4be8560a 100644 --- a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py +++ b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py @@ -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) diff --git a/lib/gpt_providers/text_to_image_generation/save_image.py b/lib/gpt_providers/text_to_image_generation/save_image.py index 4bdd7a10..3aab258c 100644 --- a/lib/gpt_providers/text_to_image_generation/save_image.py +++ b/lib/gpt_providers/text_to_image_generation/save_image.py @@ -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}")