import { useEffect, useRef, useState } from 'react' import { Network, Clock, Hash, Play, Ticket, ChevronDown, ChevronUp } from 'lucide-react' import type { AISessionDetail, AISessionStepResponse, StepResponseRequest, ResolveSessionRequest, EscalateSessionRequest, SessionDocumentation, } from '@/types/ai-session' import { ConfidenceIndicator } from './ConfidenceIndicator' import { FlowPilotStepCard } from './FlowPilotStepCard' import { FlowPilotActionBar } from './FlowPilotActionBar' import { FlowPilotMessageBar } from './FlowPilotMessageBar' import { SessionDocView } from './SessionDocView' import { SessionTicketCard } from './SessionTicketCard' import { SimilarSessions } from './SimilarSessions' 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 canResolve: boolean canEscalate: boolean documentation: SessionDocumentation | null psaPushStatus?: string | null psaPushError?: string | null memberMappingWarning?: string | null onRespond: (response: StepResponseRequest) => void onResolve: (data: ResolveSessionRequest) => Promise onEscalate: (data: EscalateSessionRequest) => Promise onPause?: () => Promise onResume?: () => Promise onRate: (rating: number) => void onReloadSession?: () => Promise } export function FlowPilotSession({ session, allSteps, currentStep, isProcessing, canResolve, canEscalate, documentation, psaPushStatus, psaPushError, memberMappingWarning, onRespond, onResolve, onEscalate, onPause, onResume, onRate, onReloadSession, }: FlowPilotSessionProps) { const scrollRef = useRef(null) const [showTicketPicker, setShowTicketPicker] = useState(false) const [linkingTicket, setLinkingTicket] = useState(false) const [showMobileSidebar, setShowMobileSidebar] = useState(false) 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) { return (
) } 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-32 provides clearance for the fixed message bar + action bar */}
{allSteps.map((step) => ( ))}
{/* Sidebar — desktop only */}
{/* 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 */} {session.status === 'active' && ( )} {/* Action bar */} {session.status === 'active' && ( )} {/* Paused banner */} {session.status === 'paused' && onResume && (
Session paused
)} {/* Ticket picker modal for mid-session linking */} setShowTicketPicker(false)} onSelect={handleLinkTicket} />
) }