from datetime import datetime, timezone from typing import Optional from sqlmodel import SQLModel, Field def _now_utc() -> datetime: return datetime.now(timezone.utc) class Novel(SQLModel, table=True): """小说主表""" id: Optional[int] = Field(default=None, primary_key=True) title: str = Field(max_length=200, index=True) description: str = Field(default="", max_length=2000) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc) class AIConfig(SQLModel, table=True): """AI服务配置(单行表)""" id: Optional[int] = Field(default=None, primary_key=True) provider: str = Field(default="openai") # "openai" | "anthropic" base_url: str = Field(default="https://api.openai.com/v1") api_key: str = Field(default="") model: str = Field(default="gpt-4o") updated_at: datetime = Field(default_factory=_now_utc) class ChatMessage(SQLModel, table=True): """聊天消息""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: Optional[int] = Field(default=None, foreign_key="novel.id", index=True) role: str = Field(max_length=20) # "user" | "assistant" content: str = Field(default="") created_at: datetime = Field(default_factory=_now_utc) class Outline(SQLModel, table=True): """大纲表,属于小说的子资源,支持两级结构(父+子)""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: int = Field(foreign_key="novel.id", index=True) parent_id: int | None = Field(default=None, foreign_key="outline.id", index=True) sort_order: int = Field(default=0) summary: str = Field(max_length=200) detail: str = Field(default="", max_length=1000) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc) class Character(SQLModel, table=True): """角色表,属于小说的子资源""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: int = Field(foreign_key="novel.id", index=True) name: str = Field(max_length=100) description: str = Field(default="", max_length=1000) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc) class Chapter(SQLModel, table=True): """章节表,属于小说的子资源""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: int = Field(foreign_key="novel.id", index=True) sort_order: int = Field(default=0) title: str = Field(max_length=200) content: str = Field(default="", max_length=5000) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc) class NovelFile(SQLModel, table=True): """小说关联文件""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: int = Field(foreign_key="novel.id", index=True) filename: str = Field(max_length=255) # 原始文件名 stored_name: str = Field(max_length=255) # 磁盘存储名(uuid + 扩展名) file_size: int = Field(default=0) # 字节数 mime_type: str = Field(default="", max_length=100) text_length: int = Field(default=0) # 解析后纯文本字符数 created_at: datetime = Field(default_factory=_now_utc) class Skill(SQLModel, table=True): """Skill 插件:自定义系统提示词 + 工具白名单""" id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(max_length=100, index=True) description: str = Field(default="", max_length=500) system_prompt: str = Field(default="") allowed_tools: str = Field(default="[]") # JSON 数组字符串,"[]" 表示全部工具 is_builtin: bool = Field(default=False) novel_id: int | None = Field(default=None, foreign_key="novel.id", index=True) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc) class WorldSetting(SQLModel, table=True): """世界观设定,每部小说一条记录""" id: Optional[int] = Field(default=None, primary_key=True) novel_id: int = Field(foreign_key="novel.id", index=True, unique=True) content: str = Field(default="", max_length=5000) created_at: datetime = Field(default_factory=_now_utc) updated_at: datetime = Field(default_factory=_now_utc)