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()) {