import { useState, useEffect, useRef } from 'react' import { X, CheckCircle2, ArrowUpRight, Pause, Loader2, Copy, Check, RefreshCw, ClipboardList, Sparkles, AlertTriangle, FileText, User, Mail, } from 'lucide-react' import { cn } from '@/lib/utils' import { MarkdownContent } from '@/components/ui/MarkdownContent' import { aiSessionsApi } from '@/api/aiSessions' type ConclusionOutcome = 'resolved' | 'escalated' | 'paused' interface ConcludeSessionModalProps { isOpen: boolean onClose: () => void onConclude: (outcome: ConclusionOutcome, notes: string) => Promise onResumeNew: (summary: string) => void chatTitle: string sessionId: string | null } const OUTCOMES: { value: ConclusionOutcome; label: string; description: string; icon: typeof CheckCircle2; color: string; bg: string; border: string }[] = [ { value: 'resolved', label: 'Resolved', description: 'Issue has been fixed or answered', icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-400/10', border: 'border-emerald-400/30', }, { value: 'escalated', label: 'Escalate', description: 'Needs to be handed off or escalated', icon: ArrowUpRight, color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', }, { value: 'paused', label: 'Paused', description: 'Continuing later — saving progress', icon: Pause, color: 'text-blue-400', bg: 'bg-blue-400/10', border: 'border-blue-400/30', }, ] type ModalStep = 'select-outcome' | 'add-notes' | 'summary' export function ConcludeSessionModal({ isOpen, onClose, onConclude, onResumeNew, chatTitle, sessionId, }: ConcludeSessionModalProps) { const [step, setStep] = useState('select-outcome') const [outcome, setOutcome] = useState(null) const [notes, setNotes] = useState('') const [summary, setSummary] = useState('') const [generating, setGenerating] = useState(false) const [copied, setCopied] = useState(false) const [error, setError] = useState(null) const [streaming, setStreaming] = useState(false) const [streamError, setStreamError] = useState(null) const [generatingUpdate, setGeneratingUpdate] = useState(false) const summaryRef = useRef('') // Reset state when modal opens useEffect(() => { if (isOpen) { setStep('select-outcome') setOutcome(null) setNotes('') setSummary('') setGenerating(false) setCopied(false) setError(null) setStreaming(false) setStreamError(null) setGeneratingUpdate(false) summaryRef.current = '' } }, [isOpen]) const handleOutcomeSelect = (selected: ConclusionOutcome) => { setOutcome(selected) setStep('add-notes') } const handleGenerate = async () => { if (!outcome) return setGenerating(true) setError(null) try { // Phase 1: Resolve/escalate/pause the session (fast) await onConclude(outcome, notes) // Phase 2: Transition to summary step immediately setStep('summary') setGenerating(false) // For resolved sessions, stream ticket notes if (outcome === 'resolved' && sessionId) { setStreaming(true) setStreamError(null) summaryRef.current = '' aiSessionsApi.streamDocumentation( sessionId, (chunk) => { summaryRef.current += chunk setSummary(summaryRef.current) }, () => { setStreaming(false) }, (err) => { setStreaming(false) setStreamError(err) // Fallback: use status update API which works with conversation context aiSessionsApi.generateStatusUpdate(sessionId, { audience: 'ticket_notes', length: 'detailed', context: 'resolution', }).then((result) => { setSummary(result.content) setStreamError(null) }).catch(() => { if (!summaryRef.current) { setSummary('Documentation generation failed. You can copy the conversation from the chat.') } }) }, ) } else { // For paused/escalated: don't set summary yet — show status update options setSummary('') } } catch { setError('Failed to conclude session. Please try again.') setGenerating(false) } } const handleCopy = async () => { try { await navigator.clipboard.writeText(summary) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch { // Fallback const textarea = document.createElement('textarea') textarea.value = summary document.body.appendChild(textarea) textarea.select() document.execCommand('copy') document.body.removeChild(textarea) setCopied(true) setTimeout(() => setCopied(false), 2000) } } const handleResumeNew = () => { onResumeNew(summary) onClose() } const handleGenerateStatusUpdate = async (audience: 'ticket_notes' | 'client_update' | 'email_draft') => { if (!sessionId) return setGeneratingUpdate(true) try { const context = outcome === 'escalated' ? 'escalation' : 'status' const result = await aiSessionsApi.generateStatusUpdate(sessionId, { audience, length: 'detailed', context, }) setSummary(result.content) setCopied(false) } catch { setSummary('Failed to generate status update. You can copy the conversation from the chat.') } finally { setGeneratingUpdate(false) } } if (!isOpen) return null const selectedOutcome = OUTCOMES.find(o => o.value === outcome) return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

Conclude Session

{chatTitle}

{/* Step indicator */}
{(['select-outcome', 'add-notes', 'summary'] as ModalStep[]).map((s, i) => (
{i > 0 && (
)}
{i + 1}
{s === 'select-outcome' ? 'Outcome' : s === 'add-notes' ? 'Notes' : 'Summary'}
))}
{/* Content */}
{/* Step 1: Select Outcome */} {step === 'select-outcome' && (

How did this session end?

{OUTCOMES.map(o => { const Icon = o.icon return ( ) })}
)} {/* Step 2: Add Notes */} {step === 'add-notes' && selectedOutcome && (
{/* Selected outcome badge */}
{selectedOutcome.label}