Files
life-echo/api/app/features/memoir/cover_eligibility.py
yangshilin 9af2060259 fix:
1. 修复安卓部分机型顶部安全区遮挡回忆录标题的问题;
2. 降低封面图生成阈值和展示逻辑,独立封面图未生成时,使用正文图;
3. 去掉“嗯。”生硬回答,去掉不合理段首承接词;
4. 新增章节封面所需最少插图数的配置项
2026-04-16 20:42:54 +08:00

112 lines
3.8 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.
"""章节封面是否可入队(与 Celery 任务共享,避免循环 import"""
from __future__ import annotations
from typing import Any
from app.core.config import settings
from app.features.memoir.asset_resolver import (
parse_asset_refs,
strip_image_placeholders,
)
from app.features.memoir.memoir_images.schema import (
IMAGE_STATUS_FAILED,
IMAGE_STATUS_PENDING,
)
def chapter_has_story_links(chapter: Any) -> bool:
return any(
getattr(link, "story", None)
for link in getattr(chapter, "story_links", None) or []
)
def effective_chapter_markdown_for_cover_gates(chapter: Any) -> str:
"""
用于封面闸门计数:优先 DB canonical若为空且已挂 stories则用内存物化串
(与列表/详情在 compose_dirty 时的临时正文对齐,避免「有图但 canonical 未落库」导致永不出封面)。
"""
md = (getattr(chapter, "canonical_markdown", None) or "").strip()
if md:
return md
if chapter_has_story_links(chapter):
from app.features.memoir.chapter_markdown_compose import (
materialize_chapter_markdown_from_loaded_chapter,
)
try:
alt = (
materialize_chapter_markdown_from_loaded_chapter(chapter) or ""
).strip()
except Exception:
return ""
return alt
return ""
def count_chapter_inline_body_images(
chapter: Any, *, markdown: str | None = None
) -> int:
"""统计 asset:// 插图次数;未传 markdown 时用 effective_chapter_markdown_for_cover_gates。"""
source = (
markdown
if markdown is not None
else effective_chapter_markdown_for_cover_gates(chapter)
)
return len(parse_asset_refs(source))
def chapter_eligible_for_cover_by_inline_body_image_count(
chapter: Any, *, markdown: str | None = None
) -> bool:
"""正文内 asset:// 数量 ≥ 配置阈值时允许封面markdown 非 None 时仅用该串计数。"""
min_required = int(settings.memoir_min_inline_images_for_chapter_cover)
return count_chapter_inline_body_images(chapter, markdown=markdown) >= min_required
def primary_chapter_memoir_image(chapter: Any) -> Any | None:
"""章节级 MemoirImage封面槽位按 order_index 最小取第一条。"""
imgs = sorted(
getattr(chapter, "images", None) or [],
key=lambda m: getattr(m, "order_index", 0),
)
return imgs[0] if imgs else None
def chapter_needs_cover_enqueue(chapter) -> bool:
"""尚无 cover_asset、有正文、且正文内 asset 插图达到 env 阈值时,可派发 generate_chapter_cover。"""
if not chapter:
return False
if not chapter_has_story_links(chapter):
return False
if getattr(chapter, "cover_asset_id", None):
return False
view = effective_chapter_markdown_for_cover_gates(chapter)
body = strip_image_placeholders(view).strip()
if not body:
return False
return chapter_eligible_for_cover_by_inline_body_image_count(chapter, markdown=view)
def chapter_has_cover_to_generate(chapter) -> bool:
"""章节是否有待生成的封面图(任一条 chapter 级 MemoirImage 为 pending/failed"""
for m in getattr(chapter, "images", None) or []:
status = (m.status or "").strip()
if status in (IMAGE_STATUS_PENDING, IMAGE_STATUS_FAILED):
return True
return False
def cover_memoir_image_pending_or_failed(chapter: Any) -> Any | None:
"""用于补图任务:按 order_index 找到第一条 pending/failed 的章节配图行。"""
images = sorted(
getattr(chapter, "images", None) or [],
key=lambda m: getattr(m, "order_index", 0),
)
for m in images:
st = (m.status or "").strip()
if st in (IMAGE_STATUS_PENDING, IMAGE_STATUS_FAILED):
return m
return None