import { useEffect, useRef, useState } from 'react' import { Network, Clock, Hash, Play, Ticket, ChevronDown, ChevronUp, FileText } from 'lucide-react' import type { AISessionDetail, AISessionStepResponse, StepResponseRequest, SessionDocumentation, StatusUpdateAudience, StatusUpdateLength, StatusUpdateContext, StatusUpdateResponse, } from '@/types/ai-session' import type { BranchResponse } from '@/types/branching' import { ConfidenceIndicator } from './ConfidenceIndicator' import { FlowPilotStepCard } from './FlowPilotStepCard' import { FlowPilotMessageBar } from './FlowPilotMessageBar' import { SessionDocView } from './SessionDocView' import { StatusUpdateModal } from './StatusUpdateModal' import { SessionTicketCard } from './SessionTicketCard' import { SimilarSessions } from './SimilarSessions' import { BranchMap } from '@/components/session/BranchMap' import { BranchTransitionBar } from '@/components/session/BranchTransitionBar' import { TicketPickerModal } from '@/components/session/TicketPickerModal' import { aiSessionsApi } from '@/api' import { toast } from '@/lib/toast' import type { PSATicketInfo } from '@/types/integrations' interface FlowPilotSessionProps { session: AISessionDetail allSteps: AISessionStepResponse[] currentStep: AISessionStepResponse | null isProcessing: boolean documentation: SessionDocumentation | null psaPushStatus?: string | null psaPushError?: string | null memberMappingWarning?: string | null onRespond: (response: StepResponseRequest) => void onResume?: () => Promise onRate: (rating: number) => void onReloadSession?: () => Promise onGenerateStatusUpdate?: (audience: StatusUpdateAudience, length: StatusUpdateLength, context: StatusUpdateContext) => Promise // Branching props (optional — only present for branching sessions) branches?: BranchResponse[] activeBranchId?: string | null onBranchSwitch?: (branchId: string) => void } export function FlowPilotSession({ session, allSteps, currentStep, isProcessing, documentation, psaPushStatus, psaPushError, memberMappingWarning, onRespond, onResume, onRate, onReloadSession, onGenerateStatusUpdate, branches, activeBranchId, onBranchSwitch, }: FlowPilotSessionProps) { const scrollRef = useRef(null) const [showTicketPicker, setShowTicketPicker] = useState(false) const [linkingTicket, setLinkingTicket] = useState(false) const [showShareCommunication, setShowShareCommunication] = useState(false) const [showMobileSidebar, setShowMobileSidebar] = useState(false) const prevBranchIdRef = useRef(null) const [branchTransition, setBranchTransition] = useState<{ from: BranchResponse | null to: BranchResponse } | null>(null) // Track branch switches and show transition bar useEffect(() => { if (!activeBranchId || !branches?.length) return const prev = prevBranchIdRef.current if (prev && prev !== activeBranchId) { const fromBranch = branches.find(b => b.id === prev) ?? null const toBranch = branches.find(b => b.id === activeBranchId) if (toBranch) { setBranchTransition({ from: fromBranch, to: toBranch }) } } prevBranchIdRef.current = activeBranchId }, [activeBranchId, branches]) // eslint-disable-next-line @typescript-eslint/no-unused-vars const handleLinkTicket = async (ticketId: string, _ticket?: PSATicketInfo) => { if (!session.psa_connection_id && !session.ticket_data) { // Need a connection ID — try to get it from the integrations API // For now, we'll need it passed in. This will work when ticket_data has it. toast.error('No PSA connection available') return } setLinkingTicket(true) setShowTicketPicker(false) try { // We need the psa_connection_id. If the session doesn't have one, // fetch it from the integrations API let connectionId = session.psa_connection_id if (!connectionId) { const { integrationsApi } = await import('@/api/integrations') const conn = await integrationsApi.getConnection() if (!conn?.id) { toast.error('No PSA connection configured') return } connectionId = conn.id } await aiSessionsApi.linkTicket(session.id, { psa_ticket_id: ticketId, psa_connection_id: connectionId, }) toast.success(`Linked to ticket #${ticketId}`) // Reload session to get updated ticket_data if (onReloadSession) { await onReloadSession() } } catch { toast.error('Failed to link ticket') } finally { setLinkingTicket(false) } } // Auto-scroll to latest step useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight } }, [allSteps.length]) const isCompleted = session.status === 'resolved' || session.status === 'escalated' // Show documentation view for completed sessions if (isCompleted && documentation) { const shareContext = session.status === 'resolved' ? 'resolution' as const : 'escalation' as const const shareLabel = session.status === 'resolved' ? 'Share Resolution' : 'Share Escalation' return (
{/* Share Resolution/Escalation button */} {onGenerateStatusUpdate && (
)}
{/* Share communication modal for resolved/escalated sessions */} {onGenerateStatusUpdate && ( setShowShareCommunication(false)} onGenerate={onGenerateStatusUpdate} context={shareContext} hasPsaTicket={!!session.psa_ticket_id} /> )}
) } return (
{/* Mobile sidebar summary (collapsible) */}
{showMobileSidebar && (
{session.psa_ticket_id ? ( | null} /> ) : session.status === 'active' ? ( ) : null} {session.problem_summary && (

Problem

{session.problem_summary}

)} {session.matched_flow_id && (
{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
)}
)}
{/* Main content area: conversation + sidebar */}
{/* Conversation column — pb-24 provides clearance for the fixed message bar */}
{/* Branch transition bar */} {branchTransition && ( )} {allSteps.map((step) => ( ))}
{/* Sidebar — desktop only */}
{/* Branch map (branching sessions only) */} {session.is_branching && branches && branches.length > 0 && onBranchSwitch && ( )} {/* Ticket context */} {session.psa_ticket_id ? ( | null} /> ) : session.status === 'active' ? ( ) : null} {/* Problem summary */} {session.problem_summary && (

Problem

{session.problem_summary}

)} {/* Domain */} {session.problem_domain && (

Domain

{session.problem_domain}
)} {/* Confidence */}

Confidence

{/* Matched flow */} {session.matched_flow_id && (

Matched flow

{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
)} {/* Steps */}
{session.step_count} steps
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{/* Similar sessions */}
{/* Message bar — now the only fixed bottom element */} {session.status === 'active' && ( )} {/* Paused banner */} {session.status === 'paused' && onResume && (
Session paused
)} {/* Ticket picker modal for mid-session linking */} setShowTicketPicker(false)} onSelect={handleLinkTicket} />
) }