From b2ce1ceb49cbdcb951b1d7822791f199a9ba0200 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Wed, 7 May 2025 16:39:28 +0530 Subject: [PATCH] AI Outline Writer - Added image generation and display for sections --- .../ai_outline_writer/get_blog_outline.py | 383 +++++------ .../ai_outline_writer/outline_ui.py | 620 ++++++++++++------ 2 files changed, 617 insertions(+), 386 deletions(-) diff --git a/lib/ai_writers/ai_outline_writer/get_blog_outline.py b/lib/ai_writers/ai_outline_writer/get_blog_outline.py index 83bba9fc..6645a78a 100644 --- a/lib/ai_writers/ai_outline_writer/get_blog_outline.py +++ b/lib/ai_writers/ai_outline_writer/get_blog_outline.py @@ -10,6 +10,7 @@ from typing import Dict, List, Optional from enum import Enum from dataclasses import dataclass from loguru import logger +import json from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image @@ -84,216 +85,196 @@ class BlogOutlineGenerator: self.outline = {} self.section_contents = {} - async def generate_outline(self, topic: str) -> Dict: - """Generate a comprehensive outline based on the topic and configuration.""" + def generate_outline(self, topic: str) -> Dict[str, List[str]]: + """Generate a blog outline based on the topic and configuration.""" try: - # Step 1: Generate main sections - main_sections = await self._generate_main_sections(topic) + # Create a focused prompt for outline generation + prompt = f"""Generate a blog outline for topic: {topic} + +Content Type: {self.config.content_type.value} +Target Audience: {self.config.target_audience} +Content Depth: {self.config.content_depth.value} +Style: {self.config.outline_style.value} +Word Count Target: {self.config.target_word_count} +Main Sections: {self.config.num_main_sections} +Subsections per Section: {self.config.num_subsections_per_section} + +Requirements: +- Create exactly {self.config.num_main_sections} main sections +- Each section should have exactly {self.config.num_subsections_per_section} subsections +- Focus on {self.config.content_type.value} content style +- Target {self.config.target_audience} audience +- Maintain {self.config.content_depth.value} depth +- Follow {self.config.outline_style.value} style +- Optimize for {self.config.target_word_count} words total + +IMPORTANT: You must return a valid JSON object with main sections as keys and lists of subsections as values. +Example format: {{"Section 1": ["Subsection 1.1", "Subsection 1.2"], "Section 2": ["Subsection 2.1", "Subsection 2.2"]}} +Do not include any additional text or explanations, only the JSON object.""" + + # Get outline from LLM + outline_json = llm_text_gen(prompt) - # Step 2: Generate subsections for each main section - detailed_sections = await self._generate_subsections(main_sections) + # Clean the response to ensure it's valid JSON + outline_json = outline_json.strip() + if not outline_json.startswith('{'): + outline_json = outline_json[outline_json.find('{'):] + if not outline_json.endswith('}'): + outline_json = outline_json[:outline_json.rfind('}')+1] - # Step 3: Add introduction and conclusion if requested + # Parse the outline + try: + outline = json.loads(outline_json) + except json.JSONDecodeError as e: + logger.error(f"JSON parsing error: {str(e)}") + logger.error(f"Raw response: {outline_json}") + # Fallback to a basic outline structure + outline = { + f"Section {i+1}": [f"Subsection {i+1}.{j+1}" for j in range(self.config.num_subsections_per_section)] + for i in range(self.config.num_main_sections) + } + + # Add introduction and conclusion if configured if self.config.include_introduction: - detailed_sections["Introduction"] = await self._generate_introduction(topic) + outline = {"Introduction": ["Overview", "Importance", "What to Expect"]} | outline if self.config.include_conclusion: - detailed_sections["Conclusion"] = await self._generate_conclusion(topic) + outline["Conclusion"] = ["Summary", "Key Takeaways", "Next Steps"] - # Step 4: Add FAQs if requested + # Add FAQs if configured if self.config.include_faqs: - detailed_sections["FAQs"] = await self._generate_faqs(topic) - - # Step 5: Add resources if requested - if self.config.include_resources: - detailed_sections["Additional Resources"] = await self._generate_resources(topic) - - self.outline = detailed_sections - - # Step 6: Generate content for each section - await self._generate_section_contents(topic) - - return self.outline - - except Exception as err: - logger.error(f"Failed to generate outline: {err}") - raise - - async def _generate_main_sections(self, topic: str) -> List[str]: - """Generate main sections for the outline.""" - prompt = f"""Generate {self.config.num_main_sections} main sections for a {self.config.content_type.value} - article about {topic} with the following characteristics: - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Target Word Count: {self.config.target_word_count} - Target Audience: {self.config.target_audience} - Style: {self.config.outline_style.value} - - Additional Requirements: - - Each section should contribute to the overall word count goal - - Sections should flow logically - - Include key concepts and important points - - Consider SEO optimization - - Keywords to include: {', '.join(self.config.keywords or [])} - - Topics to exclude: {', '.join(self.config.exclude_topics or [])} - - Please provide only the section titles, one per line.""" - - response = await llm_text_gen(prompt) - return [section.strip() for section in response.split('\n') if section.strip()] - - async def _generate_subsections(self, main_sections: List[str]) -> Dict[str, List[str]]: - """Generate subsections for each main section.""" - detailed_sections = {} - - for section in main_sections: - prompt = f"""Generate {self.config.num_subsections_per_section} subsections for the following section: - {section} - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - - Each subsection should: - - Be specific and focused - - Support the main section's topic - - Include key points to cover - - Consider SEO optimization - - Please provide only the subsection titles, one per line.""" - - response = await llm_text_gen(prompt) - detailed_sections[section] = [sub.strip() for sub in response.split('\n') if sub.strip()] - - return detailed_sections - - async def _generate_introduction(self, topic: str) -> List[str]: - """Generate introduction subsections.""" - prompt = f"""Generate introduction subsections for an article about {topic}. - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - - The introduction should: - - Hook the reader - - Present the main topic - - Outline what's to come - - Set the tone for the article - - Please provide only the subsection titles, one per line.""" - - response = await llm_text_gen(prompt) - return [sub.strip() for sub in response.split('\n') if sub.strip()] - - async def _generate_conclusion(self, topic: str) -> List[str]: - """Generate conclusion subsections.""" - prompt = f"""Generate conclusion subsections for an article about {topic}. - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - - The conclusion should: - - Summarize key points - - Provide final thoughts - - Include a call to action - - Leave a lasting impression - - Please provide only the subsection titles, one per line.""" - - response = await llm_text_gen(prompt) - return [sub.strip() for sub in response.split('\n') if sub.strip()] - - async def _generate_faqs(self, topic: str) -> List[str]: - """Generate FAQ subsections.""" - prompt = f"""Generate FAQ subsections for an article about {topic}. - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - - The FAQs should: - - Address common questions - - Cover important aspects - - Be relevant to the target audience - - Include both basic and advanced questions - - Please provide only the FAQ questions, one per line.""" - - response = await llm_text_gen(prompt) - return [sub.strip() for sub in response.split('\n') if sub.strip()] - - async def _generate_resources(self, topic: str) -> List[str]: - """Generate resource subsections.""" - prompt = f"""Generate resource subsections for an article about {topic}. - - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - - The resources should: - - Include relevant links - - Suggest further reading - - Provide tools or references - - Include related materials - - Please provide only the resource categories, one per line.""" - - response = await llm_text_gen(prompt) - return [sub.strip() for sub in response.split('\n') if sub.strip()] - - async def _generate_section_contents(self, topic: str): - """Generate content and images for each section.""" - for section, subsections in self.outline.items(): - if section not in ["Introduction", "Conclusion", "FAQs", "Additional Resources"]: - # Generate content for the main section - content_prompt = f"""Write a detailed section for a blog post about {topic}. - Section Title: {section} - Content Type: {self.config.content_type.value} - Content Depth: {self.config.content_depth.value} - Style: {self.config.outline_style.value} - Target Word Count: {self.config.target_word_count // self.config.num_main_sections} - - Include: - - Clear explanation of the main points - - Examples and illustrations - - Key takeaways - - Relevant data or statistics - """ - - content = await llm_text_gen(content_prompt) - - # Generate image prompt if images are enabled - image_prompt = None - image_path = None - - if self.config.include_images: - image_prompt = f"""Create a detailed image prompt for a blog section about {topic}. - Section: {section} - Content: {content[:200]}... - Style: {self.config.image_style} - """ + # Generate topic-specific FAQs + faq_prompt = f"""Generate 3 specific and relevant FAQ questions for a blog post about: {topic} + +Content Type: {self.config.content_type.value} +Target Audience: {self.config.target_audience} +Content Depth: {self.config.content_depth.value} + +Requirements: +- Questions should be specific to the topic +- Cover common concerns and important aspects +- Be relevant to the target audience +- Include both basic and advanced questions + +Format: Return only a JSON array of 3 questions. +Example format: ["Question 1?", "Question 2?", "Question 3?"]""" + + try: + faq_json = llm_text_gen(faq_prompt) + faq_json = faq_json.strip() + if not faq_json.startswith('['): + faq_json = faq_json[faq_json.find('['):] + if not faq_json.endswith(']'): + faq_json = faq_json[:faq_json.rfind(']')+1] - image_prompt = await llm_text_gen(image_prompt) - try: - image_path = generate_image( - image_prompt, - title=section, - description=content[:100], - tags=self.config.keywords - ) - except Exception as err: - logger.warning(f"Failed to generate image for section {section}: {err}") - - self.section_contents[section] = SectionContent( - title=section, - content=content, - image_prompt=image_prompt, - image_path=image_path - ) - + faqs = json.loads(faq_json) + outline["Frequently Asked Questions"] = faqs + except Exception as e: + logger.error(f"Error generating FAQs: {str(e)}") + outline["Frequently Asked Questions"] = [ + f"Common Question about {topic} 1", + f"Common Question about {topic} 2", + f"Common Question about {topic} 3" + ] + + # Add resources if configured + if self.config.include_resources: + outline["Additional Resources"] = [ + "Further Reading", + "Tools and References", + "Related Topics" + ] + + return outline + + except Exception as e: + logger.error(f"Error generating outline: {str(e)}") + return {} + + def generate_section_content(self, section: str, subsections: List[str]) -> Optional[SectionContent]: + """Generate content for a section.""" + try: + # Create a focused prompt for content generation + prompt = f"""Generate content for section: {section} + +Subsections: {', '.join(subsections)} +Content Type: {self.config.content_type.value} +Target Audience: {self.config.target_audience} +Content Depth: {self.config.content_depth.value} +Style: {self.config.outline_style.value} +Word Count Target: {self.config.target_word_count // self.config.num_main_sections} + +Requirements: +- Write content for each subsection +- Maintain {self.config.content_depth.value} depth +- Target {self.config.target_audience} audience +- Follow {self.config.outline_style.value} style +- Optimize for {self.config.target_word_count // self.config.num_main_sections} words +- Include relevant examples and data points +- Use clear, engaging language + +Format: Return only a JSON object with 'content' and 'image_prompt' fields. +Example format: {{"content": "Section content here...", "image_prompt": "Image description here..."}}""" + + # Get content from LLM + content_json = llm_text_gen(prompt) + content_data = json.loads(content_json) + + # Generate image if configured + image_path = None + if self.config.include_images: + image_path = self.generate_section_image(section) + + return SectionContent( + title=section, + content=content_data["content"], + image_prompt=content_data.get("image_prompt"), + image_path=image_path + ) + + except Exception as e: + logger.error(f"Error generating content for section {section}: {str(e)}") + return None + + def generate_section_image(self, section: str) -> Optional[str]: + """Generate an image for a section.""" + try: + # Create a focused prompt for image generation + prompt = f"""Generate an image prompt for section: {section} + +Style: {self.config.image_style} +Engine: {self.config.image_engine} +Content Type: {self.config.content_type.value} +Target Audience: {self.config.target_audience} + +Requirements: +- Create a {self.config.image_style} style image +- Optimize for {self.config.image_engine} engine +- Match {self.config.content_type.value} content type +- Appeal to {self.config.target_audience} audience +- Be visually engaging and relevant + +Format: Return only a JSON object with an 'image_prompt' field. +Example format: {{"image_prompt": "Detailed image description here..."}}""" + + # Get image prompt from LLM + prompt_json = llm_text_gen(prompt) + prompt_data = json.loads(prompt_json) + + # Generate image using the specified engine + if self.config.image_engine == "Gemini-AI": + image_path = generate_gemini_image(prompt_data["image_prompt"]) + elif self.config.image_engine == "Dalle3": + image_path = generate_dalle_image(prompt_data["image_prompt"]) + else: # Stability-AI + image_path = generate_stability_image(prompt_data["image_prompt"]) + + return image_path + + except Exception as e: + logger.error(f"Error generating image for section {section}: {str(e)}") + return None + def to_markdown(self) -> str: """Convert outline to markdown format with content and images.""" markdown = f"# {self.outline.get('Introduction', [''])[0]}\n\n" diff --git a/lib/ai_writers/ai_outline_writer/outline_ui.py b/lib/ai_writers/ai_outline_writer/outline_ui.py index cea46936..622b01f4 100644 --- a/lib/ai_writers/ai_outline_writer/outline_ui.py +++ b/lib/ai_writers/ai_outline_writer/outline_ui.py @@ -34,42 +34,126 @@ st.markdown(""" border-radius: 4px; border: none; font-weight: bold; + width: 100%; } .stButton>button:hover { background-color: #45a049; } + /* Add specific styling for the generate outline button */ + .generate-outline-button { + width: 100%; + margin: 20px 0; + } + .generate-outline-button > button { + width: 100%; + height: 50px; + font-size: 1.2rem; + } .section-card { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; + width: 100%; } .content-preview { background-color: #f8f9fa; padding: 15px; border-radius: 4px; margin: 10px 0; + width: 100%; } .image-container { display: flex; justify-content: center; margin: 20px 0; + width: 100%; } .stats-card { background-color: #e8f5e9; padding: 15px; border-radius: 8px; margin: 10px 0; + width: 100%; } .edit-section { background-color: #e3f2fd; padding: 15px; border-radius: 4px; margin: 10px 0; + width: 100%; } .subsection-list { margin-left: 20px; + width: 100%; + } + /* Main container width */ + .main .block-container { + max-width: 100%; + padding: 2rem; + } + /* Full width for the outline display */ + .outline-container { + width: 100%; + max-width: 100%; + margin: 0 auto; + padding: 20px; + } + /* Section styling */ + .section-header { + font-size: 1.5rem; + font-weight: bold; + color: #2c3e50; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid #e0e0e0; + } + .subsection-item { + font-size: 1.1rem; + color: #34495e; + margin: 0.5rem 0; + padding-left: 1rem; + } + /* Content area styling */ + .content-area { + background-color: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin: 1rem 0; + } + /* Make sure all Streamlit elements use full width */ + .stMarkdown, .stText, .stTextArea, .stSelectbox, .stSlider { + width: 100% !important; + } + /* Full width for code blocks */ + .stCodeBlock { + width: 100% !important; + } + /* Full width for the main content */ + .main .block-container { + padding-left: 2rem; + padding-right: 2rem; + max-width: 100%; + } + /* Adjust the main content area */ + .main .block-container > div { + max-width: 100%; + } + /* Make sure the outline content uses full width */ + .outline-content { + width: 100%; + max-width: 100%; + margin: 0; + padding: 0; + } + /* Adjust the preview section */ + .preview-section { + width: 100%; + max-width: 100%; + margin: 0; + padding: 1rem; } """, unsafe_allow_html=True) @@ -205,7 +289,7 @@ def display_section(section: str, subsections: List[str], content: Optional[Dict """Display a section with its content and subsections.""" st.markdown(f"""
-

