Base code
This commit is contained in:
351
backend/models/blog_models.py
Normal file
351
backend/models/blog_models.py
Normal file
@@ -0,0 +1,351 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PersonaInfo(BaseModel):
|
||||
persona_id: Optional[str] = None
|
||||
tone: Optional[str] = None
|
||||
audience: Optional[str] = None
|
||||
industry: Optional[str] = None
|
||||
|
||||
|
||||
class ResearchSource(BaseModel):
|
||||
title: str
|
||||
url: str
|
||||
excerpt: Optional[str] = None
|
||||
credibility_score: Optional[float] = None
|
||||
published_at: Optional[str] = None
|
||||
index: Optional[int] = None
|
||||
source_type: Optional[str] = None # e.g., 'web'
|
||||
|
||||
|
||||
class GroundingChunk(BaseModel):
|
||||
title: str
|
||||
url: str
|
||||
confidence_score: Optional[float] = None
|
||||
|
||||
|
||||
class GroundingSupport(BaseModel):
|
||||
confidence_scores: List[float] = []
|
||||
grounding_chunk_indices: List[int] = []
|
||||
segment_text: str = ""
|
||||
start_index: Optional[int] = None
|
||||
end_index: Optional[int] = None
|
||||
|
||||
|
||||
class Citation(BaseModel):
|
||||
citation_type: str # e.g., 'inline'
|
||||
start_index: int
|
||||
end_index: int
|
||||
text: str
|
||||
source_indices: List[int] = []
|
||||
reference: str # e.g., 'Source 1'
|
||||
|
||||
|
||||
class GroundingMetadata(BaseModel):
|
||||
grounding_chunks: List[GroundingChunk] = []
|
||||
grounding_supports: List[GroundingSupport] = []
|
||||
citations: List[Citation] = []
|
||||
search_entry_point: Optional[str] = None
|
||||
web_search_queries: List[str] = []
|
||||
|
||||
|
||||
class ResearchMode(str, Enum):
|
||||
"""Research modes for different depth levels."""
|
||||
BASIC = "basic"
|
||||
COMPREHENSIVE = "comprehensive"
|
||||
TARGETED = "targeted"
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
"""Types of sources to include in research."""
|
||||
WEB = "web"
|
||||
ACADEMIC = "academic"
|
||||
NEWS = "news"
|
||||
INDUSTRY = "industry"
|
||||
EXPERT = "expert"
|
||||
|
||||
|
||||
class DateRange(str, Enum):
|
||||
"""Date range filters for research."""
|
||||
LAST_WEEK = "last_week"
|
||||
LAST_MONTH = "last_month"
|
||||
LAST_3_MONTHS = "last_3_months"
|
||||
LAST_6_MONTHS = "last_6_months"
|
||||
LAST_YEAR = "last_year"
|
||||
ALL_TIME = "all_time"
|
||||
|
||||
|
||||
class ResearchProvider(str, Enum):
|
||||
"""Research provider options."""
|
||||
GOOGLE = "google" # Gemini native grounding
|
||||
EXA = "exa" # Exa neural search
|
||||
TAVILY = "tavily" # Tavily AI-powered search
|
||||
|
||||
|
||||
class ResearchConfig(BaseModel):
|
||||
"""Configuration for research execution."""
|
||||
mode: ResearchMode = ResearchMode.BASIC
|
||||
provider: ResearchProvider = ResearchProvider.GOOGLE
|
||||
date_range: Optional[DateRange] = None
|
||||
source_types: List[SourceType] = []
|
||||
max_sources: int = 10
|
||||
include_statistics: bool = True
|
||||
include_expert_quotes: bool = True
|
||||
include_competitors: bool = True
|
||||
include_trends: bool = True
|
||||
|
||||
# Exa-specific options
|
||||
exa_category: Optional[str] = None # company, research paper, news, linkedin profile, github, tweet, movie, song, personal site, pdf, financial report
|
||||
exa_include_domains: List[str] = [] # Domain whitelist
|
||||
exa_exclude_domains: List[str] = [] # Domain blacklist
|
||||
exa_search_type: Optional[str] = "auto" # "auto", "keyword", "neural"
|
||||
|
||||
# Tavily-specific options
|
||||
tavily_topic: Optional[str] = "general" # general, news, finance
|
||||
tavily_search_depth: Optional[str] = "basic" # basic (1 credit), advanced (2 credits)
|
||||
tavily_include_domains: List[str] = [] # Domain whitelist (max 300)
|
||||
tavily_exclude_domains: List[str] = [] # Domain blacklist (max 150)
|
||||
tavily_include_answer: Union[bool, str] = False # basic, advanced, true, false
|
||||
tavily_include_raw_content: Union[bool, str] = False # markdown, text, true, false
|
||||
tavily_include_images: bool = False
|
||||
tavily_include_image_descriptions: bool = False
|
||||
tavily_include_favicon: bool = False
|
||||
tavily_time_range: Optional[str] = None # day, week, month, year, d, w, m, y
|
||||
tavily_start_date: Optional[str] = None # YYYY-MM-DD
|
||||
tavily_end_date: Optional[str] = None # YYYY-MM-DD
|
||||
tavily_country: Optional[str] = None # Country code (only for general topic)
|
||||
tavily_chunks_per_source: int = 3 # 1-3 (only for advanced search)
|
||||
tavily_auto_parameters: bool = False # Auto-configure parameters based on query
|
||||
|
||||
|
||||
class BlogResearchRequest(BaseModel):
|
||||
keywords: List[str]
|
||||
topic: Optional[str] = None
|
||||
industry: Optional[str] = None
|
||||
target_audience: Optional[str] = None
|
||||
tone: Optional[str] = None
|
||||
word_count_target: Optional[int] = 1500
|
||||
persona: Optional[PersonaInfo] = None
|
||||
research_mode: Optional[ResearchMode] = ResearchMode.BASIC
|
||||
config: Optional[ResearchConfig] = None
|
||||
|
||||
|
||||
class BlogResearchResponse(BaseModel):
|
||||
success: bool = True
|
||||
sources: List[ResearchSource] = []
|
||||
keyword_analysis: Dict[str, Any] = {}
|
||||
competitor_analysis: Dict[str, Any] = {}
|
||||
suggested_angles: List[str] = []
|
||||
search_widget: Optional[str] = None # HTML content for search widget
|
||||
search_queries: List[str] = [] # Search queries generated by Gemini
|
||||
grounding_metadata: Optional[GroundingMetadata] = None # Google grounding metadata
|
||||
original_keywords: List[str] = [] # Original user-provided keywords for caching
|
||||
error_message: Optional[str] = None # Error message for graceful failures
|
||||
retry_suggested: Optional[bool] = None # Whether retry is recommended
|
||||
error_code: Optional[str] = None # Specific error code
|
||||
actionable_steps: List[str] = [] # Steps user can take to resolve the issue
|
||||
|
||||
|
||||
class BlogOutlineSection(BaseModel):
|
||||
id: str
|
||||
heading: str
|
||||
subheadings: List[str] = []
|
||||
key_points: List[str] = []
|
||||
references: List[ResearchSource] = []
|
||||
target_words: Optional[int] = None
|
||||
keywords: List[str] = []
|
||||
|
||||
|
||||
class BlogOutlineRequest(BaseModel):
|
||||
research: BlogResearchResponse
|
||||
persona: Optional[PersonaInfo] = None
|
||||
word_count: Optional[int] = 1500
|
||||
custom_instructions: Optional[str] = None
|
||||
|
||||
|
||||
class SourceMappingStats(BaseModel):
|
||||
total_sources_mapped: int = 0
|
||||
coverage_percentage: float = 0.0
|
||||
average_relevance_score: float = 0.0
|
||||
high_confidence_mappings: int = 0
|
||||
|
||||
class GroundingInsights(BaseModel):
|
||||
confidence_analysis: Optional[Dict[str, Any]] = None
|
||||
authority_analysis: Optional[Dict[str, Any]] = None
|
||||
temporal_analysis: Optional[Dict[str, Any]] = None
|
||||
content_relationships: Optional[Dict[str, Any]] = None
|
||||
citation_insights: Optional[Dict[str, Any]] = None
|
||||
search_intent_insights: Optional[Dict[str, Any]] = None
|
||||
quality_indicators: Optional[Dict[str, Any]] = None
|
||||
|
||||
class OptimizationResults(BaseModel):
|
||||
overall_quality_score: float = 0.0
|
||||
improvements_made: List[str] = []
|
||||
optimization_focus: str = "general optimization"
|
||||
|
||||
class ResearchCoverage(BaseModel):
|
||||
sources_utilized: int = 0
|
||||
content_gaps_identified: int = 0
|
||||
competitive_advantages: List[str] = []
|
||||
|
||||
class BlogOutlineResponse(BaseModel):
|
||||
success: bool = True
|
||||
title_options: List[str] = []
|
||||
outline: List[BlogOutlineSection] = []
|
||||
|
||||
# Additional metadata for enhanced UI
|
||||
source_mapping_stats: Optional[SourceMappingStats] = None
|
||||
grounding_insights: Optional[GroundingInsights] = None
|
||||
optimization_results: Optional[OptimizationResults] = None
|
||||
research_coverage: Optional[ResearchCoverage] = None
|
||||
|
||||
|
||||
class BlogOutlineRefineRequest(BaseModel):
|
||||
outline: List[BlogOutlineSection]
|
||||
operation: str
|
||||
section_id: Optional[str] = None
|
||||
payload: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class BlogSectionRequest(BaseModel):
|
||||
section: BlogOutlineSection
|
||||
keywords: List[str] = []
|
||||
tone: Optional[str] = None
|
||||
persona: Optional[PersonaInfo] = None
|
||||
mode: Optional[str] = "polished" # 'draft' | 'polished'
|
||||
|
||||
|
||||
class BlogSectionResponse(BaseModel):
|
||||
success: bool = True
|
||||
markdown: str
|
||||
citations: List[ResearchSource] = []
|
||||
continuity_metrics: Optional[Dict[str, float]] = None
|
||||
|
||||
|
||||
class BlogOptimizeRequest(BaseModel):
|
||||
content: str
|
||||
goals: List[str] = []
|
||||
|
||||
|
||||
class BlogOptimizeResponse(BaseModel):
|
||||
success: bool = True
|
||||
optimized: str
|
||||
diff_preview: Optional[str] = None
|
||||
|
||||
|
||||
class BlogSEOAnalyzeRequest(BaseModel):
|
||||
content: str
|
||||
blog_title: Optional[str] = None
|
||||
keywords: List[str] = []
|
||||
research_data: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class BlogSEOAnalyzeResponse(BaseModel):
|
||||
success: bool = True
|
||||
seo_score: float
|
||||
density: Dict[str, Any] = {}
|
||||
structure: Dict[str, Any] = {}
|
||||
readability: Dict[str, Any] = {}
|
||||
link_suggestions: List[Dict[str, Any]] = []
|
||||
image_alt_status: Dict[str, Any] = {}
|
||||
recommendations: List[str] = []
|
||||
|
||||
|
||||
class BlogSEOMetadataRequest(BaseModel):
|
||||
content: str
|
||||
title: Optional[str] = None
|
||||
keywords: List[str] = []
|
||||
research_data: Optional[Dict[str, Any]] = None
|
||||
outline: Optional[List[Dict[str, Any]]] = None # Add outline structure
|
||||
seo_analysis: Optional[Dict[str, Any]] = None # Add SEO analysis results
|
||||
|
||||
|
||||
class BlogSEOMetadataResponse(BaseModel):
|
||||
success: bool = True
|
||||
title_options: List[str] = []
|
||||
meta_descriptions: List[str] = []
|
||||
seo_title: Optional[str] = None
|
||||
meta_description: Optional[str] = None
|
||||
url_slug: Optional[str] = None
|
||||
blog_tags: List[str] = []
|
||||
blog_categories: List[str] = []
|
||||
social_hashtags: List[str] = []
|
||||
open_graph: Dict[str, Any] = {}
|
||||
twitter_card: Dict[str, Any] = {}
|
||||
json_ld_schema: Dict[str, Any] = {}
|
||||
canonical_url: Optional[str] = None
|
||||
reading_time: float = 0.0
|
||||
focus_keyword: Optional[str] = None
|
||||
generated_at: Optional[str] = None
|
||||
optimization_score: int = 0
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class BlogPublishRequest(BaseModel):
|
||||
platform: str = Field(pattern="^(wix|wordpress)$")
|
||||
html: str
|
||||
metadata: BlogSEOMetadataResponse
|
||||
schedule_time: Optional[str] = None
|
||||
|
||||
|
||||
class BlogPublishResponse(BaseModel):
|
||||
success: bool = True
|
||||
platform: str
|
||||
url: Optional[str] = None
|
||||
post_id: Optional[str] = None
|
||||
|
||||
|
||||
class HallucinationCheckRequest(BaseModel):
|
||||
content: str
|
||||
sources: List[str] = []
|
||||
|
||||
|
||||
class HallucinationCheckResponse(BaseModel):
|
||||
success: bool = True
|
||||
claims: List[Dict[str, Any]] = []
|
||||
suggestions: List[Dict[str, Any]] = []
|
||||
|
||||
|
||||
# -----------------------
|
||||
# Medium Blog Generation
|
||||
# -----------------------
|
||||
|
||||
class MediumSectionOutline(BaseModel):
|
||||
"""Lightweight outline payload for medium blog generation."""
|
||||
id: str
|
||||
heading: str
|
||||
keyPoints: List[str] = []
|
||||
subheadings: List[str] = []
|
||||
keywords: List[str] = []
|
||||
targetWords: Optional[int] = None
|
||||
references: List[ResearchSource] = []
|
||||
|
||||
|
||||
class MediumBlogGenerateRequest(BaseModel):
|
||||
"""Request to generate an entire medium-length blog in one pass."""
|
||||
title: str
|
||||
sections: List[MediumSectionOutline]
|
||||
persona: Optional[PersonaInfo] = None
|
||||
tone: Optional[str] = None
|
||||
audience: Optional[str] = None
|
||||
globalTargetWords: Optional[int] = 1000
|
||||
researchKeywords: Optional[List[str]] = None # Original research keywords for better caching
|
||||
|
||||
|
||||
class MediumGeneratedSection(BaseModel):
|
||||
id: str
|
||||
heading: str
|
||||
content: str
|
||||
wordCount: int
|
||||
sources: Optional[List[ResearchSource]] = None
|
||||
|
||||
|
||||
class MediumBlogGenerateResult(BaseModel):
|
||||
success: bool = True
|
||||
title: str
|
||||
sections: List[MediumGeneratedSection]
|
||||
model: Optional[str] = None
|
||||
generation_time_ms: Optional[int] = None
|
||||
safety_flags: Optional[Dict[str, Any]] = None
|
||||
Reference in New Issue
Block a user