"""请求 / 响应模型。""" from __future__ import annotations import re from pydantic import BaseModel, Field, field_validator from .config import PROFILE_RE class ProviderIn(BaseModel): id: str = Field(min_length=1, max_length=64) name: str = Field(min_length=1, max_length=100) adapter: str = Field(default="openai_compat") base_url: str = Field(min_length=1) api_key: str = "" default_model: str = "" enabled: bool = True note: str = "" @field_validator("id") @classmethod def check_id(cls, v: str) -> str: v = v.strip() if not PROFILE_RE.match(v): raise ValueError("供应商 ID 只能包含字母数字下划线和短横线") return v @field_validator("adapter") @classmethod def check_adapter(cls, v: str) -> str: if v not in ("openai_compat", "agnes"): raise ValueError("不支持的适配器类型") return v @field_validator("base_url") @classmethod def check_base_url(cls, v: str) -> str: v = v.strip().rstrip("/") if not v.startswith("http://") and not v.startswith("https://"): raise ValueError("Base URL 必须以 http:// 或 https:// 开头") return v class DiscoverIn(BaseModel): """手动填写 Base URL + Key 时的临时发现(供应商尚未保存)。""" adapter: str = "openai_compat" base_url: str = Field(min_length=1) api_key: str = Field(min_length=1) check_adapter = field_validator("adapter")(ProviderIn.check_adapter.__func__) check_base_url = field_validator("base_url")(ProviderIn.check_base_url.__func__) class ModelUpsertIn(BaseModel): model_ids: list[str] = Field(default_factory=list) enabled: bool = True note: str = "" class ModelPatchIn(BaseModel): enabled: bool | None = None note: str | None = None class ModelBulkIn(BaseModel): ids: list[str] = Field(default_factory=list) enabled: bool class GenerateIn(BaseModel): provider_id: str = Field(min_length=1) model: str = Field(min_length=1) prompt: str = Field(min_length=1) size: str = "1024x1024" ratio: str = "" n: int = Field(default=1, ge=1, le=4) images: list[str] = Field(default_factory=list) profile_name: str | None = None @field_validator("size") @classmethod def check_size(cls, v: str) -> str: v = v.strip().upper() if v in ("1K", "2K", "3K", "4K"): return v match = re.fullmatch(r"([1-9]\d{0,4})[X×]([1-9]\d{0,4})", v) if not match: raise ValueError("尺寸必须为正整数 WxH 或 1K/2K/3K/4K") return f"{int(match[1])}x{int(match[2])}" @field_validator("profile_name") @classmethod def check_profile(cls, v: str | None) -> str | None: if v is None or v == "": return None v = v.strip() if not PROFILE_RE.match(v): raise ValueError("profile_name 只能包含字母数字下划线和短横线") return v @field_validator("prompt") @classmethod def check_prompt(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("提示词不能为空") return v