{section}

+
{section}
""", unsafe_allow_html=True) # Section editing controls @@ -225,37 +309,78 @@ def display_section(section: str, subsections: List[str], content: Optional[Dict
""", unsafe_allow_html=True) - # Display image if available - if content.image_path: - st.markdown('
', unsafe_allow_html=True) - st.image(content.image_path, caption=section, use_column_width=True) - st.markdown('
', unsafe_allow_html=True) + # Image generation and display - Always show if images are enabled + if generator and generator.config.include_images: + st.markdown("### Image Generation") + col1, col2, col3 = st.columns([2, 2, 1]) - # Display image prompt in expander - if content.image_prompt: - with st.expander("View Image Prompt"): - st.code(content.image_prompt, language="text") + with col1: + image_style = st.selectbox( + "Image Style", + ["realistic", "illustration", "minimalist", "photographic", "artistic"], + index=["realistic", "illustration", "minimalist", "photographic", "artistic"].index(generator.config.image_style), + key=f"img_style_{section}" + ) + + with col2: + image_engine = st.selectbox( + "Image Engine", + ["Gemini-AI", "Dalle3", "Stability-AI"], + index=["Gemini-AI", "Dalle3", "Stability-AI"].index(generator.config.image_engine), + key=f"img_engine_{section}" + ) + + with col3: + if st.button("Generate Image", key=f"gen_img_{section}"): + with st.spinner(f"Generating image for {section}..."): + # Update config with selected options + generator.config.image_style = image_style + generator.config.image_engine = image_engine + image_path = generator.generate_section_image(section) + if image_path: + st.success("Image generated successfully!") + st.experimental_rerun() + else: + st.error("Failed to generate image") + + # Display existing image if available + if content.image_path: + st.markdown('
', unsafe_allow_html=True) + st.image(content.image_path, caption=section, use_column_width=True) + st.markdown('
', unsafe_allow_html=True) + + # Display image prompt in expander + if content.image_prompt: + with st.expander("View Image Prompt"): + st.code(content.image_prompt, language="text") # Edit mode controls if edit_mode: + st.markdown("### Edit Content") # Edit content edited_content = edit_section_content(section, content.content) - content.content = edited_content + if edited_content != content.content: + content.content = edited_content + st.experimental_rerun() + st.markdown("### Edit Subsections") # Edit subsections edited_subsections = edit_subsections(section, subsections) - subsections[:] = edited_subsections + if edited_subsections != subsections: + subsections[:] = edited_subsections + st.experimental_rerun() + st.markdown("### Edit Metadata") # Edit metadata if generator: edit_section_metadata(section, generator) - - # Display subsections - st.markdown("### Subsections") - st.markdown('
', unsafe_allow_html=True) - for subsection in subsections: - st.markdown(f"- {subsection}") - st.markdown('
', unsafe_allow_html=True) + else: + # Display subsections in view mode + st.markdown("### Subsections") + st.markdown('
', unsafe_allow_html=True) + for subsection in subsections: + st.markdown(f'
• {subsection}
', unsafe_allow_html=True) + st.markdown('
', unsafe_allow_html=True) st.markdown("", unsafe_allow_html=True) @@ -297,13 +422,6 @@ def display_stats(generator, outline): """, unsafe_allow_html=True) def main(): - st.set_page_config( - page_title="Blog Outline Generator", - page_icon="📝", - layout="wide", - initial_sidebar_state="expanded" - ) - # Header with description st.title("Blog Outline Generator") st.markdown(""" @@ -311,179 +429,311 @@ def main(): Customize your outline with various options and get detailed content for each section. """) - # Sidebar for configuration - with st.sidebar: - st.header("Configuration") + # Main content area with full width + st.markdown('
', unsafe_allow_html=True) + + # Move topic input to main area and make it more prominent + st.markdown("### Enter Your Blog Topic") + topic = st.text_input("", placeholder="Enter your blog topic here for creating outline...", key="blog_topic") + + st.markdown("---") # Add a separator + st.markdown("### Configuration Options") + + # Create tabs for different configuration sections + tab1, tab2, tab3, tab4 = st.tabs([ + "📝 Content Type & Target", + "📊 Content Structure", + "🎨 Style & Sections", + "🖼️ Image & Optimization" + ]) + + with tab1: + st.markdown("#### Content Type & Target") + col1, col2, col3 = st.columns(3) - # Basic settings - topic = st.text_input("Blog Topic", placeholder="Enter your blog topic") - content_type = st.selectbox( - "Content Type", - [type.value for type in ContentType] - ) - content_depth = st.selectbox( - "Content Depth", - [depth.value for depth in ContentDepth] - ) - outline_style = st.selectbox( - "Outline Style", - [style.value for style in OutlineStyle] - ) + with col1: + content_type = st.selectbox( + "Content Type", + [type.value for type in ContentType], + index=[type.value for type in ContentType].index(ContentType.GUIDE.value), + help="Select the type of content you want to generate" + ) - # Content structure - st.subheader("Content Structure") - target_word_count = st.slider("Target Word Count", 500, 5000, 2000, 100) - num_main_sections = st.slider("Number of Main Sections", 3, 10, 5) - num_subsections = st.slider("Subsections per Section", 2, 5, 3) + with col2: + target_audience = st.selectbox( + "Target Audience", + ["General", "Technical", "Professional", "Academic", "Business", "Students", "Developers"], + index=0, + help="Select your target audience" + ) - # Advanced settings - with st.expander("Advanced Settings"): - include_intro = st.checkbox("Include Introduction", value=True) - include_conclusion = st.checkbox("Include Conclusion", value=True) - include_faqs = st.checkbox("Include FAQs", value=True) - include_resources = st.checkbox("Include Resources", value=True) + with col3: + language = st.selectbox( + "Language", + ["English", "Spanish", "French", "German", "Italian", "Portuguese", "Chinese", "Japanese", "Korean"], + index=0, + help="Select the language for your content" + ) + + with tab2: + st.markdown("#### Content Structure") + col1, col2 = st.columns(2) + + with col1: + num_main_sections = st.slider( + "Number of Main Sections", + min_value=3, + max_value=10, + value=5, + step=1, + help="Choose how many main sections your outline should have" + ) + + num_subsections = st.slider( + "Subsections per Section", + min_value=2, + max_value=5, + value=3, + step=1, + help="Choose how many subsections each main section should have" + ) + + with col2: + target_word_count = st.slider( + "Target Word Count", + min_value=500, + max_value=5000, + value=2000, + step=100, + help="Set your target word count for the entire blog post" + ) + + # Display content statistics + st.markdown("##### Content Statistics") + st.markdown(f""" + - Estimated Sections: {num_main_sections} + - Total Subsections: {num_main_sections * num_subsections} + - Target Word Count: {target_word_count} + - Average Words per Section: {target_word_count // num_main_sections} + """) + + with tab3: + st.markdown("#### Style & Sections") + col1, col2 = st.columns(2) + + with col1: + content_depth = st.selectbox( + "Content Depth", + [depth.value for depth in ContentDepth], + index=[depth.value for depth in ContentDepth].index(ContentDepth.INTERMEDIATE.value), + help="Select the depth of content coverage" + ) + + outline_style = st.selectbox( + "Outline Style", + [style.value for style in OutlineStyle], + index=[style.value for style in OutlineStyle].index(OutlineStyle.MODERN.value), + help="Select the style of your outline" + ) + + with col2: + st.markdown("##### Additional Sections") + include_intro = st.checkbox("Include Introduction", value=True, help="Add an introduction section") + include_conclusion = st.checkbox("Include Conclusion", value=True, help="Add a conclusion section") + include_faqs = st.checkbox("Include FAQs", value=True, help="Add a FAQ section") + include_resources = st.checkbox("Include Resources", value=True, help="Add a resources section") + + with tab4: + st.markdown("#### Image & Optimization") + col1, col2 = st.columns(2) + + with col1: + st.markdown("##### Image Settings") + include_images = st.checkbox("Enable Image Generation", value=True, help="Enable AI image generation for sections") - # Image settings - st.subheader("Image Settings") - include_images = st.checkbox("Include Images", value=True) if include_images: image_style = st.selectbox( "Image Style", - ["realistic", "illustration", "minimalist", "photographic", "artistic"] + ["realistic", "illustration", "minimalist", "photographic", "artistic"], + index=0, + help="Select the style for generated images" ) + image_engine = st.selectbox( "Image Engine", - ["Gemini-AI", "Dalle3", "Stability-AI"] + ["Gemini-AI", "Dalle3", "Stability-AI"], + index=0, + help="Select the AI engine for image generation" ) + + with col2: + st.markdown("##### Content Optimization") + keywords = st.text_area( + "Keywords (comma-separated)", + help="Enter keywords for SEO optimization, separated by commas" + ) - # Target audience and language - st.subheader("Target Audience") - target_audience = st.text_input("Target Audience", value="general") - language = st.text_input("Language", value="English") - - # Keywords and exclusions - st.subheader("Content Optimization") - keywords = st.text_area("Keywords (comma-separated)") - exclude_topics = st.text_area("Topics to Exclude (comma-separated)") + exclude_topics = st.text_area( + "Topics to Exclude (comma-separated)", + help="Enter topics you want to exclude from the content" + ) - # Main content area - if topic: - # Create configuration - config = OutlineConfig( - content_type=ContentType(content_type), - content_depth=ContentDepth(content_depth), - outline_style=OutlineStyle(outline_style), - target_word_count=target_word_count, - num_main_sections=num_main_sections, - num_subsections_per_section=num_subsections, - include_introduction=include_intro, - include_conclusion=include_conclusion, - include_faqs=include_faqs, - include_resources=include_resources, - include_images=include_images, - image_style=image_style if include_images else "realistic", - image_engine=image_engine if include_images else "Gemini-AI", - target_audience=target_audience, - language=language, - keywords=[k.strip() for k in keywords.split(',')] if keywords else None, - exclude_topics=[t.strip() for t in exclude_topics.split(',')] if exclude_topics else None - ) - - # Initialize generator - generator = BlogOutlineGenerator(config) - - # Generate outline - if st.button("Generate Outline"): - with st.spinner("Generating outline and content..."): - try: - # Add progress bar - progress_bar = st.progress(0) - for i in range(100): - time.sleep(0.01) - progress_bar.progress(i + 1) - - outline = asyncio.run(generator.generate_outline(topic)) - - # Display results - st.success("Outline generated successfully!") - - # Display statistics - display_stats(generator, outline) - - # Output format selection - output_format = st.radio( - "Output Format", - ["Preview", "Markdown", "JSON", "HTML"] - ) - - if output_format == "Preview": - # Display outline with content and images - for section, subsections in outline.items(): - content = generator.section_contents.get(section) - display_section(section, subsections, content) - - elif output_format == "Markdown": - st.code(generator.to_markdown(), language="markdown") - st.download_button( - "Download Markdown", - generator.to_markdown(), - file_name="blog_outline.md", - mime="text/markdown" - ) - - elif output_format == "JSON": - json_output = json.dumps({ - "outline": outline, - "contents": { - section: { - "title": content.title, - "content": content.content, - "image_prompt": content.image_prompt, - "image_path": content.image_path - } - for section, content in generator.section_contents.items() - } - }, indent=2) - st.code(json_output, language="json") - st.download_button( - "Download JSON", - json_output, - file_name="blog_outline.json", - mime="application/json" - ) - - elif output_format == "HTML": - # Add HTML export functionality - html_output = f""" - - - - {topic} - Blog Outline - - - -

