- Fix text selection menu not showing: wire contentRef via inputRef on multiline TextField - Fix blog title not truncating: add min-w-0 for flex item overflow - Fix outline generation 500: escape curly braces in f-string prompt template - Fix content generation 'NoneType not callable': replace SessionLocal() with get_session_for_user(), add db param to MediumBlogGenerator, fix signature mismatch in database_task_manager - Fix writing assistant suggest 500: add auth + user_id to API endpoint and service, replace sync requests with httpx.AsyncClient - Fix hallucination detector 404: explicitly include router in main.py and app.py - Fix missing error_data in task failure responses - Hide CopilotKit web inspector button - Remove hardcoded fallback suggestions from SmartTypingAssist - Fix stale closure refs in SmartTypingAssist handleTypingChange - Add two-column editor layout, stats bar, section hover menu - Various subscription, billing, and research module improvements
128 lines
5.6 KiB
Python
128 lines
5.6 KiB
Python
"""
|
|
Prompt Builder - Handles building of AI prompts for outline generation.
|
|
|
|
Constructs comprehensive prompts with research data, keywords, and strategic requirements.
|
|
"""
|
|
|
|
from typing import Dict, Any, List
|
|
|
|
|
|
class PromptBuilder:
|
|
"""Handles building of comprehensive AI prompts for outline generation."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the prompt builder."""
|
|
pass
|
|
|
|
def build_outline_prompt(self, primary_keywords: List[str], secondary_keywords: List[str],
|
|
content_angles: List[str], sources: List, search_intent: str,
|
|
request, custom_instructions: str = None) -> str:
|
|
"""Build the comprehensive outline generation prompt using filtered research data."""
|
|
|
|
# Use the filtered research data (already cleaned by ResearchDataFilter)
|
|
research = request.research
|
|
|
|
primary_kw_text = ', '.join(primary_keywords) if primary_keywords else (request.topic or ', '.join(getattr(request.research, 'original_keywords', []) or ['the target topic']))
|
|
secondary_kw_text = ', '.join(secondary_keywords) if secondary_keywords else "None provided"
|
|
long_tail_text = ', '.join(research.keyword_analysis.get('long_tail', [])) if research and research.keyword_analysis else "None discovered"
|
|
semantic_text = ', '.join(research.keyword_analysis.get('semantic_keywords', [])) if research and research.keyword_analysis else "None discovered"
|
|
trending_text = ', '.join(research.keyword_analysis.get('trending_terms', [])) if research and research.keyword_analysis else "None discovered"
|
|
content_gap_text = ', '.join(research.keyword_analysis.get('content_gaps', [])) if research and research.keyword_analysis else "None identified"
|
|
content_angle_text = ', '.join(content_angles) if content_angles else "No explicit angles provided; infer compelling angles from research insights."
|
|
competitor_text = ', '.join(research.competitor_analysis.get('top_competitors', [])) if research and research.competitor_analysis else "Not available"
|
|
opportunity_text = ', '.join(research.competitor_analysis.get('opportunities', [])) if research and research.competitor_analysis else "Not available"
|
|
advantages_text = ', '.join(research.competitor_analysis.get('competitive_advantages', [])) if research and research.competitor_analysis else "Not available"
|
|
|
|
return f"""Create a comprehensive blog outline for: {primary_kw_text}
|
|
|
|
CONTEXT:
|
|
Search Intent: {search_intent}
|
|
Target: {request.word_count or 1500} words
|
|
Industry: {getattr(request.persona, 'industry', 'General') if request.persona else 'General'}
|
|
Audience: {getattr(request.persona, 'target_audience', 'General') if request.persona else 'General'}
|
|
|
|
KEYWORDS:
|
|
Primary: {primary_kw_text}
|
|
Secondary: {secondary_kw_text}
|
|
Long-tail: {long_tail_text}
|
|
Semantic: {semantic_text}
|
|
Trending: {trending_text}
|
|
Content Gaps: {content_gap_text}
|
|
|
|
CONTENT ANGLES / STORYLINES: {content_angle_text}
|
|
|
|
COMPETITIVE INTELLIGENCE:
|
|
Top Competitors: {competitor_text}
|
|
Market Opportunities: {opportunity_text}
|
|
Competitive Advantages: {advantages_text}
|
|
|
|
RESEARCH SOURCES: {len(sources)} authoritative sources available
|
|
|
|
{f"CUSTOM INSTRUCTIONS: {custom_instructions}" if custom_instructions else ""}
|
|
|
|
STRATEGIC REQUIREMENTS:
|
|
- Create SEO-optimized headings with natural keyword integration
|
|
- Surface the strongest research-backed angles within the outline
|
|
- Build logical narrative flow from problem to solution
|
|
- Include data-driven insights from research sources
|
|
- Address content gaps and market opportunities
|
|
- Optimize for search intent and user questions
|
|
- Ensure engaging, actionable content throughout
|
|
|
|
Return JSON format:
|
|
{{
|
|
"title_options": [
|
|
"Title option 1",
|
|
"Title option 2",
|
|
"Title option 3"
|
|
],
|
|
"outline": [
|
|
{{
|
|
"heading": "Section heading with primary keyword",
|
|
"subheadings": ["Subheading 1", "Subheading 2", "Subheading 3"],
|
|
"key_points": ["Key point 1", "Key point 2", "Key point 3"],
|
|
"target_words": 300,
|
|
"keywords": ["primary keyword", "secondary keyword"]
|
|
}}
|
|
]
|
|
}}"""
|
|
|
|
def get_outline_schema(self) -> Dict[str, Any]:
|
|
"""Get the structured JSON schema for outline generation."""
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"title_options": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"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": ["title_options", "outline"],
|
|
"propertyOrdering": ["title_options", "outline"]
|
|
}
|