KI-AGENT: Mailantworten und Entitätsverknüpfungen ergänzen

This commit is contained in:
2026-09-07 19:00:08 +02:00
parent 4e4466ff11
commit f3384cfc74
10 changed files with 774 additions and 30 deletions

View File

@@ -21,6 +21,55 @@ const preloadedContent = ref("")
const loadedDocuments = ref([])
const loaded = ref(false)
const noAccountsPresent = ref(false)
const sourceMessage = ref(null)
const composeMode = computed(() => {
const mode = String(route.query.mode || "")
return ["reply", "replyAll", "forward"].includes(mode) ? mode : "new"
})
const pageTitle = computed(() => ({
reply: "Antworten",
replyAll: "Allen antworten",
forward: "E-Mail weiterleiten",
}[composeMode.value] || "Neue E-Mail"))
const escapeHtml = (value = "") => String(value)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
const formatAddress = (address) => {
if (!address?.address) return ""
return address.name ? `"${String(address.name).replace(/"/g, "\\\"")}" <${address.address}>` : address.address
}
const formatAddressList = (addresses = []) => addresses.map(formatAddress).filter(Boolean).join(", ")
const prefixedSubject = (prefix, subject = "") => {
const cleanSubject = String(subject || "")
return cleanSubject.toLowerCase().startsWith(prefix.toLowerCase()) ? cleanSubject : `${prefix} ${cleanSubject}`.trim()
}
const originalMessageBlock = (message, mode) => {
const sentAt = message.sentAt || message.receivedAt
const date = sentAt ? new Intl.DateTimeFormat("de-DE", {
dateStyle: "full",
timeStyle: "short",
}).format(new Date(sentAt)) : ""
const originalText = escapeHtml(message.body?.text || message.preview || "").replace(/\n/g, "<br>")
if (mode === "forward") {
return `<hr><p><strong>Weitergeleitete Nachricht</strong><br>`
+ `Von: ${escapeHtml(formatAddressList(message.from))}<br>`
+ `Datum: ${escapeHtml(date)}<br>`
+ `Betreff: ${escapeHtml(message.subject || "(kein Betreff)")}<br>`
+ `An: ${escapeHtml(formatAddressList(message.to))}</p><blockquote>${originalText}</blockquote>`
}
return `<hr><blockquote><p>Am ${escapeHtml(date)} schrieb ${escapeHtml(formatAddressList(message.from))}:</p>${originalText}</blockquote>`
}
const setupPage = async () => {
//emailAccounts.value = await useEntities("emailAccounts").select()
@@ -31,7 +80,41 @@ const setupPage = async () => {
} else {
emailData.value.account = emailAccounts.value[0].id
preloadedContent.value = `<p></p><p></p><p></p>${auth.profile.email_signature || ""}`
let initialContent = `<p></p><p></p><p></p>${auth.profile.email_signature || ""}`
if (route.query.source && composeMode.value !== "new") {
sourceMessage.value = await useNuxtApp().$api(`/api/email/messages/${route.query.source}`)
const sourceAccount = emailAccounts.value.find(account => account.id === sourceMessage.value.accountId)
if (sourceAccount) emailData.value.account = sourceAccount.id
if (composeMode.value === "forward") {
emailData.value.subject = prefixedSubject("Fw:", sourceMessage.value.subject)
} else {
const ownAddress = String(sourceAccount?.email || "").toLowerCase()
const replyRecipients = sourceMessage.value.replyTo?.length
? sourceMessage.value.replyTo
: sourceMessage.value.from || []
if (composeMode.value === "replyAll") {
const allRecipients = [
...replyRecipients,
...(sourceMessage.value.to || []),
...(sourceMessage.value.cc || []),
].filter(address => address?.address && address.address.toLowerCase() !== ownAddress)
const uniqueRecipients = Array.from(new Map(
allRecipients.map(address => [address.address.toLowerCase(), address])
).values())
emailData.value.to = formatAddressList(uniqueRecipients)
} else {
emailData.value.to = formatAddressList(replyRecipients)
}
emailData.value.subject = prefixedSubject("Re:", sourceMessage.value.subject)
}
initialContent += originalMessageBlock(sourceMessage.value, composeMode.value)
}
preloadedContent.value = initialContent
//Check Query
if(route.query.to) emailData.value.to = route.query.to
@@ -120,7 +203,9 @@ const sendEmail = async () => {
let body = {
...emailData.value,
attachments: []
attachments: [],
sourceMessageId: sourceMessage.value?.id,
composeMode: composeMode.value === "new" ? undefined : composeMode.value,
}
@@ -151,26 +236,28 @@ const sendEmail = async () => {
})
}
console.log(body)
try {
const res = await useNuxtApp().$api("/api/email/send",{
method: "POST",
body: body,
})
const res = await useNuxtApp().$api("/api/email/send",{
method: "POST",
body: body,
})
console.log(res)
if(!res.success) {
toast.add({title: "Fehler beim Absenden der E-Mail", color: "error"})
} else {
navigateTo("/")
toast.add({title: "E-Mail zum Senden eingereiht"})
if(!res.success) {
toast.add({title: "Fehler beim Absenden der E-Mail", color: "error"})
} else {
await navigateTo("/email")
toast.add({title: "E-Mail gesendet", color: "success"})
}
} catch (err) {
toast.add({
title: "Fehler beim Absenden der E-Mail",
description: err?.data?.error || err?.message,
color: "error",
})
} finally {
loaded.value = true
}
loaded.value = true
}
@@ -192,7 +279,7 @@ const sendEmail = async () => {
<div v-else>
<UDashboardNavbar
title="Neue E-Mail"
:title="pageTitle"
>
<template #right>
<UButton
@@ -280,6 +367,12 @@ const sendEmail = async () => {
>
<span v-if="doc.createddocument">Dokument - {{doc.createddocument.documentNumber}}</span>
</li>
<li
v-if="composeMode === 'forward' && sourceMessage?.attachments?.length"
class="list-disc"
>
{{ sourceMessage.attachments.length }} Originalanhang/Originalanhänge werden übernommen
</li>
</ul>
</div>