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 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-07 01:56:24 -05:00
parent a9251a0ee3
commit 1d1ca3f294
5 changed files with 54 additions and 3 deletions

View File

@@ -12,9 +12,11 @@ interface UseEditorAIOptions {
treeId?: string | null
/** Returns the live flow structure from the editor for AI context */
getFlowContext?: () => Record<string, unknown> | null
/** Called when the AI response contains a working_tree update */
onFlowUpdate?: (workingTree: Record<string, unknown>, metadata?: Record<string, unknown> | 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<string | null>(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) => {

View File

@@ -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<string, unknown>, metadata?: Record<string, unknown> | 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'

View File

@@ -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<string, unknown>) => {
// 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<string, unknown>,
}
}, [treeStructure, name, description]),
onFlowUpdate: handleFlowUpdate,
})
const previousEditingNodeRef = useRef<string | null>(null)

View File

@@ -112,6 +112,9 @@ interface ProceduralEditorState {
updateField: (index: number, updates: Partial<IntakeFormField>) => 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<ProceduralEditorState>()(
})
},
// 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 }),

View File

@@ -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<TreeEditorState>()(
}
},
// 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 })
},