From 52d253642e5410e58a2acd5dc11c7e6a3b709c6b Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sat, 7 Mar 2026 00:15:20 -0500 Subject: [PATCH] refactor: remove standalone Flow Assist page and old AI chat components Remove the old /ai/chat page, AI wizard modal, and all associated components/stores/types now replaced by the editor-embedded AI panel. Deleted: - AIChatBuilderPage, ai-chat/ components, aiChatStore, aiChat API, ai-chat types - AIFlowBuilderModal, ai-builder/ components, aiFlowBuilderStore Cleaned up: - Router (removed /ai/chat route) - Sidebar (removed Flow Assist nav item) - MyTreesPage (removed AI builder modal and button) - TreeLibraryPage (removed Flow Assist button) - API and type barrel exports Co-Authored-By: Claude Opus 4.6 --- frontend/src/api/aiChat.ts | 44 --- frontend/src/api/index.ts | 1 - .../ai-builder/AIFlowBuilderModal.tsx | 168 ---------- .../ai-builder/BranchDetailView.tsx | 259 ---------------- .../components/ai-builder/BranchSelector.tsx | 292 ------------------ .../components/ai-builder/FoundationForm.tsx | 163 ---------- .../ai-builder/GeneratingAnimation.tsx | 24 -- .../components/ai-builder/QuotaDisplay.tsx | 48 --- .../components/ai-builder/TreePreviewCard.tsx | 98 ------ .../ai-builder/WizardStepIndicator.tsx | 70 ----- frontend/src/components/ai-chat/ChatInput.tsx | 76 ----- .../src/components/ai-chat/ChatMessage.tsx | 41 --- frontend/src/components/ai-chat/ChatPanel.tsx | 47 --- .../src/components/ai-chat/ChatToolbar.tsx | 98 ------ .../src/components/ai-chat/EmptyPreview.tsx | 13 - .../src/components/ai-chat/PhaseIndicator.tsx | 50 --- .../components/ai-chat/StaticStepsPreview.tsx | 112 ------- .../components/ai-chat/StaticTreePreview.tsx | 80 ----- frontend/src/components/layout/Sidebar.tsx | 4 +- frontend/src/pages/AIChatBuilderPage.tsx | 167 ---------- frontend/src/pages/MyTreesPage.tsx | 34 +- frontend/src/pages/TreeLibraryPage.tsx | 11 +- frontend/src/router.tsx | 9 - frontend/src/store/aiChatStore.ts | 197 ------------ frontend/src/store/aiFlowBuilderStore.ts | 237 -------------- frontend/src/types/ai-chat.ts | 43 --- frontend/src/types/index.ts | 9 - 27 files changed, 3 insertions(+), 2392 deletions(-) delete mode 100644 frontend/src/api/aiChat.ts delete mode 100644 frontend/src/components/ai-builder/AIFlowBuilderModal.tsx delete mode 100644 frontend/src/components/ai-builder/BranchDetailView.tsx delete mode 100644 frontend/src/components/ai-builder/BranchSelector.tsx delete mode 100644 frontend/src/components/ai-builder/FoundationForm.tsx delete mode 100644 frontend/src/components/ai-builder/GeneratingAnimation.tsx delete mode 100644 frontend/src/components/ai-builder/QuotaDisplay.tsx delete mode 100644 frontend/src/components/ai-builder/TreePreviewCard.tsx delete mode 100644 frontend/src/components/ai-builder/WizardStepIndicator.tsx delete mode 100644 frontend/src/components/ai-chat/ChatInput.tsx delete mode 100644 frontend/src/components/ai-chat/ChatMessage.tsx delete mode 100644 frontend/src/components/ai-chat/ChatPanel.tsx delete mode 100644 frontend/src/components/ai-chat/ChatToolbar.tsx delete mode 100644 frontend/src/components/ai-chat/EmptyPreview.tsx delete mode 100644 frontend/src/components/ai-chat/PhaseIndicator.tsx delete mode 100644 frontend/src/components/ai-chat/StaticStepsPreview.tsx delete mode 100644 frontend/src/components/ai-chat/StaticTreePreview.tsx delete mode 100644 frontend/src/pages/AIChatBuilderPage.tsx delete mode 100644 frontend/src/store/aiChatStore.ts delete mode 100644 frontend/src/store/aiFlowBuilderStore.ts delete mode 100644 frontend/src/types/ai-chat.ts diff --git a/frontend/src/api/aiChat.ts b/frontend/src/api/aiChat.ts deleted file mode 100644 index 1722b272..00000000 --- a/frontend/src/api/aiChat.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { apiClient } from './client' -import type { - AIChatStartResponse, - AIChatMessageResponse, - AIChatSessionResponse, - AIChatGenerateResponse, - AIChatImportResponse, -} from '@/types' - -export const aiChatApi = { - startSession: async (flowType: 'troubleshooting' | 'procedural'): Promise => { - const { data } = await apiClient.post('/ai/chat/sessions', { flow_type: flowType }) - return data - }, - - sendMessage: async (sessionId: string, content: string): Promise => { - const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/messages`, { content }) - return data - }, - - getSession: async (sessionId: string): Promise => { - const { data } = await apiClient.get(`/ai/chat/sessions/${sessionId}`) - return data - }, - - generateTree: async (sessionId: string): Promise => { - const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/generate`) - return data - }, - - importTree: async ( - sessionId: string, - params?: { name?: string; description?: string; category_id?: string; tags?: string[] } - ): Promise => { - const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/import`, params || {}) - return data - }, - - abandonSession: async (sessionId: string): Promise => { - await apiClient.delete(`/ai/chat/sessions/${sessionId}`) - }, -} - -export default aiChatApi diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 420aafb0..b2f866ba 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -17,7 +17,6 @@ export { targetListsApi } from './targetLists' export { maintenanceSchedulesApi, batchLaunchApi } from './maintenanceSchedules' export { default as feedbackApi } from './feedback' export { default as aiBuilderApi } from './aiBuilder' -export { default as aiChatApi } from './aiChat' export { copilotApi } from './copilot' export { assistantChatApi } from './assistantChat' export { flowTransferApi } from './flowTransfer' diff --git a/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx b/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx deleted file mode 100644 index 77d84f7b..00000000 --- a/frontend/src/components/ai-builder/AIFlowBuilderModal.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useEffect, useRef } from 'react' -import { useNavigate } from 'react-router-dom' -import { Modal } from '@/components/common/Modal' -import { useAIFlowBuilderStore } from '@/store/aiFlowBuilderStore' -import { treesApi } from '@/api/trees' -import { toast } from '@/lib/toast' -import { WizardStepIndicator } from './WizardStepIndicator' -import { FoundationForm } from './FoundationForm' -import { BranchSelector } from './BranchSelector' -import { BranchDetailView } from './BranchDetailView' -import { TreePreviewCard } from './TreePreviewCard' -import { GeneratingAnimation } from './GeneratingAnimation' - -interface AIFlowBuilderModalProps { - isOpen: boolean - onClose: () => void -} - -export function AIFlowBuilderModal({ isOpen, onClose }: AIFlowBuilderModalProps) { - const navigate = useNavigate() - const { - phase, - metadata, - assembledTree, - loadQuota, - scaffold, - reset, - } = useAIFlowBuilderStore() - - // Load quota when modal opens - useEffect(() => { - if (isOpen) { - loadQuota() - } - }, [isOpen, loadQuota]) - - // Auto-trigger scaffold after conversation starts (ref prevents double-fire) - const hasTriggeredScaffold = useRef(false) - useEffect(() => { - // Reset guard when wizard resets to foundation (Start Over or close) - if (phase === 'foundation') { - hasTriggeredScaffold.current = false - return - } - if (phase === 'scaffolding' && !hasTriggeredScaffold.current && !useAIFlowBuilderStore.getState().suggestedBranches.length) { - hasTriggeredScaffold.current = true - scaffold() - } - }, [phase, scaffold]) - - const handleClose = () => { - reset() - onClose() - } - - const createTree = async () => { - if (!assembledTree) return null - try { - return await treesApi.create({ - name: assembledTree.suggested_name, - description: assembledTree.suggested_description, - tree_structure: assembledTree.tree_structure, - tree_type: metadata.flow_type, - status: 'draft', - }) - } 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': - return 'Flow Assist' - case 'scaffolding': - case 'generating': - return 'AI Scaffold' - case 'detailing': - return 'Branch Detail' - case 'reviewing': - return 'Review & Assemble' - case 'error': - return 'Flow Assist' - default: - return 'Flow Assist' - } - } - - return ( - - } - > - {phase === 'foundation' && } - {phase === 'scaffolding' && } - {phase === 'generating' && } - {phase === 'detailing' && } - {phase === 'reviewing' && ( - - )} - {phase === 'error' && } - - ) -} - -function ErrorView() { - const { error, reset, setPhase } = useAIFlowBuilderStore() - - return ( -
-
- {error || 'An unexpected error occurred.'} -
-
- - -
-
- ) -} diff --git a/frontend/src/components/ai-builder/BranchDetailView.tsx b/frontend/src/components/ai-builder/BranchDetailView.tsx deleted file mode 100644 index 0f004a61..00000000 --- a/frontend/src/components/ai-builder/BranchDetailView.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import { Check, RefreshCw, SkipForward, ChevronRight, ChevronLeft, Zap, Square } from 'lucide-react' -import { useAIFlowBuilderStore } from '@/store/aiFlowBuilderStore' -import { GeneratingAnimation } from './GeneratingAnimation' -import { cn } from '@/lib/utils' - -export function BranchDetailView() { - const { - selectedBranches, - currentBranchIndex, - generateBranchDetail, - assemble, - isLoading, - error, - phase, - setError, - isGeneratingAll, - generateAllBranchDetails, - cancelGenerateAll, - } = useAIFlowBuilderStore() - - const viewingIndex = currentBranchIndex - const setViewingIndex = (i: number) => useAIFlowBuilderStore.setState({ currentBranchIndex: i }) - const currentBranch = selectedBranches[viewingIndex] - - const allBranchesHaveDetail = selectedBranches.every((b) => b.steps) - const branchesWithDetail = selectedBranches.filter((b) => b.steps).length - - const handleGenerate = async (branchName: string) => { - setError(null) - await generateBranchDetail(branchName) - } - - const handleAssemble = async () => { - await assemble() - } - - if (phase === 'generating' && isLoading) { - return ( - b.steps).length + 1, - total: selectedBranches.length, - } - : undefined - } - /> - ) - } - - return ( -
- - {/* Content area */} -
- {/* Branch tabs */} -
- {selectedBranches.map((branch, i) => ( - - ))} -
- - {/* Current branch detail */} - {currentBranch && ( -
-
-
-

{currentBranch.name}

-

{currentBranch.description}

-
-
- - {currentBranch.steps ? ( -
- {/* Mini tree preview */} -
- -
- -
- -
-
- ) : ( -
-

- 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 -
-
- )} - -
- - -
-
- )} -
- )} - - {/* Error */} - {error && ( -
- {error} -
- )} -
- - {/* Navigation — sticky so it's always visible */} -
-
- - -
- -
- - {branchesWithDetail}/{selectedBranches.length} detailed - - -
-
-
- ) -} - -/** Recursive mini-preview of a node tree */ -function NodePreview({ node, depth }: { node: Record; depth: number }) { - const type = node.type as string - const label = - type === 'decision' - ? (node.question as string) - : (node.title as string) || 'Untitled' - const children = (node.children as Record[]) || [] - - const typeColors: Record = { - decision: 'bg-blue-400', - action: 'bg-amber-400', - solution: 'bg-green-400', - } - - return ( -
-
-
- {label} - {type} -
- {children.map((child) => ( - - ))} -
- ) -} diff --git a/frontend/src/components/ai-builder/BranchSelector.tsx b/frontend/src/components/ai-builder/BranchSelector.tsx deleted file mode 100644 index f939c024..00000000 --- a/frontend/src/components/ai-builder/BranchSelector.tsx +++ /dev/null @@ -1,292 +0,0 @@ -import { useState } from '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' - -export function BranchSelector() { - const { - suggestedBranches, - selectedBranches, - selectBranches, - setPhase, - scaffold, - isLoading, - error, - } = useAIFlowBuilderStore() - - const [editingIndex, setEditingIndex] = useState(null) - const [editName, setEditName] = useState('') - const [editDesc, setEditDesc] = useState('') - const [showAddForm, setShowAddForm] = useState(false) - const [newName, setNewName] = useState('') - const [newDesc, setNewDesc] = useState('') - - const toggleBranch = (branch: AIBranch) => { - const isSelected = selectedBranches.some((b) => b.name === branch.name) - if (isSelected) { - selectBranches(selectedBranches.filter((b) => b.name !== branch.name)) - } else { - selectBranches([...selectedBranches, branch]) - } - } - - const startEditing = (index: number) => { - const branch = selectedBranches[index] - setEditingIndex(index) - setEditName(branch.name) - setEditDesc(branch.description) - } - - const saveEdit = () => { - if (editingIndex === null || !editName.trim()) return - const updated = [...selectedBranches] - updated[editingIndex] = { - ...updated[editingIndex], - name: editName.trim(), - description: editDesc.trim(), - } - selectBranches(updated) - setEditingIndex(null) - } - - const addCustomBranch = () => { - if (!newName.trim()) return - const branch: AIBranch = { - name: newName.trim(), - description: newDesc.trim(), - isCustom: true, - } - selectBranches([...selectedBranches, branch]) - setNewName('') - setNewDesc('') - setShowAddForm(false) - } - - const moveBranch = (fromIndex: number, direction: 'up' | 'down') => { - const toIndex = direction === 'up' ? fromIndex - 1 : fromIndex + 1 - if (toIndex < 0 || toIndex >= selectedBranches.length) return - const updated = [...selectedBranches] - ;[updated[fromIndex], updated[toIndex]] = [updated[toIndex], updated[fromIndex]] - selectBranches(updated) - } - - const canProceed = selectedBranches.length >= 2 - - return ( -
-
-

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

- -
- - {/* Branch list */} -
- {suggestedBranches.map((branch) => { - const isSelected = selectedBranches.some((b) => b.name === branch.name) - const selectedIndex = selectedBranches.findIndex((b) => b.name === branch.name) - - return ( -
toggleBranch(branch)} - > -
- {isSelected && } -
-
- {editingIndex !== null && selectedIndex === editingIndex ? ( -
e.stopPropagation()}> - setEditName(e.target.value)} - className="w-full rounded border border-border bg-card px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none" - autoFocus - /> - setEditDesc(e.target.value)} - className="w-full rounded border border-border bg-card px-2 py-1 text-xs text-muted-foreground focus:border-primary focus:outline-none" - /> -
- - -
-
- ) : ( - <> -
{branch.name}
-
{branch.description}
- - )} -
- {isSelected && editingIndex !== selectedIndex && ( -
e.stopPropagation()}> - - -
- )} -
- ) - })} - - {/* Custom branches (not in suggested) */} - {selectedBranches - .filter((b) => b.isCustom) - .map((branch, i) => { - return ( -
-
- -
-
-
{branch.name}
-
{branch.description}
- - Custom - -
- -
- ) - })} -
- - {/* Add custom branch */} - {showAddForm ? ( -
- setNewName(e.target.value)} - placeholder="Branch name" - className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none" - autoFocus - /> - setNewDesc(e.target.value)} - placeholder="Brief description" - className="w-full rounded border border-border bg-card px-2 py-1.5 text-xs text-muted-foreground placeholder:text-muted-foreground/60 focus:border-primary focus:outline-none" - /> -
- - -
-
- ) : ( - - )} - - {/* Error */} - {error && ( -
- {error} -
- )} - - {/* Footer */} -
- - {selectedBranches.length} branch{selectedBranches.length !== 1 ? 'es' : ''} selected (min 2) - - -
-
- ) -} diff --git a/frontend/src/components/ai-builder/FoundationForm.tsx b/frontend/src/components/ai-builder/FoundationForm.tsx deleted file mode 100644 index ef07dff4..00000000 --- a/frontend/src/components/ai-builder/FoundationForm.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { useState } from 'react' -import { useAIFlowBuilderStore } from '@/store/aiFlowBuilderStore' -import { QuotaDisplay } from './QuotaDisplay' -import { cn } from '@/lib/utils' - -export function FoundationForm() { - const { metadata, setMetadata, quota, start, isLoading, error } = useAIFlowBuilderStore() - const [tagInput, setTagInput] = useState('') - - const canSubmit = - metadata.name.trim().length > 0 && - metadata.description.trim().length > 0 && - !isLoading && - (quota?.allowed !== false) - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - if (!canSubmit) return - await start() - } - - const addTag = () => { - const tag = tagInput.trim() - if (tag && !metadata.environment_tags.includes(tag)) { - setMetadata({ environment_tags: [...metadata.environment_tags, tag] }) - } - setTagInput('') - } - - const removeTag = (tag: string) => { - setMetadata({ environment_tags: metadata.environment_tags.filter((t) => t !== tag) }) - } - - return ( -
- {quota && } - - {/* Flow Type */} -
- -
- {(['troubleshooting', 'procedural'] as const).map((type) => ( - - ))} -
-
- - {/* Name */} -
- - setMetadata({ name: e.target.value })} - placeholder="e.g. DNS Resolution Failures" - className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20" - maxLength={255} - /> -
- - {/* Description */} -
- -