351 lines
11 KiB
Python
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 |