diff --git a/src/transcription/ui/components/transcript.py b/src/transcription/ui/components/transcript.py deleted file mode 100644 index 2b04e73..0000000 --- a/src/transcription/ui/components/transcript.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Reusable transcript UI components.""" - -from __future__ import annotations - -from collections.abc import Awaitable -from collections.abc import Callable -from datetime import datetime -from typing import Any - -from nicegui import ui - -from transcription.db.models import Job -from transcription.db.models import Source - -type RevisionAction = Callable[[Source], Awaitable[None] | None] - - -def render_original_transcription_card(*, job: Job, classes: str = "w-full") -> Any: - """Render the immutable original job transcription output.""" - latest_error_detail = _latest_job_error_detail(job) - status_label = "Failed" if latest_error_detail else "Transcribed" - header = f"Original Transcription | {status_label}" - provider = job.provider or "unknown" - model = job.model or "unknown" - caption = f"{provider} | {model} | {_format_created_at(job.date_updated)}" - - card = ui.card().classes(f"{classes} q-pa-md ui-card-surface") - with card, ui.column().classes("w-full q-gutter-y-sm"): - ui.label(header).classes("text-subtitle1 text-weight-medium") - ui.label(caption).classes("text-caption ui-text-muted") - _metadata_row(label="Prompt", value=_latest_job_prompt(job) or "unknown") - _metadata_row(label="Updated", value=_format_created_at(job.date_updated)) - - latest_transcription = _latest_job_transcription(job) - - if latest_transcription: - with ui.card().classes("w-full q-pa-sm ui-card-surface"): - ui.markdown(latest_transcription) - - if latest_error_detail: - with ui.card().classes("w-full ui-card-error q-pa-sm"): - ui.label("Failure detail").classes("text-caption text-uppercase") - ui.label(latest_error_detail).classes("text-body2") - - return card - - -def render_revision_row( - *, - revision: Source | None, - initially_expanded: bool = False, - classes: str = "w-full", - on_delete: RevisionAction | None = None, -) -> Any: - """Render a collapsible row for the single optional source revision.""" - if revision is None: - return None - - header = "Source revision | User-authored" - caption = _format_created_at(revision.date_revised or revision.date_uploaded) - - expansion = ui.expansion(value=initially_expanded, group="group").classes(f"{classes} ui-card-surface") - - with expansion, ui.column().classes("w-full q-gutter-y-sm q-pa-sm"): - with expansion.add_slot("header"), ui.row().classes("w-full items-start justify-between q-gutter-md"): - with ui.column().classes("q-gutter-none"): - ui.label(header).classes("text-subtitle1 text-weight-medium") - ui.label(caption).classes("text-caption ui-text-muted") - - if on_delete is not None: - with ui.dialog() as delete_dialog, ui.card().classes("q-pa-md ui-card-surface"): - ui.label("Delete this source revision?").classes("text-body1") - with ui.row().classes("w-full justify-end q-gutter-sm"): - ui.button("Cancel", on_click=lambda: delete_dialog.submit(False)).props("flat") - ui.button("Delete", on_click=lambda: delete_dialog.submit(True)).props( - 'unelevated color="negative"' - ) - - async def delete_current_transcript() -> None: - delete_dialog.open() - confirmed = await delete_dialog - if not confirmed: - return - - maybe_awaitable = on_delete(revision) - if isinstance(maybe_awaitable, Awaitable): - await maybe_awaitable - - with ui.column(align_items="center").classes("self-center q-gutter-none"): - ui.button(icon="delete", on_click=delete_current_transcript).props( - 'flat round dense color="negative"' - ) - _metadata_row(label="Created", value=_format_created_at(revision.date_revised or revision.date_uploaded)) - - if revision.revised_text: - with ui.card().classes("w-full q-pa-sm ui-card-surface"): - ui.markdown(revision.revised_text) - - return expansion - - -def _latest_job_transcription(job: Job) -> str | None: - for job_source in sorted(job.job_sources, key=lambda item: item.executed_at, reverse=True): - if job_source.raw_transcription: - return job_source.raw_transcription - return None - - -def _latest_job_error_detail(job: Job) -> str | None: - for job_source in sorted(job.job_sources, key=lambda item: item.executed_at, reverse=True): - if job_source.error_detail: - return job_source.error_detail - return None - - -def _latest_job_prompt(job: Job) -> str | None: - for job_source in sorted(job.job_sources, key=lambda item: item.executed_at, reverse=True): - if job_source.job and job_source.job.prompt_name: - return job_source.job.prompt_name - return None - - -def _format_created_at(value: datetime) -> str: - """Return a compact UTC-like timestamp for row captions.""" - return value.strftime("%Y-%m-%d %H:%M:%S %Z") - - -def _metadata_row(*, label: str, value: str) -> None: - with ui.row().classes("w-md items-start justify-between q-gutter-x-md"): - ui.label(label).classes("text-caption ui-text-muted text-uppercase") - ui.label(value).classes("text-body2 text-right break-all")