diff --git a/studio-v2/app/alerts-b2b/_components/DecisionTraceModal.tsx b/studio-v2/app/alerts-b2b/_components/DecisionTraceModal.tsx new file mode 100644 index 0000000..8fa232e --- /dev/null +++ b/studio-v2/app/alerts-b2b/_components/DecisionTraceModal.tsx @@ -0,0 +1,120 @@ +'use client' + +import { useTheme } from '@/lib/ThemeContext' +import type { B2BHit } from '@/lib/AlertsB2BContext' + +export function DecisionTraceModal({ + hit, + onClose +}: { + hit: B2BHit + onClose: () => void +}) { + const { isDark } = useTheme() + const trace = hit.decisionTrace + + return ( +
+
+
+
+

+ Decision Trace +

+ +
+ + {trace ? ( +
+ {/* Rules Triggered */} +
+

+ Regeln ausgeloest +

+
+ {trace.rulesTriggered.map((rule, idx) => ( + + {rule} + + ))} +
+
+ + {/* LLM Used */} +
+
+ LLM verwendet + + {trace.llmUsed ? `Ja (${Math.round((trace.llmConfidence || 0) * 100)}% Konfidenz)` : 'Nein'} + +
+
+ + {/* Signals */} +
+ {trace.signals.procurementSignalsFound.length > 0 && ( +
+

+ Beschaffungs-Signale +

+

+ {trace.signals.procurementSignalsFound.join(', ')} +

+
+ )} + + {trace.signals.publicBuyerSignalsFound.length > 0 && ( +
+

+ Oeffentliche Auftraggeber +

+

+ {trace.signals.publicBuyerSignalsFound.join(', ')} +

+
+ )} + + {trace.signals.productSignalsFound.length > 0 && ( +
+

+ Produkt-Signale +

+

+ {trace.signals.productSignalsFound.join(', ')} +

+
+ )} + + {trace.signals.negativesFound.length > 0 && ( +
+

+ Negative Signale +

+

+ {trace.signals.negativesFound.join(', ')} +

+
+ )} +
+
+ ) : ( +

+ Kein Decision Trace verfuegbar. +

+ )} +
+
+ ) +} diff --git a/studio-v2/app/alerts-b2b/_components/DigestView.tsx b/studio-v2/app/alerts-b2b/_components/DigestView.tsx new file mode 100644 index 0000000..35f619d --- /dev/null +++ b/studio-v2/app/alerts-b2b/_components/DigestView.tsx @@ -0,0 +1,49 @@ +'use client' + +import { useTheme } from '@/lib/ThemeContext' +import type { B2BHit } from '@/lib/AlertsB2BContext' +import { HitCard } from './HitCard' + +export function DigestView({ + hits, + onHitClick +}: { + hits: B2BHit[] + onHitClick: (hit: B2BHit) => void +}) { + const { isDark } = useTheme() + + return ( +
+
+ 📬 +

+ Tages-Digest (Top 10) +

+
+ {hits.length === 0 ? ( +
+ 🎉 +

Keine relevanten Hits heute

+
+ ) : ( +
+ {hits.map((hit, idx) => ( +
+ + {idx + 1} + +
+ onHitClick(hit)} /> +
+
+ ))} +
+ )} +
+ ) +} diff --git a/studio-v2/app/alerts-b2b/_components/EmailImportModal.tsx b/studio-v2/app/alerts-b2b/_components/EmailImportModal.tsx new file mode 100644 index 0000000..8d8870f --- /dev/null +++ b/studio-v2/app/alerts-b2b/_components/EmailImportModal.tsx @@ -0,0 +1,163 @@ +'use client' + +import { useState } from 'react' +import { useTheme } from '@/lib/ThemeContext' + +export function EmailImportModal({ + onClose, + onImport +}: { + onClose: () => void + onImport: (content: string, subject?: string) => void +}) { + const { isDark } = useTheme() + const [emailSubject, setEmailSubject] = useState('') + const [emailContent, setEmailContent] = useState('') + const [isProcessing, setIsProcessing] = useState(false) + + const handleImport = async () => { + if (!emailContent.trim()) return + setIsProcessing(true) + // Simulate processing delay + await new Promise(resolve => setTimeout(resolve, 800)) + onImport(emailContent, emailSubject || undefined) + setIsProcessing(false) + onClose() + } + + return ( +
+
+
+
+
+
+ 📧 +
+
+

+ E-Mail manuell einfuegen +

+

+ Fuegen Sie den Inhalt einer Google Alert E-Mail ein +

+
+
+ +
+ +
+ {/* Subject (optional) */} +
+ + setEmailSubject(e.target.value)} + className={`w-full px-4 py-3 rounded-xl border ${ + isDark + ? 'bg-white/10 border-white/20 text-white placeholder-white/40' + : 'bg-white border-slate-200 text-slate-900 placeholder-slate-400' + }`} + /> +
+ + {/* Email Content */} +
+ +