{% extends "base.html" %} {% from "_macros.html" import status, bar %} {# The title is a title. Everything below it used to live inside this block: an unclosed `{% if %}` swallowed three panels — stop, rename and remove — into ``, so the tab carried 1,705 characters of markup and the job screen offered no way to stop, rename or delete a job at all. The controls existed, were tested at the route, and could not be reached. #} {# Truncated as well as capped at entry. New names are limited when they are typed, but a row imported from earlier output, or written before the limit existed, can still be any length — and a two-thousand-character tab title is the same defect arriving by a different door. #} {% block title %}{{ job.name | truncate(80, true, '…') }} — Video to LLM{% endblock %} {% block content %} {# An action this screen offers may still be refused — a delete the database would reject, a rerun on a job the worker is mid-way through. The refusal belongs here, on the screen the button was pressed from, and not as a redirect that looks like success. #} {% if problems %} <div class="panel" style="border-left:3px solid var(--color-accent)" role="alert"> <h4 style="margin:0 0 var(--space-2)">That did not happen</h4> <ul style="margin:0;padding-left:var(--space-4);font-size:14px"> {% for problem in problems %}<li>{{ problem }}</li>{% endfor %} </ul> </div> {% endif %} <div class="page-head"> <div> <h1>{{ job.name }}</h1> {# "took" is past tense. A job still being processed has not taken its elapsed time, it has been running for it — and the dashboard already said "running", so the two screens disagreed about the same job. #} <p class="page-sub"> {{ videos|length }} video{{ '' if videos|length == 1 else 's' }} {%- if elapsed and elapsed != '—' %} {%- if job.status in ("preparing", "transcribing", "analyzing", "waiting_retry") %} · running {{ elapsed }} {%- else %} · took {{ elapsed }}{% endif %} {%- endif %} {%- if total_size %} · {{ total_size }} on disk{% endif %} {%- if queue_position > 1 %} · {{ queue_position }} of {{ queue_length }} in the queue, behind “{{ ahead_name }}”{% endif %} </p> </div> <div class="actions"> {{ status(job_status) }} {% if job.status in ("ready", "preparing", "transcribing", "analyzing", "waiting_retry") %} {# Only when there is a queue and this job is not already at its front. A control that cannot change anything is worse than no control. #} {% if queue_length > 1 and queue_position > 1 %} <form method="post" action="/jobs/{{ job.id }}/run-next" style="display:inline"> <button class="btn btn-secondary" type="submit">Run next</button> </form> {% endif %} <form method="post" action="/jobs/{{ job.id }}/pause" style="display:inline"> <button class="btn btn-secondary" type="submit">Pause</button> </form> {% elif job.status == "paused" %} <form method="post" action="/jobs/{{ job.id }}/resume" style="display:inline"> <button class="btn btn-primary" type="submit">Start again</button> </form> {% endif %} <a class="btn btn-secondary" href="/jobs/{{ job.id }}/review">Review</a> {# "again" is a claim about the past. A job processed with descriptions turned off has never been described once, and offering to do it "again" on a screen that says "Descriptions — Not run" contradicts the line above the button. #} <a class="btn btn-secondary" href="/jobs/{{ job.id }}/rerun"> {%- if job.visual_provider in (None, "", "none") %}Describe{% else %}Describe again{% endif -%} </a> <a class="btn btn-secondary" href="/jobs/{{ job.id }}/outputs">Files</a> </div> </div> <div class="panel"> {% for video in videos %} <div style="padding:var(--space-3) 0;border-bottom:1px solid var(--color-divider)"> <div style="display:flex;align-items:baseline;gap:var(--space-3)"> <span class="mono text-muted">{{ video.number }}</span> <h5 style="margin:0">{{ video.name }}</h5> <span class="text-muted" style="font-size:13px"> {{ video.length }}{% if video.frames %} · {{ "{:,}".format(video.frames) }} pictures{% endif %} </span> <span style="margin-left:auto">{{ status(video.status) }}</span> </div> <div class="grid grid-4" style="margin-top:var(--space-2)"> {# `stage.eta` and `stage.indeterminate` are read defensively. Templates are re-read from disk on every request while route code stays in memory, so a server started before this change serves this markup against the old context — and a hard reference would 500 the job screen of whatever was running at the time. #} {% for stage in video.stages %} <div> <div style="display:flex;justify-content:space-between;font-size:12px"> <span>{{ stage.label }}</span> <span class="text-muted">{{ stage.detail }}</span> </div> {% if stage.get('indeterminate') %} <div class="bar bar-working" role="progressbar" aria-valuetext="Working" aria-valuemin="0" aria-valuemax="100"><span></span></div> {% else %} {{ bar(stage.percent, accent=(stage.percent > 0 and stage.percent < 100)) }} {% endif %} {% if stage.get('eta') %} <p class="text-muted" style="margin:var(--space-1) 0 0;font-size:11.5px">{{ stage.eta }}</p> {% endif %} </div> {% endfor %} </div> {% if video.error %} <p class="note" style="margin-top:var(--space-2)">{{ video.error }}</p> {% endif %} </div> {% endfor %} </div> <div class="panel"> <div class="panel-head"><h4 style="margin:0">What has happened</h4></div> {% if events %} <ul class="timeline"> {% for event in events %} <li> <time class="mono">{{ event.time }}</time> <span{% if event.level != 'info' %} style="color:var(--color-accent-800)"{% endif %}>{{ event.text }}</span> </li> {% endfor %} </ul> {% else %} <p class="text-muted">Nothing recorded yet.</p> {% endif %} </div> {% if job.status in ("draft", "ready", "preparing", "transcribing", "analyzing", "waiting_retry", "paused") %} <div class="panel"> <div class="panel-head"><h4 style="margin:0">Stop this job</h4></div> <p style="font-size:14px"> Stopping keeps everything already finished. Pictures, transcripts and descriptions that exist stay exactly where they are — nothing is thrown away. </p> <form method="post" action="/jobs/{{ job.id }}/cancel"> <button class="btn btn-secondary" type="submit">Stop this job</button> </form> </div> {% endif %} <div class="panel"> <div class="panel-head"><h4 style="margin:0">This job</h4></div> <form method="post" action="/jobs/{{ job.id }}/rename" class="inline-form"> <label for="rename" class="visually-hidden">Rename this job</label> <input class="input" id="rename" name="name" value="{{ job.name }}" style="max-width:340px"> <button class="btn btn-secondary" type="submit">Rename</button> </form> <details style="margin-top:var(--space-4)"> <summary style="cursor:pointer;font-size:14px">Remove this job</summary> <form method="post" action="/jobs/{{ job.id }}/delete" style="margin-top:var(--space-3)"> <p style="font-size:14px"> Removing the job takes it off this list. By default the pictures, transcripts and documents it produced are left exactly where they are — they are the expensive part and the list entry is not. </p> <label class="radio" style="margin-bottom:var(--space-3)"> <input type="checkbox" name="remove_files" value="1"> <span class="dot" aria-hidden="true"></span> <span>Also delete everything this job produced{% if total_size %} ({{ total_size }}){% endif %}</span> </label> <div><button class="btn btn-secondary" type="submit">Remove this job</button></div> </form> </details> </div> {% endblock %}