import { useEffect, useRef, useState } from 'react' import { useParams, useSearchParams, useLocation, useBlocker, useNavigate } from 'react-router-dom' import { Sparkles, Loader2, AlertTriangle, CheckCircle2, ArrowUpRight, FileText, MoreHorizontal, Pause, X } from 'lucide-react' import { useFlowPilotSession } from '@/hooks/useFlowPilotSession' import { useBranching } from '@/hooks/useBranching' import { FlowPilotIntake, FlowPilotSession, SessionBriefing } from '@/components/flowpilot' import { EscalateModal } from '@/components/flowpilot/EscalateModal' import { StatusUpdateModal } from '@/components/flowpilot/StatusUpdateModal' import { HandoffModal } from '@/components/session/HandoffModal' import { handoffsApi } from '@/api/handoffs' import { aiSessionsApi } from '@/api' import { toast } from '@/lib/toast' export default function FlowPilotSessionPage() { const { sessionId } = useParams<{ sessionId?: string }>() const [searchParams, setSearchParams] = useSearchParams() const navigate = useNavigate() const location = useLocation() const prefill = (location.state as { prefill?: string } | null)?.prefill || '' const isPickup = searchParams.get('pickup') === 'true' const fp = useFlowPilotSession() const branching = useBranching() const prefillHandledRef = useRef(false) const [showOverflow, setShowOverflow] = useState(false) const [showResolve, setShowResolve] = useState(false) const [showEscalate, setShowEscalate] = useState(false) const [showAbandon, setShowAbandon] = useState(false) const [showStatusUpdate, setShowStatusUpdate] = useState(false) const [showHandoff, setShowHandoff] = useState(false) const [resolutionSummary, setResolutionSummary] = useState('') const [submitting, setSubmitting] = useState(false) // Block navigation when session is active const isActiveSession = fp.session?.status === 'active' const blocker = useBlocker( ({ currentLocation, nextLocation }) => !!isActiveSession && currentLocation.pathname !== nextLocation.pathname ) // Auto-submit when navigating from dashboard with prefilled problem useEffect(() => { if (prefill && !prefillHandledRef.current && !sessionId && !fp.session && !fp.isLoading) { prefillHandledRef.current = true fp.startSession({ intake_type: 'free_text', intake_content: { text: prefill } }) } }, [prefill, sessionId, fp.session, fp.isLoading]) // eslint-disable-line react-hooks/exhaustive-deps const [pickingUp, setPickingUp] = useState(false) // Load existing session if ID in URL useEffect(() => { if (sessionId && !fp.session) { fp.loadSession(sessionId) } }, [sessionId]) // eslint-disable-line react-hooks/exhaustive-deps // Load branches when session is branching useEffect(() => { if (fp.session?.is_branching && fp.session.id) { branching.loadBranches(fp.session.id) } }, [fp.session?.is_branching, fp.session?.id]) // eslint-disable-line react-hooks/exhaustive-deps const handlePickupContinue = async () => { if (!sessionId) return setPickingUp(true) try { await aiSessionsApi.pickupSession(sessionId, { resume_mode: 'continue' }) // Clear pickup param and reload the session as active setSearchParams({}) await fp.loadSession(sessionId) } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to pick up session' toast.error(message) } finally { setPickingUp(false) } } const handleBranchSwitch = async (branchId: string) => { if (!fp.session) return const result = await branching.switchBranch(fp.session.id, branchId) if (result) { // Reload session to get updated steps for the switched branch await fp.loadSession(fp.session.id) } } const handlePickupFresh = async (context: string) => { if (!sessionId) return setPickingUp(true) try { await aiSessionsApi.pickupSession(sessionId, { resume_mode: 'fresh', additional_context: context, }) setSearchParams({}) await fp.loadSession(sessionId) } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to pick up session' toast.error(message) } finally { setPickingUp(false) } } // Error state if (fp.error && !fp.session) { return (

{fp.error}

) } // Loading if (fp.isLoading && !fp.session) { return (
) } // Intake screen (no session yet) if (!fp.session) { return (
) } // Escalation pickup briefing if (isPickup && fp.session.status === 'requesting_escalation' && fp.session.escalation_reason) { // Build escalation package from session detail // The escalation_package is in the session but not directly on AISessionDetail — // we use what's available from the session fields const escalationPackage = { problem_summary: fp.session.problem_summary ?? undefined, escalation_reason: fp.session.escalation_reason ?? undefined, // Steps are available from the session detail steps_tried: fp.allSteps.map(step => ({ step_type: step.step_type, description: (step.content as Record)?.text || '', })), } return (
{/* Header */}

Escalation Pickup — {fp.session.problem_summary || 'FlowPilot Session'}

Awaiting pickup
{/* Briefing */}
) } // Active/completed session return (
{/* Navigation guard modal */} {blocker.state === 'blocked' && (

Active Session

You have an active troubleshooting session. If you leave, your session will be paused and you can resume it later.

)} {/* Header with actions */}

{fp.session.problem_summary || 'FlowPilot Session'}

{/* Action buttons — desktop inline, mobile overflow menu */} {fp.session.status === 'active' && ( <> {/* Desktop actions */}
{fp.allSteps.length >= 2 && ( )} {/* Overflow: Pause / Close */}
{showOverflow && ( <>
setShowOverflow(false)} />
)}
{/* Mobile: single overflow menu */}
{showOverflow && ( <>
setShowOverflow(false)} />
{fp.allSteps.length >= 2 && ( )}
)}
)} {/* Status badge — non-active states */} {fp.session.status !== 'active' && ( {fp.session.status} )}
{/* Session content */}
fp.loadSession(fp.session!.id)} onGenerateStatusUpdate={(audience, length, context) => fp.generateStatusUpdate({ audience, length, context })} branches={branching.branches} activeBranchId={branching.activeBranchId} onBranchSwitch={handleBranchSwitch} />
{/* ── Page-level modals (moved from action bar) ── */} {/* Resolve modal */} {showResolve && (

Resolve Session

Summarize what fixed the issue. This will be included in the auto-generated documentation.