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:
@@ -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) => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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 })
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user