6.4 KiB · text History d9ed3b6
{% extends "root.html" %}

{% block inner %}
<div class="space-y-4 text-left">
  <a href="/uploads" class="link link-secondary text-sm">&larr; All uploads</a>
  <h2 class="text-2xl font-bold text-accent">{{ upload.item_name or upload.file_name_original }}</h2>

  {% if upload.content_type is startingwith("image/") %}
    <img class="max-h-96 rounded-sm shadow" src="/files/{{ upload.item_path }}" alt="{{ upload.file_name_original }}"/>
  {% elif upload.content_type is startingwith("video/") %}
    {# Poster attribute so the frame shows before anything is fetched; these
       are mostly silent converted GIFs, so loop and mute suit them. #}
    <video class="max-h-96 rounded-sm shadow" controls loop muted playsinline preload="metadata"
           poster="/files/{{ upload.item_path | derived("poster", "jpg") }}">
      <source src="/files/{{ upload.item_path }}" type="{{ upload.content_type }}"/>
    </video>
  {% endif %}

  <div class="card bg-base-200 p-4 text-sm">
    <div><strong>File:</strong> {{ upload.file_name_original }}</div>
    <div><strong>Size:</strong> {{ upload.file_size_bytes }} bytes</div>
    <div><strong>Type:</strong> {{ upload.content_type }}</div>
    <div><strong>Status:</strong> {{ upload.processing_status }}</div>
    <div><strong>Uploaded:</strong> {{ upload.date_uploaded }}</div>
    {% if upload.item_description %}
      <div class="mt-2"><strong>Description:</strong><p>{{ upload.item_description }}</p></div>
    {% endif %}
  </div>

  <details class="card bg-base-200 p-4" {% if upload.processing_status != "complete" %}open{% endif %}>
    <summary class="font-bold cursor-pointer flex items-center gap-2">
      Pipelines
      {% if upload.processing_status == "complete" %}
        <span class="badge badge-success badge-sm">all complete</span>
      {% elif upload.processing_status == "partial" %}
        <span class="badge badge-error badge-sm">partial</span>
      {% elif upload.processing_status == "processing" %}
        <span class="badge badge-warning badge-sm">running</span>
      {% endif %}
    </summary>
    <p class="text-xs opacity-70 mb-3 mt-2">Each row tracks a categorizer's last attempt. "force" clears the result before re-running.</p>
    <table class="table table-sm">
      <tbody>
        {% for kind, label, icon in [
          ("thumbnail",   "Thumbnails",   "fa-image"),
          ("ocr",         "OCR",          "fa-font"),
          ("describe",    "Describe",     "fa-comment-dots"),
          ("extract_tags","Extract tags", "fa-tags")
        ] %}
          {% set j = job_by_kind[kind] %}
          <tr>
            <td class="w-10"><i class="fas fa-fw {{ icon }}"></i></td>
            <td>{{ label }}</td>
            <td>
              {% if j %}
                {% if j.status == "complete" %}<span class="badge badge-success badge-sm">complete</span>
                {% elif j.status == "failed" %}<span class="badge badge-error badge-sm" title="{{ j.error }}">failed</span>
                {% elif j.status == "processing" %}<span class="badge badge-warning badge-sm">processing</span>
                {% else %}<span class="badge badge-ghost badge-sm">{{ j.status }}</span>
                {% endif %}
              {% else %}
                <span class="badge badge-outline badge-sm opacity-60">not run</span>
              {% endif %}
            </td>
            <td class="text-xs opacity-60">
              {% if j and j.completed_at %}{{ j.completed_at }}
              {% elif j and j.started_at %}started {{ j.started_at }}
              {% endif %}
              {% if j and j.attempts > 1 %} · attempt {{ j.attempts }}{% endif %}
              {% if j and j.error %}<div class="text-error truncate max-w-md" title="{{ j.error }}">{{ j.error }}</div>{% endif %}
            </td>
            <td>
              <form method="post" action="/uploads/{{ upload.id }}/jobs" class="flex items-center gap-1">
                <input type="hidden" name="kind" value="{{ kind }}"/>
                <button type="submit" class="btn btn-xs btn-outline">Run</button>
                <label class="cursor-pointer flex items-center gap-1 text-xs opacity-70">
                  <input type="checkbox" name="force" class="checkbox checkbox-xs"/> force
                </label>
              </form>
            </td>
          </tr>
        {% endfor %}
      </tbody>
    </table>
  </details>

  <div class="card bg-base-200 p-4">
    <h3 class="font-bold mb-2">OCR</h3>
    {% if ocr_rows|length == 0 %}
      <p class="opacity-70 text-sm">No OCR results yet.</p>
    {% else %}
      {% for row in ocr_rows %}
        <div class="text-sm{% if not loop.first %} mt-3 pt-3 border-t border-base-300{% endif %}">
          <div class="text-xs opacity-60">{{ row.processing_engine or 'unknown engine' }} · {{ row.processed_at }}</div>
          <pre class="whitespace-pre-wrap mt-1">{{ row.extracted_text or '(empty)' }}</pre>
        </div>
      {% endfor %}
    {% endif %}
  </div>

  <div class="card bg-base-200 p-4">
    <h3 class="font-bold mb-2">Description</h3>
    {% if description_rows|length == 0 %}
      <p class="opacity-70 text-sm">No descriptions yet.</p>
    {% else %}
      {% for row in description_rows %}
        <div class="text-sm{% if not loop.first %} mt-3 pt-3 border-t border-base-300{% endif %}">
          <div class="text-xs opacity-60">{{ row.model_used or 'unknown model' }} · {{ row.processed_at }}</div>
          <p class="mt-1">{{ row.description }}</p>
        </div>
      {% endfor %}
    {% endif %}
  </div>

  <div class="card bg-base-200 p-4">
    <h3 class="font-bold mb-2">Tags</h3>
    {% if tags|length == 0 %}
      <p class="opacity-70 text-sm">No tags yet — categorizers may still be running.</p>
    {% else %}
      <div class="flex flex-wrap gap-1">
        {% for t in tags %}
          <a href="/uploads?tags={{ (t.namespace or '') ~ ':' ~ t.tag_value }}"
             class="badge badge-outline badge-sm">{{ t.namespace or '' }}{% if t.namespace %}:{% endif %}{{ t.tag_value }}</a>
        {% endfor %}
      </div>
    {% endif %}

    <form method="post" action="/uploads/{{ upload.id }}/tags" class="flex gap-2 mt-4">
      <input
        type="text"
        name="tags"
        class="input input-sm flex-1"
        placeholder="user:keep Series:Friends desc:dog"
      />
      <button type="submit" class="btn btn-sm btn-primary">Add tags</button>
    </form>
    <p class="text-xs opacity-60 mt-1">Whitespace or comma separated. Bare values become unnamespaced; <code>ns:value</code> sets a namespace.</p>
  </div>
</div>
{% endblock %}