feat(web): 立项新增 基调/结局/视角 三字段(书级常量入 stable_core 缓存前缀)

灵感计划 §3④ + §4 D 枚举决策:
- projects 表加 tone/ending_type/narrative_pov(Text nullable、无 CHECK,同 genre/structure;
  迁移 e5f6a7b8c9d0 nullable 免 backfill)。
- ProjectCreateRequest/ProjectResponse + domain ProjectCreate/ProjectView 全链路透传(snake_case)。
- 完整 stable_core 链路:ProjectSpecView + SqlProjectSpecRepo.spec + _build_spec_section 渲染进
  「作品蓝本」块——书级常量随书稳定,字节稳定守缓存前缀不变量 #9,绝不入 volatile。
- 前端 wizard.ts 三字段 + 预设数组 TONES/ENDING_TYPES/NARRATIVE_POVS + ProjectWizard 控件与确认页回显。
- 契约变更已 pnpm gen:api + memory/contracts.md 登记。
This commit is contained in:
Yaojia Wang
2026-07-06 15:59:49 +02:00
parent 491d9342ef
commit 821ace5989
17 changed files with 247 additions and 1 deletions

View File

@@ -29,6 +29,9 @@ class ProjectView:
theme: str | None = None
selling_points: list[Any] = field(default_factory=list)
structure: str | None = None
tone: str | None = None
ending_type: str | None = None
narrative_pov: str | None = None
updated_at: datetime | None = None
pending_review_count: int = 0
@@ -44,6 +47,9 @@ class ProjectCreate:
theme: str | None = None
selling_points: list[Any] = field(default_factory=list)
structure: str | None = None
tone: str | None = None
ending_type: str | None = None
narrative_pov: str | None = None
class ProjectRepo(Protocol):
@@ -71,6 +77,9 @@ def _to_view(
theme=row.theme,
selling_points=list(row.selling_points or []),
structure=row.structure,
tone=row.tone,
ending_type=row.ending_type,
narrative_pov=row.narrative_pov,
updated_at=updated_at or row.updated_at,
pending_review_count=pending_review_count,
)
@@ -92,6 +101,9 @@ class SqlProjectRepo:
theme=data.theme,
selling_points=list(data.selling_points),
structure=data.structure,
tone=data.tone,
ending_type=data.ending_type,
narrative_pov=data.narrative_pov,
)
self._s.add(row)
await self._s.commit()

View File

@@ -101,6 +101,10 @@ class ProjectSpecView(BaseModel):
theme: str | None = None
# 题材(每书稳定项)——供 assemble 注入 genre-aware craft 片段(灵感 F1
genre: str | None = None
# 立项书级常量(灵感④):基调/结局取向/叙事视角——每书稳定 → 入 stable_core 缓存前缀。
tone: str | None = None
ending_type: str | None = None
narrative_pov: str | None = None
# ---- Repository 协议async统一按 project_id 过滤)----

View File

@@ -69,6 +69,13 @@ def _build_spec_section(spec: ProjectSpecView | None) -> str | None:
lines.append(f"【前提】{spec.premise}")
if spec.theme:
lines.append(f"【主题】{spec.theme}")
# 立项书级常量(灵感④):每书稳定 → 随蓝本进缓存前缀,跨章字节不变(不变量 #9
if spec.tone:
lines.append(f"【基调】{spec.tone}")
if spec.narrative_pov:
lines.append(f"【叙事视角】{spec.narrative_pov}")
if spec.ending_type:
lines.append(f"【结局取向】{spec.ending_type}")
return _section("作品蓝本", "\n".join(lines))

View File

@@ -216,6 +216,9 @@ class SqlProjectSpecRepo:
premise=row.premise,
theme=row.theme,
genre=row.genre,
tone=row.tone,
ending_type=row.ending_type,
narrative_pov=row.narrative_pov,
)