Files
moreminimore-marketing/backend/models/blog_models.py
Kunthawat Greethong c35fa52117 Base code
2026-01-08 22:39:53 +07:00

351 lines
11 KiB
Python

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