From 1d1ca3f29484315cb198c08d4eba7bd6f5053f19 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sat, 7 Mar 2026 01:56:24 -0500 Subject: [PATCH] fix: wire AI-generated flow structures into editor stores The useEditorAI hook was ignoring result.working_tree from AI responses, so generated steps/trees never appeared in the editor. Now: - useEditorAI calls onFlowUpdate when working_tree is present in response - ProceduralEditorPage handles steps + intake form updates via replaceSteps - TreeEditorPage handles tree structure updates via replaceTreeStructure - Both stores have new bulk-replace methods for AI integration Co-Authored-By: Claude Opus 4.6 --- frontend/src/hooks/useEditorAI.ts | 11 +++++++++-- frontend/src/pages/ProceduralEditorPage.tsx | 13 ++++++++++++- frontend/src/pages/TreeEditorPage.tsx | 9 +++++++++ frontend/src/store/proceduralEditorStore.ts | 12 ++++++++++++ frontend/src/store/treeEditorStore.ts | 12 ++++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/useEditorAI.ts b/frontend/src/hooks/useEditorAI.ts index d5dc9fa1..cbbadcc6 100644 --- a/frontend/src/hooks/useEditorAI.ts +++ b/frontend/src/hooks/useEditorAI.ts @@ -12,9 +12,11 @@ interface UseEditorAIOptions { treeId?: string | null /** Returns the live flow structure from the editor for AI context */ getFlowContext?: () => Record | null + /** Called when the AI response contains a working_tree update */ + onFlowUpdate?: (workingTree: Record, metadata?: Record | null) => void } -export function useEditorAI({ flowType, treeId, getFlowContext }: UseEditorAIOptions) { +export function useEditorAI({ flowType, treeId, getFlowContext, onFlowUpdate }: UseEditorAIOptions) { const [isOpen, setIsOpen] = useState(false) const [focalNodeId, setFocalNodeId] = useState(null) const [contextMenu, setContextMenu] = useState<{ @@ -108,6 +110,11 @@ export function useEditorAI({ flowType, treeId, getFlowContext }: UseEditorAIOpt timestamp: new Date().toISOString(), }, ]) + + // Apply AI-generated flow structure to the editor + if (result.working_tree && onFlowUpdate) { + onFlowUpdate(result.working_tree, result.tree_metadata || null) + } } catch { setMessages((prev) => [ ...prev, @@ -121,7 +128,7 @@ export function useEditorAI({ flowType, treeId, getFlowContext }: UseEditorAIOpt setIsLoading(false) pendingActionRef.current = 'open_chat' } - }, [input, isLoading, ensureSession, focalNodeId, getFlowContext]) + }, [input, isLoading, ensureSession, focalNodeId, getFlowContext, onFlowUpdate]) const triggerAction = useCallback( (nodeId: string, actionType: AIActionType, prompt: string) => { diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx index b9505cac..01030d29 100644 --- a/frontend/src/pages/ProceduralEditorPage.tsx +++ b/frontend/src/pages/ProceduralEditorPage.tsx @@ -15,7 +15,7 @@ import { ContextMenu } from '@/components/common/ContextMenu' import { useEditorAI } from '@/hooks/useEditorAI' import { cn } from '@/lib/utils' import { toast } from '@/lib/toast' -import type { TreeType, MaintenanceSchedule, TargetList } from '@/types' +import type { TreeType, MaintenanceSchedule, TargetList, ProceduralStep, IntakeFormField } from '@/types' type SectionKey = 'details' | 'intake' | 'schedule' @@ -46,10 +46,20 @@ export function ProceduralEditorPage() { setIsSaving, markSaved, getTreeForSave, + replaceSteps, } = useProceduralEditorStore() const steps = useProceduralEditorStore(s => s.steps) + const handleFlowUpdate = useCallback((workingTree: Record, metadata?: Record | null) => { + const stepsData = workingTree.steps as ProceduralStep[] | undefined + if (stepsData && Array.isArray(stepsData)) { + // Intake form may be in working_tree or in metadata + const intakeData = (workingTree.intake_form || metadata?.intake_form) as IntakeFormField[] | undefined + replaceSteps(stepsData, intakeData) + } + }, [replaceSteps]) + const editorAI = useEditorAI({ flowType: 'procedural', treeId: id, @@ -61,6 +71,7 @@ export function ProceduralEditorPage() { intake_form: intakeForm, } }, [steps, intakeForm, name, description]), + onFlowUpdate: handleFlowUpdate, }) const isMaintenance = treeType === 'maintenance' diff --git a/frontend/src/pages/TreeEditorPage.tsx b/frontend/src/pages/TreeEditorPage.tsx index a604b488..42efcb75 100644 --- a/frontend/src/pages/TreeEditorPage.tsx +++ b/frontend/src/pages/TreeEditorPage.tsx @@ -58,6 +58,7 @@ export function TreeEditorPage() { deleteNode, setEditorMode, getAllNodeIds, + replaceTreeStructure, } = useTreeEditorStore() // Access undo/redo from temporal store @@ -83,6 +84,13 @@ export function TreeEditorPage() { }, []) // AI Assist panel + const handleFlowUpdate = useCallback((workingTree: Record) => { + // For troubleshooting flows, working_tree is the tree structure directly + if (workingTree.type && workingTree.id) { + replaceTreeStructure(workingTree as unknown as TreeStructure) + } + }, [replaceTreeStructure]) + const editorAI = useEditorAI({ flowType: 'troubleshooting', treeId: id, @@ -94,6 +102,7 @@ export function TreeEditorPage() { tree_structure: treeStructure as unknown as Record, } }, [treeStructure, name, description]), + onFlowUpdate: handleFlowUpdate, }) const previousEditingNodeRef = useRef(null) diff --git a/frontend/src/store/proceduralEditorStore.ts b/frontend/src/store/proceduralEditorStore.ts index b9beff1d..ae85559b 100644 --- a/frontend/src/store/proceduralEditorStore.ts +++ b/frontend/src/store/proceduralEditorStore.ts @@ -112,6 +112,9 @@ interface ProceduralEditorState { updateField: (index: number, updates: Partial) => void moveField: (fromIndex: number, toIndex: number) => void + // Actions - AI Integration + replaceSteps: (steps: ProceduralStep[], intakeForm?: IntakeFormField[]) => void + // Actions - Save setIsSaving: (saving: boolean) => void markSaved: () => void @@ -341,6 +344,15 @@ export const useProceduralEditorStore = create()( }) }, + // AI Integration + replaceSteps: (steps, intakeForm) => { + set((state) => { + state.steps = steps + if (intakeForm) state.intakeForm = intakeForm + state.isDirty = true + }) + }, + // Save setIsSaving: (saving) => set((state) => { state.isSaving = saving }), markSaved: () => set((state) => { state.isDirty = false }), diff --git a/frontend/src/store/treeEditorStore.ts b/frontend/src/store/treeEditorStore.ts index d069ce66..9a67bcc4 100644 --- a/frontend/src/store/treeEditorStore.ts +++ b/frontend/src/store/treeEditorStore.ts @@ -283,6 +283,9 @@ interface TreeEditorState { syncMarkdownToTree: () => void syncTreeToMarkdown: () => void + // Actions - AI Integration + replaceTreeStructure: (structure: TreeStructure) => void + // Actions - State setLoading: (loading: boolean) => void setSaving: (saving: boolean) => void @@ -1015,6 +1018,15 @@ export const useTreeEditorStore = create()( } }, + // AI Integration + replaceTreeStructure: (structure: TreeStructure) => { + set((state) => { + state.treeStructure = structure + state.selectedNodeId = structure.id + state.isDirty = true + }) + }, + setLoading: (loading: boolean) => { set((state) => { state.isLoading = loading }) },