from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any 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 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 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 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 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