import React, { useState, useEffect } from 'react'; import { Send, Sparkles, AlertCircle, CheckCircle } from 'lucide-react'; import { API_BASE_URL } from '@/app/config'; interface StreamingDraftProps { emailId: string; emailSubject?: string; emailSender: string; onSendEmail: (to: string, subject: string, body: string) => Promise; } export default function StreamingDraft({ emailId, emailSubject, emailSender, onSendEmail, }: StreamingDraftProps) { const [tone, setTone] = useState<'professional' | 'casual' | 'short'>('professional'); const [draftContent, setDraftContent] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [isSending, setIsSending] = useState(false); const [sendStatus, setSendStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [errorMsg, setErrorMsg] = useState(''); // Clear draft when email selection changes useEffect(() => { setDraftContent(''); setSendStatus('idle'); }, [emailId]); const generateDraft = async () => { setIsGenerating(true); setDraftContent(''); setSendStatus('idle'); // Connect to FastAPI SSE endpoint const url = `${API_BASE_URL}/api/emails/${emailId}/draft?tone=${tone}`; // Access token from localStorage const token = localStorage.getItem('token') || ''; try { const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to generate draft stream.'); } const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) { throw new Error('No readable stream body.'); } let done = false; let fullText = ''; while (!done) { const { value, done: doneReading } = await reader.read(); done = doneReading; if (value) { const chunkStr = decoder.decode(value); // Parse Server Sent Events format "data: text\n\n" const lines = chunkStr.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const content = line.substring(6); fullText += content; setDraftContent(fullText); } } } } } catch (err: any) { console.error(err); setErrorMsg(err.message || 'Connection lost during drafting.'); setSendStatus('error'); } finally { setIsGenerating(false); } }; const handleSend = async () => { if (!draftContent.trim()) return; setIsSending(true); setSendStatus('idle'); // Extract target email address let toEmail = emailSender; const match = emailSender.match(/<([^>]+)>/); if (match) { toEmail = match[1]; } const subject = emailSubject ? `Re: ${emailSubject}` : 'Re: Email'; const success = await onSendEmail(toEmail, subject, draftContent); setIsSending(false); if (success) { setSendStatus('success'); } else { setErrorMsg('Failed to deliver reply.'); setSendStatus('error'); } }; const tones = [ { label: '💼 Professional', value: 'professional' }, { label: '☕ Casual', value: 'casual' }, { label: '⚡ Short / Quick', value: 'short' }, ]; return (
AI Streaming Reply Composer
Powered by GPT-4o-Mini
{/* Tone Selection */}
{tones.map((t) => ( ))}
{/* Generator trigger button */} {draftContent === '' && !isGenerating && ( )} {/* Draft text editor */} {(draftContent !== '' || isGenerating) && (