optimize ui

add new ki endpoint params
This commit is contained in:
team2
2026-02-17 20:36:47 +01:00
parent 5b2a633a99
commit 6822c8f3f8
23 changed files with 1915 additions and 608 deletions

View File

@@ -3,53 +3,73 @@
{% block title %}Dokumente{% endblock %}
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Dokumente</h1>
<a href="{{ path('admin_document_new') }}" class="btn btn-sm btn-light">
+ Neues Dokument
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">Dokumente</h1>
<a href="{{ path('admin_document_new') }}"
class="btn btn-sm btn-outline-info">
Neues Dokument
</a>
</div>
{% if documents is empty %}
<div class="alert alert-secondary">
Keine Dokumente vorhanden.
</div>
{% else %}
<div class="card bg-black text-info border-secondary">
<div class="card bg-black border-secondary">
<div class="card-body p-0">
<table class="table table-dark table-hover mb-0 align-middle">
<thead>
<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>Indexiert</th>
<th>Indexierung</th>
<th>Versionen</th>
<th>Aktive Version</th>
<th>Erstellt am</th>
<th>Aktionen</th>
<th>Erstellt</th>
<th class="text-end">Aktionen</th>
</tr>
</thead>
<tbody>
{% for document in documents %}
<tr>
{# Titel #}
<td>
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="text-decoration-none text-light">
class="text-light text-decoration-none">
{{ document.title }}
</a>
</td>
<td>{{ document.id }}</td>
{# ID #}
<td class="small text-secondary">
{{ document.id }}
</td>
{# Typ #}
<td>
{% if document.currentVersion %}
<span class="badge bg-secondary">
{{ document.currentVersion.fileTypeLabel }}
</span>
{{ document.currentVersion.fileTypeLabel }}
</span>
{% else %}
<span class="badge bg-dark">-</span>
<span class="badge bg-dark border border-secondary">
-
</span>
{% endif %}
</td>
{# Dokument Status #}
<td>
{% if document.status == 'ACTIVE' %}
<span class="badge bg-success">Aktiv</span>
@@ -57,18 +77,32 @@
<span class="badge bg-secondary">Archiviert</span>
{% endif %}
</td>
{# Ingest Status #}
<td>
{% if document.currentVersion.ingestStatus == 'INDEXED' %}
<span class="badge bg-success">
{{ document.currentVersion.ingestStatus }}
</span>
{% 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>
{% else %}
<span class="badge bg-dark border border-secondary">
{{ document.currentVersion.ingestStatus }}
</span>
{% endif %}
{% else %}
<span class="badge bg-danger">
{{ document.currentVersion.ingestStatus }}
</span>
<span class="badge bg-dark border border-secondary">-</span>
{% endif %}
</td>
<td>{{ document.versions|length }}</td>
{# Version Count #}
<td>
{{ document.versions|length }}
</td>
{# Aktive Version #}
<td>
{% if document.currentVersion %}
v{{ document.currentVersion.versionNumber }}
@@ -76,33 +110,52 @@
-
{% endif %}
</td>
<td>{{ document.createdAt|date('d.m.Y H:i') }}</td>
<td class="d-flex gap-2">
<a class="btn btn-sm btn-outline-light"
{# Created At #}
<td class="small">
{{ document.createdAt|date('d.m.Y H:i') }}
</td>
{# Aktionen #}
<td class="text-end">
<a class="btn btn-sm btn-outline-light me-2"
href="{{ path('admin_document_show', {id: document.id}) }}">
Details
</a>
<form method="post"
action="{{ path('admin_document_delete', {id: document.id}) }}"
onsubmit="return confirm('Dokument wirklich endgültig löschen? Diese Aktion entfernt das Dokument aus Datenbank und Index.');">
{% 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.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_document') }}">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_document_' ~ document.id) }}">
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</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>
{% endblock %}

View File

@@ -3,24 +3,84 @@
{% block title %}Neues Dokument{% endblock %}
{% block body %}
<h1 class="h4 mb-4">Neues Dokument</h1>
<form method="post" enctype="multipart/form-data">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">Neues Dokument</h1>
<a href="{{ path('admin_documents') }}"
class="btn btn-sm btn-outline-secondary">
Zurück zur Übersicht
</a>
</div>
<div class="card bg-black border-secondary text-light">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<input type="hidden"
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).
</div>
<input class="form-control bg-dark text-light border-secondary"
name="title"
placeholder="z. B. Sicherheitsdatenblatt Produkt XY">
</div>
{# ============================= #}
{# Datei Upload #}
{# ============================= #}
<div class="mb-4">
<label class="form-label">Datei</label>
<input type="file"
class="form-control bg-dark text-light border-secondary"
name="file"
required>
<div class="form-text text-secondary">
Unterstützte Formate: PDF, DOCX, TXT, MD.
Das Dokument wird versioniert gespeichert und anschließend
indexiert.
</div>
</div>
{# ============================= #}
{# Submit #}
{# ============================= #}
<div class="d-flex justify-content-end">
<button class="btn btn-outline-info">
Dokument speichern
</button>
</div>
</form>
<div class="mb-3">
<label class="form-label">Titel:</label>
<div class="mb-2"><b>Bitte geben Sie einen aussagekräftigen Titel ein.</b><br>
Der Titel ist entscheidend, damit in jedem Chunk ein sinnvoller thematischer Bezug hergestellt und eine saubere semantische Zuordnung ermöglicht werden kann.<br>
Wenn kein Titel angegeben wird, wird automatisch der Dateiname als Titel verwendet (nicht empfohlen).</div>
<input class="form-control" name="title">
</div>
</div>
<div class="mb-3">
<label class="form-label">Datei:</label>
<input type="file" class="form-control" name="file" required>
</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.
</div>
<button class="btn btn-light">Speichern</button>
</form>
{% endblock %}

View File

@@ -1,29 +1,86 @@
{% extends 'admin/base.html.twig' %}
{% block title %}Neue Version{% endblock %}
{% block title %}Neue Dokumentversion{% endblock %}
{% block body %}
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="btn btn-sm btn-outline-light mb-3">
← Zurück
</a>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
Neue Version
</h1>
<h1 class="h4 mb-4">
Neue Version für: {{ document.title }}
</h1>
<a href="{{ path('admin_document_show', {id: document.id}) }}"
class="btn btn-sm btn-outline-secondary">
Zurück zum Dokument
</a>
</div>
<form method="post" enctype="multipart/form-data">
<div class="card bg-black border-secondary mb-4 text-light">
<div class="card-body">
<div class="mb-3">
<strong>Dokument:</strong>
<span class="text-light">{{ document.title }}</span>
</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="mb-3">
<label class="form-label">Datei auswählen</label>
<input type="file" class="form-control" name="file" required>
</div>
</div>
<button class="btn btn-light">
Version hochladen
</button>
<div class="card bg-black border-secondary text-light">
<div class="card-body">
</form>
<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>
<input type="file"
class="form-control bg-dark text-light border-secondary"
name="file"
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.
</div>
</div>
{# ============================= #}
{# Submit #}
{# ============================= #}
{% if is_granted('ROLE_SUPER_ADMIN') %}
<div class="d-flex justify-content-end">
<button class="btn btn-outline-info">
Version hochladen
</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.
</div>
{% endblock %}

View File

@@ -4,14 +4,22 @@
{% block body %}
<a href="{{ path('admin_documents') }}" class="btn btn-sm btn-outline-light mb-3">
← Zurück
</a>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{{ document.title }}</h1>
<a href="{{ path('admin_documents') }}"
class="btn btn-sm btn-outline-secondary">
Zurück zur Übersicht
</a>
</div>
{% if document %}
<h1 class="h4 mb-3">{{ document.title }}</h1>
<div class="card bg-black text-info border-secondary mb-4">
{# ============================= #}
{# Dokument-Meta #}
{# ============================= #}
<div class="card bg-black border-secondary mb-5 text-light">
<div class="card-body">
<div class="mb-2">
@@ -25,7 +33,7 @@
<div class="mb-2">
<strong>Erstellt von:</strong>
{{ document.createdBy.email }}
{{ document.createdBy ? document.createdBy.email : '-' }}
</div>
<div class="mb-2">
@@ -36,7 +44,9 @@
<div class="mb-2">
<strong>Aktive Version:</strong>
{% if document.currentVersion %}
v{{ document.currentVersion.versionNumber }}
<span class="badge bg-info text-dark">
v{{ document.currentVersion.versionNumber }}
</span>
{% else %}
-
{% endif %}
@@ -45,43 +55,65 @@
</div>
</div>
<h2 class="h5 mb-3">Versionen</h2>
<a href="{{ path('admin_document_version_new', {id: document.id}) }}"
class="btn btn-sm btn-light mb-3">
+ Neue Version
</a>
{# ============================= #}
{# 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 %}
</div>
{% if document.versions is empty %}
<div class="alert alert-secondary">
Keine Versionen vorhanden.
</div>
{% else %}
<div class="card bg-black text-info border-secondary">
<div class="card bg-black border-secondary">
<div class="card-body p-0">
<table class="table table-dark table-hover mb-0">
<thead>
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead class="table-secondary text-dark">
<tr>
<th>Version</th>
<th>Aktiv</th>
<th>Status</th>
<th>Ingest</th>
<th>Checksum</th>
<th>Erstellt von</th>
<th>Datum</th>
<th>Aktion</th>
<th class="text-end">Aktionen</th>
</tr>
</thead>
<tbody>
{% for version in document.versions %}
<tr>
<td>v{{ version.versionNumber }}</td>
{% for version in document.versions %}
<tr>
<td>
<strong>v{{ version.versionNumber }}</strong>
</td>
{# Aktivstatus #}
<td>
{% if version.isActive %}
<span class="badge bg-success">Ja</span>
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-secondary">Nein</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>
@@ -89,69 +121,103 @@
<span class="badge bg-warning text-dark">RUNNING</span>
{% elseif version.ingestStatus == 'FAILED' %}
<span class="badge bg-danger">FAILED</span>
{% else %}
{% elseif version.ingestStatus == 'PENDING' %}
<span class="badge bg-secondary">PENDING</span>
{% else %}
<span class="badge bg-dark border border-secondary">
{{ version.ingestStatus }}
</span>
{% endif %}
</td>
<td>
{{ version.checksum[:10] }}...
{# Checksum #}
<td class="small text-secondary">
{{ version.checksum ? version.checksum[:10] ~ '…' : '-' }}
</td>
{# Created by #}
<td>
{{ version.createdBy.email }}
{{ version.createdBy ? version.createdBy.email : '-' }}
</td>
<td>
{# Date #}
<td class="small">
{{ version.createdAt|date('d.m.Y H:i') }}
</td>
<td>
{# Aktionen #}
<td class="text-end">
{% if version.isActive %}
{# Optional: manuelles Re-Ingest nur bei PENDING/FAILED #}
{% if version.ingestStatus in ['PENDING', 'FAILED'] %}
{% if version.ingestStatus in ['PENDING', 'FAILED'] and is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_document_version_ingest', {versionId: version.id}) }}"
style="display:inline;">
<input type="hidden" name="_token"
value="{{ csrf_token('ingest_version') }}">
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="text-success">Ingested</span>
<span class="text-success small">
Bereits indexiert
</span>
{% endif %}
{% else %}
<form method="post"
action="{{ path('admin_document_version_activate', {versionId: version.id}) }}"
style="display:inline;">
<input type="hidden" name="_token"
value="{{ csrf_token('activate_version') }}">
<button class="btn btn-sm btn-outline-light">
Aktivieren
</button>
</form>
{% 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 %}
</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 %}
<h1 class="h4 mb-3">Ein Fehler trat auf</h1>
<h2 class="h5 mb-3">Fehler:</h2>
{% for message in app.flashes('danger') %}
<div class="alert alert-danger">
{{ message }}
</div>
{% endfor %}
<div class="alert alert-danger">
Dokument nicht gefunden.
</div>
{% endif %}
{% endblock %}
{% endblock %}