AI Outline Writer - Added image generation and display for sections

This commit is contained in:
ajaysi
2025-05-07 16:39:28 +05:30
parent 5f7d319859
commit b2ce1ceb49
2 changed files with 617 additions and 386 deletions

View File

@@ -10,6 +10,7 @@ from typing import Dict, List, Optional
from enum import Enum from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass
from loguru import logger from loguru import logger
import json
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen 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 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.outline = {}
self.section_contents = {} self.section_contents = {}
async def generate_outline(self, topic: str) -> Dict: def generate_outline(self, topic: str) -> Dict[str, List[str]]:
"""Generate a comprehensive outline based on the topic and configuration.""" """Generate a blog outline based on the topic and configuration."""
try: try:
# Step 1: Generate main sections # Create a focused prompt for outline generation
main_sections = await self._generate_main_sections(topic) 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 # Clean the response to ensure it's valid JSON
detailed_sections = await self._generate_subsections(main_sections) 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: 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: 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: if self.config.include_faqs:
detailed_sections["FAQs"] = await self._generate_faqs(topic) # Generate topic-specific FAQs
faq_prompt = f"""Generate 3 specific and relevant FAQ questions for a blog post about: {topic}
# Step 5: Add resources if requested
if self.config.include_resources: Content Type: {self.config.content_type.value}
detailed_sections["Additional Resources"] = await self._generate_resources(topic) Target Audience: {self.config.target_audience}
Content Depth: {self.config.content_depth.value}
self.outline = detailed_sections
Requirements:
# Step 6: Generate content for each section - Questions should be specific to the topic
await self._generate_section_contents(topic) - Cover common concerns and important aspects
- Be relevant to the target audience
return self.outline - Include both basic and advanced questions
except Exception as err: Format: Return only a JSON array of 3 questions.
logger.error(f"Failed to generate outline: {err}") Example format: ["Question 1?", "Question 2?", "Question 3?"]"""
raise
try:
async def _generate_main_sections(self, topic: str) -> List[str]: faq_json = llm_text_gen(faq_prompt)
"""Generate main sections for the outline.""" faq_json = faq_json.strip()
prompt = f"""Generate {self.config.num_main_sections} main sections for a {self.config.content_type.value} if not faq_json.startswith('['):
article about {topic} with the following characteristics: faq_json = faq_json[faq_json.find('['):]
if not faq_json.endswith(']'):
Content Type: {self.config.content_type.value} faq_json = faq_json[:faq_json.rfind(']')+1]
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}
"""
image_prompt = await llm_text_gen(image_prompt) faqs = json.loads(faq_json)
try: outline["Frequently Asked Questions"] = faqs
image_path = generate_image( except Exception as e:
image_prompt, logger.error(f"Error generating FAQs: {str(e)}")
title=section, outline["Frequently Asked Questions"] = [
description=content[:100], f"Common Question about {topic} 1",
tags=self.config.keywords f"Common Question about {topic} 2",
) f"Common Question about {topic} 3"
except Exception as err: ]
logger.warning(f"Failed to generate image for section {section}: {err}")
# Add resources if configured
self.section_contents[section] = SectionContent( if self.config.include_resources:
title=section, outline["Additional Resources"] = [
content=content, "Further Reading",
image_prompt=image_prompt, "Tools and References",
image_path=image_path "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: def to_markdown(self) -> str:
"""Convert outline to markdown format with content and images.""" """Convert outline to markdown format with content and images."""
markdown = f"# {self.outline.get('Introduction', [''])[0]}\n\n" markdown = f"# {self.outline.get('Introduction', [''])[0]}\n\n"

View File

@@ -34,42 +34,126 @@ st.markdown("""
border-radius: 4px; border-radius: 4px;
border: none; border: none;
font-weight: bold; font-weight: bold;
width: 100%;
} }
.stButton>button:hover { .stButton>button:hover {
background-color: #45a049; 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 { .section-card {
background-color: white; background-color: white;
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px; margin-bottom: 20px;
width: 100%;
} }
.content-preview { .content-preview {
background-color: #f8f9fa; background-color: #f8f9fa;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
margin: 10px 0; margin: 10px 0;
width: 100%;
} }
.image-container { .image-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: 20px 0; margin: 20px 0;
width: 100%;
} }
.stats-card { .stats-card {
background-color: #e8f5e9; background-color: #e8f5e9;
padding: 15px; padding: 15px;
border-radius: 8px; border-radius: 8px;
margin: 10px 0; margin: 10px 0;
width: 100%;
} }
.edit-section { .edit-section {
background-color: #e3f2fd; background-color: #e3f2fd;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
margin: 10px 0; margin: 10px 0;
width: 100%;
} }
.subsection-list { .subsection-list {
margin-left: 20px; 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;
} }
</style> </style>
""", unsafe_allow_html=True) """, 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.""" """Display a section with its content and subsections."""
st.markdown(f""" st.markdown(f"""
<div class="section-card"> <div class="section-card">
<h2>{section}</h2> <div class="section-header">{section}</div>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
# Section editing controls # Section editing controls
@@ -225,37 +309,78 @@ def display_section(section: str, subsections: List[str], content: Optional[Dict
</div> </div>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
# Display image if available # Image generation and display - Always show if images are enabled
if content.image_path: if generator and generator.config.include_images:
st.markdown('<div class="image-container">', unsafe_allow_html=True) st.markdown("### Image Generation")
st.image(content.image_path, caption=section, use_column_width=True) col1, col2, col3 = st.columns([2, 2, 1])
st.markdown('</div>', unsafe_allow_html=True)
# Display image prompt in expander with col1:
if content.image_prompt: image_style = st.selectbox(
with st.expander("View Image Prompt"): "Image Style",
st.code(content.image_prompt, language="text") ["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('<div class="image-container">', unsafe_allow_html=True)
st.image(content.image_path, caption=section, use_column_width=True)
st.markdown('</div>', 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 # Edit mode controls
if edit_mode: if edit_mode:
st.markdown("### Edit Content")
# Edit content # Edit content
edited_content = edit_section_content(section, content.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 # Edit subsections
edited_subsections = edit_subsections(section, 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 # Edit metadata
if generator: if generator:
edit_section_metadata(section, generator) edit_section_metadata(section, generator)
else:
# Display subsections # Display subsections in view mode
st.markdown("### Subsections") st.markdown("### Subsections")
st.markdown('<div class="subsection-list">', unsafe_allow_html=True) st.markdown('<div class="subsection-list">', unsafe_allow_html=True)
for subsection in subsections: for subsection in subsections:
st.markdown(f"- {subsection}") st.markdown(f'<div class="subsection-item">• {subsection}</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True) st.markdown('</div>', unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True) st.markdown("</div>", unsafe_allow_html=True)
@@ -297,13 +422,6 @@ def display_stats(generator, outline):
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
def main(): def main():
st.set_page_config(
page_title="Blog Outline Generator",
page_icon="📝",
layout="wide",
initial_sidebar_state="expanded"
)
# Header with description # Header with description
st.title("Blog Outline Generator") st.title("Blog Outline Generator")
st.markdown(""" st.markdown("""
@@ -311,179 +429,311 @@ def main():
Customize your outline with various options and get detailed content for each section. Customize your outline with various options and get detailed content for each section.
""") """)
# Sidebar for configuration # Main content area with full width
with st.sidebar: st.markdown('<div class="outline-container">', unsafe_allow_html=True)
st.header("Configuration")
# 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 with col1:
topic = st.text_input("Blog Topic", placeholder="Enter your blog topic") content_type = st.selectbox(
content_type = st.selectbox( "Content Type",
"Content Type", [type.value for type in ContentType],
[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_depth = st.selectbox( )
"Content Depth",
[depth.value for depth in ContentDepth]
)
outline_style = st.selectbox(
"Outline Style",
[style.value for style in OutlineStyle]
)
# Content structure with col2:
st.subheader("Content Structure") target_audience = st.selectbox(
target_word_count = st.slider("Target Word Count", 500, 5000, 2000, 100) "Target Audience",
num_main_sections = st.slider("Number of Main Sections", 3, 10, 5) ["General", "Technical", "Professional", "Academic", "Business", "Students", "Developers"],
num_subsections = st.slider("Subsections per Section", 2, 5, 3) index=0,
help="Select your target audience"
)
# Advanced settings with col3:
with st.expander("Advanced Settings"): language = st.selectbox(
include_intro = st.checkbox("Include Introduction", value=True) "Language",
include_conclusion = st.checkbox("Include Conclusion", value=True) ["English", "Spanish", "French", "German", "Italian", "Portuguese", "Chinese", "Japanese", "Korean"],
include_faqs = st.checkbox("Include FAQs", value=True) index=0,
include_resources = st.checkbox("Include Resources", value=True) 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: if include_images:
image_style = st.selectbox( image_style = st.selectbox(
"Image Style", "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 = st.selectbox(
"Image Engine", "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 exclude_topics = st.text_area(
st.subheader("Target Audience") "Topics to Exclude (comma-separated)",
target_audience = st.text_input("Target Audience", value="general") help="Enter topics you want to exclude from the content"
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)")
# Main content area st.markdown("---") # Add a separator before the generate button
if topic:
# Create configuration # Create configuration
config = OutlineConfig( config = OutlineConfig(
content_type=ContentType(content_type), content_type=ContentType(content_type),
content_depth=ContentDepth(content_depth), content_depth=ContentDepth(content_depth),
outline_style=OutlineStyle(outline_style), outline_style=OutlineStyle(outline_style),
target_word_count=target_word_count, target_word_count=target_word_count,
num_main_sections=num_main_sections, num_main_sections=num_main_sections,
num_subsections_per_section=num_subsections, num_subsections_per_section=num_subsections,
include_introduction=include_intro, include_introduction=include_intro,
include_conclusion=include_conclusion, include_conclusion=include_conclusion,
include_faqs=include_faqs, include_faqs=include_faqs,
include_resources=include_resources, include_resources=include_resources,
include_images=include_images, include_images=include_images,
image_style=image_style if include_images else "realistic", image_style=image_style if include_images else "realistic",
image_engine=image_engine if include_images else "Gemini-AI", image_engine=image_engine if include_images else "Gemini-AI",
target_audience=target_audience, target_audience=target_audience,
language=language, language=language,
keywords=[k.strip() for k in keywords.split(',')] if keywords else None, 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 exclude_topics=[t.strip() for t in exclude_topics.split(',')] if exclude_topics else None
) )
# Initialize generator # Initialize generator
generator = BlogOutlineGenerator(config) generator = BlogOutlineGenerator(config)
# Generate outline # Store the generated outline in session state
if st.button("Generate Outline"): if 'outline' not in st.session_state:
with st.spinner("Generating outline and content..."): st.session_state.outline = None
try: if 'section_contents' not in st.session_state:
# Add progress bar st.session_state.section_contents = {}
progress_bar = st.progress(0)
for i in range(100): # Generate outline button with full width
time.sleep(0.01) st.markdown('<div class="generate-outline-button">', unsafe_allow_html=True)
progress_bar.progress(i + 1) if not topic:
st.warning("Please enter a blog topic to generate the outline.")
outline = asyncio.run(generator.generate_outline(topic)) if st.button("Generate Outline", type="primary", use_container_width=True, disabled=not topic):
with st.spinner("Generating outline and content..."):
# Display results try:
st.success("Outline generated successfully!") # Add progress bar
progress_bar = st.progress(0)
# Display statistics for i in range(100):
display_stats(generator, outline) time.sleep(0.01)
progress_bar.progress(i + 1)
# 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"""
<!DOCTYPE html>
<html>
<head>
<title>{topic} - Blog Outline</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
.section {{ margin-bottom: 30px; }}
.content {{ background: #f8f9fa; padding: 15px; border-radius: 4px; }}
img {{ max-width: 100%; height: auto; }}
</style>
</head>
<body>
<h1>{topic}</h1>
{generator.to_markdown().replace('#', '##')}
</body>
</html>
"""
st.code(html_output, language="html")
st.download_button(
"Download HTML",
html_output,
file_name="blog_outline.html",
mime="text/html"
)
except Exception as e: outline = generator.generate_outline(topic)
st.error(f"Error generating outline: {str(e)}") st.session_state.outline = outline
else: st.session_state.section_contents = generator.section_contents
st.info("Please enter a blog topic to get started.")
# Display results
st.success("Outline generated successfully!")
# Add copy button and display outline in full width
st.markdown('<div class="outline-content">', 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'<script>navigator.clipboard.writeText(`{outline_text}`)</script>',
unsafe_allow_html=True))
st.markdown('</div>', 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('<div class="preview-section">', unsafe_allow_html=True)
for section, subsections in outline.items():
content = generator.section_contents.get(section)
display_section(section, subsections, content, generator)
st.markdown('</div>', unsafe_allow_html=True)
elif output_format == "Markdown":
markdown_output = generator.to_markdown()
st.markdown('<div class="outline-content">', 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('</div>', 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('<div class="outline-content">', 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('</div>', unsafe_allow_html=True)
elif output_format == "HTML":
html_output = f"""
<!DOCTYPE html>
<html>
<head>
<title>{topic} - Blog Outline</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 100%; margin: 0 auto; padding: 20px; }}
.section {{ margin-bottom: 30px; }}
.content {{ background: #f8f9fa; padding: 15px; border-radius: 4px; }}
img {{ max-width: 100%; height: auto; }}
</style>
</head>
<body>
<h1>{topic}</h1>
{generator.to_markdown().replace('#', '##')}
</body>
</html>
"""
st.markdown('<div class="outline-content">', 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('</div>', unsafe_allow_html=True)
except Exception as e:
st.error(f"Error generating outline: {str(e)}")
st.markdown('</div>', unsafe_allow_html=True)
# Display the outline if it exists in session state
if st.session_state.outline:
st.markdown('<div class="preview-section">', 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('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True) # Close the outline container
if __name__ == "__main__": if __name__ == "__main__":
main() main()