feat: Phase 3 — Library page create dropdown + AI Builder + pin wiring
- Replace single Create link with dropdown menu (3 flow types + AI Builder) - Wire pinnedFlowsStore to all view components - AI Builder modal integration via useCachedQuota hook Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react'
|
import { useEffect, useState, useCallback } from 'react'
|
||||||
import { useNavigate, Link, useSearchParams } from 'react-router-dom'
|
import { useNavigate, Link, useSearchParams } from 'react-router-dom'
|
||||||
import { Plus, X, RotateCcw, Play } from 'lucide-react'
|
import { Plus, X, RotateCcw, Play, ChevronDown, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
|
||||||
import { treesApi } from '@/api/trees'
|
import { treesApi } from '@/api/trees'
|
||||||
import { categoriesApi } from '@/api/categories'
|
import { categoriesApi } from '@/api/categories'
|
||||||
import { foldersApi } from '@/api/folders'
|
import { foldersApi } from '@/api/folders'
|
||||||
@@ -17,6 +17,9 @@ import { cn, safeGetItem } from '@/lib/utils'
|
|||||||
import { getSessionResumePath } from '@/lib/routing'
|
import { getSessionResumePath } from '@/lib/routing'
|
||||||
import { usePermissions } from '@/hooks/usePermissions'
|
import { usePermissions } from '@/hooks/usePermissions'
|
||||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||||
|
import { usePinnedFlowsStore, selectPinnedTreeIds, selectPinLoadingTreeIds } from '@/store/pinnedFlowsStore'
|
||||||
|
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
||||||
|
import { AIFlowBuilderModal } from '@/components/ai-builder/AIFlowBuilderModal'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
|
|
||||||
export function TreeLibraryPage() {
|
export function TreeLibraryPage() {
|
||||||
@@ -69,6 +72,17 @@ export function TreeLibraryPage() {
|
|||||||
// Fork state
|
// Fork state
|
||||||
const [isForkingTree, setIsForkingTree] = useState(false)
|
const [isForkingTree, setIsForkingTree] = useState(false)
|
||||||
|
|
||||||
|
// Create menu & AI builder state
|
||||||
|
const [showCreateMenu, setShowCreateMenu] = useState(false)
|
||||||
|
const [showAIBuilder, setShowAIBuilder] = useState(false)
|
||||||
|
const { aiEnabled } = useCachedQuota()
|
||||||
|
|
||||||
|
// Pin store
|
||||||
|
const pinnedTreeIds = usePinnedFlowsStore(selectPinnedTreeIds)
|
||||||
|
const pinLoadingTreeIds = usePinnedFlowsStore(selectPinLoadingTreeIds)
|
||||||
|
const togglePin = usePinnedFlowsStore((s) => s.toggle)
|
||||||
|
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
||||||
|
|
||||||
// Repeat Last Session
|
// Repeat Last Session
|
||||||
const lastSessionData = (() => {
|
const lastSessionData = (() => {
|
||||||
const raw = safeGetItem('last-session')
|
const raw = safeGetItem('last-session')
|
||||||
@@ -102,6 +116,9 @@ export function TreeLibraryPage() {
|
|||||||
.catch((err) => console.error('Failed to load incomplete sessions:', err))
|
.catch((err) => console.error('Failed to load incomplete sessions:', err))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Load pinned flows
|
||||||
|
useEffect(() => { loadPinned() }, [loadPinned])
|
||||||
|
|
||||||
const dismissSession = (sessionId: string) => {
|
const dismissSession = (sessionId: string) => {
|
||||||
const next = new Set(dismissedSessionIds)
|
const next = new Set(dismissedSessionIds)
|
||||||
next.add(sessionId)
|
next.add(sessionId)
|
||||||
@@ -263,16 +280,78 @@ export function TreeLibraryPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{canCreateTrees && (
|
{canCreateTrees && (
|
||||||
<Link
|
<div className="relative">
|
||||||
to={typeFilter === 'procedural' ? '/flows/new' : typeFilter === 'maintenance' ? '/flows/new?type=maintenance' : '/trees/new'}
|
<button
|
||||||
className={cn(
|
onClick={() => setShowCreateMenu(!showCreateMenu)}
|
||||||
'flex items-center gap-2 rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
className={cn(
|
||||||
'hover:opacity-90'
|
'flex items-center gap-2 rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
||||||
|
'hover:opacity-90'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Create New
|
||||||
|
<ChevronDown className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
{showCreateMenu && (
|
||||||
|
<>
|
||||||
|
<div className="fixed inset-0 z-10" onClick={() => setShowCreateMenu(false)} />
|
||||||
|
<div className="absolute right-0 z-20 mt-1 w-56 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-sm">
|
||||||
|
<Link
|
||||||
|
to="/trees/new"
|
||||||
|
onClick={() => setShowCreateMenu(false)}
|
||||||
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
|
>
|
||||||
|
<FolderTree className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">Troubleshooting Tree</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Branching decision flow</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/flows/new"
|
||||||
|
onClick={() => setShowCreateMenu(false)}
|
||||||
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
|
>
|
||||||
|
<ListOrdered className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">Procedural Flow</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Step-by-step procedure</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/flows/new?type=maintenance"
|
||||||
|
onClick={() => setShowCreateMenu(false)}
|
||||||
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
|
>
|
||||||
|
<Wrench className="h-4 w-4 text-amber-400" />
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">Maintenance Flow</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Scheduled multi-target tasks</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
{aiEnabled && (
|
||||||
|
<>
|
||||||
|
<div className="my-1 border-t border-border" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setShowCreateMenu(false)
|
||||||
|
setShowAIBuilder(true)
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
|
>
|
||||||
|
<Sparkles className="h-4 w-4 text-primary" />
|
||||||
|
<div className="text-left">
|
||||||
|
<div className="font-medium">Build with AI</div>
|
||||||
|
<div className="text-xs text-muted-foreground">AI-assisted flow creation</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
</div>
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
{typeFilter === 'procedural' ? 'New Project' : typeFilter === 'maintenance' ? 'New Maintenance Flow' : 'Create Flow'}
|
|
||||||
</Link>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -474,6 +553,9 @@ export function TreeLibraryPage() {
|
|||||||
setShowDeleteConfirm(true)
|
setShowDeleteConfirm(true)
|
||||||
}}
|
}}
|
||||||
onForkTree={handleForkTree}
|
onForkTree={handleForkTree}
|
||||||
|
pinnedTreeIds={pinnedTreeIds}
|
||||||
|
onTogglePin={togglePin}
|
||||||
|
pinLoadingTreeIds={pinLoadingTreeIds}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{treeLibraryView === 'list' && (
|
{treeLibraryView === 'list' && (
|
||||||
@@ -487,6 +569,9 @@ export function TreeLibraryPage() {
|
|||||||
setShowDeleteConfirm(true)
|
setShowDeleteConfirm(true)
|
||||||
}}
|
}}
|
||||||
onForkTree={handleForkTree}
|
onForkTree={handleForkTree}
|
||||||
|
pinnedTreeIds={pinnedTreeIds}
|
||||||
|
onTogglePin={togglePin}
|
||||||
|
pinLoadingTreeIds={pinLoadingTreeIds}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{treeLibraryView === 'table' && (
|
{treeLibraryView === 'table' && (
|
||||||
@@ -505,6 +590,9 @@ export function TreeLibraryPage() {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
onForkTree={handleForkTree}
|
onForkTree={handleForkTree}
|
||||||
|
pinnedTreeIds={pinnedTreeIds}
|
||||||
|
onTogglePin={togglePin}
|
||||||
|
pinLoadingTreeIds={pinLoadingTreeIds}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -538,6 +626,14 @@ export function TreeLibraryPage() {
|
|||||||
confirmVariant="destructive"
|
confirmVariant="destructive"
|
||||||
isLoading={isDeleting}
|
isLoading={isDeleting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* AI Builder Modal */}
|
||||||
|
{showAIBuilder && (
|
||||||
|
<AIFlowBuilderModal
|
||||||
|
isOpen={showAIBuilder}
|
||||||
|
onClose={() => setShowAIBuilder(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user