138 lines
5.5 KiB
Python
138 lines
5.5 KiB
Python
"""
|
|
Outline Optimizer - AI-powered outline optimization and rebalancing.
|
|
|
|
Optimizes outlines for better flow, SEO, and engagement.
|
|
"""
|
|
|
|
from typing import List
|
|
from loguru import logger
|
|
|
|
from models.blog_models import BlogOutlineSection
|
|
|
|
|
|
class OutlineOptimizer:
|
|
"""Optimizes outlines for better flow, SEO, and engagement."""
|
|
|
|
async def optimize(self, outline: List[BlogOutlineSection], focus: str, user_id: str) -> List[BlogOutlineSection]:
|
|
"""Optimize entire outline for better flow, SEO, and engagement.
|
|
|
|
Args:
|
|
outline: List of outline sections to optimize
|
|
focus: Optimization focus (e.g., "general optimization")
|
|
user_id: User ID (required for subscription checks and usage tracking)
|
|
|
|
Returns:
|
|
List of optimized outline sections
|
|
|
|
Raises:
|
|
ValueError: If user_id is not provided
|
|
"""
|
|
if not user_id:
|
|
raise ValueError("user_id is required for outline optimization (subscription checks and usage tracking)")
|
|
|
|
outline_text = "\n".join([f"{i+1}. {s.heading}" for i, s in enumerate(outline)])
|
|
|
|
optimization_prompt = f"""Optimize this blog outline for better flow, engagement, and SEO:
|
|
|
|
Current Outline:
|
|
{outline_text}
|
|
|
|
Optimization Focus: {focus}
|
|
|
|
Goals: Improve narrative flow, enhance SEO, increase engagement, ensure comprehensive coverage.
|
|
|
|
Return JSON format:
|
|
{{
|
|
"outline": [
|
|
{{
|
|
"heading": "Optimized heading",
|
|
"subheadings": ["subheading 1", "subheading 2"],
|
|
"key_points": ["point 1", "point 2"],
|
|
"target_words": 300,
|
|
"keywords": ["keyword1", "keyword2"]
|
|
}}
|
|
]
|
|
}}"""
|
|
|
|
try:
|
|
from services.llm_providers.main_text_generation import llm_text_gen
|
|
|
|
optimization_schema = {
|
|
"type": "object",
|
|
"properties": {
|
|
"outline": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"heading": {"type": "string"},
|
|
"subheadings": {"type": "array", "items": {"type": "string"}},
|
|
"key_points": {"type": "array", "items": {"type": "string"}},
|
|
"target_words": {"type": "integer"},
|
|
"keywords": {"type": "array", "items": {"type": "string"}}
|
|
},
|
|
"required": ["heading", "subheadings", "key_points", "target_words", "keywords"]
|
|
}
|
|
}
|
|
},
|
|
"required": ["outline"],
|
|
"propertyOrdering": ["outline"]
|
|
}
|
|
|
|
optimized_data = llm_text_gen(
|
|
prompt=optimization_prompt,
|
|
json_struct=optimization_schema,
|
|
system_prompt=None,
|
|
user_id=user_id
|
|
)
|
|
|
|
# Handle the new schema format with "outline" wrapper
|
|
if isinstance(optimized_data, dict) and 'outline' in optimized_data:
|
|
optimized_sections = []
|
|
for i, section_data in enumerate(optimized_data['outline']):
|
|
section = BlogOutlineSection(
|
|
id=f"s{i+1}",
|
|
heading=section_data.get('heading', f'Section {i+1}'),
|
|
subheadings=section_data.get('subheadings', []),
|
|
key_points=section_data.get('key_points', []),
|
|
references=outline[i].references if i < len(outline) else [],
|
|
target_words=section_data.get('target_words', 300),
|
|
keywords=section_data.get('keywords', [])
|
|
)
|
|
optimized_sections.append(section)
|
|
logger.info(f"✅ Outline optimization completed: {len(optimized_sections)} sections optimized")
|
|
return optimized_sections
|
|
else:
|
|
logger.warning(f"Invalid optimization response format: {type(optimized_data)}")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"AI outline optimization failed: {e}")
|
|
logger.info("Returning original outline without optimization")
|
|
|
|
return outline
|
|
|
|
def rebalance_word_counts(self, outline: List[BlogOutlineSection], target_words: int) -> List[BlogOutlineSection]:
|
|
"""Rebalance word count distribution across sections."""
|
|
total_sections = len(outline)
|
|
if total_sections == 0:
|
|
return outline
|
|
|
|
# Calculate target distribution
|
|
intro_words = int(target_words * 0.12) # 12% for intro
|
|
conclusion_words = int(target_words * 0.12) # 12% for conclusion
|
|
main_content_words = target_words - intro_words - conclusion_words
|
|
|
|
# Distribute main content words across sections
|
|
words_per_section = main_content_words // total_sections
|
|
remainder = main_content_words % total_sections
|
|
|
|
for i, section in enumerate(outline):
|
|
if i == 0: # First section (intro)
|
|
section.target_words = intro_words
|
|
elif i == total_sections - 1: # Last section (conclusion)
|
|
section.target_words = conclusion_words
|
|
else: # Main content sections
|
|
section.target_words = words_per_section + (1 if i < remainder else 0)
|
|
|
|
return outline
|