from pydantic import BaseModel, Field from typing import List, Dict, Optional, Any class CompetitorText(BaseModel): id: int text: str class AnalysisRequest(BaseModel): target_text: str # Текст пользователя competitors: List[str] # Список текстов конкурентов keywords: List[str] # Список ключевых фраз (сырых) language: str = "en" # en, ru, de, es, it, pl, pt target_title: str = "" # Title пользователя competitor_titles: List[str] = Field(default_factory=list) # Title конкурентов class AnalysisResponse(BaseModel): ngram_stats: dict # Статистика униграм/биграм bm25_recommendations: List[dict] # Рекомендации "добавить/убрать" bert_analysis: Dict[str, Any] # Векторный анализ word_counts: Dict[str, Any] # {'target': 500, 'competitors': [600, 450], 'avg': 525} title_analysis: Dict[str, Any] = Field(default_factory=dict) # Анализ Title class SemanticAnalyzeRequest(BaseModel): text: str competitors: List[str] = Field(default_factory=list) language: str = "ru" threshold: int = 50 compression_ratio: float = 0.1 class SemanticAnalyzeResponse(BaseModel): target: Dict[str, Any] competitors: List[Dict[str, Any]] comparison: Dict[str, Any] class SemanticSearchRequest(BaseModel): query_text: str text: str language: str = "ru" top_n: int = 20 class SemanticSearchResponse(BaseModel): results: List[Dict[str, Any]] class UrlFetchRequest(BaseModel): url: str user_agent: str = "chrome_desktop" timeout_seconds: int = 15 class UrlFetchResponse(BaseModel): ok: bool = False url: str = "" final_url: str = "" status_code: int = 0 user_agent_key: str = "" user_agent_value: str = "" title: str = "" text: str = "" error: str = "" class UserAgentInfo(BaseModel): key: str name: str value: str class UserAgentsResponse(BaseModel): user_agents: List[UserAgentInfo] = Field(default_factory=list) class OptimizerRequest(BaseModel): target_text: str competitors: List[str] = Field(default_factory=list) keywords: List[str] = Field(default_factory=list) language: str = "en" target_title: str = "" competitor_titles: List[str] = Field(default_factory=list) # Base for highlighting what changed in this optimization run. # - diff_from_input: compare with `target_text` passed in this request (snapshot before optimization) # - diff_from_original: compare with `original_target_text` from the first snapshot in session diff_mode: str = "diff_from_input" # diff_from_input | diff_from_original original_target_text: Optional[str] = None original_target_title: Optional[str] = None api_key: str api_base_url: str = "https://api.deepseek.com/v1" model: str = "deepseek-chat" max_iterations: int = 2 candidates_per_iteration: int = 2 temperature: float = 0.25 optimization_mode: str = "balanced" phrase_strategy_mode: str = "auto" # auto | exact_preferred | distributed_preferred | ensemble bert_stage_target: float = 0.70 # Optional stage control. If empty -> default full pipeline order. enabled_stages: List[str] = Field(default_factory=list) # bert|bm25|ngram|semantic|title # Per-stage manual goal selection and custom additions. # Example: # { # "bm25": {"mode":"mixed","selected":["canadian online casino"],"custom_add":["online casinos canada"]}, # "bert": {"mode":"manual","selected":["best payout casinos"],"custom_add":[]} # } stage_goal_overrides: Dict[str, Dict[str, Any]] = Field(default_factory=dict) class OptimizerResponse(BaseModel): ok: bool = True optimized_text: str = "" optimized_title: str = "" baseline_metrics: Dict[str, Any] = Field(default_factory=dict) final_metrics: Dict[str, Any] = Field(default_factory=dict) iterations: List[Dict[str, Any]] = Field(default_factory=list) applied_changes: int = 0 optimization_mode: str = "balanced" phrase_strategy_mode: str = "auto" bert_stage_target: float = 0.70 diff_mode: str = "" # HTML with around changed parts. diff_body_html: str = "" diff_title_html: str = "" # List of (type/from/to) blocks for "что именно поменять". diff_changes: List[Dict[str, str]] = Field(default_factory=list) diff_title_changes: List[Dict[str, str]] = Field(default_factory=list) error: str = "" stopped_early: bool = False stop_reason: str = "" class OptimizerCancelRequest(BaseModel): job_id: str = Field(..., min_length=8)