ALwrity Version 0.5.1 (Fastapi + React)
This commit is contained in:
317
ToBeMigrated/ai_writers/ai_outline_writer/get_blog_outline.py
Normal file
317
ToBeMigrated/ai_writers/ai_outline_writer/get_blog_outline.py
Normal file
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
Enhanced Blog Outline Generator
|
||||
|
||||
This module provides a sophisticated outline generation system that creates detailed,
|
||||
well-structured outlines for blog posts based on user preferences and content requirements.
|
||||
"""
|
||||
|
||||
import sys
|
||||
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
|
||||
|
||||
logger.remove()
|
||||
logger.add(sys.stdout,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}")
|
||||
|
||||
class ContentType(Enum):
|
||||
"""Types of content that can be generated."""
|
||||
HOW_TO = "how-to"
|
||||
TUTORIAL = "tutorial"
|
||||
LISTICLE = "listicle"
|
||||
COMPARISON = "comparison"
|
||||
CASE_STUDY = "case-study"
|
||||
OPINION = "opinion"
|
||||
NEWS = "news"
|
||||
REVIEW = "review"
|
||||
GUIDE = "guide"
|
||||
|
||||
class ContentDepth(Enum):
|
||||
"""Depth levels for content coverage."""
|
||||
BASIC = "basic"
|
||||
INTERMEDIATE = "intermediate"
|
||||
ADVANCED = "advanced"
|
||||
EXPERT = "expert"
|
||||
|
||||
class OutlineStyle(Enum):
|
||||
"""Styles for outline structure."""
|
||||
TRADITIONAL = "traditional"
|
||||
MODERN = "modern"
|
||||
CONVERSATIONAL = "conversational"
|
||||
ACADEMIC = "academic"
|
||||
SEO_OPTIMIZED = "seo-optimized"
|
||||
|
||||
@dataclass
|
||||
class OutlineConfig:
|
||||
"""Configuration for outline generation."""
|
||||
content_type: ContentType = ContentType.GUIDE
|
||||
content_depth: ContentDepth = ContentDepth.INTERMEDIATE
|
||||
outline_style: OutlineStyle = OutlineStyle.MODERN
|
||||
target_word_count: int = 2000
|
||||
num_main_sections: int = 5
|
||||
num_subsections_per_section: int = 3
|
||||
include_introduction: bool = True
|
||||
include_conclusion: bool = True
|
||||
include_faqs: bool = True
|
||||
include_resources: bool = True
|
||||
target_audience: str = "general"
|
||||
language: str = "English"
|
||||
keywords: List[str] = None
|
||||
exclude_topics: List[str] = None
|
||||
include_images: bool = True
|
||||
image_style: str = "realistic"
|
||||
image_engine: str = "Gemini-AI"
|
||||
|
||||
@dataclass
|
||||
class SectionContent:
|
||||
"""Content for a section including text and image."""
|
||||
title: str
|
||||
content: str
|
||||
image_prompt: Optional[str] = None
|
||||
image_path: Optional[str] = None
|
||||
|
||||
class BlogOutlineGenerator:
|
||||
"""Enhanced blog outline generator with comprehensive controls."""
|
||||
|
||||
def __init__(self, config: Optional[OutlineConfig] = None):
|
||||
"""Initialize the outline generator with optional configuration."""
|
||||
self.config = config or OutlineConfig()
|
||||
self.outline = {}
|
||||
self.section_contents = {}
|
||||
|
||||
def generate_outline(self, topic: str) -> Dict[str, List[str]]:
|
||||
"""Generate a blog outline based on the topic and configuration."""
|
||||
try:
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
outline = {"Introduction": ["Overview", "Importance", "What to Expect"]} | outline
|
||||
|
||||
if self.config.include_conclusion:
|
||||
outline["Conclusion"] = ["Summary", "Key Takeaways", "Next Steps"]
|
||||
|
||||
# Add FAQs if configured
|
||||
if self.config.include_faqs:
|
||||
# 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]
|
||||
|
||||
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"
|
||||
|
||||
for section, subsections in self.outline.items():
|
||||
if section not in ["Introduction", "Conclusion", "FAQs", "Additional Resources"]:
|
||||
markdown += f"## {section}\n\n"
|
||||
|
||||
# Add section content if available
|
||||
if section in self.section_contents:
|
||||
content = self.section_contents[section]
|
||||
markdown += f"{content.content}\n\n"
|
||||
|
||||
# Add image if available
|
||||
if content.image_path:
|
||||
markdown += f"\n\n"
|
||||
|
||||
# Add subsections
|
||||
for subsection in subsections:
|
||||
markdown += f"- {subsection}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "Conclusion" in self.outline:
|
||||
markdown += "## Conclusion\n\n"
|
||||
for subsection in self.outline["Conclusion"]:
|
||||
markdown += f"- {subsection}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "FAQs" in self.outline:
|
||||
markdown += "## Frequently Asked Questions\n\n"
|
||||
for faq in self.outline["FAQs"]:
|
||||
markdown += f"- {faq}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "Additional Resources" in self.outline:
|
||||
markdown += "## Additional Resources\n\n"
|
||||
for resource in self.outline["Additional Resources"]:
|
||||
markdown += f"- {resource}\n"
|
||||
|
||||
return markdown
|
||||
Reference in New Issue
Block a user