feat: add generation trace and partial-ready workflow status

This commit is contained in:
2026-04-18 21:53:55 +08:00
parent 96dfc677e2
commit e99a7fbe14
36 changed files with 2597 additions and 144 deletions

View File

@@ -0,0 +1,107 @@
"""add story text status and partial ready semantics
Revision ID: 0012_story_text_status
Revises: 0011_add_generation_jobs
Create Date: 2026-04-18
"""
import sqlalchemy as sa
from alembic import op
revision = "0012_story_text_status"
down_revision = "0011_add_generation_jobs"
branch_labels = None
depends_on = None
stories = sa.table(
"stories",
sa.column("id", sa.Integer),
sa.column("story_text", sa.Text),
sa.column("pages", sa.JSON),
sa.column("cover_prompt", sa.Text),
sa.column("image_url", sa.String(length=500)),
sa.column("generation_status", sa.String(length=32)),
sa.column("text_status", sa.String(length=32)),
sa.column("image_status", sa.String(length=32)),
sa.column("audio_status", sa.String(length=32)),
)
def _has_narrative(row: dict) -> bool:
return bool(row.get("story_text")) or bool(row.get("pages"))
def _has_pending_image(row: dict) -> bool:
if row.get("image_status") in {"ready", "generating"}:
return False
pages = row.get("pages") or []
has_missing_page_image = any(
isinstance(page, dict)
and page.get("image_prompt")
and not page.get("image_url")
for page in pages
)
return bool(row.get("cover_prompt") and not row.get("image_url")) or has_missing_page_image
def _resolve_generation_status(row: dict) -> str:
if not _has_narrative(row):
return "failed"
image_status = row.get("image_status") or "not_requested"
audio_status = row.get("audio_status") or "not_requested"
if "generating" in {image_status, audio_status}:
return "assets_generating"
if "failed" in {image_status, audio_status}:
return "degraded_completed"
has_pending_audio = bool(row.get("story_text")) and audio_status not in {
"ready",
"generating",
}
if _has_pending_image(row) or has_pending_audio:
return "partial_ready"
if image_status == "not_requested" and audio_status == "not_requested":
return "narrative_ready"
return "completed"
def upgrade() -> None:
op.add_column(
"stories",
sa.Column("text_status", sa.String(length=32), nullable=False, server_default="ready"),
)
connection = op.get_bind()
rows = connection.execute(
sa.select(
stories.c.id,
stories.c.story_text,
stories.c.pages,
stories.c.cover_prompt,
stories.c.image_url,
stories.c.image_status,
stories.c.audio_status,
)
).mappings()
for row in rows:
text_status = "ready" if _has_narrative(row) else "failed"
generation_status = _resolve_generation_status(row)
connection.execute(
stories.update()
.where(stories.c.id == row["id"])
.values(text_status=text_status, generation_status=generation_status)
)
def downgrade() -> None:
op.drop_column("stories", "text_status")