From 4c41e165d5806447ebf4ce2afb2fe15b8dd995d0 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Sun, 8 Feb 2026 12:52:57 +0100 Subject: [PATCH] Erweiterungen --- Dockerfile | 34 ++---------- pom.xml | 2 +- .../mail/domain/OrderEmail.java | 10 ++++ .../mail/service/ImapEmailService.java | 14 +++-- .../aimailassistant/mail/ui/MainView.java | 12 ++++- .../mail/ui/OrderDetailDialog.java | 54 ++++++++++++++++++- 6 files changed, 89 insertions(+), 37 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6b1484f..608c3bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,4 @@ -FROM ghcr.io/jqlang/jq:latest AS jq-stage - -FROM eclipse-temurin:21-jdk AS build -COPY --from=jq-stage /jq /usr/bin/jq -# Test that jq works after copying -RUN jq --version - -ENV HOME=/app -RUN mkdir -p $HOME -WORKDIR $HOME -COPY . $HOME - -# If you have a Vaadin Pro key, pass it as a secret with id "proKey": -# -# $ docker build --secret id=proKey,src=$HOME/.vaadin/proKey . -# -# If you have a Vaadin Offline key, pass it as a secret with id "offlineKey": -# -# $ docker build --secret id=offlineKey,src=$HOME/.vaadin/offlineKey . - -RUN --mount=type=cache,target=/root/.m2 \ - --mount=type=secret,id=proKey \ - --mount=type=secret,id=offlineKey \ - sh -c 'PRO_KEY=$(jq -r ".proKey // empty" /run/secrets/proKey 2>/dev/null || echo "") && \ - OFFLINE_KEY=$(cat /run/secrets/offlineKey 2>/dev/null || echo "") && \ - ./mvnw clean package -DskipTests -Dvaadin.proKey=${PRO_KEY} -Dvaadin.offlineKey=${OFFLINE_KEY}' - -FROM eclipse-temurin:21-jre-alpine -COPY --from=build /app/target/*.jar app.jar -ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=prod"] +FROM eclipse-temurin:21-jre +COPY target/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=production"] diff --git a/pom.xml b/pom.xml index a72222d..a01039f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.assecutor.aimailassistant aimailassistant - 1.0-SNAPSHOT + 0.8.0 jar diff --git a/src/main/java/de/assecutor/aimailassistant/mail/domain/OrderEmail.java b/src/main/java/de/assecutor/aimailassistant/mail/domain/OrderEmail.java index a28c27f..491eded 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/domain/OrderEmail.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/domain/OrderEmail.java @@ -33,6 +33,8 @@ public class OrderEmail { private boolean deleted; + private boolean analyzing; + @Column(columnDefinition = "TEXT") private String summaryJson; @@ -117,6 +119,14 @@ public class OrderEmail { this.deleted = deleted; } + public boolean isAnalyzing() { + return analyzing; + } + + public void setAnalyzing(boolean analyzing) { + this.analyzing = analyzing; + } + public String getSummaryJson() { return summaryJson; } diff --git a/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java b/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java index ff8638a..3a780f5 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java @@ -304,23 +304,31 @@ public class ImapEmailService { orderEmail.setFromAddress("unknown@unknown.com"); } - // Save first to get ID + // Save first with analyzing=true + orderEmail.setAnalyzing(true); orderEmail = orderEmailRepository.save(orderEmail); + // Notify UI about new email (shows "Analyse läuft") + EmailBroadcaster.broadcast(); + // Process with LLM try { OrderSummary summary = llmService.summarizeEmail(orderEmail); orderEmail.setSummaryJson(llmService.serializeSummary(summary)); orderEmail.setType(summary.getOrderType()); + orderEmail.setAnalyzing(false); // processed bleibt false - wird erst gesetzt wenn der Benutzer die Email öffnet orderEmailRepository.save(orderEmail); log.info("Email processed successfully: {} (Type: {})", subject, summary.getOrderType()); - // Notify UI about new email + // Notify UI that analysis is complete EmailBroadcaster.broadcast(); } catch (Exception e) { log.error("Error processing email with LLM: {}", subject, e); - // Email is saved but not processed - still notify UI + // Mark as no longer analyzing even on error + orderEmail.setAnalyzing(false); + orderEmailRepository.save(orderEmail); + // Notify UI EmailBroadcaster.broadcast(); } diff --git a/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java b/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java index 0f64e04..2fb3a28 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java @@ -186,7 +186,11 @@ public class MainView extends VerticalLayout { String bgClass; String textClass; - if (email.isConfirmed()) { + if (email.isAnalyzing()) { + statusText = "Analyse läuft"; + bgClass = LumoUtility.Background.CONTRAST_10; + textClass = LumoUtility.TextColor.SECONDARY; + } else if (email.isConfirmed()) { statusText = "Bearbeitet"; bgClass = LumoUtility.Background.SUCCESS_10; textClass = LumoUtility.TextColor.SUCCESS; @@ -248,6 +252,12 @@ public class MainView extends VerticalLayout { } private void openDetailDialog(OrderEmail email) { + if (email.isAnalyzing()) { + Notification.show("Die Email wird noch analysiert. Bitte warten Sie, bis die Analyse abgeschlossen ist.", + 3000, Notification.Position.MIDDLE); + return; + } + OrderDetailDialog dialog = new OrderDetailDialog( email, llmService, diff --git a/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java b/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java index 63f0cf8..aa69fcb 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java @@ -1063,14 +1063,17 @@ public class OrderDetailDialog extends Dialog { // Read-only mode: only show close and delete buttons getFooter().add(deleteButton, spacer, closeButton); } else { + Button reanalyzeButton = new Button("Erneut analysieren", new Icon(VaadinIcon.REFRESH), e -> reanalyzeEmail()); + reanalyzeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + if (orderEmail.getType() == EmailType.QUOTE_REQUEST) { Button sendOfferButton = new Button("Angebot senden", e -> sendOffer()); sendOfferButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - getFooter().add(deleteButton, spacer, closeButton, sendOfferButton); + getFooter().add(deleteButton, reanalyzeButton, spacer, closeButton, sendOfferButton); } else { Button acceptButton = new Button("Auftrag annehmen", e -> acceptOrder()); acceptButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - getFooter().add(deleteButton, spacer, closeButton, acceptButton); + getFooter().add(deleteButton, reanalyzeButton, spacer, closeButton, acceptButton); } } } @@ -1115,6 +1118,53 @@ public class OrderDetailDialog extends Dialog { } } + private void reanalyzeEmail() { + Notification loadingNotification = Notification.show( + "Email wird erneut analysiert...", + 0, + Notification.Position.MIDDLE + ); + + getUI().ifPresent(ui -> { + new Thread(() -> { + try { + OrderSummary newSummary = llmService.summarizeEmail(orderEmail); + + ui.access(() -> { + loadingNotification.close(); + + if (newSummary != null) { + summary = newSummary; + orderEmail.setSummaryJson(llmService.serializeSummary(summary)); + orderEmail.setType(summary.getOrderType()); + onProcessed.accept(orderEmail); + + // UI komplett neu aufbauen + remove(contentLayout); + getFooter().removeAll(); + contentLayout = createContent(); + add(contentLayout); + createFooter(); + + Notification.show("Analyse abgeschlossen", 3000, Notification.Position.BOTTOM_START); + } else { + Notification.show("Analyse fehlgeschlagen", 3000, Notification.Position.MIDDLE); + } + }); + } catch (Exception e) { + ui.access(() -> { + loadingNotification.close(); + Notification.show( + "Fehler bei der Analyse: " + e.getMessage(), + 5000, + Notification.Position.MIDDLE + ); + }); + } + }).start(); + }); + } + private void sendOffer() { // Validierung prüfen if (binder != null && !binder.isValid()) {