""" 爽文(Fiction / Novel)数据模型。 """ from __future__ import annotations from typing import Dict, List, Optional from pydantic import BaseModel, Field class EmotionFlowStep(BaseModel): key: str text: str class EmotionFlow(BaseModel): id: str intro: str tags: List[str] = Field(default_factory=list) steps: List[EmotionFlowStep] = Field(default_factory=list) class EmotionFlowCatalog(BaseModel): flows: List[EmotionFlow] = Field(default_factory=list) class GuideGlobalEntry(BaseModel): layer: str title: str content: str class GuideGlobalEntries(BaseModel): entries: List[GuideGlobalEntry] = Field(default_factory=list) class FictionGuideWorldbook(BaseModel): """Book-local guide 世界书:本书具体人设 / 爽点 / 用户体验 / 禁区。""" persona: str = "" highlight: str = "" experience: str = "" forbiddenZones: str = "" class FictionBookMeta(BaseModel): id: str title: str allowedFlowIds: List[str] = Field(default_factory=list) createdAt: str = "" updatedAt: str = "" class FictionBookSummary(BaseModel): id: str title: str allowedFlowIds: List[str] = Field(default_factory=list) updatedAt: str = "" class FictionPrompts(BaseModel): openBook: str = "" coarseOutline: str = "" eventPlan: str = "" chapter: str = "" nudge: str = "" class FictionReaderSettings(BaseModel): contextWindowChars: int = 2000 prefetchRemainingWords: int = 300 class FictionPipelineSettings(BaseModel): """半自动流水线:各层 ON=自动,OFF=需手动触发。""" semiAuto: bool = False autoCoarse: bool = True autoEventPlan: bool = True autoChapter: bool = True class FictionBookSettings(BaseModel): prompts: FictionPrompts = Field(default_factory=FictionPrompts) reader: FictionReaderSettings = Field(default_factory=FictionReaderSettings) pipeline: FictionPipelineSettings = Field(default_factory=FictionPipelineSettings) class CreateFictionBookRequest(BaseModel): title: str inspiration: str = "" guide: FictionGuideWorldbook allowedFlowIds: List[str] = Field(default_factory=list) class OpenBookRequest(BaseModel): inspiration: str profile_id: Optional[str] = None api_config: Optional[Dict[str, str]] = None class OpenBookResult(BaseModel): title: str optimizedIntro: str guide: FictionGuideWorldbook allowedFlowIds: List[str] = Field(default_factory=list) class UpdateFictionBookSettingsRequest(BaseModel): prompts: Optional[FictionPrompts] = None reader: Optional[FictionReaderSettings] = None pipeline: Optional[FictionPipelineSettings] = None class FictionPipelineTickResult(BaseModel): run: FictionRunState started: bool = False pendingStages: List[str] = Field(default_factory=list) class VolumeOutline(BaseModel): """卷纲:当前 10~30 章的阶段规划,不是整本书全局大纲。""" id: str order: int = 1 title: str = "" goal: str = "" coreConflict: str = "" powerProgression: str = "" emotionalPromise: str = "" endingHook: str = "" targetChapterCount: int = 20 primaryEmotionFlowId: str = "" status: str = "active" class CoarseOutlineEvent(BaseModel): """兼容旧 coarseOutline.events,同时作为新版事件串条目的轻量视图。""" id: str title: str summary: str = "" order: int = 1 volumeId: str = "" class CoarseOutline(BaseModel): events: List[CoarseOutlineEvent] = Field(default_factory=list) version: int = 1 class EventChainItem(BaseModel): """事件串:卷纲 + 情感链在剧情层的落地。""" id: str volumeId: str = "" order: int = 1 title: str = "" summary: str = "" purpose: str = "" conflict: str = "" turningPoint: str = "" expectedPayoff: str = "" targetChapterCount: int = 3 emotionFlowId: str = "" emotionStepKey: str = "" emotionStepText: str = "" status: str = "planned" class FlowStepsPlan(BaseModel): 起: str = "" 承: str = "" 转: str = "" 合: str = "" class ChapterPlanItem(BaseModel): seq: int # 旧字段:保留以兼容现有 metadata.events[eventId].chapterPlan。 phaseKey: str = "" phaseSlice: str = "" brief: str = "" # 新字段:章纲是写章前最具体的规划层。 eventId: str = "" title: str = "" goal: str = "" opening: str = "" mainConflict: str = "" emotionalTurn: str = "" emotionStepKey: str = "" emotionGoal: str = "" payoff: str = "" endingHook: str = "" forbidden: str = "" targetWords: int = 2000 status: str = "planned" class EventPlanEntry(BaseModel): emotionFlowId: str = "" flowStepsPlan: FlowStepsPlan = Field(default_factory=FlowStepsPlan) chapterPlan: List[ChapterPlanItem] = Field(default_factory=list) class FictionProgress(BaseModel): currentChapterSeq: int = 0 charOffset: int = 0 ttsPaused: bool = False genPaused: bool = False class FictionChapter(BaseModel): seq: int title: str = "" body: str = "" charCount: int = 0 status: str = "written" eventId: str = "" phaseKey: str = "" createdAt: str = "" class FictionChapterSummary(BaseModel): seq: int title: str = "" charCount: int = 0 eventId: str = "" phaseKey: str = "" class FictionBookMetadata(BaseModel): # v2 新规划结构:不再保留旧 coarseOutline/events 作为持久化主结构。 version: int = 2 volumes: List[VolumeOutline] = Field(default_factory=list) eventChains: Dict[str, List[EventChainItem]] = Field(default_factory=dict) chapterPlans: Dict[str, List[ChapterPlanItem]] = Field(default_factory=dict) progress: FictionProgress = Field(default_factory=FictionProgress) class FictionRunState(BaseModel): status: str = "idle" pipelineStage: Optional[str] = None stage: str = "idle" message: Optional[str] = None progress: Optional[Dict[str, int]] = None updatedAt: str = "" class FictionStartReadingResult(BaseModel): run: FictionRunState started: bool = False class FictionGenerationRequest(BaseModel): profile_id: Optional[str] = None api_config: Optional[Dict[str, str]] = None event_id: Optional[str] = None seq: Optional[int] = None stream: bool = False class UpdateFictionProgressRequest(BaseModel): currentChapterSeq: Optional[int] = None charOffset: Optional[int] = None ttsPaused: Optional[bool] = None genPaused: Optional[bool] = None class FictionPrefetchRequest(BaseModel): profile_id: Optional[str] = None api_config: Optional[Dict[str, str]] = None currentChapterSeq: int charOffset: int = 0 remainingWords: Optional[int] = None class FictionPrefetchResult(BaseModel): run: FictionRunState started: bool = False targetSeq: Optional[int] = None skippedReason: Optional[str] = None