feat: Add custom step continuation flow with save/use/branch options
Custom steps during tree navigation now support a complete workflow: - PostStepActionModal: Save for Later / Use Now / Both options - ContinuationModal: Pick descendant nodes or build custom branch - ForkTreeModal: Save modified tree as personal copy at completion - Custom steps are recorded in decisions array for export - Fix popular-tags API endpoint URL mismatch - Add aria-labels for accessibility on select/button elements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
139
frontend/src/components/session/ContinuationModal.tsx
Normal file
139
frontend/src/components/session/ContinuationModal.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Modal } from '@/components/common/Modal'
|
||||
import { ArrowRight, GitBranch, AlertTriangle, HelpCircle, Zap, CheckCircle } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { NodeType } from '@/types/tree'
|
||||
|
||||
export interface DescendantNode {
|
||||
id: string
|
||||
label: string
|
||||
type: NodeType
|
||||
parentOptionLabel: string
|
||||
}
|
||||
|
||||
interface ContinuationModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
descendantNodes: DescendantNode[]
|
||||
onSelectNode: (nodeId: string) => void
|
||||
onBuildCustomBranch: () => void
|
||||
}
|
||||
|
||||
const nodeTypeIcons: Record<NodeType, React.ReactNode> = {
|
||||
decision: <HelpCircle className="h-4 w-4 text-blue-500" />,
|
||||
action: <Zap className="h-4 w-4 text-amber-500" />,
|
||||
solution: <CheckCircle className="h-4 w-4 text-green-500" />
|
||||
}
|
||||
|
||||
const nodeTypeLabels: Record<NodeType, string> = {
|
||||
decision: 'Decision',
|
||||
action: 'Action',
|
||||
solution: 'Solution'
|
||||
}
|
||||
|
||||
export function ContinuationModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
descendantNodes,
|
||||
onSelectNode,
|
||||
onBuildCustomBranch
|
||||
}: ContinuationModalProps) {
|
||||
// Group nodes by parent option
|
||||
const groupedNodes = descendantNodes.reduce((acc, node) => {
|
||||
if (!acc[node.parentOptionLabel]) {
|
||||
acc[node.parentOptionLabel] = []
|
||||
}
|
||||
acc[node.parentOptionLabel].push(node)
|
||||
return acc
|
||||
}, {} as Record<string, DescendantNode[]>)
|
||||
|
||||
const hasDescendants = descendantNodes.length > 0
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Where does this lead?" size="lg">
|
||||
<div className="space-y-6">
|
||||
{/* Descendant Selection */}
|
||||
{hasDescendants && (
|
||||
<div>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
Select the next step in your troubleshooting path:
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{Object.entries(groupedNodes).map(([parentOption, nodes]) => (
|
||||
<div key={parentOption}>
|
||||
<p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
From: {parentOption}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{nodes.map(node => (
|
||||
<button
|
||||
key={node.id}
|
||||
onClick={() => onSelectNode(node.id)}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-3 rounded-lg border border-border p-3 text-left transition-colors',
|
||||
'hover:border-primary hover:bg-accent'
|
||||
)}
|
||||
>
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-muted">
|
||||
{nodeTypeIcons[node.type]}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium">{node.label}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{nodeTypeLabels[node.type]}
|
||||
</p>
|
||||
</div>
|
||||
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Divider */}
|
||||
{hasDescendants && (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Or
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Build Custom Branch */}
|
||||
<div>
|
||||
<button
|
||||
onClick={onBuildCustomBranch}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-3 rounded-lg border-2 border-dashed border-amber-500/50 bg-amber-500/5 p-4 text-left transition-colors',
|
||||
'hover:border-amber-500 hover:bg-amber-500/10'
|
||||
)}
|
||||
>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-amber-500/10">
|
||||
<GitBranch className="h-5 w-5 text-amber-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">Build Custom Branch</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Create your own troubleshooting path with custom steps
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Warning */}
|
||||
<div className="mt-3 flex items-start gap-2 rounded-md bg-amber-500/10 p-3">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-500" />
|
||||
<p className="text-sm text-amber-700 dark:text-amber-400">
|
||||
You'll need to complete this branch manually or mark the issue as resolved.
|
||||
Custom branches can be saved as a personal tree when your session ends.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user