first commit

This commit is contained in:
team 1
2026-04-20 16:36:28 +02:00
parent a0ec07a99c
commit 2587ac8b4b
41 changed files with 5126 additions and 2280 deletions

View File

@@ -4,8 +4,15 @@
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0"><i class="bi bi-card-list"></i> Dokumente</h1>
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
<div>
<h1 class="h3 mb-1">
<i class="bi bi-card-list"></i> Dokumente
</h1>
<div class="small text-muted">
Übersicht über Dokumente, aktive Versionen, Ingest-Zustände und Tag-Zuordnungen.
</div>
</div>
<a href="{{ path('admin_document_new') }}"
class="btn btn-sm btn-outline-info">
@@ -13,154 +20,222 @@
</a>
</div>
{% for message in app.flashes('success') %}
<div class="alert alert-success shadow-sm">
{{ message }}
</div>
{% endfor %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger shadow-sm">
{{ message }}
</div>
{% endfor %}
{% for message in app.flashes('info') %}
<div class="alert alert-info shadow-sm">
{{ message }}
</div>
{% endfor %}
<div class="card bg-dark border-secondary text-light mb-4 shadow-sm">
<div class="card-body row g-4">
<div class="col-lg-7">
<h5 class="text-info mb-3">Worauf achten?</h5>
<ul class="small mb-0">
<li><strong>INDEXED</strong> bedeutet: aktive Version ist sauber im Wissensindex.</li>
<li><strong>PENDING</strong> oder <strong>FAILED</strong> bedeuten: Dokument prüfen und ggf. Ingest erneut anstoßen.</li>
<li><strong>Tags</strong> sollten fachlich präzise sein und nicht nur generische Oberbegriffe abbilden.</li>
<li>Die aktive Version ist die fachlich führende Version des Dokuments.</li>
</ul>
</div>
<div class="col-lg-5">
<h5 class="text-info mb-3">Schnellzugriff</h5>
<div class="small text-light">
Über <strong>Tags</strong> gelangst du direkt in die Tag-Pflege des Dokuments.
Über <strong>Details</strong> steuerst du Versionen, Aktivierung, Re-Ingest und Löschung.
</div>
</div>
</div>
</div>
{% if documents is empty %}
<div class="alert alert-secondary">
<div class="alert alert-secondary shadow-sm">
Keine Dokumente vorhanden.
</div>
{% else %}
<div class="card bg-black border-secondary">
<div class="card bg-black border-secondary shadow-sm">
<div class="card-body p-0">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead class="table-secondary text-dark">
<tr>
<th>Titel</th>
<th>ID</th>
<th>Typ</th>
<th>Status</th>
<th>Indexierung</th>
<th>Versionen</th>
<th>Aktive Version</th>
<th>Erstellt</th>
<th class="text-end">Aktionen</th>
</tr>
</thead>
<tbody>
<div class="d-flex justify-content-between align-items-center px-3 py-3 border-bottom border-secondary flex-wrap gap-2">
<div>
<strong class="text-info">Vorhandene Dokumente</strong>
<span class="small text-muted ms-2">{{ documents|length }} Einträge</span>
</div>
{% for document in documents %}
<div class="small text-muted">
Neueste Dokumente stehen oben.
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead class="table-secondary text-dark">
<tr>
<th style="width: 20%">Titel</th>
<th style="width: 14%">ID</th>
<th style="width: 8%">Typ</th>
<th style="width: 8%">Status</th>
<th style="width: 10%">Indexierung</th>
<th style="width: 7%">Versionen</th>
<th style="width: 8%">Aktive Version</th>
<th style="width: 7%">Tags</th>
<th style="width: 8%">Erstellt</th>
<th class="text-end" style="width: 10%">Aktionen</th>
</tr>
</thead>
<tbody>
{# Titel #}
<td>
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="text-light text-decoration-none">
{{ document.title }}
</a>
</td>
{% for document in documents %}
<tr>
<td>
<div class="fw-semibold">
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="text-light text-decoration-none">
{{ document.title }}
</a>
</div>
{# ID #}
<td class="small text-info">
{{ document.id }}
</td>
{% if document.currentVersion and document.currentVersion.filePath %}
<div class="small text-muted mt-1">
Aktive Datei vorhanden
</div>
{% endif %}
</td>
{# Typ #}
<td>
{% if document.currentVersion %}
<span class="badge bg-secondary">
{{ document.currentVersion.fileTypeLabel }}
</span>
{% else %}
<span class="badge bg-dark border border-secondary">
-
</span>
{% endif %}
</td>
<td class="small text-info">
<code>{{ document.id }}</code>
</td>
{# Dokument Status #}
<td>
{% if document.status == 'ACTIVE' %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-secondary">Archiviert</span>
{% endif %}
</td>
{# Ingest Status #}
<td>
{% if document.currentVersion %}
{% if document.currentVersion.ingestStatus == 'INDEXED' %}
<span class="badge bg-success">INDEXED</span>
{% elseif document.currentVersion.ingestStatus == 'PENDING' %}
<span class="badge bg-warning text-dark">PENDING</span>
{% elseif document.currentVersion.ingestStatus == 'FAILED' %}
<span class="badge bg-danger">FAILED</span>
<td>
{% if document.currentVersion %}
<span class="badge bg-secondary">
{{ document.currentVersion.fileTypeLabel }}
</span>
{% else %}
<span class="badge bg-dark border border-secondary">
{{ document.currentVersion.ingestStatus }}
</span>
-
</span>
{% endif %}
{% else %}
<span class="badge bg-dark border border-secondary">-</span>
{% endif %}
</td>
</td>
{# Version Count #}
<td>
{{ document.versions|length }}
</td>
<td>
{% if document.status == 'ACTIVE' %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-secondary">Archiviert</span>
{% endif %}
</td>
{# Aktive Version #}
<td>
{% if document.currentVersion %}
v{{ document.currentVersion.versionNumber }}
{% else %}
-
{% endif %}
</td>
<td>
{% if document.currentVersion %}
{% if document.currentVersion.ingestStatus == 'INDEXED' %}
<span class="badge bg-success">INDEXED</span>
{% elseif document.currentVersion.ingestStatus == 'PENDING' %}
<span class="badge bg-warning text-dark">PENDING</span>
{% elseif document.currentVersion.ingestStatus == 'RUNNING' %}
<span class="badge bg-warning text-dark">RUNNING</span>
{% elseif document.currentVersion.ingestStatus == 'FAILED' %}
<span class="badge bg-danger">FAILED</span>
{% else %}
<span class="badge bg-dark border border-secondary">
{{ document.currentVersion.ingestStatus ?: '-' }}
</span>
{% endif %}
{% else %}
<span class="badge bg-dark border border-secondary">-</span>
{% endif %}
</td>
{# Created At #}
<td class="small">
{{ document.createdAt|date('d.m.Y H:i') }}
</td>
<td>
<span class="badge text-bg-dark border border-secondary">
{{ document.versions|length }}
</span>
</td>
{# Aktionen #}
<td class="text-end">
<td>
{% if document.currentVersion %}
<span class="badge bg-info text-dark">
v{{ document.currentVersion.versionNumber }}
</span>
{% else %}
-
{% endif %}
</td>
<a class="btn btn-sm btn-outline-info me-2"
href="{{ path('admin_document_tags_edit', {id: document.id}) }}">
Tags
</a>
<td>
<span class="badge text-bg-dark border border-secondary">
{{ document.tags|length }}
</span>
</td>
<a class="btn btn-sm btn-outline-light me-2"
href="{{ path('admin_document_show', {id: document.id}) }}">
Details
</a>
<td class="small">
{{ document.createdAt|date('d.m.Y H:i') }}
</td>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_delete', {id: document.id}) }}"
class="d-inline"
onsubmit="return confirm('Dokument wirklich endgültig löschen? Diese Aktion entfernt Dokument, Versionen und Index-Daten.');">
<td class="text-end">
<div class="d-flex justify-content-end flex-wrap gap-2">
<a class="btn btn-sm btn-outline-info"
href="{{ path('admin_document_tags_edit', {id: document.id}) }}">
Tags
</a>
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_document_' ~ document.id) }}">
<a class="btn btn-sm btn-outline-light"
href="{{ path('admin_document_show', {id: document.id}) }}">
Details
</a>
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
{% endif %}
{% if is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_delete', {id: document.id}) }}"
class="d-inline"
onsubmit="return confirm('Dokument wirklich löschen? Der Inhalt wird per Delete-Job aus dem Index entfernt.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_document_' ~ document.id) }}">
</td>
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="mt-4 small text-secondary">
Hinweis: Das Löschen eines Dokuments entfernt alle Versionen und
erfordert eine Aktualisierung des NDJSON-Indexes.
<div class="card bg-dark border-secondary text-light mt-4 shadow-sm">
<div class="card-body">
<h5 class="text-info mb-3">Hinweis zum Dokument-Lifecycle</h5>
<div class="small text-light">
Änderungen an aktiven Versionen und Löschvorgänge wirken sich direkt auf den Wissensindex aus.
Zugewiesene Tags beeinflussen zusätzlich die semantische Routing-Ebene des Systems.
Dokumente mit schwachen oder fehlenden Tags sind oft ein guter Kandidat für fachliche Nachpflege.
</div>
</div>
</div>
{% endblock %}

View File

@@ -4,8 +4,13 @@
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">Neues Dokument</h1>
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
<div>
<h1 class="h3 mb-1">Neues Dokument</h1>
<div class="small text-muted">
Neuer Upload mit initialer Version und anschließendem asynchronen Ingest.
</div>
</div>
<a href="{{ path('admin_documents') }}"
class="btn btn-sm btn-outline-secondary">
@@ -13,7 +18,49 @@
</a>
</div>
<div class="card bg-black border-secondary text-light">
{% for message in app.flashes('success') %}
<div class="alert alert-success shadow-sm">
{{ message }}
</div>
{% endfor %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger shadow-sm">
{{ message }}
</div>
{% endfor %}
{% for message in app.flashes('info') %}
<div class="alert alert-info shadow-sm">
{{ message }}
</div>
{% endfor %}
<div class="card bg-dark border-secondary text-light mb-4 shadow-sm">
<div class="card-body row g-4">
<div class="col-lg-7">
<h5 class="text-info mb-3">Warum ist der Titel wichtig?</h5>
<ul class="small mb-0">
<li>Der Titel wird später Teil des fachlichen Kontexts des Dokuments.</li>
<li>Ein präziser Titel verbessert Retrieval, Chunk-Einordnung und spätere Tag-Pflege.</li>
<li>Generische Titel wie <code>Dokument 1</code> oder nur Dateinamen sind deutlich schwächer.</li>
</ul>
</div>
<div class="col-lg-5">
<h5 class="text-info mb-3">Gute Beispiele</h5>
<ul class="small mb-0">
<li><code>Testomat 808 Technisches Datenblatt</code></li>
<li><code>Resthärte-Messung Produktübersicht</code></li>
<li><code>Indikator 300 Anwendung und Dosierung</code></li>
</ul>
</div>
</div>
</div>
<div class="card bg-black border-secondary text-light shadow-sm">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
@@ -22,31 +69,24 @@
name="_token"
value="{{ csrf_token('create_document') }}">
{# ============================= #}
{# Titel #}
{# ============================= #}
<div class="mb-4">
<label class="form-label">Titel</label>
<div class="alert alert-secondary small">
<strong>Hinweis zur Qualität:</strong><br>
Der Titel ist entscheidend für die semantische Einordnung
der erzeugten Chunks. Jeder Chunk erhält den Titel als Kontext,
wodurch Retrieval und Antwortqualität signifikant verbessert werden.<br><br>
Wird kein Titel angegeben, wird automatisch der Dateiname
verwendet (nicht empfohlen).
Verwende einen fachlich präzisen Titel, der Produkt, Thema oder Dokumenttyp klar beschreibt.
Wenn kein Titel angegeben wird, wird automatisch der Dateiname verwendet.
</div>
<input class="form-control bg-dark text-light border-secondary"
name="title"
placeholder="z. B. Sicherheitsdatenblatt Produkt XY">
</div>
value="{{ app.request.get('title') }}"
placeholder="z. B. Testomat 808 Technisches Datenblatt">
{# ============================= #}
{# Datei Upload #}
{# ============================= #}
<div class="form-text text-secondary">
Der Titel muss nicht lang sein, aber fachlich eindeutig.
</div>
</div>
<div class="mb-4">
<label class="form-label">Datei</label>
@@ -58,14 +98,22 @@
<div class="form-text text-secondary">
Unterstützte Formate: PDF, DOCX, TXT, MD.
Das Dokument wird versioniert gespeichert und anschließend
indexiert.
Nach dem Upload wird automatisch Version 1 erstellt und ein Ingest-Job gestartet.
</div>
</div>
{# ============================= #}
{# Submit #}
{# ============================= #}
<div class="card bg-dark border-secondary mb-4">
<div class="card-body">
<h6 class="text-info mb-3">Was passiert nach dem Speichern?</h6>
<ul class="small mb-0">
<li>Das Dokument wird versioniert gespeichert.</li>
<li>Die erste Version wird als aktuelle Version gesetzt.</li>
<li>Ein asynchroner Ingest-Job verarbeitet das Dokument für den Wissensindex.</li>
<li>Später können dem Dokument gezielt Tags zugewiesen werden.</li>
</ul>
</div>
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-outline-info">
@@ -79,8 +127,7 @@
</div>
<div class="mt-4 small text-secondary">
Hinweis: Nach dem Upload wird automatisch eine neue Dokumentversion erstellt.
Die Indexierung erfolgt asynchron über einen Ingest-Job.
Hinweis: Ein sauber benanntes Dokument ist die beste Grundlage für gutes Retrieval und späteres präzises Tagging.
</div>
{% endblock %}
{% endblock %}

View File

@@ -4,10 +4,13 @@
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
Neue Version
</h1>
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
<div>
<h1 class="h3 mb-1">Neue Version</h1>
<div class="small text-muted">
Neue unveränderliche Version für ein bestehendes Dokument hochladen.
</div>
</div>
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="btn btn-sm btn-outline-secondary">
@@ -15,36 +18,99 @@
</a>
</div>
<div class="card bg-dark border-secondary mb-4 text-light">
<div class="card-body">
{% for message in app.flashes('success') %}
<div class="alert alert-success shadow-sm">
{{ message }}
</div>
{% endfor %}
<div class="mb-3">
<strong>Dokument:</strong>
<span class="text-light">{{ document.title }}</span>
{% for message in app.flashes('danger') %}
<div class="alert alert-danger shadow-sm">
{{ message }}
</div>
{% endfor %}
{% for message in app.flashes('info') %}
<div class="alert alert-info shadow-sm">
{{ message }}
</div>
{% endfor %}
<div class="card bg-dark border-secondary text-light mb-4 shadow-sm">
<div class="card-body row g-4">
<div class="col-lg-7">
<h5 class="text-info mb-3">Dokumentkontext</h5>
<div class="mb-2">
<strong>Dokument:</strong>
<span class="text-light">{{ document.title }}</span>
</div>
<div class="small text-secondary">
Eine neue Version erzeugt eine zusätzliche, unveränderliche Dokumentversion.
Die bestehende aktive Version bleibt zunächst unverändert.
</div>
</div>
<div class="small text-secondary">
Das Hochladen einer neuen Version erzeugt eine zusätzliche
unveränderliche Dokumentversion. Die Aktivierung erfolgt separat
und löst einen deterministischen Re-Ingest aus.
</div>
<div class="col-lg-5">
<h5 class="text-info mb-3">Aktueller Stand</h5>
<div class="small mb-2">
<strong>Aktive Version:</strong>
{% if document.currentVersion %}
<span class="badge bg-info text-dark">
v{{ document.currentVersion.versionNumber }}
</span>
{% else %}
-
{% endif %}
</div>
<div class="small mb-2">
<strong>Vorhandene Versionen:</strong>
{{ document.versions|length }}
</div>
<div class="small">
<strong>Zugewiesene Tags:</strong>
{{ document.tags|length }}
</div>
</div>
</div>
</div>
<div class="card bg-black border-secondary text-light">
<div class="card bg-dark border-secondary text-light mb-4 shadow-sm">
<div class="card-body row g-4">
<div class="col-lg-7">
<h5 class="text-info mb-3">Wichtig für den Lifecycle</h5>
<ul class="small mb-0">
<li>Der Upload erzeugt nur eine <strong>neue Version</strong>, aber aktiviert sie nicht automatisch.</li>
<li>Erst die spätere <strong>Aktivierung</strong> löst den deterministischen Re-Ingest aus.</li>
<li>Tags bleiben auf <strong>Dokumentebene</strong> bestehen und gelten weiterhin für das Dokument als Ganzes.</li>
</ul>
</div>
<div class="col-lg-5">
<h5 class="text-info mb-3">Gute Praxis</h5>
<ul class="small mb-0">
<li>Nur fachlich wirklich passende Nachfolgeversionen hochladen.</li>
<li>Kein anderes Thema oder anderes Produkt in dieselbe Dokumentlinie mischen.</li>
<li>Bei stark verändertem Fachinhalt später Tagging mitprüfen.</li>
</ul>
</div>
</div>
</div>
<div class="card bg-black border-secondary text-light shadow-sm">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<input type="hidden"
name="_token"
value="{{ csrf_token('create_document_version_' ~ document.id) }}">
{# ============================= #}
{# Datei Upload #}
{# ============================= #}
<div class="mb-4">
<label class="form-label">Datei auswählen</label>
@@ -54,15 +120,23 @@
required>
<div class="form-text text-secondary">
Unterstützte Formate: PDF, DOCX, TXT, MD.<br>
Die Datei wird versioniert gespeichert und mit einer
eindeutigen Checksum versehen.
Unterstützte Formate: PDF, DOCX, TXT, MD.
Die Datei wird versioniert gespeichert und mit einer eindeutigen Checksum versehen.
</div>
</div>
{# ============================= #}
{# Submit #}
{# ============================= #}
<div class="card bg-dark border-secondary mb-4">
<div class="card-body">
<h6 class="text-info mb-3">Was passiert nach dem Upload?</h6>
<ul class="small mb-0">
<li>Es wird eine neue, unveränderliche Dokumentversion angelegt.</li>
<li>Die aktive Version bleibt zunächst unverändert.</li>
<li>Ein Re-Ingest erfolgt erst nach späterer Aktivierung dieser Version.</li>
<li>Danach wird der Wissensindex deterministisch neu aufgebaut.</li>
</ul>
</div>
</div>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<div class="d-flex justify-content-end">
@@ -71,16 +145,14 @@
</button>
</div>
{% endif %}
</form>
</div>
</div>
<div class="mt-4 small text-secondary">
Hinweis: Eine neue Version ersetzt nicht automatisch die aktive Version.
Erst nach Aktivierung wird ein Re-Ingest durchgeführt und der Index
neu aufgebaut.
Hinweis: Eine neue Version verbessert den Dokument-Lifecycle nur dann sauber, wenn sie fachlich wirklich zu diesem Dokument gehört.
Bei stark verändertem Inhalt sollten nach der späteren Aktivierung auch die Tags geprüft werden.
</div>
{% endblock %}
{% endblock %}

View File

@@ -4,116 +4,225 @@
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{{ document.title ?? 'Ein Fehler trat auf' }}</h1>
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
<div>
<h1 class="h3 mb-1">{{ document.title }}</h1>
<div class="small text-muted">
Detailansicht für Dokument, Versionen und Tag-Zuordnung.
</div>
</div>
<a href="{{ path('admin_documents') }}"
class="btn btn-sm btn-outline-secondary">
Zurück zur Übersicht
</a>
<div class="d-flex flex-wrap gap-2">
<a href="{{ path('admin_document_tags_edit', {id: document.id}) }}"
class="btn btn-sm btn-outline-info">
Tags bearbeiten
</a>
<a href="{{ path('admin_documents') }}"
class="btn btn-sm btn-outline-secondary">
Zurück zur Übersicht
</a>
</div>
</div>
{% if document %}
{# ============================= #}
{# Dokument-Meta #}
{# ============================= #}
<div class="card bg-dark border-secondary mb-5 text-light">
<div class="card-body">
<div class="mb-2">
<strong>Status:</strong>
{% if document.status == 'ACTIVE' %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-secondary">Archiviert</span>
{% endif %}
</div>
<div class="mb-2">
<strong>Erstellt von:</strong>
{{ document.createdBy ? document.createdBy.email : '-' }}
</div>
<div class="mb-2">
<strong>Erstellt am:</strong>
{{ document.createdAt|date('d.m.Y H:i') }}
</div>
<div class="mb-2">
<strong>Aktive Version:</strong>
{% if document.currentVersion %}
<span class="badge bg-info text-dark">
v{{ document.currentVersion.versionNumber }}
</span>
{% else %}
-
{% endif %}
</div>
</div>
{% for message in app.flashes('success') %}
<div class="alert alert-success shadow-sm">
{{ message }}
</div>
{% endfor %}
{# ============================= #}
{# Versionen #}
{# ============================= #}
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h5 mb-0">Versionen</h2>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<a href="{{ path('admin_document_version_new', {id: document.id}) }}"
class="btn btn-sm btn-outline-info">
Neue Version
</a>
{% endif %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger shadow-sm">
{{ message }}
</div>
{% endfor %}
{% if document.versions is empty %}
{% for message in app.flashes('info') %}
<div class="alert alert-info shadow-sm">
{{ message }}
</div>
{% endfor %}
<div class="alert alert-secondary">
Keine Versionen vorhanden.
</div>
<div class="row g-4 mb-4">
{% else %}
<div class="card bg-black border-secondary">
<div class="col-lg-7">
<div class="card bg-dark border-secondary text-light h-100 shadow-sm">
<div class="card-body">
<h5 class="text-info mb-3">Dokument-Metadaten</h5>
<div class="row g-3">
<div class="col-md-6">
<div class="small text-muted mb-1">Status</div>
<div>
{% if document.status == 'ACTIVE' %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-secondary">Archiviert</span>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">Aktive Version</div>
<div>
{% if document.currentVersion %}
<span class="badge bg-info text-dark">
v{{ document.currentVersion.versionNumber }}
</span>
{% else %}
-
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">Erstellt von</div>
<div>{{ document.createdBy ? document.createdBy.email : '-' }}</div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">Erstellt am</div>
<div>{{ document.createdAt|date('d.m.Y H:i:s') }}</div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">Anzahl Versionen</div>
<div>{{ document.versions|length }}</div>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">Zugewiesene Tags</div>
<div>{{ document.tags|length }}</div>
</div>
</div>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<hr class="border-secondary">
<div class="d-flex flex-wrap gap-2">
<a href="{{ path('admin_document_version_new', {id: document.id}) }}"
class="btn btn-sm btn-outline-info">
Neue Version
</a>
<form method="post"
action="{{ path('admin_document_delete', {id: document.id}) }}"
class="d-inline"
onsubmit="return confirm('Dokument wirklich löschen? Der Inhalt wird per Delete-Job aus dem Index entfernt.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_document_' ~ document.id) }}">
<button class="btn btn-sm btn-outline-danger">
Dokument löschen
</button>
</form>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-5">
<div class="card bg-dark border-secondary text-light h-100 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="text-info mb-0">Tags</h5>
<a href="{{ path('admin_document_tags_edit', {id: document.id}) }}"
class="btn btn-sm btn-outline-light">
Bearbeiten
</a>
</div>
{% if document.tags is empty %}
<div class="alert alert-secondary mb-0">
Diesem Dokument sind noch keine Tags zugewiesen.
</div>
{% else %}
<div class="d-flex flex-wrap gap-2">
{% for tag in document.tags %}
<span class="badge px-3 py-2
{% if tag.type == 'catalog_entity' %}
text-bg-info
{% elseif tag.type == 'sales_signal' %}
text-bg-warning
{% else %}
text-bg-secondary
{% endif %}">
{{ tag.label }}
</span>
{% endfor %}
</div>
<div class="small text-muted mt-3">
Tags steuern die semantische Routing-Ebene. Weise nur fachlich wirklich passende Tags zu.
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<div>
<h2 class="h5 mb-1">Versionen</h2>
<div class="small text-muted">
Beim Aktivieren einer Version wird automatisch ein Re-Ingest ausgelöst.
</div>
</div>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<a href="{{ path('admin_document_version_new', {id: document.id}) }}"
class="btn btn-sm btn-outline-info">
Neue Version
</a>
{% endif %}
</div>
{% if document.versions is empty %}
<div class="alert alert-secondary shadow-sm">
Keine Versionen vorhanden.
</div>
{% else %}
<div class="card bg-black border-secondary shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead class="table-secondary text-dark">
<tr>
<th>Version</th>
<th>Status</th>
<th>Ingest</th>
<th>Checksum</th>
<th>Erstellt von</th>
<th>Datum</th>
<th class="text-end">Aktionen</th>
<th style="width: 10%">Version</th>
<th style="width: 10%">Aktiv</th>
<th style="width: 14%">Ingest</th>
<th style="width: 18%">Checksum</th>
<th style="width: 16%">Erstellt von</th>
<th style="width: 14%">Datum</th>
<th class="text-end" style="width: 18%">Aktionen</th>
</tr>
</thead>
<tbody>
{% for version in document.versions %}
<tr>
<td>
<strong>v{{ version.versionNumber }}</strong>
{% if document.currentVersion and version.id == document.currentVersion.id %}
<div class="small text-info mt-1">Current</div>
{% endif %}
</td>
{# Aktivstatus #}
<td>
{% if version.isActive %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-dark border border-secondary">
Inaktiv
</span>
<span class="badge bg-dark border border-secondary">Inaktiv</span>
{% endif %}
</td>
{# Ingest Status #}
<td>
{% if version.ingestStatus == 'INDEXED' %}
<span class="badge bg-success">INDEXED</span>
@@ -125,99 +234,85 @@
<span class="badge bg-secondary">PENDING</span>
{% else %}
<span class="badge bg-dark border border-secondary">
{{ version.ingestStatus }}
</span>
{{ version.ingestStatus ?: '-' }}
</span>
{% endif %}
</td>
{# Checksum #}
<td class="small text-secondary">
{{ version.checksum ? version.checksum[:10] ~ '…' : '-' }}
{% if version.checksum %}
<code>{{ version.checksum[:12] ~ '…' }}</code>
{% else %}
-
{% endif %}
</td>
{# Created by #}
<td>
{{ version.createdBy ? version.createdBy.email : '-' }}
</td>
{# Date #}
<td class="small">
{{ version.createdAt|date('d.m.Y H:i') }}
{{ version.createdAt|date('d.m.Y H:i:s') }}
</td>
{# Aktionen #}
<td class="text-end">
{% if version.isActive %}
{% if version.ingestStatus in ['PENDING', 'FAILED'] and is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_version_ingest', {versionId: version.id}) }}"
class="d-inline"
onsubmit="return confirm('Ingest erneut starten?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('ingest_version_' ~ version.id) }}">
<button class="btn btn-sm btn-outline-info">
Ingest starten
</button>
</form>
<div class="d-flex justify-content-end flex-wrap gap-2">
{% if version.isActive %}
{% if version.ingestStatus in ['PENDING', 'FAILED'] and is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_version_ingest', {versionId: version.id}) }}"
class="d-inline"
onsubmit="return confirm('Ingest erneut starten?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('ingest_version_' ~ version.id) }}">
<button class="btn btn-sm btn-outline-info">
Ingest starten
</button>
</form>
{% else %}
<span class="small text-success align-self-center">
Keine Aktion nötig
</span>
{% endif %}
{% else %}
<span class="text-success small">
Bereits indexiert
</span>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_version_activate', {versionId: version.id}) }}"
class="d-inline"
onsubmit="return confirm('Diese Version aktivieren? Es wird ein Re-Ingest ausgelöst.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('activate_version_' ~ version.id) }}">
<button class="btn btn-sm btn-outline-light">
Aktivieren
</button>
</form>
{% endif %}
{% endif %}
{% else %}
{% if is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_version_activate', {versionId: version.id}) }}"
class="d-inline"
onsubmit="return confirm('Diese Version aktivieren? Es wird ein Re-Ingest ausgelöst.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('activate_version_' ~ version.id) }}">
<button class="btn btn-sm btn-outline-light">
Aktivieren
</button>
</form>
{% endif %}
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="mt-4 small text-secondary">
Hinweis: Beim Aktivieren einer Version wird automatisch ein Re-Ingest
durchgeführt. Der NDJSON-Index und der FAISS-Index werden deterministisch
neu aufgebaut.
</div>
{% else %}
<div class="alert alert-danger">
Dokument nicht gefunden.
</div>
{% endif %}
{% endblock %}
<div class="card bg-dark border-secondary text-light mt-4 shadow-sm">
<div class="card-body">
<h5 class="text-info mb-3">Hinweis zum Lifecycle</h5>
<div class="small text-light">
Beim Aktivieren einer Version wird automatisch ein Re-Ingest durchgeführt.
Der NDJSON-Bestand und der Vektorindex werden deterministisch neu aufgebaut.
Wenn Tags zugewiesen sind, beeinflusst dieses Dokument zusätzlich die semantische Routing-Ebene.
</div>
</div>
</div>
{% endblock %}