feat: "Als Email senden" Button im Compliance Advisor

Chat-Verlauf wird als strukturiertes Beratungsprotokoll per Email
an den DSB gesendet. Button erscheint im Header sobald Nachrichten
vorhanden sind. Zeigt Checkmark nach erfolgreichem Versand.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-28 23:17:13 +02:00
parent 3c9ac03ccc
commit ac8eb1bf99
2 changed files with 103 additions and 0 deletions
@@ -141,6 +141,54 @@ export function ComplianceAdvisorWidget({ currentStep = 'default' }: ComplianceA
setIsTyping(false)
}, [])
const [emailSending, setEmailSending] = useState(false)
const [emailSent, setEmailSent] = useState(false)
const handleSendAsEmail = useCallback(async () => {
if (messages.length === 0 || emailSending) return
setEmailSending(true)
try {
// Build HTML from chat messages
const qaPairs = messages.reduce<{ q: string; a: string }[]>((acc, m, i) => {
if (m.role === 'user') {
const next = messages[i + 1]
acc.push({ q: m.content, a: next?.role === 'agent' ? next.content : '(keine Antwort)' })
}
return acc
}, [])
const qaHtml = qaPairs.map(({ q, a }) =>
`<div style="margin-bottom:16px;"><p style="font-weight:600;color:#1e293b;">Frage: ${q}</p><p style="color:#475569;white-space:pre-wrap;">${a}</p></div>`
).join('')
const bodyHtml = `
<h2 style="color:#1e293b;">Compliance Advisor — Beratungsprotokoll</h2>
<p style="color:#64748b;font-size:13px;">Datum: ${new Date().toLocaleString('de-DE')} | Land: ${selectedCountry} | Kontext: ${currentStep}</p>
<hr style="border-color:#e2e8f0;margin:16px 0;">
${qaHtml}
<hr style="border-color:#e2e8f0;margin:16px 0;">
<p style="color:#94a3b8;font-size:11px;">Automatisch erstellt vom BreakPilot Compliance Advisor (Qwen)</p>
`
await fetch('/api/sdk/v1/agent/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
recipient: 'dsb@breakpilot.local',
subject: `Compliance Advisor — ${qaPairs.length} Fragen (${currentStep})`,
body_html: bodyHtml,
role: 'Datenschutzbeauftragter',
}),
})
setEmailSent(true)
setTimeout(() => setEmailSent(false), 3000)
} catch (e) {
console.error('Email send failed:', e)
} finally {
setEmailSending(false)
}
}, [messages, emailSending, selectedCountry, currentStep])
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
@@ -188,6 +236,31 @@ export function ComplianceAdvisorWidget({ currentStep = 'default' }: ComplianceA
</div>
</div>
<div className="flex items-center gap-1">
{/* Send as Email */}
{messages.length > 0 && (
<button
onClick={handleSendAsEmail}
disabled={emailSending}
className={`text-white/80 hover:text-white transition-colors ${emailSent ? 'text-green-300' : ''}`}
aria-label="Als Email an DSB senden"
title={emailSent ? 'Email gesendet!' : 'Beratungsprotokoll als Email senden'}
>
{emailSent ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : emailSending ? (
<svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
)}
</button>
)}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-white/80 hover:text-white transition-colors"