{topic}

- {generator.to_markdown().replace('#', '##')} - - - """ - st.code(html_output, language="html") - st.download_button( - "Download HTML", - html_output, - file_name="blog_outline.html", - mime="text/html" - ) + st.markdown("---") # Add a separator before the generate button + + # Create configuration + config = OutlineConfig( + content_type=ContentType(content_type), + content_depth=ContentDepth(content_depth), + outline_style=OutlineStyle(outline_style), + target_word_count=target_word_count, + num_main_sections=num_main_sections, + num_subsections_per_section=num_subsections, + include_introduction=include_intro, + include_conclusion=include_conclusion, + include_faqs=include_faqs, + include_resources=include_resources, + include_images=include_images, + image_style=image_style if include_images else "realistic", + image_engine=image_engine if include_images else "Gemini-AI", + target_audience=target_audience, + language=language, + keywords=[k.strip() for k in keywords.split(',')] if keywords else None, + exclude_topics=[t.strip() for t in exclude_topics.split(',')] if exclude_topics else None + ) + + # Initialize generator + generator = BlogOutlineGenerator(config) + + # Store the generated outline in session state + if 'outline' not in st.session_state: + st.session_state.outline = None + if 'section_contents' not in st.session_state: + st.session_state.section_contents = {} + + # Generate outline button with full width + st.markdown('
', unsafe_allow_html=True) + if not topic: + st.warning("Please enter a blog topic to generate the outline.") + if st.button("Generate Outline", type="primary", use_container_width=True, disabled=not topic): + with st.spinner("Generating outline and content..."): + try: + # Add progress bar + progress_bar = st.progress(0) + for i in range(100): + time.sleep(0.01) + progress_bar.progress(i + 1) - except Exception as e: - st.error(f"Error generating outline: {str(e)}") - else: - st.info("Please enter a blog topic to get started.") + outline = generator.generate_outline(topic) + st.session_state.outline = outline + st.session_state.section_contents = generator.section_contents + + # Display results + st.success("Outline generated successfully!") + + # Add copy button and display outline in full width + st.markdown('
', unsafe_allow_html=True) + outline_text = json.dumps(outline, indent=2) + st.code(outline_text, language="json") + st.button("Copy Outline", key="copy_outline", + help="Copy the outline to clipboard", + on_click=lambda: st.write(f'', + unsafe_allow_html=True)) + st.markdown('
', unsafe_allow_html=True) + + # Display statistics + display_stats(generator, outline) + + # Output format selection + output_format = st.radio( + "Output Format", + ["Preview", "Markdown", "JSON", "HTML"] + ) + + if output_format == "Preview": + # Display outline with content and images + st.markdown('
', unsafe_allow_html=True) + for section, subsections in outline.items(): + content = generator.section_contents.get(section) + display_section(section, subsections, content, generator) + st.markdown('
', unsafe_allow_html=True) + + elif output_format == "Markdown": + markdown_output = generator.to_markdown() + st.markdown('
', unsafe_allow_html=True) + st.code(markdown_output, language="markdown") + st.download_button( + "Download Markdown", + markdown_output, + file_name="blog_outline.md", + mime="text/markdown" + ) + st.markdown('
', unsafe_allow_html=True) + + elif output_format == "JSON": + json_output = json.dumps({ + "outline": outline, + "contents": { + section: { + "title": content.title, + "content": content.content, + "image_prompt": content.image_prompt, + "image_path": content.image_path + } + for section, content in generator.section_contents.items() + } + }, indent=2) + st.markdown('
', unsafe_allow_html=True) + st.code(json_output, language="json") + st.download_button( + "Download JSON", + json_output, + file_name="blog_outline.json", + mime="application/json" + ) + st.markdown('
', unsafe_allow_html=True) + + elif output_format == "HTML": + html_output = f""" + + + + {topic} - Blog Outline + + + +

{topic}

+ {generator.to_markdown().replace('#', '##')} + + + """ + st.markdown('
', unsafe_allow_html=True) + st.code(html_output, language="html") + st.download_button( + "Download HTML", + html_output, + file_name="blog_outline.html", + mime="text/html" + ) + st.markdown('
', unsafe_allow_html=True) + + except Exception as e: + st.error(f"Error generating outline: {str(e)}") + st.markdown('
', unsafe_allow_html=True) + + # Display the outline if it exists in session state + if st.session_state.outline: + st.markdown('
', unsafe_allow_html=True) + for section, subsections in st.session_state.outline.items(): + content = st.session_state.section_contents.get(section) + display_section(section, subsections, content, generator) + st.markdown('
', unsafe_allow_html=True) + + st.markdown('
', unsafe_allow_html=True) # Close the outline container if __name__ == "__main__": main() \ No newline at end of file