Fix #43: Benutzerfreundliche Fehlermeldungen bei FAILED-Einträgen im Detailbereich

Jeder Fehlertyp erhält jetzt eine präzise deutsche Meldung:
- Kein lesbarer Text (NO_USABLE_TEXT) → OCR-Hinweis
- Titel zu lang → Titeltext + tatsächliche Länge + Limit
- Defekte/nicht extrahierbare PDF → Beschädigungshinweis
- Verbindungsfehler/Timeout → Verbindungs- und Konfigurationshinweis
- Unbekannter Fehler → neutraler Fallback ohne Log-Verweis

Der Verweis auf "Details im Anwendungslog" wurde vollständig entfernt.
Das "Fehler:"-Präfix in buildDetailText() entfällt; bei vorhandener
Fehlermeldung wird NO_REASONING_TEXT unterdrückt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 12:24:50 +02:00
parent 3f5602de01
commit c3f8103572
3 changed files with 188 additions and 32 deletions
@@ -1,13 +1,13 @@
package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;
/**
* Übersetzt technische KI-Fehlermeldungen in benutzerfreundliche deutsche Texte.
* Übersetzt strukturierte Fehlermeldungen aus der Anwendungsschicht in
* benutzerfreundliche deutsche Texte für den Detailbereich des Verarbeitungslauf-Tabs.
* <p>
* Die Klasse wertet die rohe, englischsprachige Fehlermeldung aus dem
* Verarbeitungsversuch aus und liefert eine für den Endbenutzer lesbare
* Beschreibung des Fehlergrunds. Das ursprüngliche Datenmodell bleibt
* unverändert; die Übersetzung findet ausschließlich in der Darstellungsschicht
* statt.
* Die Klasse wertet die englischsprachige Fehlermeldung aus dem Verarbeitungsversuch
* musterbasiert aus und liefert eine für den Endbenutzer lesbare Beschreibung des
* Fehlergrunds. Das ursprüngliche Datenmodell bleibt unverändert; die Übersetzung
* findet ausschließlich in der Darstellungsschicht statt.
* <p>
* Die Mustererkennung erfolgt ohne Berücksichtigung der Groß-/Kleinschreibung
* und prüft die definierten Schlüsselbegriffe in festgelegter Reihenfolge,
@@ -26,14 +26,35 @@ final class AiFailureMessageTranslator {
* Fallback-Text zurückgegeben.
*
* @param technicalMessage die rohe technische Fehlermeldung; darf {@code null} sein
* @return eine nicht-leere deutsche Benutzerfehlermeldung
* @return eine nicht-leere deutsche Benutzerfehlermeldung ohne führendes Warnsymbol
*/
static String translate(String technicalMessage) {
if (technicalMessage == null || technicalMessage.isBlank()) {
return "KI-Aufruf fehlgeschlagen. Details im Anwendungslog.";
return "Verarbeitung fehlgeschlagen. Bitte Konfiguration prüfen und ggf. erneut verarbeiten.";
}
String lower = technicalMessage.toLowerCase(java.util.Locale.ROOT);
// Pre-Check-Fehler: kein lesbarer Text im PDF
if (lower.contains("no usable text")) {
return "PDF enthält keinen lesbaren Text. Möglicherweise handelt es sich um einen Scan"
+ " ohne Texterkennung (OCR). Eine automatische Benennung ist nicht möglich.";
}
// KI-Validierungsfehler: Titel überschreitet die konfigurierte Maximallänge
if (lower.contains("title exceeds")) {
return buildTitleExceedsMessage(technicalMessage);
}
// Defekte oder strukturell nicht lesbare PDF-Datei
if (lower.contains("content not extractable")
|| lower.contains("ioexception")
|| lower.contains("end of file")
|| lower.contains("endoffileexception")
|| lower.contains("eof")) {
return "Die PDF-Datei ist ungültig oder beschädigt und kann nicht verarbeitet werden.";
}
// HTTP-Authentifizierungsfehler
if (lower.contains("http_401")) {
return "KI-Dienst: Ungültiger API-Schlüssel. Bitte in den Einstellungen prüfen.";
}
@@ -46,9 +67,51 @@ final class AiFailureMessageTranslator {
if (lower.contains("http_5")) {
return "KI-Dienst vorübergehend nicht erreichbar. Bitte später erneut versuchen.";
}
// Netzwerk- und Verbindungsfehler
if (lower.contains("connection") || lower.contains("timeout") || lower.contains("refused")) {
return "KI-Dienst nicht erreichbar. Bitte Netzwerkverbindung prüfen.";
return "KI-Dienst nicht erreichbar. Bitte Verbindung und Konfiguration prüfen.";
}
return "KI-Aufruf fehlgeschlagen. Details im Anwendungslog.";
return "Verarbeitung fehlgeschlagen. Bitte Konfiguration prüfen und ggf. erneut verarbeiten.";
}
/**
* Baut aus einer „Title exceeds"-Fehlermeldung einen benutzerfreundlichen Text,
* der Titel, tatsächliche Länge und konfiguriertes Limit nennt.
* <p>
* Erwartet wird das Format:
* {@code … Title exceeds N characters (base title): 'Titel' …}
* <p>
* Kann das Format nicht geparst werden, wird ein generischer Hinweis zurückgegeben.
*
* @param technicalMessage die vollständige technische Fehlermeldung
* @return benutzerfreundlicher Hinweis auf den zu langen Titel
*/
private static String buildTitleExceedsMessage(String technicalMessage) {
try {
int exceedsIdx = technicalMessage.indexOf("Title exceeds ");
if (exceedsIdx >= 0) {
String afterExceeds = technicalMessage.substring(exceedsIdx + "Title exceeds ".length());
int charIdx = afterExceeds.indexOf(" characters");
if (charIdx > 0) {
int limit = Integer.parseInt(afterExceeds.substring(0, charIdx).trim());
int colonQuote = technicalMessage.indexOf(": '", exceedsIdx);
if (colonQuote >= 0) {
String afterQuote = technicalMessage.substring(colonQuote + 3);
int closingQuote = afterQuote.lastIndexOf("'");
if (closingQuote > 0) {
String title = afterQuote.substring(0, closingQuote);
return "KI-Vorschlag abgelehnt: '" + title + "' ist zu lang ("
+ title.length() + " Zeichen, Limit: " + limit
+ "). Bitte Dateinamen manuell kürzen.";
}
}
}
}
} catch (NumberFormatException | StringIndexOutOfBoundsException ignored) {
// Fallback unten
}
return "KI-Vorschlag abgelehnt: Titel überschreitet die maximale Länge. Bitte Dateinamen manuell kürzen.";
}
}
@@ -1277,13 +1277,9 @@ public final class GuiBatchRunTab {
builder.append('\n');
row.aiReasoning().ifPresentOrElse(
reasoning -> builder.append(reasoning),
() -> {
row.aiFailureMessage().ifPresent(msg ->
builder.append("\u26A0 Fehler: ")
.append(AiFailureMessageTranslator.translate(msg))
.append("\n\n"));
builder.append(NO_REASONING_TEXT);
});
() -> row.aiFailureMessage().ifPresentOrElse(
msg -> builder.append("\u26A0 ").append(AiFailureMessageTranslator.translate(msg)),
() -> builder.append(NO_REASONING_TEXT)));
return builder.toString();
}