1414from enum import Enum
1515from datetime import datetime , timezone
1616from pathlib import Path
17- from typing import Any , Callable , Dict , List , Optional , Set , Tuple
17+ from typing import Any , Callable , Dict , List , Literal , Optional , Set , Tuple
1818from uuid import uuid4 , uuid5 , NAMESPACE_URL
1919
2020from fastapi import FastAPI , HTTPException , Request , UploadFile , File
2121from fastapi .middleware .cors import CORSMiddleware
2222from fastapi .responses import FileResponse , StreamingResponse
2323from starlette .background import BackgroundTask
2424from starlette .middleware .trustedhost import TrustedHostMiddleware
25- from pydantic import BaseModel , Field , model_validator
25+ from pydantic import BaseModel , ConfigDict , Field , model_validator
2626from pydantic_settings import BaseSettings , SettingsConfigDict
2727
2828from agents .studio import AGENT_PROMPTS , AgentStudio , StudioWorkflow
@@ -1650,6 +1650,7 @@ def build_outline_messages(
16501650 identity : str ,
16511651 continuation_mode : bool = False ,
16521652 batch_direction : Optional [str ] = None ,
1653+ scope : Optional [str ] = None ,
16531654) -> List [Dict [str , str ]]:
16541655 phase_hints = build_outline_phase_hints (chapter_count , continuation_mode )
16551656 constraints = [
@@ -1664,7 +1665,38 @@ def build_outline_messages(
16641665 ]
16651666 if continuation_mode :
16661667 constraints .extend (build_serial_continuation_constraints ())
1667- result = [
1668+ payload = {
1669+ "task" : "根据一句话梗概拆成章节蓝图" ,
1670+ "chapter_count" : chapter_count ,
1671+ "prompt" : prompt ,
1672+ "genre" : project .genre ,
1673+ "style" : project .style ,
1674+ "identity" : identity ,
1675+ "continuation_mode" : continuation_mode ,
1676+ "scope" : scope ,
1677+ "constraints" : constraints ,
1678+ "forbidden_title_keywords" : [
1679+ "起势递进" ,
1680+ "代价扩张" ,
1681+ "阶段收束" ,
1682+ "里程碑" ,
1683+ "第二阶段钩子" ,
1684+ ],
1685+ "title_style_examples" : [
1686+ "镜城残响" ,
1687+ "第二次心跳" ,
1688+ "车祸后的合法复活" ,
1689+ "监控者的真面目" ,
1690+ "在雪夜醒来的那个人" ,
1691+ ],
1692+ "phase_hints" : phase_hints ,
1693+ }
1694+ if batch_direction :
1695+ payload ["batch_direction_guidance" ] = (
1696+ f"用户对这批章节的创作方向要求:{ batch_direction } 。请在拆章和内容生成时参考此方向。"
1697+ )
1698+
1699+ return [
16681700 {
16691701 "role" : "system" ,
16701702 "content" : (
@@ -1675,41 +1707,9 @@ def build_outline_messages(
16751707 },
16761708 {
16771709 "role" : "user" ,
1678- "content" : json .dumps (
1679- {
1680- "task" : "根据一句话梗概拆成章节蓝图" ,
1681- "chapter_count" : chapter_count ,
1682- "prompt" : prompt ,
1683- "genre" : project .genre ,
1684- "style" : project .style ,
1685- "identity" : identity ,
1686- "continuation_mode" : continuation_mode ,
1687- "constraints" : constraints ,
1688- "forbidden_title_keywords" : [
1689- "起势递进" ,
1690- "代价扩张" ,
1691- "阶段收束" ,
1692- "里程碑" ,
1693- "第二阶段钩子" ,
1694- ],
1695- "title_style_examples" : [
1696- "镜城残响" ,
1697- "第二次心跳" ,
1698- "车祸后的合法复活" ,
1699- "监控者的真面目" ,
1700- "在雪夜醒来的那个人" ,
1701- ],
1702- "phase_hints" : phase_hints ,
1703- },
1704- ensure_ascii = False ,
1705- ),
1710+ "content" : json .dumps (payload , ensure_ascii = False ),
17061711 },
17071712 ]
1708- if batch_direction :
1709- result [- 1 ]["content" ] += (
1710- f"\n \n 用户对这批章节的创作方向要求:{ batch_direction } \n 请在拆章和内容生成时参考此方向。"
1711- )
1712- return result
17131713
17141714
17151715def build_chapter_outline (
@@ -1723,6 +1723,7 @@ def build_chapter_outline(
17231723 start_chapter_number : int = 1 ,
17241724 existing_titles : Optional [List [str ]] = None ,
17251725 batch_direction : Optional [str ] = None ,
1726+ scope : Optional [str ] = None ,
17261727) -> List [Dict [str , str ]]:
17271728 identity = store .three_layer .get_identity ()[:2500 ]
17281729 messages = build_outline_messages (
@@ -1732,6 +1733,7 @@ def build_chapter_outline(
17321733 identity = identity ,
17331734 continuation_mode = continuation_mode ,
17341735 batch_direction = batch_direction ,
1736+ scope = scope ,
17351737 )
17361738 raw = studio .llm_client .chat (
17371739 messages ,
@@ -2834,20 +2836,15 @@ class ChapterDirectionRequest(BaseModel):
28342836
28352837
28362838class OneShotBookRequest (BaseModel ):
2837- prompt : str = ""
2838- batch_direction : Optional [str ] = None
2839+ model_config = ConfigDict (extra = "forbid" )
2840+
2841+ batch_direction : str = Field (min_length = 1 )
28392842 mode : GenerationMode = GenerationMode .STUDIO
28402843 chapter_count : Optional [int ] = Field (default = None , ge = 1 , le = 60 )
28412844 words_per_chapter : int = Field (default = 1600 , ge = 300 , le = 12000 )
28422845 auto_approve : bool = False
28432846 continuation_mode : bool = False
2844- scope : Optional [str ] = None
2845-
2846- @model_validator (mode = "after" )
2847- def apply_batch_direction_alias (self ):
2848- if not self .prompt and self .batch_direction :
2849- self .prompt = self .batch_direction
2850- return self
2847+ scope : Optional [Literal ["volume" , "book" ]] = None
28512848
28522849
28532850class PromptPreviewRequest (BaseModel ):
@@ -4262,7 +4259,7 @@ async def run_one_shot_book_generation(
42624259 )
42634260 outline = await asyncio .to_thread (
42644261 build_chapter_outline ,
4265- prompt = req .prompt .strip (),
4262+ prompt = req .batch_direction .strip (),
42664263 chapter_count = chapter_count ,
42674264 project = project ,
42684265 store = store ,
@@ -4271,6 +4268,7 @@ async def run_one_shot_book_generation(
42714268 start_chapter_number = next_number ,
42724269 existing_titles = existing_titles ,
42734270 batch_direction = req .batch_direction ,
4271+ scope = req .scope ,
42744272 )
42754273 await emit_progress (
42764274 progress ,
@@ -4617,7 +4615,7 @@ async def book_stage_callback(
46174615 response_payload = {
46184616 "project_id" : project_id ,
46194617 "mode" : req .mode .value ,
4620- "prompt" : req .prompt .strip (),
4618+ "prompt" : req .batch_direction .strip (),
46214619 "continuation_mode" : req .continuation_mode ,
46224620 "generated_chapters" : len (created ),
46234621 "chapters" : created ,
@@ -4633,9 +4631,9 @@ async def generate_one_shot_book(project_id: str, req: OneShotBookRequest):
46334631 if not project :
46344632 raise HTTPException (status_code = 404 , detail = "Project not found" )
46354633
4636- prompt = req .prompt .strip ()
4637- if not prompt :
4638- raise HTTPException (status_code = 400 , detail = "prompt is required" )
4634+ direction = req .batch_direction .strip ()
4635+ if not direction :
4636+ raise HTTPException (status_code = 400 , detail = "batch_direction is required" )
46394637
46404638 store = get_or_create_store (project_id )
46414639 studio = get_or_create_studio (project_id )
@@ -4664,9 +4662,9 @@ async def generate_one_shot_book_stream(project_id: str, req: OneShotBookRequest
46644662 if not project :
46654663 raise HTTPException (status_code = 404 , detail = "Project not found" )
46664664
4667- prompt = req .prompt .strip ()
4668- if not prompt :
4669- raise HTTPException (status_code = 400 , detail = "prompt is required" )
4665+ direction = req .batch_direction .strip ()
4666+ if not direction :
4667+ raise HTTPException (status_code = 400 , detail = "batch_direction is required" )
46704668
46714669 store = get_or_create_store (project_id )
46724670 studio = get_or_create_studio (project_id )
0 commit comments