From b8998025e2fb4e1b35dd66412596f4efb773c155 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:35:21 -0500 Subject: [PATCH] delete button on transcript header --- src/transcription/ui/components/transcript.py | 36 +++++++++++++++++-- src/transcription/ui/pages/jobs_page.py | 33 +++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/transcription/ui/components/transcript.py b/src/transcription/ui/components/transcript.py index bff9a80..3ec2cad 100644 --- a/src/transcription/ui/components/transcript.py +++ b/src/transcription/ui/components/transcript.py @@ -2,6 +2,8 @@ from __future__ import annotations +from collections.abc import Awaitable +from collections.abc import Callable from datetime import datetime from typing import Any @@ -9,12 +11,15 @@ from nicegui import ui from transcription.models import Transcript +type TranscriptAction = Callable[[Transcript], Awaitable[None] | None] + def render_transcript_revision_row( *, transcript: Transcript, initially_expanded: bool = False, classes: str = "w-full", + on_delete: TranscriptAction | None = None, ) -> Any: """Render one collapsible row for a single transcript revision.""" status_label = "Failed" if transcript.error_detail else "Transcribed" @@ -26,9 +31,34 @@ def render_transcript_revision_row( ) with expansion, ui.column().classes("w-full q-gutter-y-sm q-pa-sm"): - with expansion.add_slot("header"), ui.column().classes("w-full"): - ui.label(header).classes("text-subtitle1 text-weight-medium") - ui.label(caption).classes("text-caption text-grey-5") + 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 text-grey-5") + + if on_delete is not None: + with ui.dialog() as delete_dialog, ui.card().classes("q-pa-md"): + ui.label("Delete this transcript 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(transcript) + 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="Provider", value=transcript.provider) _metadata_row(label="Model", value=transcript.model) _metadata_row(label="Created", value=_format_created_at(transcript.created_at)) diff --git a/src/transcription/ui/pages/jobs_page.py b/src/transcription/ui/pages/jobs_page.py index d91c5e8..b7afa91 100644 --- a/src/transcription/ui/pages/jobs_page.py +++ b/src/transcription/ui/pages/jobs_page.py @@ -10,7 +10,9 @@ from nicegui import ui from transcription.app_state import resolve_session_factory from transcription.models import JobStatus from transcription.services.jobs import JobService +from transcription.services.transcription import TranscriptionService from transcription.ui.components.app_shell import render_navigation_header +from transcription.ui.components.error_presenter import show_error from transcription.ui.components.table.jobs import render_jobs_table from ..components.document_panzoom import render_document_panzoom @@ -49,6 +51,7 @@ def register_page() -> None: async def job_detail_page(job_id: str, request: Request) -> None: session_factory = resolve_session_factory(request.app.state) jobs_service = JobService(session_factory=session_factory) + transcription_service = TranscriptionService(session_factory=session_factory) render_navigation_header(current_path="/jobs") job = await jobs_service.read_job(job_id=UUID(job_id)) @@ -66,5 +69,31 @@ def register_page() -> None: ui.chip(job.status.value.upper(), color="green", text_color="white").props("outline") case _: ui.label(f"{job.status.value}").classes("text-subtitle1 text-weight-medium") - for i, transcript in enumerate(job.transcripts): - render_transcript_revision_row(transcript=transcript, initially_expanded=(i == 0)) + + async def delete_transcript_by_id(transcript_id: UUID, revision: int) -> None: + try: + transcript = await transcription_service.read_transcript(transcript_id=transcript_id) + await transcription_service.delete_transcript(transcript) + except Exception as exc: # noqa: BLE001 + show_error(exc, title="Delete failed", operation="jobs.delete_transcript") + return + + ui.notify(f"Deleted revision {revision}", type="positive") + await render_transcript_list.refresh() + + @ui.refreshable + async def render_transcript_list() -> None: + refreshed_job = await jobs_service.read_job(job_id=UUID(job_id)) + for i, transcript in enumerate(refreshed_job.transcripts): + render_transcript_revision_row( + transcript=transcript, + initially_expanded=(i == 0), + on_delete=( + lambda _transcript, tid=transcript.id, rev=transcript.revision: delete_transcript_by_id( + tid, + rev, + ) + ), + ) + + await render_transcript_list()