From 1f8201a3157b5e48493129c2862c32e9f8700193 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Tue, 24 Feb 2026 01:38:45 -0500 Subject: [PATCH] feat: improve AI builder UX - regenerate scaffold, Generate All layout, honest loading, 3-action review - BranchSelector: add Regenerate button to re-trigger scaffold suggestions - BranchDetailView: promote Generate All as primary action above Generate Detail/Skip - GeneratingAnimation: replace rotating messages with plain honest status text - TreePreviewCard: add Start Flow + Build Another alongside Open in Editor - AIFlowBuilderModal: wire up handleStartFlow (navigates to flow) and handleBuildAnother (resets) Co-Authored-By: Claude Opus 4.6 --- .../ai-builder/AIFlowBuilderModal.tsx | 45 ++++++++--- .../ai-builder/BranchDetailView.tsx | 74 ++++++++++--------- .../components/ai-builder/BranchSelector.tsx | 16 +++- .../ai-builder/GeneratingAnimation.tsx | 56 +++----------- .../components/ai-builder/TreePreviewCard.tsx | 47 +++++++----- 5 files changed, 127 insertions(+), 111 deletions(-) diff --git a/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx b/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx index ba4d2e0a..49013055 100644 --- a/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx +++ b/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx @@ -53,27 +53,48 @@ export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps) onClose() } - const handleOpenInEditor = async () => { - if (!assembledTree) return + const createTree = async () => { + if (!assembledTree) return null try { - const tree = await treesApi.create({ + return await treesApi.create({ name: assembledTree.suggested_name, description: assembledTree.suggested_description, tree_structure: assembledTree.tree_structure, tree_type: metadata.flow_type, status: 'draft', }) - handleClose() - const editorPath = - metadata.flow_type === 'procedural' - ? `/flows/${tree.id}/edit` - : `/trees/${tree.id}/edit` - navigate(editorPath) } catch { toast.error('Failed to create flow. Please try again.') + return null } } + const handleOpenInEditor = async () => { + const tree = await createTree() + if (!tree) return + handleClose() + const editorPath = + metadata.flow_type === 'procedural' + ? `/flows/${tree.id}/edit` + : `/trees/${tree.id}/edit` + navigate(editorPath) + } + + const handleStartFlow = async () => { + const tree = await createTree() + if (!tree) return + handleClose() + const navigatePath = + metadata.flow_type === 'procedural' + ? `/flows/${tree.id}/navigate` + : `/trees/${tree.id}/navigate` + navigate(navigatePath) + } + + const handleBuildAnother = () => { + reset() + } + const getTitle = () => { switch (phase) { case 'foundation': @@ -107,7 +128,11 @@ export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps) {phase === 'generating' && } {phase === 'detailing' && } {phase === 'reviewing' && ( - + )} {phase === 'error' && } diff --git a/frontend/src/components/ai-builder/BranchDetailView.tsx b/frontend/src/components/ai-builder/BranchDetailView.tsx index dfb73147..0f004a61 100644 --- a/frontend/src/components/ai-builder/BranchDetailView.tsx +++ b/frontend/src/components/ai-builder/BranchDetailView.tsx @@ -107,21 +107,52 @@ export function BranchDetailView() { ) : ( -
+

Generate AI detail for this branch

+ + {/* Generate All — primary action, shown when multiple branches remain */} + {selectedBranches.filter((b) => !b.steps).length > 1 && ( + isGeneratingAll ? ( + + ) : ( + + ) + )} + + {/* Divider + secondary actions */} + {selectedBranches.filter((b) => !b.steps).length > 1 && ( +
+
+ or +
+
+ )} +
- - {/* Subtle separator + Generate All */} - {selectedBranches.filter((b) => !b.steps).length > 1 && ( - <> -
- {isGeneratingAll ? ( - - ) : ( - - )} - - )}
)} diff --git a/frontend/src/components/ai-builder/BranchSelector.tsx b/frontend/src/components/ai-builder/BranchSelector.tsx index 458f5b80..f939c024 100644 --- a/frontend/src/components/ai-builder/BranchSelector.tsx +++ b/frontend/src/components/ai-builder/BranchSelector.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { GripVertical, Plus, X, Pencil, Check } from 'lucide-react' +import { GripVertical, Plus, X, Pencil, Check, RefreshCw } from 'lucide-react' import { useAIFlowBuilderStore } from '@/store/aiFlowBuilderStore' import { cn } from '@/lib/utils' import type { AIBranch } from '@/types' @@ -10,6 +10,8 @@ export function BranchSelector() { selectedBranches, selectBranches, setPhase, + scaffold, + isLoading, error, } = useAIFlowBuilderStore() @@ -73,10 +75,20 @@ export function BranchSelector() { return (
-
+

AI suggested {suggestedBranches.length} branches. Select, reorder, rename, or add your own.

+
{/* Branch list */} diff --git a/frontend/src/components/ai-builder/GeneratingAnimation.tsx b/frontend/src/components/ai-builder/GeneratingAnimation.tsx index 834e9ffe..a9794bac 100644 --- a/frontend/src/components/ai-builder/GeneratingAnimation.tsx +++ b/frontend/src/components/ai-builder/GeneratingAnimation.tsx @@ -1,62 +1,24 @@ -import { useEffect, useState } from 'react' -import { cn } from '@/lib/utils' - -const MESSAGES = [ - 'Setting up your flow...', - 'Building diagnostic paths...', - 'Putting the pieces in place...', - 'Almost there...', -] as const - -const MESSAGE_DURATIONS = [4000, 8000, 8000, Infinity] // ms each message shows - interface GeneratingAnimationProps { branchContext?: { current: number; total: number } } export function GeneratingAnimation({ branchContext }: GeneratingAnimationProps) { - const [messageIndex, setMessageIndex] = useState(0) - - // Reset and advance message on mount/remount - useEffect(() => { - setMessageIndex(0) - let current = 0 - - const advance = () => { - current += 1 - if (current < MESSAGES.length - 1) { - setMessageIndex(current) - timer = setTimeout(advance, MESSAGE_DURATIONS[current]) - } else { - setMessageIndex(MESSAGES.length - 1) - } - } - - let timer = setTimeout(advance, MESSAGE_DURATIONS[0]) - return () => clearTimeout(timer) - }, []) - return (
{/* Spinner */}
{/* Branch context (Generate All mode) */} - {branchContext && ( -

- Branch {branchContext.current} of {branchContext.total} -

+ {branchContext ? ( + <> +

+ Branch {branchContext.current} of {branchContext.total} +

+

Generating branch detail...

+ + ) : ( +

Generating...

)} - - {/* Rotating message */} -

- {MESSAGES[messageIndex]} -

) } diff --git a/frontend/src/components/ai-builder/TreePreviewCard.tsx b/frontend/src/components/ai-builder/TreePreviewCard.tsx index 91a383b7..730ca20d 100644 --- a/frontend/src/components/ai-builder/TreePreviewCard.tsx +++ b/frontend/src/components/ai-builder/TreePreviewCard.tsx @@ -1,13 +1,15 @@ -import { GitBranch, Layers, CheckCircle, ArrowRight, RotateCcw } from 'lucide-react' +import { GitBranch, Layers, CheckCircle, ArrowRight, RotateCcw, Play } from 'lucide-react' import { useAIFlowBuilderStore } from '@/store/aiFlowBuilderStore' import { cn } from '@/lib/utils' interface TreePreviewCardProps { onOpenInEditor: () => void + onStartFlow: () => void + onBuildAnother: () => void } -export function TreePreviewCard({ onOpenInEditor }: TreePreviewCardProps) { - const { assembledTree, reset, isLoading } = useAIFlowBuilderStore() +export function TreePreviewCard({ onOpenInEditor, onStartFlow, onBuildAnother }: TreePreviewCardProps) { + const { assembledTree, isLoading } = useAIFlowBuilderStore() if (!assembledTree) return null @@ -58,27 +60,38 @@ export function TreePreviewCard({ onOpenInEditor }: TreePreviewCardProps) { )} {/* Actions */} -
+
- +
+ + +
)