Files
life-echo/api/app/features/memoir/router.py
Kevin 786ebf8ae6 refactor(api,expo): 多智能体与会话收敛、回忆录兼容层移除、后端测试集大幅删减
- 对齐「多智能体收敛」与「回忆录 stories-first / markdown-first」方向:收紧运行时契约、
  删除过渡兼容路径与双轨逻辑,并同步更新客户端与文档。

- Chat:以 ChatOrchestrator 为实时编排入口;删除独立 conversation_agent,精简 prompts。
- Memoir:删除 memory_agent;MemoirOrchestrator、classification / story_route 与 prompts 收敛到
  prepare_batches + run_story_pipeline_for_category_batch 主链路。
- 将 agents 侧 processor 迁入 feature 层为 background_runner,并移除 features 下重复/过时
  processor 封装。

- 新增 history_store,强化「conversation_messages 为 DB 真源、Redis 为缓存」模型。
- 调整 models、repo、service、session_history;精简 WS message_types,重构 pipeline 与 router。

- 移除章节占位、整章再生等旧路径;章节列表与封面逻辑要求 story 关联;收紧 cover 资格与
  enqueue。
- helpers、repo、service、router、reading_segment_materialize、story_pipeline_sync、pdf_service
  等按 canonical markdown / cover_asset_id 收缩;删除 memoir_images/provider 等冗余。
- tasks:memoir_tasks、chapter_cover_tasks 等大幅瘦身;story_image_tasks 等与当前图片任务对齐。

- core:config、logging、redis、task_tracker 小幅调整。
- auth / user / payment / quota:路由或服务侧删减过时接口或逻辑(如 payment router 行数减少)。

- pyproject.toml、development.sh、.env.example / .env.production、README 等同步说明或变量。

- Alembic 0001_initial_schema 微调(与当前 schema 叙事一致的小改动)。

- 回忆录:types / mappers / api、章节页与 memoir 页与后端契约对齐;markdown-renderer 调整。
- 语音:删除 voice/player,voice-segment-store 相应精简。

- api/tests:删除 conftest 及绝大部分既有测试文件(websocket_baseline、conversation、memoir
  图片、PDF、SMS 等),属有意收缩/待按 backend-test-system 重建的信号。
- docs:新增多智能体收敛与移除兼容层计划摘要;更新 story-first 设计、backend-test-system、
  multi-agent-refactor-plan、实施总结等。

BREAKING CHANGE: 后端对外契约、回忆录章节字段与若干路由/任务行为已变更;大量 API 测试被移除,
  CI 若依赖这些用例需按新策略补测或调整流水线。
2026-03-22 18:10:28 +08:00

170 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
回忆录 feature — books / chapters / memoir-state 合并路由
"""
from typing import List, Optional
from fastapi import APIRouter, Body, Depends, Query
from app.core.dependencies import get_current_user
from app.core.logging import get_logger
from app.features.memoir.deps import get_memoir_service
from app.features.memoir.schemas import (
ExportPdfRequest,
SetChapterStoryOrderRequest,
UpdateBookRequest,
)
from app.features.memoir.service import MemoirService
from app.features.user.models import User
router = APIRouter(
prefix="/api",
tags=["memoir"],
responses={
401: {"description": "认证失败"},
403: {"description": "权限不足"},
404: {"description": "资源不存在"},
},
)
logger = get_logger(__name__)
# ===========================================================================
# Books endpoints
# ===========================================================================
@router.get("/books/current")
async def get_current_book(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""获取当前回忆录(需要认证)"""
return await service.get_current_book(current_user.id)
@router.post("/books/clear-update")
async def clear_book_update(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""清除回忆录更新标记"""
return await service.clear_book_update(current_user.id)
@router.put("/books/{book_id}")
async def update_book(
book_id: str,
request: UpdateBookRequest = Body(...),
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""更新书籍标题(需要认证,只能更新自己的回忆录)"""
return await service.update_book(book_id, current_user.id, request.title)
@router.post("/books/export-pdf")
async def export_pdf(
request: ExportPdfRequest = Body(...),
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""导出 PDF需要认证只能导出自己的回忆录"""
return await service.export_pdf(current_user.id, request.book_id)
# ===========================================================================
# Chapters endpoints
# ===========================================================================
@router.get("/chapters", response_model=List[dict])
async def get_chapters(
current_user: User = Depends(get_current_user),
is_new: Optional[bool] = Query(None, description="仅返回未读章节"),
service: MemoirService = Depends(get_memoir_service),
):
"""
获取用户所有有效章节(需要认证,仅返回 active 章节)。
"""
return await service.get_chapters(current_user.id, is_new=is_new)
@router.get("/chapters/{chapter_id}", response_model=dict)
async def get_chapter(
chapter_id: str,
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""获取章节详情(需要认证,只能访问自己的章节)"""
return await service.get_chapter(chapter_id, current_user.id)
@router.post("/chapters/check-cover-generation")
async def check_cover_generation(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""
检查可生成封面的章节story-linked 且正文 asset 插图 > 3
若有则触发生成任务。已有封面的章节不再检查。
"""
return await service.check_and_trigger_cover_generation(current_user.id)
@router.delete("/chapters/{chapter_id}")
async def disable_chapter(
chapter_id: str,
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""清除章节(将章节标记为 disabled需要认证只能操作自己的章节"""
return await service.disable_chapter(chapter_id, current_user.id)
@router.put("/chapters/{chapter_id}/story-order")
async def set_chapter_story_order(
chapter_id: str,
request: SetChapterStoryOrderRequest = Body(...),
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""
设置章节收录的 stories 顺序(覆盖 chapter_story_links并立即物化 canonical_markdown。
"""
return await service.set_chapter_story_order(
chapter_id, current_user.id, request.story_ids
)
# ===========================================================================
# Memoir-state endpoints
# ===========================================================================
@router.get("/memoir-state")
async def get_memoir_state(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""获取当前用户回忆录状态"""
return await service.get_memoir_state(current_user.id)
@router.get("/memoir-state/next-question")
async def get_next_question_context(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""获取下一步问题的上下文(当前阶段与空 slot"""
return await service.get_next_question_context(current_user.id)
@router.post("/memoir-state/mark-read")
async def mark_memoir_read(
current_user: User = Depends(get_current_user),
service: MemoirService = Depends(get_memoir_service),
):
"""标记回忆录更新已读"""
return await service.mark_memoir_read(current_user.id)