diff --git a/frontend/src/components/l1/L1WalkTreeVariant.tsx b/frontend/src/components/l1/L1WalkTreeVariant.tsx index da9a12cb..8b00d8e2 100644 --- a/frontend/src/components/l1/L1WalkTreeVariant.tsx +++ b/frontend/src/components/l1/L1WalkTreeVariant.tsx @@ -1,173 +1,183 @@ -import { useState } from 'react' -import { ChevronLeft } from 'lucide-react' -import { Link } from 'react-router-dom' +import { useCallback, useEffect, useState } from 'react' +import type { TreeNode, WalkSession } from '@/types/l1' import { l1Api } from '@/api/l1' -import type { WalkSession } from '@/types/l1' -import { EscalateModal, ResolveModal } from '@/components/l1/WalkModals' +import { WalkModals } from './WalkModals' interface Props { session: WalkSession - onSessionUpdate: (s: WalkSession) => void - onDone: () => void } -export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) { +/** + * L1WalkTreeVariant — renders a decision-tree walk. + * + * For `ai_build` sessions it drives real, node-by-node generation via + * POST /l1/sessions/{id}/next-node: fetch the first node on mount, then on each + * answer/acknowledge POST the current node id (+ its text, so the walked path and + * captured tree stay legible) and render the returned node. Terminal nodes + * (resolved / escalate / needs_review) hand off to the existing Resolve/Escalate + * modals. Published `flow` / `proposal` walks keep the Phase-1 stub for now. + */ +export function L1WalkTreeVariant({ session }: Props) { + const isAiBuild = session.session_kind === 'ai_build' const [showResolve, setShowResolve] = useState(false) const [showEscalate, setShowEscalate] = useState(false) - const [note, setNote] = useState('') + const [node, setNode] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) - // 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, + // Fetch the first node on mount (ai_build only). + useEffect(() => { + if (!isAiBuild) return + let cancelled = false + setLoading(true) + l1Api + .nextNode(session.id, {}) + .then((r) => { + if (!cancelled) setNode(r.node) }) - onSessionUpdate(updated) - setNote('') - } catch (err) { - // Keep silent for v1 — Phase 2 wires real error UI - console.error('step failed', err) + .catch(() => { + if (!cancelled) setError('Could not generate the next step.') + }) + .finally(() => { + if (!cancelled) setLoading(false) + }) + return () => { + cancelled = true } - } + }, [isAiBuild, session.id]) - 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' - } + const advance = useCallback( + async (body: { answer?: 'yes' | 'no'; acknowledged?: boolean }) => { + if (!node) return + setLoading(true) + setError(null) + try { + const r = await l1Api.nextNode(session.id, { + node_id: node.id, + node_text: node.text, + ...body, + }) + setNode(r.node) + } catch { + setError('Could not generate the next step.') + } finally { + setLoading(false) + } + }, + [node, session.id], + ) + + const isTerminal = + node?.node_type === 'resolved' || + node?.node_type === 'escalate' || + node?.node_type === 'needs_review' return ( -
- {/* Header */} -
- - - #{session.id.slice(0, 8)} - {session.session_kind === 'proposal' && ( - AI-built - )} - -
- - +
+ {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.
-
+ )} - {/* Two-pane body */} -
-
-

- Step {session.walked_path.length + 1} + {!isAiBuild && ( +

+

+ Walking flow {session.flow_id}

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

- This session is {session.status}. -

- -
- ) : ( -
-

Continue the walk:

+

Synthetic step rendering (Phase 1 stub).

+
+ )} + + {isAiBuild && ( +
+ {loading && ( +

Thinking through the next step…

+ )} + {error &&

{error}

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

{node.text}

-