from typing import Any from pydantic import BaseModel, Field class RewriteRequest(BaseModel): source_text: str = Field(..., min_length=20) title_hint: str = "" writing_style: str = "科普解读" tone: str = "专业、可信、可读性强" audience: str = "公众号读者" keep_points: str = "" avoid_words: str = "" target_body_chars: int = Field( default=500, ge=180, le=2200, description="目标改写正文字数,前端可调(中文字符数近似)", ) class RewriteResponse(BaseModel): title: str summary: str body_markdown: str mode: str = "ai" quality_notes: list[str] = [] trace: dict[str, Any] | None = Field( default=None, description="改写链路追踪:请求 ID、耗时、模型、质检与降级原因,便于监测与回溯", ) class WechatPublishRequest(BaseModel): title: str summary: str = "" body_markdown: str author: str = "" thumb_media_id: str = "" class IMPublishRequest(BaseModel): title: str body_markdown: str class PublishResponse(BaseModel): ok: bool detail: str data: dict | None = None class AuthCredentialRequest(BaseModel): username: str password: str remember_me: bool = False class ChangePasswordRequest(BaseModel): old_password: str new_password: str class ForgotPasswordResetRequest(BaseModel): username: str reset_key: str new_password: str class DeleteAccountRequest(BaseModel): password: str reset_key: str class WechatBindingRequest(BaseModel): account_name: str = "" appid: str secret: str author: str = "" thumb_media_id: str = "" thumb_image_path: str = "" class WechatSwitchRequest(BaseModel): account_id: int class WechatDeleteRequest(BaseModel): account_id: int class WechatCoverUploadByUrlRequest(BaseModel): image_url: str class WechatCoverGenerateRequest(BaseModel): title: str = "" summary: str = "" style_hint: str = "" upload_to_wechat: bool = True class AIModelCreateRequest(BaseModel): model_name: str api_key: str base_url: str = "" model: str image_model: str = "" timeout_sec: float = 120.0 max_output_tokens: int = 8192 max_retries: int = 0 class AIModelSwitchRequest(BaseModel): model_id: int class AIModelDeleteRequest(BaseModel): model_id: int class VipToggleRequest(BaseModel): enabled: bool = True class VipRechargeRequest(BaseModel): tokens: int = Field(default=10000, ge=1, le=10_000_000) class PosterGenerateRequest(BaseModel): title: str = "" summary: str = "" body_markdown: str = Field(..., min_length=20) style_hint: str = "" upload_to_wechat: bool = True max_images: int = Field(default=6, ge=1, le=12) class PosterPreviewItem(BaseModel): paragraph_index: int paragraph_excerpt: str = "" prompt: str = "" preview_data_url: str wechat_url: str = "" uploaded: bool = False note: str = "" class PosterGenerateResponse(BaseModel): ok: bool detail: str skipped_first_paragraph: bool = True posters: list[PosterPreviewItem] = Field(default_factory=list) body_markdown_with_posters: str = "" warnings: list[str] = Field(default_factory=list) class CoverGenerateResponse(BaseModel): ok: bool detail: str preview_data_url: str = "" thumb_media_id: str = "" width: int = 900 height: int = 383 note: str = "" warnings: list[str] = Field(default_factory=list)