diff --git a/frontend/src/components/assistant/ConcludeSessionModal.tsx b/frontend/src/components/assistant/ConcludeSessionModal.tsx index f874cab3..c32e71cd 100644 --- a/frontend/src/components/assistant/ConcludeSessionModal.tsx +++ b/frontend/src/components/assistant/ConcludeSessionModal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { X, CheckCircle2, @@ -10,9 +10,11 @@ import { RefreshCw, ClipboardList, Sparkles, + AlertTriangle, } from 'lucide-react' import { cn } from '@/lib/utils' import { MarkdownContent } from '@/components/ui/MarkdownContent' +import { aiSessionsApi } from '@/api/aiSessions' type ConclusionOutcome = 'resolved' | 'escalated' | 'paused' @@ -22,6 +24,7 @@ interface ConcludeSessionModalProps { 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 }[] = [ @@ -62,6 +65,7 @@ export function ConcludeSessionModal({ onConclude, onResumeNew, chatTitle, + sessionId, }: ConcludeSessionModalProps) { const [step, setStep] = useState('select-outcome') const [outcome, setOutcome] = useState(null) @@ -70,6 +74,9 @@ export function ConcludeSessionModal({ 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 summaryRef = useRef('') // Reset state when modal opens useEffect(() => { @@ -81,6 +88,9 @@ export function ConcludeSessionModal({ setGenerating(false) setCopied(false) setError(null) + setStreaming(false) + setStreamError(null) + summaryRef.current = '' } }, [isOpen]) @@ -95,12 +105,50 @@ export function ConcludeSessionModal({ setError(null) try { - const result = await onConclude(outcome, notes) - setSummary(result) + // 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) + // Try non-streaming fallback + aiSessionsApi.getDocumentation(sessionId).then((doc) => { + const fallback = `## Problem Summary\n${doc.problem_summary}\n\n## Steps Taken\n${doc.diagnostic_steps.map(s => `- ${s.description}`).join('\n')}\n\n## Resolution\n${doc.resolution_summary || 'See conversation'}\n\n## Next Steps\nNone` + setSummary(fallback) + setStreamError(null) + }).catch(() => { + if (!summaryRef.current) { + setSummary('Documentation generation failed. You can copy the conversation from the chat.') + } + }) + }, + ) + } else if (outcome === 'escalated') { + setSummary('Session escalated. Ticket notes will be generated when the session is resolved.') + } else { + setSummary('Session paused. Progress saved — you can resume anytime.') + } } catch { - setError('Failed to generate summary. Please try again.') - } finally { + setError('Failed to conclude session. Please try again.') setGenerating(false) } } @@ -306,7 +354,7 @@ export function ConcludeSessionModal({ )} - {/* Generated summary */} + {/* Generated ticket notes */}
- Generated Ticket Notes + Ticket Notes + {streaming && ( + + )}
-
- -
+ + {/* Streaming content or skeleton */} + {summary ? ( +
+ +
+ ) : streaming ? ( +
+
+
+
+
+
+
+
+ ) : streamError ? ( +
+ + {streamError} +
+ ) : null}
)} @@ -384,27 +453,29 @@ export function ConcludeSessionModal({ )}
- + {summary && !streaming && ( + + )}