import { useState, useEffect, useCallback } from 'react' import { ChevronLeft } from 'lucide-react' import { Link } from 'react-router-dom' import { l1Api } from '@/api/l1' import type { TreeNode, WalkSession } from '@/types/l1' import { EscalateModal, ResolveModal } from '@/components/l1/WalkModals' interface Props { session: WalkSession onSessionUpdate: (s: WalkSession) => void onDone: () => void } export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) { const [showResolve, setShowResolve] = useState(false) const [showEscalate, setShowEscalate] = useState(false) const [note, setNote] = useState('') // Phase 2A: ai_build sessions are walked node-by-node against /next-node // (real AI-generated decision tree), not the synthetic stepping below. const isAiBuild = session.session_kind === 'ai_build' const [node, setNode] = useState(null) const [nodeLoading, setNodeLoading] = useState(false) const [nodeError, setNodeError] = useState(null) useEffect(() => { if (!isAiBuild || session.status !== 'active') return let cancelled = false setNodeLoading(true) l1Api .nextNode(session.id, {}) .then((r) => { if (!cancelled) setNode(r.node) }) .catch(() => { if (!cancelled) setNodeError('Could not generate the next step.') }) .finally(() => { if (!cancelled) setNodeLoading(false) }) return () => { cancelled = true } }, [isAiBuild, session.id, session.status]) const advanceNode = useCallback( async (body: { answer?: 'yes' | 'no' }) => { if (!node) return setNodeLoading(true) setNodeError(null) try { const r = await l1Api.nextNode(session.id, { node_id: node.id, node_text: node.text, ...body, }) setNode(r.node) } catch { setNodeError('Could not generate the next step.') } finally { setNodeLoading(false) } }, [node, session.id], ) const isTerminalNode = node?.node_type === 'resolved' || node?.node_type === 'escalate' || node?.node_type === 'needs_review' // Phase 1: we don't have the live flow-tree fetch wired up here yet // (the tree-navigation pages have their own loader). The walker shows the // walked-path side panel, advance buttons stubbed for now — Phase 2 wires // the actual flow tree fetching + node advancement against tree data. // The "Yes/No" buttons record a synthetic step so the walked_path JSONB // grows; this gives us a functional roundtrip until Phase 2 wires the tree. const handleAnswer = async (answer: 'yes' | 'no') => { const nodeId = session.current_node_id || `step-${session.walked_path.length + 1}` try { const updated = await l1Api.step(session.id, { node_id: nodeId, question: `Step ${session.walked_path.length + 1}`, answer, note: note || null, }) onSessionUpdate(updated) setNote('') } catch (err) { // Keep silent for v1 — Phase 2 wires real error UI console.error('step failed', err) } } const lastError = (err: unknown): string => { if (typeof err === 'object' && err && 'response' in err) { const detail = (err as { response?: { data?: { detail?: string } } }).response?.data?.detail if (typeof detail === 'string') return detail } return 'Unexpected error' } return (
{/* Header */}
#{session.id.slice(0, 8)} {(session.session_kind === 'proposal' || session.session_kind === 'ai_build') && ( AI-built )}
{/* Two-pane body */}
{isAiBuild && (
These are high-confidence troubleshooting steps, but they come from outside your organization’s knowledge base — review them before acting. When in doubt, escalate early.
)}

Step {session.walked_path.length + 1}

{session.status !== 'active' ? (

This session is {session.status}.

) : isAiBuild ? (
{nodeLoading && (

Thinking through the next step…

)} {nodeError &&

{nodeError}

} {!nodeLoading && node?.node_type === 'question' && ( <>

{node.text}

)} {!nodeLoading && node?.node_type === 'instruction' && ( <>

{node.text}

)} {!nodeLoading && isTerminalNode && node && ( <>

{node.text}

{node.node_type === 'resolved' ? ( ) : ( )} )}
) : (

Continue the walk: