first commit
This commit is contained in:
@@ -4,19 +4,24 @@
|
||||
|
||||
{% block body %}
|
||||
|
||||
{# ========================================================= #}
|
||||
{# LIVE REBUILD STATUS (SSE) #}
|
||||
{# ========================================================= #}
|
||||
|
||||
<div id="rebuild-status" class="mb-5">
|
||||
<div class="alert alert-secondary shadow-sm">
|
||||
Status wird geladen…
|
||||
</div>
|
||||
<div id="rebuild-status" class="mb-4">
|
||||
{% if latestJob %}
|
||||
<div class="alert alert-secondary shadow-sm mb-0">
|
||||
Status wird geladen…
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">
|
||||
<i class="bi bi-tag-fill"></i> Tag: {{ tag.label }}
|
||||
</h1>
|
||||
<div>
|
||||
<h1 class="h3 mb-1">
|
||||
<i class="bi bi-tag-fill"></i> Tag: {{ tag.label }}
|
||||
</h1>
|
||||
|
||||
<div class="small text-muted">
|
||||
Slug: <code>{{ tag.slug }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ path('admin_tags_index') }}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
@@ -24,7 +29,6 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
const statusBox = document.getElementById('rebuild-status');
|
||||
const source = new EventSource("{{ path('admin_tags_rebuild_stream') }}");
|
||||
@@ -35,9 +39,9 @@
|
||||
|
||||
if (data.status === '{{ statusRunning }}') {
|
||||
html = `
|
||||
<div class="alert alert-info shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div class="alert alert-info shadow-sm d-flex justify-content-between align-items-center mb-0">
|
||||
<div>
|
||||
Tag-Rebuild läuft<br>
|
||||
<strong>Tag-Rebuild läuft</strong><br>
|
||||
${data.startedAt ? 'Gestartet: ' + new Date(data.startedAt).toLocaleString() : ''}
|
||||
</div>
|
||||
<div class="spinner-border spinner-border-sm"></div>
|
||||
@@ -45,20 +49,20 @@
|
||||
`;
|
||||
} else if (data.status === '{{ statusQueued }}') {
|
||||
html = `
|
||||
<div class="alert alert-secondary shadow-sm">
|
||||
Tag-Rebuild in Warteschlange
|
||||
<div class="alert alert-secondary shadow-sm mb-0">
|
||||
<strong>Tag-Rebuild in Warteschlange</strong>
|
||||
</div>
|
||||
`;
|
||||
} else if (data.status === '{{ statusCompleted }}') {
|
||||
html = `
|
||||
<div class="alert alert-success shadow-sm">
|
||||
<div class="alert alert-success shadow-sm mb-0">
|
||||
<i class="bi bi-check-lg"></i> Tag-Rebuild erfolgreich abgeschlossen
|
||||
</div>
|
||||
`;
|
||||
} else if (data.status === '{{ statusFailed }}') {
|
||||
html = `
|
||||
<div class="alert alert-danger shadow-sm">
|
||||
Tag-Rebuild fehlgeschlagen<br>
|
||||
<div class="alert alert-danger shadow-sm mb-0">
|
||||
<strong>Tag-Rebuild fehlgeschlagen</strong><br>
|
||||
${data.error ? '<code>' + data.error + '</code>' : ''}
|
||||
</div>
|
||||
`;
|
||||
@@ -70,100 +74,179 @@
|
||||
source.onerror = function () {
|
||||
console.warn('SSE Verbindung verloren');
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
source.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
{# ============================= #}
|
||||
{# Flash Messages #}
|
||||
{# ============================= #}
|
||||
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="alert alert-success">
|
||||
<div class="alert alert-success shadow-sm">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for message in app.flashes('danger') %}
|
||||
<div class="alert alert-danger">
|
||||
<div class="alert alert-danger shadow-sm">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# ============================= #}
|
||||
{# Tag → Dokumente #}
|
||||
{# ============================= #}
|
||||
<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">Einordnung des Tags</h5>
|
||||
|
||||
<div class="mb-2">
|
||||
{% if tag.type == 'catalog_entity' %}
|
||||
<span class="badge text-bg-info">Catalog Entity</span>
|
||||
{% elseif tag.type == 'sales_signal' %}
|
||||
<span class="badge text-bg-warning">Sales Signal</span>
|
||||
{% else %}
|
||||
<span class="badge text-bg-secondary">Generic</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="small mb-2">
|
||||
{{ tag.description ?: 'Keine Beschreibung hinterlegt.' }}
|
||||
</p>
|
||||
|
||||
<p class="small text-muted mb-0">
|
||||
Weise diesen Tag nur Dokumenten zu, die fachlich wirklich denselben Gegenstand,
|
||||
dieselbe Produktfamilie oder denselben Anwendungsfall abbilden.
|
||||
Zu breite Zuweisungen machen das Routing weicher.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5">
|
||||
<h5 class="text-info mb-3">Aktueller Stand</h5>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge text-bg-dark border border-secondary">
|
||||
Zugewiesen: {{ assignedDocIds|length }}
|
||||
</span>
|
||||
<span class="badge text-bg-dark border border-secondary">
|
||||
Verfügbar: {{ documents|length }}
|
||||
</span>
|
||||
<span class="badge text-bg-dark border border-secondary">
|
||||
Nicht zugewiesen: {{ documents|length - assignedDocIds|length }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
|
||||
<input type="hidden"
|
||||
name="_token"
|
||||
value="{{ csrf_token('assign_tag_' ~ tag.id) }}">
|
||||
|
||||
<div class="card bg-black border-secondary">
|
||||
<div class="card-body p-0 row">
|
||||
<div class=" col-lg-6">
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<th style="width:60px;"><i class="bi bi-three-dots"></i></th>
|
||||
<th>Zugewiesene Dokumente</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="card bg-black border-secondary shadow-sm h-100">
|
||||
<div class="card-header bg-secondary-subtle text-dark fw-semibold">
|
||||
Zugewiesene Dokumente
|
||||
</div>
|
||||
|
||||
<tbody>
|
||||
{% for doc in documents %}
|
||||
{% if doc.id in assignedDocIds %}
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
name="documents[]"
|
||||
value="{{ doc.id }}"
|
||||
checked>
|
||||
</td>
|
||||
<td>
|
||||
{{ doc.title }}
|
||||
</td>
|
||||
<th style="width: 60px;">
|
||||
<i class="bi bi-check2-square"></i>
|
||||
</th>
|
||||
<th>Dokument</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set hasAssigned = false %}
|
||||
{% for doc in documents %}
|
||||
{% if doc.id in assignedDocIds %}
|
||||
{% set hasAssigned = true %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
name="documents[]"
|
||||
value="{{ doc.id }}"
|
||||
checked>
|
||||
</td>
|
||||
<td class="fw-semibold">
|
||||
{{ doc.title }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{% if not hasAssigned %}
|
||||
<tr>
|
||||
<td colspan="2" class="text-center text-muted p-4">
|
||||
Noch keine Dokumente zugewiesen.
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" col-lg-6">
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle col-lg-6">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<th style="width:60px;"><i class="bi bi-three-dots"></i></th>
|
||||
<th>Nicht zugewiesene Dokumente</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</div>
|
||||
|
||||
<tbody>
|
||||
{% for doc in documents %}
|
||||
{% if doc.id not in assignedDocIds %}
|
||||
<div class="col-lg-6">
|
||||
<div class="card bg-black border-secondary shadow-sm h-100">
|
||||
<div class="card-header bg-secondary-subtle text-dark fw-semibold">
|
||||
Verfügbare Dokumente
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
name="documents[]"
|
||||
value="{{ doc.id }}"
|
||||
>
|
||||
</td>
|
||||
<td class="opacity-50">
|
||||
{{ doc.title }}
|
||||
</td>
|
||||
<th style="width: 60px;">
|
||||
<i class="bi bi-square"></i>
|
||||
</th>
|
||||
<th>Dokument</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set hasUnassigned = false %}
|
||||
{% for doc in documents %}
|
||||
{% if doc.id not in assignedDocIds %}
|
||||
{% set hasUnassigned = true %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
name="documents[]"
|
||||
value="{{ doc.id }}">
|
||||
</td>
|
||||
<td class="opacity-75">
|
||||
{{ doc.title }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if not hasUnassigned %}
|
||||
<tr>
|
||||
<td colspan="2" class="text-center text-muted p-4">
|
||||
Keine weiteren aktiven Dokumente verfügbar.
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary mt-3">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button class="btn btn-primary">
|
||||
Zuweisungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -4,77 +4,52 @@
|
||||
|
||||
{% block body %}
|
||||
|
||||
{# ========================================================= #}
|
||||
{# LIVE REBUILD STATUS (SSE) #}
|
||||
{# ========================================================= #}
|
||||
|
||||
<div id="rebuild-status" class="mb-5">
|
||||
<div id="rebuild-status" class="mb-4">
|
||||
{% if latestJob %}
|
||||
<div class="alert alert-secondary shadow-sm">
|
||||
<div class="alert alert-secondary shadow-sm mb-0">
|
||||
Status wird geladen…
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0"><i class="bi bi-tag-fill"></i> Tag-Management</h1>
|
||||
<h1 class="h3 mb-0">
|
||||
<i class="bi bi-tag-fill"></i> Tag-Management
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{# ========================================================= #}
|
||||
{# TAG SYSTEM DESCRIPTION #}
|
||||
{# ========================================================= #}
|
||||
<div class="card bg-dark border-secondary text-light mb-4 shadow-sm">
|
||||
<div class="card-body row">
|
||||
<div class="card-body row g-4">
|
||||
<div class="col-lg-6">
|
||||
<h5 class="text-info mb-3">Was machen Tags im System?</h5>
|
||||
|
||||
<p class="small text-light mb-2">
|
||||
Tags dienen als semantische Routing-Ebene innerhalb des RAG-Systems.
|
||||
Sie strukturieren Dokumente thematisch und beeinflussen,
|
||||
welche Inhalte bei einer Nutzeranfrage priorisiert werden.
|
||||
Tags sind die semantische Routing-Ebene innerhalb des Systems.
|
||||
Sie helfen dabei, thematisch passende Dokumenträume schneller zu erkennen
|
||||
und gute Retrieval-Kandidaten zu priorisieren.
|
||||
</p>
|
||||
|
||||
<ul class="small text-light mb-3">
|
||||
<li>
|
||||
Tags werden Dokumenten manuell zugewiesen.
|
||||
</li>
|
||||
<li>
|
||||
Beim Rebuild wird aus allen Tags eine eigene
|
||||
<code>tags.ndjson</code> erzeugt.
|
||||
</li>
|
||||
<li>
|
||||
Zusätzlich wird ein separater Vektorindex
|
||||
(<code>vector_tags.index</code>) aufgebaut.
|
||||
</li>
|
||||
<li>
|
||||
Bei einer Anfrage erfolgt zunächst ein Tag-Matching,
|
||||
danach wird das Chunk-Retrieval entsprechend gewichtet.
|
||||
</li>
|
||||
<ul class="small text-light mb-0">
|
||||
<li>Tags werden Dokumenten manuell zugewiesen.</li>
|
||||
<li>Beim Rebuild wird aus den aktiven Tag-Zuordnungen eine <code>tags.ndjson</code> erzeugt.</li>
|
||||
<li>Zusätzlich wird ein eigener Tag-Vektorindex (<code>vector_tags.index</code>) gebaut.</li>
|
||||
<li>Bei Anfragen erfolgt zunächst ein semantisches Tag-Matching, danach das eigentliche Chunk-Retrieval.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<h6 class="text-info mt-3">Wie werden Tags bewertet?</h6>
|
||||
<h5 class="text-info mb-3">Was ist gutes Tagging?</h5>
|
||||
|
||||
<p class="small text-light mb-2">
|
||||
Die Bewertung erfolgt über einen eigenen Vektor-Similarity-Score
|
||||
im Tag-Index. Das System berechnet:
|
||||
</p>
|
||||
|
||||
<ul class="small text-light">
|
||||
<li>
|
||||
Ähnlichkeit zwischen Nutzeranfrage und Tag-Embedding
|
||||
</li>
|
||||
<li>
|
||||
Top-K Treffer im Tag-Index
|
||||
</li>
|
||||
<li>
|
||||
Gewichtete Übergabe an das Chunk-Retrieval
|
||||
</li>
|
||||
<ul class="small text-light mb-3">
|
||||
<li><strong>Präzise statt generisch:</strong> lieber <code>Produktnamen</code> als <code>Gerät</code>.</li>
|
||||
<li><strong>Fachlich sauber:</strong> Tags sollen echte Produktfamilien, Anwendungsfälle oder Entitäten abbilden.</li>
|
||||
<li><strong>Wenig Überschneidung:</strong> keine unnötig breiten oder doppeldeutigen Tags.</li>
|
||||
<li><strong>Bewusst typisieren:</strong> <code>catalog_entity</code> für echte Katalog-/Entity-Tags, <code>generic</code> nur für allgemeine Zusatzsemantik.</li>
|
||||
</ul>
|
||||
|
||||
<p class="small text-light mt-2 mb-0">
|
||||
Tags wirken somit als semantischer Verstärker.
|
||||
Sie ersetzen kein Chunk-Retrieval, sondern steuern dessen Priorisierung.
|
||||
<p class="small text-warning mb-0">
|
||||
Zu breite Tags wie „Produkt“, „System“ oder „Gerät“ machen das Routing weicher
|
||||
und bringen meist weniger Nutzen als präzise fachliche Tags.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,9 +65,9 @@
|
||||
|
||||
if (data.status === '{{ statusRunning }}') {
|
||||
html = `
|
||||
<div class="alert alert-info shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div class="alert alert-info shadow-sm d-flex justify-content-between align-items-center mb-0">
|
||||
<div>
|
||||
Tag-Rebuild läuft<br>
|
||||
<strong>Tag-Rebuild läuft</strong><br>
|
||||
${data.startedAt ? 'Gestartet: ' + new Date(data.startedAt).toLocaleString() : ''}
|
||||
</div>
|
||||
<div class="spinner-border spinner-border-sm"></div>
|
||||
@@ -100,20 +75,20 @@
|
||||
`;
|
||||
} else if (data.status === '{{ statusQueued }}') {
|
||||
html = `
|
||||
<div class="alert alert-secondary shadow-sm">
|
||||
Tag-Rebuild in Warteschlange
|
||||
<div class="alert alert-secondary shadow-sm mb-0">
|
||||
<strong>Tag-Rebuild in Warteschlange</strong>
|
||||
</div>
|
||||
`;
|
||||
} else if (data.status === '{{ statusCompleted }}') {
|
||||
html = `
|
||||
<div class="alert alert-success shadow-sm">
|
||||
<div class="alert alert-success shadow-sm mb-0">
|
||||
<i class="bi bi-check-lg"></i> Tag-Rebuild erfolgreich abgeschlossen
|
||||
</div>
|
||||
`;
|
||||
} else if (data.status === '{{ statusFailed }}') {
|
||||
html = `
|
||||
<div class="alert alert-danger shadow-sm">
|
||||
Tag-Rebuild fehlgeschlagen<br>
|
||||
<div class="alert alert-danger shadow-sm mb-0">
|
||||
<strong>Tag-Rebuild fehlgeschlagen</strong><br>
|
||||
${data.error ? '<code>' + data.error + '</code>' : ''}
|
||||
</div>
|
||||
`;
|
||||
@@ -125,11 +100,12 @@
|
||||
source.onerror = function () {
|
||||
console.warn('SSE Verbindung verloren');
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
source.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
{# ========================================================= #}
|
||||
{# Create Tag Card #}
|
||||
{# ========================================================= #}
|
||||
<div class="card bg-black border-secondary text-light mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="text-info mb-3">Neuen Tag hinzufügen</h5>
|
||||
@@ -153,24 +129,26 @@
|
||||
required/>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small text-muted">Beschreibung</label>
|
||||
<input class="form-control form-control-sm"
|
||||
name="description"
|
||||
placeholder="Semantische Beschreibung des Tags"
|
||||
required/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<select name="type" class="form-select">
|
||||
<option value="generic">Generic</option>
|
||||
<option value="catalog_entity">Catalog Entity</option>
|
||||
<option value="sales_signal">Sales Signal</option>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted">Typ</label>
|
||||
<select name="type" class="form-select form-select-sm">
|
||||
{% for choiceLabel, choiceValue in tagTypeChoices %}
|
||||
<option value="{{ choiceValue }}"
|
||||
{% if choiceValue == 'generic' %}selected{% endif %}>
|
||||
{{ choiceLabel }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 d-grid align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-muted">Beschreibung</label>
|
||||
<input class="form-control form-control-sm"
|
||||
name="description"
|
||||
placeholder="Optional: fachlicher Kontext des Tags"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-grid d-md-flex justify-content-md-end">
|
||||
<button class="btn btn-sm btn-outline-info">
|
||||
Anlegen
|
||||
</button>
|
||||
@@ -179,66 +157,85 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ========================================================= #}
|
||||
{# Tag Table #}
|
||||
{# ========================================================= #}
|
||||
<div class="card bg-black border-secondary text-light shadow-sm">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="mb-3">
|
||||
<strong class="text-info">Vorhandene Tags:</strong>
|
||||
<span class="text-muted small ms-2">
|
||||
{{ tags|length }} Einträge
|
||||
</span>
|
||||
<div class="mb-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong class="text-info">Vorhandene Tags:</strong>
|
||||
<span class="text-muted small ms-2">
|
||||
{{ tags|length }} Einträge
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted">
|
||||
Dokumentanzahl bezieht sich auf aktive Dokumente.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<th style="width: 25%">Label</th>
|
||||
<th style="width: 25%">Slug</th>
|
||||
<th style="width: 35%">Beschreibung</th>
|
||||
<th class="text-end" style="width: 15%">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tag in tags %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-secondary text-dark">
|
||||
<tr>
|
||||
<td class="fw-semibold">{{ tag.label }}</td>
|
||||
<td><code>{{ tag.slug }}</code></td>
|
||||
<td>{{ tag.description ?: '-' }}</td>
|
||||
<td class="text-end">
|
||||
|
||||
<a href="{{ path('admin_tags_assign', { id: tag.id }) }}"
|
||||
class="btn btn-sm btn-outline-info me-2">
|
||||
Zuweisen
|
||||
</a>
|
||||
|
||||
<form method="post"
|
||||
action="{{ path('admin_tags_delete', {id: tag.id}) }}"
|
||||
style="display:inline-block;">
|
||||
|
||||
<input type="hidden"
|
||||
name="_token"
|
||||
value="{{ csrf_token('admin_tag_delete_' ~ tag.id) }}"/>
|
||||
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Tag wirklich löschen? Zuweisungen werden entfernt.')">
|
||||
Löschen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</td>
|
||||
<th style="width: 18%">Label</th>
|
||||
<th style="width: 18%">Slug</th>
|
||||
<th style="width: 14%">Typ</th>
|
||||
<th style="width: 10%">Aktive Dokumente</th>
|
||||
<th style="width: 25%">Beschreibung</th>
|
||||
<th class="text-end" style="width: 15%">Aktion</th>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="p-4 text-center text-muted">
|
||||
Noch keine Tags vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tag in tags %}
|
||||
{% set activeDocumentCount = documentCountByTagId[tag.id.toRfc4122] ?? 0 %}
|
||||
<tr>
|
||||
<td class="fw-semibold">{{ tag.label }}</td>
|
||||
<td><code>{{ tag.slug }}</code></td>
|
||||
<td>
|
||||
{% if tag.type == 'catalog_entity' %}
|
||||
<span class="badge text-bg-info">Catalog Entity</span>
|
||||
{% elseif tag.type == 'sales_signal' %}
|
||||
<span class="badge text-bg-warning">Sales Signal</span>
|
||||
{% else %}
|
||||
<span class="badge text-bg-secondary">Generic</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge text-bg-dark border border-secondary">
|
||||
{{ activeDocumentCount }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ tag.description ?: '-' }}</td>
|
||||
<td class="text-end">
|
||||
<a href="{{ path('admin_tags_assign', { id: tag.id }) }}"
|
||||
class="btn btn-sm btn-outline-info me-2">
|
||||
Zuweisen
|
||||
</a>
|
||||
|
||||
<form method="post"
|
||||
action="{{ path('admin_tags_delete', {id: tag.id}) }}"
|
||||
style="display:inline-block;">
|
||||
<input type="hidden"
|
||||
name="_token"
|
||||
value="{{ csrf_token('admin_tag_delete_' ~ tag.id) }}"/>
|
||||
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Tag wirklich löschen? Zuweisungen werden entfernt.')">
|
||||
Löschen
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="p-4 text-center text-muted">
|
||||
Noch keine Tags vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user