Revise Source detail page to improve line-wrap issues.
Quality Gate / gate (push) Failing after 11s

This commit is contained in:
Jim Lancaster
2026-08-22 18:56:22 -05:00
parent 0d554c0648
commit 94db493756
3 changed files with 160 additions and 19 deletions
+123 -18
View File
@@ -4,10 +4,14 @@ from __future__ import annotations
import base64
import json
import logging
from pathlib import Path
from uuid import UUID
from fastapi import Request
from nicegui import ui
from PIL import Image
from PIL import UnidentifiedImageError
from transcription.config import Settings
from transcription.db.models import ExecutionAttempt
@@ -18,6 +22,7 @@ from transcription.services.errors import SourceDeleteBlockedError
from transcription.services.errors import TranscriptionNotFoundError
from transcription.services.evidence import EvidenceService
from transcription.services.evidence import LatestExecutionAttempt
from transcription.services.source_media import lookup_source_mime_type
from transcription.services.sources import SourceService
from transcription.ui.components.app_shell import render_navigation_header
from transcription.ui.components.cards import archival_card
@@ -43,6 +48,10 @@ from transcription.ui.theme import page_header
from ...db.session import SessionFactoryDep
logger = logging.getLogger(__name__)
_WIDE_NARROW_ASPECT_RATIO_THRESHOLD = 2.2
def register_page() -> None: # noqa: PLR0915
"""Register sources list, detail, and deletion routes."""
@@ -120,6 +129,7 @@ def register_page() -> None: # noqa: PLR0915
sources_service = SourceService(session_factory=session_factory)
evidence_service = EvidenceService(session_factory=session_factory)
render_navigation_header(current_path="/sources")
settings = resolve_runtime_settings(request)
parsed_source_id = parsed_record_id(source_id, noun="Source")
if parsed_source_id is None:
@@ -143,6 +153,7 @@ def register_page() -> None: # noqa: PLR0915
return
original_transcription = _resolve_original_transcription(source=source, latest_attempt=latest_attempt)
layout_mode = _resolve_source_detail_layout(source=source, settings=settings)
with ui.column().classes("w-full max-w-[1800px] mx-auto p-4 gap-4"):
with section_header_row():
@@ -176,23 +187,27 @@ def register_page() -> None: # noqa: PLR0915
extra_classes="text-xs",
)
with ui.grid().classes("w-full grid-cols-12 gap-4"):
with ui.column().classes("col-span-12 lg:col-span-8 gap-4"):
with ui.grid().classes("w-full grid-cols-1 lg:grid-cols-2 gap-4"):
with ui.column().classes("gap-2"):
_render_source_navigation(navigation.previous_id, navigation.next_id)
_render_source_viewer_zone(
source,
settings=resolve_runtime_settings(request),
request=request,
)
_render_source_transcription_column(
source=source,
original_transcription=original_transcription,
latest_job_source=latest_job_source,
sources_service=sources_service,
)
_render_machine_candidates(
if layout_mode == "wide_narrow":
_render_wide_narrow_source_content(
source=source,
navigation=navigation,
request=request,
settings=settings,
original_transcription=original_transcription,
latest_job_source=latest_job_source,
sources_service=sources_service,
attempts=attempts,
evidence_service=evidence_service,
)
else:
_render_standard_source_content(
source=source,
navigation=navigation,
request=request,
settings=settings,
original_transcription=original_transcription,
latest_job_source=latest_job_source,
sources_service=sources_service,
attempts=attempts,
evidence_service=evidence_service,
)
@@ -200,7 +215,10 @@ def register_page() -> None: # noqa: PLR0915
source=source,
latest_job_source=latest_job_source,
latest_attempt=latest_attempt,
settings=resolve_runtime_settings(request),
settings=settings,
column_classes="col-span-12 lg:col-span-4 gap-4"
if layout_mode == "wide_narrow"
else "col-span-12 lg:col-span-3 gap-4",
)
@ui.page("/sources/{source_id}/delete")
@@ -312,14 +330,72 @@ def _render_source_transcription_column(
)
def _render_standard_source_content(
*,
source: Source,
navigation: SourceNavigation,
request: Request,
settings: Settings,
original_transcription: str | None,
latest_job_source: JobSource | None,
sources_service: SourceService,
attempts: list[ExecutionAttempt],
evidence_service: EvidenceService,
) -> None:
with ui.column().classes("col-span-12 lg:col-span-4 gap-2"):
_render_source_navigation(navigation.previous_id, navigation.next_id)
_render_source_viewer_zone(source, settings=settings, request=request)
with ui.column().classes("col-span-12 lg:col-span-5 gap-4"):
_render_source_transcription_zone(
source=source,
original_transcription=original_transcription,
latest_job_source=latest_job_source,
sources_service=sources_service,
)
_render_machine_candidates(
source=source,
attempts=attempts,
evidence_service=evidence_service,
)
def _render_wide_narrow_source_content(
*,
source: Source,
navigation: SourceNavigation,
request: Request,
settings: Settings,
original_transcription: str | None,
latest_job_source: JobSource | None,
sources_service: SourceService,
attempts: list[ExecutionAttempt],
evidence_service: EvidenceService,
) -> None:
with ui.column().classes("col-span-12 lg:col-span-8 gap-4"):
_render_source_navigation(navigation.previous_id, navigation.next_id)
_render_source_viewer_zone(source, settings=settings, request=request)
_render_source_transcription_zone(
source=source,
original_transcription=original_transcription,
latest_job_source=latest_job_source,
sources_service=sources_service,
)
_render_machine_candidates(
source=source,
attempts=attempts,
evidence_service=evidence_service,
)
def _render_source_metadata_column(
*,
source: Source,
latest_job_source: JobSource | None,
latest_attempt: LatestExecutionAttempt | None,
settings: Settings,
column_classes: str = "col-span-12 lg:col-span-4 gap-4",
) -> None:
with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
with ui.column().classes(column_classes):
_render_source_metadata_zone(source, settings=settings)
_render_source_job_metadata_zone(
latest_job_source,
@@ -328,6 +404,35 @@ def _render_source_metadata_column(
_render_source_revision_logistics_zone(source)
def _resolve_source_detail_layout(*, source: Source, settings: Settings) -> str:
media_type = lookup_source_mime_type(source.file_path)
if not media_type.startswith("image/"):
return "standard"
absolute = (settings.upload_dir / Path(source.file_path)).resolve()
dimensions = _read_image_dimensions(absolute)
if dimensions is None:
return "standard"
width, height = dimensions
if _is_wide_narrow_image(width=width, height=height):
return "wide_narrow"
return "standard"
def _is_wide_narrow_image(*, width: int, height: int) -> bool:
if width <= 0 or height <= 0:
return False
return (width / height) >= _WIDE_NARROW_ASPECT_RATIO_THRESHOLD
def _read_image_dimensions(path: Path) -> tuple[int, int] | None:
try:
with Image.open(path) as image:
return image.size
except (FileNotFoundError, OSError, UnidentifiedImageError):
logger.debug("Unable to read image dimensions for source detail layout: %s", path)
return None
def _render_source_metadata_zone(source: Source, *, settings: Settings) -> None:
with archival_card(title="Source Metadata"):
metadata_row("Upload Name:", source.upload_name)