修复:CI 部署环境与 ref 错配、迁移碎片化、图片意图 source_span、章节物化脏版式、会话历史与本地语音不一致

新增:TTS 上传 COS 与分片、章节 reading_segments 物化与快照、markdown 清洗、会话消息 repository、语音 store 重构与相关测试
This commit is contained in:
Kevin
2026-03-20 16:36:42 +08:00
parent 7317bf10cd
commit 8af37e5e8e
65 changed files with 1704 additions and 504 deletions

View File

@@ -14,7 +14,11 @@ from app.core.config import settings
from app.core.db import get_sync_db
from app.core.logging import get_logger
from app.features.memoir.asset_resolver import strip_legacy_image_placeholders
from app.features.memoir.cover_eligibility import primary_chapter_memoir_image
from app.features.memoir.cover_eligibility import (
chapter_eligible_for_cover_by_inline_body_image_count,
chapter_needs_cover_enqueue,
primary_chapter_memoir_image,
)
from app.features.memoir.models import Chapter
logger = get_logger(__name__)
@@ -42,6 +46,8 @@ def _chapter_eligible_for_http_enqueue(chapter: Chapter | None) -> bool:
body = strip_legacy_image_placeholders(body).strip()
if not body:
return False
if not chapter_eligible_for_cover_by_inline_body_image_count(chapter):
return False
cover_rec = primary_chapter_memoir_image(chapter)
if cover_rec and (cover_rec.status or "").strip() == "completed":
return False
@@ -49,12 +55,8 @@ def _chapter_eligible_for_http_enqueue(chapter: Chapter | None) -> bool:
def _chapter_eligible_for_pipeline_enqueue(chapter: Chapter | None) -> bool:
"""与 memoir.cover_eligibility.chapter_needs_cover_enqueue 一致"""
if not chapter:
return False
if getattr(chapter, "cover_asset_id", None):
return False
return bool((getattr(chapter, "canonical_markdown", None) or "").strip())
"""尚无 cover_asset、正文插图数 > 3与 HTTP 闸门共用 chapter_needs_cover_enqueue 核心)"""
return bool(chapter_needs_cover_enqueue(chapter))
def _load_chapter_for_enqueue_sync(chapter_id: str) -> Chapter | None:

View File

@@ -22,6 +22,9 @@ from app.features.memoir.chapter_cover import (
aggregate_cover_prompt_from_chapter,
aggregate_cover_prompt_from_stories,
)
from app.features.memoir.cover_eligibility import (
chapter_eligible_for_cover_by_inline_body_image_count,
)
from app.features.memoir.memoir_images.storage import TencentCosStorageService
from app.features.memoir.models import Chapter, ChapterCoverIntent, ChapterStoryLink
from app.ports.image_gen import TaskStatus
@@ -197,6 +200,13 @@ def generate_chapter_cover(self, chapter_id: str):
)
return {"status": "no_chapter"}
if not chapter_eligible_for_cover_by_inline_body_image_count(chapter):
logger.info(
"generate_chapter_cover: chapter=%s, reason=insufficient_inline_body_images",
chapter_id,
)
return {"status": "insufficient_inline_body_images"}
if getattr(chapter, "cover_asset_id", None):
logger.info(
"generate_chapter_cover: chapter=%s, reason=has_cover_asset",

View File

@@ -263,11 +263,13 @@ def generate_story_image(self, story_id: str):
return {"status": "success_no_snapshot", "asset_id": asset_id}
base_md = ver.markdown_snapshot or ""
alt_text = (getattr(intent_db, "prompt_brief", None) or "").strip()
if not alt_text:
alt_text = (getattr(intent_db, "caption", None) or "").strip()
backfilled_md = backfill_image_into_markdown(
base_md,
asset_id=asset_id,
caption=intent_db.caption or "主插图",
source_span=intent_db.source_span,
image_alt=alt_text or "主插图",
)
max_stmt = select(func.max(StoryVersion.version_no)).where(
StoryVersion.story_id == story_id