feat: 初始提交
This commit is contained in:
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
49
backend/app/schemas/admin.py
Normal file
49
backend/app/schemas/admin.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Statistics(BaseModel):
|
||||
novel_count: int
|
||||
user_count: int
|
||||
api_request_count: int
|
||||
|
||||
|
||||
class DailyRequestLimit(BaseModel):
|
||||
limit: int = Field(..., ge=0, description="匿名用户每日可用次数")
|
||||
|
||||
|
||||
class UpdateLogRead(BaseModel):
|
||||
id: int
|
||||
content: str
|
||||
created_at: datetime
|
||||
created_by: Optional[str] = None
|
||||
is_pinned: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UpdateLogBase(BaseModel):
|
||||
content: Optional[str] = None
|
||||
is_pinned: Optional[bool] = None
|
||||
|
||||
|
||||
class UpdateLogCreate(UpdateLogBase):
|
||||
content: str
|
||||
|
||||
|
||||
class UpdateLogUpdate(UpdateLogBase):
|
||||
pass
|
||||
|
||||
|
||||
class AdminNovelSummary(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
owner_id: int
|
||||
owner_username: str
|
||||
genre: str
|
||||
last_edited: str
|
||||
completed_chapters: int
|
||||
total_chapters: int
|
||||
23
backend/app/schemas/config.py
Normal file
23
backend/app/schemas/config.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SystemConfigBase(BaseModel):
|
||||
key: str = Field(..., description="配置键,需全局唯一")
|
||||
value: str = Field(..., description="配置值,统一存储为字符串")
|
||||
description: Optional[str] = Field(default=None, description="配置用途说明")
|
||||
|
||||
|
||||
class SystemConfigCreate(SystemConfigBase):
|
||||
pass
|
||||
|
||||
|
||||
class SystemConfigUpdate(BaseModel):
|
||||
value: Optional[str] = Field(default=None)
|
||||
description: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class SystemConfigRead(SystemConfigBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
20
backend/app/schemas/llm_config.py
Normal file
20
backend/app/schemas/llm_config.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, HttpUrl, Field
|
||||
|
||||
|
||||
class LLMConfigBase(BaseModel):
|
||||
llm_provider_url: Optional[HttpUrl] = Field(default=None, description="自定义 LLM 服务地址")
|
||||
llm_provider_api_key: Optional[str] = Field(default=None, description="自定义 LLM API Key")
|
||||
llm_provider_model: Optional[str] = Field(default=None, description="自定义模型名称")
|
||||
|
||||
|
||||
class LLMConfigCreate(LLMConfigBase):
|
||||
pass
|
||||
|
||||
|
||||
class LLMConfigRead(LLMConfigBase):
|
||||
user_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
170
backend/app/schemas/novel.py
Normal file
170
backend/app/schemas/novel.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ChoiceOption(BaseModel):
|
||||
"""前端选择项描述,用于动态 UI 控件。"""
|
||||
|
||||
id: str
|
||||
label: str
|
||||
|
||||
|
||||
class UIControl(BaseModel):
|
||||
"""描述前端应渲染的组件类型与配置。"""
|
||||
|
||||
type: str = Field(..., description="控件类型,如 single_choice/text_input")
|
||||
options: Optional[List[ChoiceOption]] = Field(default=None, description="可选项列表")
|
||||
placeholder: Optional[str] = Field(default=None, description="输入提示文案")
|
||||
|
||||
|
||||
class ConverseResponse(BaseModel):
|
||||
"""概念对话接口的统一返回体。"""
|
||||
|
||||
ai_message: str
|
||||
ui_control: UIControl
|
||||
conversation_state: Dict[str, Any]
|
||||
is_complete: bool = False
|
||||
ready_for_blueprint: Optional[bool] = None
|
||||
|
||||
|
||||
class ConverseRequest(BaseModel):
|
||||
"""概念对话接口的请求体。"""
|
||||
|
||||
user_input: Dict[str, Any]
|
||||
conversation_state: Dict[str, Any]
|
||||
|
||||
|
||||
class ChapterGenerationStatus(str, Enum):
|
||||
NOT_GENERATED = "not_generated"
|
||||
GENERATING = "generating"
|
||||
EVALUATING = "evaluating"
|
||||
SELECTING = "selecting"
|
||||
FAILED = "failed"
|
||||
EVALUATION_FAILED = "evaluation_failed"
|
||||
WAITING_FOR_CONFIRM = "waiting_for_confirm"
|
||||
SUCCESSFUL = "successful"
|
||||
|
||||
|
||||
class ChapterOutline(BaseModel):
|
||||
chapter_number: int
|
||||
title: str
|
||||
summary: str
|
||||
|
||||
|
||||
class Chapter(ChapterOutline):
|
||||
real_summary: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
versions: Optional[List[str]] = None
|
||||
evaluation: Optional[str] = None
|
||||
generation_status: ChapterGenerationStatus = ChapterGenerationStatus.NOT_GENERATED
|
||||
|
||||
|
||||
class Relationship(BaseModel):
|
||||
character_from: str
|
||||
character_to: str
|
||||
description: str
|
||||
|
||||
|
||||
class Blueprint(BaseModel):
|
||||
title: str
|
||||
target_audience: str = ""
|
||||
genre: str = ""
|
||||
style: str = ""
|
||||
tone: str = ""
|
||||
one_sentence_summary: str = ""
|
||||
full_synopsis: str = ""
|
||||
world_setting: Dict[str, Any] = {}
|
||||
characters: List[Dict[str, Any]] = []
|
||||
relationships: List[Relationship] = []
|
||||
chapter_outline: List[ChapterOutline] = []
|
||||
|
||||
|
||||
class NovelProject(BaseModel):
|
||||
id: str
|
||||
user_id: int
|
||||
title: str
|
||||
initial_prompt: str
|
||||
conversation_history: List[Dict[str, Any]] = []
|
||||
blueprint: Optional[Blueprint] = None
|
||||
chapters: List[Chapter] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NovelProjectSummary(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
genre: str
|
||||
last_edited: str
|
||||
completed_chapters: int
|
||||
total_chapters: int
|
||||
|
||||
|
||||
class BlueprintGenerationResponse(BaseModel):
|
||||
blueprint: Blueprint
|
||||
ai_message: str
|
||||
|
||||
|
||||
class ChapterGenerationResponse(BaseModel):
|
||||
ai_message: str
|
||||
chapter_versions: List[Dict[str, Any]]
|
||||
|
||||
|
||||
class NovelSectionType(str, Enum):
|
||||
OVERVIEW = "overview"
|
||||
WORLD_SETTING = "world_setting"
|
||||
CHARACTERS = "characters"
|
||||
RELATIONSHIPS = "relationships"
|
||||
CHAPTER_OUTLINE = "chapter_outline"
|
||||
CHAPTERS = "chapters"
|
||||
|
||||
|
||||
class NovelSectionResponse(BaseModel):
|
||||
section: NovelSectionType
|
||||
data: Dict[str, Any]
|
||||
|
||||
|
||||
class GenerateChapterRequest(BaseModel):
|
||||
chapter_number: int
|
||||
writing_notes: Optional[str] = Field(default=None, description="章节额外写作指令")
|
||||
|
||||
|
||||
class SelectVersionRequest(BaseModel):
|
||||
chapter_number: int
|
||||
version_index: int
|
||||
|
||||
|
||||
class EvaluateChapterRequest(BaseModel):
|
||||
chapter_number: int
|
||||
|
||||
|
||||
class UpdateChapterOutlineRequest(BaseModel):
|
||||
chapter_number: int
|
||||
title: str
|
||||
summary: str
|
||||
|
||||
|
||||
class DeleteChapterRequest(BaseModel):
|
||||
chapter_numbers: List[int]
|
||||
|
||||
|
||||
class GenerateOutlineRequest(BaseModel):
|
||||
start_chapter: int
|
||||
num_chapters: int
|
||||
|
||||
|
||||
class BlueprintPatch(BaseModel):
|
||||
one_sentence_summary: Optional[str] = None
|
||||
full_synopsis: Optional[str] = None
|
||||
world_setting: Optional[Dict[str, Any]] = None
|
||||
characters: Optional[List[Dict[str, Any]]] = None
|
||||
relationships: Optional[List[Relationship]] = None
|
||||
chapter_outline: Optional[List[ChapterOutline]] = None
|
||||
|
||||
|
||||
class EditChapterRequest(BaseModel):
|
||||
chapter_number: int
|
||||
content: str
|
||||
56
backend/app/schemas/prompt.py
Normal file
56
backend/app/schemas/prompt.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PromptBase(BaseModel):
|
||||
"""Prompt 基础模型。"""
|
||||
|
||||
name: str = Field(..., description="唯一标识,用于代码引用")
|
||||
title: Optional[str] = Field(default=None, description="可读标题")
|
||||
content: str = Field(..., description="提示词具体内容")
|
||||
tags: Optional[List[str]] = Field(default=None, description="标签集合")
|
||||
|
||||
|
||||
class PromptCreate(PromptBase):
|
||||
"""创建 Prompt 时使用的模型。"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PromptUpdate(BaseModel):
|
||||
"""更新 Prompt 时使用的模型。"""
|
||||
|
||||
title: Optional[str] = Field(default=None)
|
||||
content: Optional[str] = Field(default=None)
|
||||
tags: Optional[List[str]] = Field(default=None)
|
||||
|
||||
|
||||
class PromptRead(PromptBase):
|
||||
"""对外暴露的 Prompt 数据结构。"""
|
||||
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@classmethod
|
||||
def model_validate(cls, obj: Any, *args: Any, **kwargs: Any) -> "PromptRead": # type: ignore[override]
|
||||
"""在转换 ORM 模型时,将字符串标签拆分为列表。"""
|
||||
if hasattr(obj, "id") and hasattr(obj, "name"):
|
||||
raw_tags = getattr(obj, "tags", None)
|
||||
if isinstance(raw_tags, str):
|
||||
processed = [tag for tag in raw_tags.split(",") if tag]
|
||||
elif isinstance(raw_tags, list):
|
||||
processed = raw_tags
|
||||
else:
|
||||
processed = None
|
||||
data = {
|
||||
"id": getattr(obj, "id"),
|
||||
"name": getattr(obj, "name"),
|
||||
"title": getattr(obj, "title", None),
|
||||
"content": getattr(obj, "content", None),
|
||||
"tags": processed,
|
||||
}
|
||||
return super().model_validate(data, *args, **kwargs)
|
||||
return super().model_validate(obj, *args, **kwargs)
|
||||
74
backend/app/schemas/user.py
Normal file
74
backend/app/schemas/user.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
"""用户基础数据结构,供多处复用。"""
|
||||
|
||||
username: str = Field(..., description="用户名")
|
||||
email: Optional[EmailStr] = Field(default=None, description="邮箱,可选")
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
"""注册时使用的模型。"""
|
||||
|
||||
password: str = Field(..., min_length=6, description="明文密码")
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
"""用户信息修改模型。"""
|
||||
|
||||
email: Optional[EmailStr] = Field(default=None, description="邮箱")
|
||||
password: Optional[str] = Field(default=None, min_length=6, description="新密码")
|
||||
|
||||
|
||||
class User(UserBase):
|
||||
"""对外暴露的用户信息。"""
|
||||
|
||||
id: int = Field(..., description="用户主键")
|
||||
is_admin: bool = Field(default=False, description="是否为管理员")
|
||||
must_change_password: bool = Field(default=False, description="是否需要强制修改密码")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
"""数据库内部使用的模型,包含哈希后的密码。"""
|
||||
|
||||
hashed_password: str
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
"""登录成功后返回的访问令牌。"""
|
||||
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
must_change_password: bool = Field(default=False, description="是否需要强制修改密码")
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
"""JWT 负载信息。"""
|
||||
|
||||
sub: str
|
||||
is_admin: bool = False
|
||||
|
||||
|
||||
class UserRegistration(UserCreate):
|
||||
"""注册接口需要的字段,包含邮箱验证码。"""
|
||||
|
||||
verification_code: str = Field(..., min_length=4, max_length=10, description="邮箱验证码")
|
||||
|
||||
|
||||
class PasswordChangeRequest(BaseModel):
|
||||
"""管理员修改密码请求模型。"""
|
||||
|
||||
old_password: str = Field(..., min_length=6, description="当前密码")
|
||||
new_password: str = Field(..., min_length=8, description="新密码")
|
||||
|
||||
|
||||
class AuthOptions(BaseModel):
|
||||
"""认证相关开关信息,供前端动态控制功能。"""
|
||||
|
||||
allow_registration: bool = Field(..., description="是否允许开放用户注册")
|
||||
enable_linuxdo_login: bool = Field(..., description="是否启用 Linux.do 登录")
|
||||
Reference in New Issue
Block a user