Erweiterungen

This commit is contained in:
2026-02-08 12:52:57 +01:00
parent 860ca1a09e
commit 4c41e165d5
6 changed files with 89 additions and 37 deletions

View File

@@ -1,30 +1,4 @@
FROM ghcr.io/jqlang/jq:latest AS jq-stage FROM eclipse-temurin:21-jre
COPY target/*.jar app.jar
FROM eclipse-temurin:21-jdk AS build EXPOSE 8080
COPY --from=jq-stage /jq /usr/bin/jq ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=production"]
# 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"]

View File

@@ -4,7 +4,7 @@
<!-- Project from https://start.vaadin.com/65cb55a1-ddc8-4889-a49a-7208c5f943e7 --> <!-- Project from https://start.vaadin.com/65cb55a1-ddc8-4889-a49a-7208c5f943e7 -->
<groupId>de.assecutor.aimailassistant</groupId> <groupId>de.assecutor.aimailassistant</groupId>
<artifactId>aimailassistant</artifactId> <artifactId>aimailassistant</artifactId>
<version>1.0-SNAPSHOT</version> <version>0.8.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View File

@@ -33,6 +33,8 @@ public class OrderEmail {
private boolean deleted; private boolean deleted;
private boolean analyzing;
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String summaryJson; private String summaryJson;
@@ -117,6 +119,14 @@ public class OrderEmail {
this.deleted = deleted; this.deleted = deleted;
} }
public boolean isAnalyzing() {
return analyzing;
}
public void setAnalyzing(boolean analyzing) {
this.analyzing = analyzing;
}
public String getSummaryJson() { public String getSummaryJson() {
return summaryJson; return summaryJson;
} }

View File

@@ -304,23 +304,31 @@ public class ImapEmailService {
orderEmail.setFromAddress("unknown@unknown.com"); orderEmail.setFromAddress("unknown@unknown.com");
} }
// Save first to get ID // Save first with analyzing=true
orderEmail.setAnalyzing(true);
orderEmail = orderEmailRepository.save(orderEmail); orderEmail = orderEmailRepository.save(orderEmail);
// Notify UI about new email (shows "Analyse läuft")
EmailBroadcaster.broadcast();
// Process with LLM // Process with LLM
try { try {
OrderSummary summary = llmService.summarizeEmail(orderEmail); OrderSummary summary = llmService.summarizeEmail(orderEmail);
orderEmail.setSummaryJson(llmService.serializeSummary(summary)); orderEmail.setSummaryJson(llmService.serializeSummary(summary));
orderEmail.setType(summary.getOrderType()); orderEmail.setType(summary.getOrderType());
orderEmail.setAnalyzing(false);
// processed bleibt false - wird erst gesetzt wenn der Benutzer die Email öffnet // processed bleibt false - wird erst gesetzt wenn der Benutzer die Email öffnet
orderEmailRepository.save(orderEmail); orderEmailRepository.save(orderEmail);
log.info("Email processed successfully: {} (Type: {})", subject, summary.getOrderType()); log.info("Email processed successfully: {} (Type: {})", subject, summary.getOrderType());
// Notify UI about new email // Notify UI that analysis is complete
EmailBroadcaster.broadcast(); EmailBroadcaster.broadcast();
} catch (Exception e) { } catch (Exception e) {
log.error("Error processing email with LLM: {}", subject, 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(); EmailBroadcaster.broadcast();
} }

View File

@@ -186,7 +186,11 @@ public class MainView extends VerticalLayout {
String bgClass; String bgClass;
String textClass; 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"; statusText = "Bearbeitet";
bgClass = LumoUtility.Background.SUCCESS_10; bgClass = LumoUtility.Background.SUCCESS_10;
textClass = LumoUtility.TextColor.SUCCESS; textClass = LumoUtility.TextColor.SUCCESS;
@@ -248,6 +252,12 @@ public class MainView extends VerticalLayout {
} }
private void openDetailDialog(OrderEmail email) { 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( OrderDetailDialog dialog = new OrderDetailDialog(
email, email,
llmService, llmService,

View File

@@ -1063,14 +1063,17 @@ public class OrderDetailDialog extends Dialog {
// Read-only mode: only show close and delete buttons // Read-only mode: only show close and delete buttons
getFooter().add(deleteButton, spacer, closeButton); getFooter().add(deleteButton, spacer, closeButton);
} else { } else {
Button reanalyzeButton = new Button("Erneut analysieren", new Icon(VaadinIcon.REFRESH), e -> reanalyzeEmail());
reanalyzeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
if (orderEmail.getType() == EmailType.QUOTE_REQUEST) { if (orderEmail.getType() == EmailType.QUOTE_REQUEST) {
Button sendOfferButton = new Button("Angebot senden", e -> sendOffer()); Button sendOfferButton = new Button("Angebot senden", e -> sendOffer());
sendOfferButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); sendOfferButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
getFooter().add(deleteButton, spacer, closeButton, sendOfferButton); getFooter().add(deleteButton, reanalyzeButton, spacer, closeButton, sendOfferButton);
} else { } else {
Button acceptButton = new Button("Auftrag annehmen", e -> acceptOrder()); Button acceptButton = new Button("Auftrag annehmen", e -> acceptOrder());
acceptButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); 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() { private void sendOffer() {
// Validierung prüfen // Validierung prüfen
if (binder != null && !binder.isValid()) { if (binder != null && !binder.isValid()) {