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}
# Step 2: Generate subsections for each main section Content Type: {self.config.content_type.value}
detailed_sections = await self._generate_subsections(main_sections) 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}
# Step 3: Add introduction and conclusion if requested 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)
# 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]
# 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 Content Type: {self.config.content_type.value}
if self.config.include_resources: Target Audience: {self.config.target_audience}
detailed_sections["Additional Resources"] = await self._generate_resources(topic) Content Depth: {self.config.content_depth.value}
self.outline = detailed_sections 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
# Step 6: Generate content for each section Format: Return only a JSON array of 3 questions.
await self._generate_section_contents(topic) Example format: ["Question 1?", "Question 2?", "Question 3?"]"""
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}
"""
image_prompt = await llm_text_gen(image_prompt)
try: try:
image_path = generate_image( faq_json = llm_text_gen(faq_prompt)
image_prompt, faq_json = faq_json.strip()
title=section, if not faq_json.startswith('['):
description=content[:100], faq_json = faq_json[faq_json.find('['):]
tags=self.config.keywords if not faq_json.endswith(']'):
) faq_json = faq_json[:faq_json.rfind(']')+1]
except Exception as err:
logger.warning(f"Failed to generate image for section {section}: {err}")
self.section_contents[section] = SectionContent( 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, title=section,
content=content, content=content_data["content"],
image_prompt=image_prompt, image_prompt=content_data.get("image_prompt"),
image_path=image_path 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,7 +309,41 @@ 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 generator and generator.config.include_images:
st.markdown("### Image Generation")
col1, col2, col3 = st.columns([2, 2, 1])
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: if content.image_path:
st.markdown('<div class="image-container">', unsafe_allow_html=True) st.markdown('<div class="image-container">', unsafe_allow_html=True)
st.image(content.image_path, caption=section, use_column_width=True) st.image(content.image_path, caption=section, use_column_width=True)
@@ -238,23 +356,30 @@ def display_section(section: str, subsections: List[str], content: Optional[Dict
# 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)
if edited_content != content.content:
content.content = edited_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)
if edited_subsections != subsections:
subsections[:] = edited_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,63 +429,157 @@ 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")
# Basic settings # Move topic input to main area and make it more prominent
topic = st.text_input("Blog Topic", placeholder="Enter your blog topic") 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)
with col1:
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"
) )
with col2:
target_audience = st.selectbox(
"Target Audience",
["General", "Technical", "Professional", "Academic", "Business", "Students", "Developers"],
index=0,
help="Select your target audience"
)
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 = st.selectbox(
"Content Depth", "Content Depth",
[depth.value for depth in ContentDepth] [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 = st.selectbox(
"Outline Style", "Outline Style",
[style.value for style in OutlineStyle] [style.value for style in OutlineStyle],
index=[style.value for style in OutlineStyle].index(OutlineStyle.MODERN.value),
help="Select the style of your outline"
) )
# Content structure with col2:
st.subheader("Content Structure") st.markdown("##### Additional Sections")
target_word_count = st.slider("Target Word Count", 500, 5000, 2000, 100) include_intro = st.checkbox("Include Introduction", value=True, help="Add an introduction section")
num_main_sections = st.slider("Number of Main Sections", 3, 10, 5) include_conclusion = st.checkbox("Include Conclusion", value=True, help="Add a conclusion section")
num_subsections = st.slider("Subsections per Section", 2, 5, 3) 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")
# Advanced settings with tab4:
with st.expander("Advanced Settings"): st.markdown("#### Image & Optimization")
include_intro = st.checkbox("Include Introduction", value=True) col1, col2 = st.columns(2)
include_conclusion = st.checkbox("Include Conclusion", value=True)
include_faqs = st.checkbox("Include FAQs", value=True) with col1:
include_resources = st.checkbox("Include Resources", value=True) 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"
) )
# Target audience and language with col2:
st.subheader("Target Audience") st.markdown("##### Content Optimization")
target_audience = st.text_input("Target Audience", value="general") keywords = st.text_area(
language = st.text_input("Language", value="English") "Keywords (comma-separated)",
help="Enter keywords for SEO optimization, separated by commas"
)
# Keywords and exclusions exclude_topics = st.text_area(
st.subheader("Content Optimization") "Topics to Exclude (comma-separated)",
keywords = st.text_area("Keywords (comma-separated)") help="Enter topics you want to exclude from the content"
exclude_topics = st.text_area("Topics to Exclude (comma-separated)") )
st.markdown("---") # Add a separator before the generate button
# Main content area
if topic:
# Create configuration # Create configuration
config = OutlineConfig( config = OutlineConfig(
content_type=ContentType(content_type), content_type=ContentType(content_type),
@@ -392,8 +604,17 @@ def main():
# 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:
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('<div class="generate-outline-button">', 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..."): with st.spinner("Generating outline and content..."):
try: try:
# Add progress bar # Add progress bar
@@ -402,11 +623,23 @@ def main():
time.sleep(0.01) time.sleep(0.01)
progress_bar.progress(i + 1) progress_bar.progress(i + 1)
outline = asyncio.run(generator.generate_outline(topic)) outline = generator.generate_outline(topic)
st.session_state.outline = outline
st.session_state.section_contents = generator.section_contents
# Display results # Display results
st.success("Outline generated successfully!") 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 statistics
display_stats(generator, outline) display_stats(generator, outline)
@@ -418,18 +651,23 @@ def main():
if output_format == "Preview": if output_format == "Preview":
# Display outline with content and images # Display outline with content and images
st.markdown('<div class="preview-section">', unsafe_allow_html=True)
for section, subsections in outline.items(): for section, subsections in outline.items():
content = generator.section_contents.get(section) content = generator.section_contents.get(section)
display_section(section, subsections, content) display_section(section, subsections, content, generator)
st.markdown('</div>', unsafe_allow_html=True)
elif output_format == "Markdown": elif output_format == "Markdown":
st.code(generator.to_markdown(), language="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( st.download_button(
"Download Markdown", "Download Markdown",
generator.to_markdown(), markdown_output,
file_name="blog_outline.md", file_name="blog_outline.md",
mime="text/markdown" mime="text/markdown"
) )
st.markdown('</div>', unsafe_allow_html=True)
elif output_format == "JSON": elif output_format == "JSON":
json_output = json.dumps({ json_output = json.dumps({
@@ -444,6 +682,7 @@ def main():
for section, content in generator.section_contents.items() for section, content in generator.section_contents.items()
} }
}, indent=2) }, indent=2)
st.markdown('<div class="outline-content">', unsafe_allow_html=True)
st.code(json_output, language="json") st.code(json_output, language="json")
st.download_button( st.download_button(
"Download JSON", "Download JSON",
@@ -451,16 +690,16 @@ def main():
file_name="blog_outline.json", file_name="blog_outline.json",
mime="application/json" mime="application/json"
) )
st.markdown('</div>', unsafe_allow_html=True)
elif output_format == "HTML": elif output_format == "HTML":
# Add HTML export functionality
html_output = f""" html_output = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>{topic} - Blog Outline</title> <title>{topic} - Blog Outline</title>
<style> <style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }} body {{ font-family: Arial, sans-serif; max-width: 100%; margin: 0 auto; padding: 20px; }}
.section {{ margin-bottom: 30px; }} .section {{ margin-bottom: 30px; }}
.content {{ background: #f8f9fa; padding: 15px; border-radius: 4px; }} .content {{ background: #f8f9fa; padding: 15px; border-radius: 4px; }}
img {{ max-width: 100%; height: auto; }} img {{ max-width: 100%; height: auto; }}
@@ -472,6 +711,7 @@ def main():
</body> </body>
</html> </html>
""" """
st.markdown('<div class="outline-content">', unsafe_allow_html=True)
st.code(html_output, language="html") st.code(html_output, language="html")
st.download_button( st.download_button(
"Download HTML", "Download HTML",
@@ -479,11 +719,21 @@ def main():
file_name="blog_outline.html", file_name="blog_outline.html",
mime="text/html" mime="text/html"
) )
st.markdown('</div>', unsafe_allow_html=True)
except Exception as e: except Exception as e:
st.error(f"Error generating outline: {str(e)}") st.error(f"Error generating outline: {str(e)}")
else: st.markdown('</div>', unsafe_allow_html=True)
st.info("Please enter a blog topic to get started.")
# 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()