'use client' import { useMemo } from 'react' import type { Node, Edge } from 'reactflow' import { MarkerType } from 'reactflow' import { SDK_FLOW_STEPS, FLOW_PACKAGES, type SDKFlowStep } from '../flow-data' import { NODE_WIDTH, PACKAGE_X_OFFSET, STEP_Y_START, completionColor, getStepPosition, type PackageFilter, } from './helpers' export function useFlowGraph( packageFilter: PackageFilter, showDb: boolean, showRag: boolean, selectedStep: SDKFlowStep | null, ): { nodes: Node[]; edges: Edge[] } { return useMemo(() => { const nodes: Node[] = [] const edges: Edge[] = [] const visibleSteps = packageFilter === 'alle' ? SDK_FLOW_STEPS : SDK_FLOW_STEPS.filter(s => s.package === packageFilter) const visibleStepIds = new Set(visibleSteps.map(s => s.id)) // Step Nodes visibleSteps.forEach(step => { const pkg = FLOW_PACKAGES[step.package] const pos = getStepPosition(step) const isSelected = selectedStep?.id === step.id // Checkpoint badge text let badge = '' if (step.checkpointId) { badge = step.checkpointType === 'REQUIRED' ? ' *' : ' ~' if (step.checkpointReviewer && step.checkpointReviewer !== 'NONE') { badge += ` [${step.checkpointReviewer}]` } } nodes.push({ id: step.id, type: 'default', position: pos, data: { label: (
{step.nameShort}
{step.checkpointId || ''} {badge}
{step.completion !== undefined && (
{step.completion}%
)}
), }, style: { background: isSelected ? pkg.color.border : pkg.color.bg, color: isSelected ? 'white' : pkg.color.text, border: `2px solid ${pkg.color.border}`, borderRadius: '10px', padding: '8px 4px', minWidth: `${NODE_WIDTH}px`, maxWidth: `${NODE_WIDTH}px`, cursor: 'pointer', boxShadow: isSelected ? `0 0 16px ${pkg.color.border}` : '0 1px 3px rgba(0,0,0,0.08)', opacity: step.isOptional ? 0.85 : 1, borderStyle: step.isOptional ? 'dashed' : 'solid', }, }) }) // Prerequisite Edges visibleSteps.forEach(step => { step.prerequisiteSteps.forEach(preId => { if (visibleStepIds.has(preId)) { edges.push({ id: `e-${preId}-${step.id}`, source: preId, target: step.id, type: 'smoothstep', animated: selectedStep?.id === preId || selectedStep?.id === step.id, style: { stroke: selectedStep?.id === preId || selectedStep?.id === step.id ? '#7c3aed' : '#94a3b8', strokeWidth: selectedStep?.id === preId || selectedStep?.id === step.id ? 2.5 : 1.5, }, markerEnd: { type: MarkerType.ArrowClosed, color: selectedStep?.id === preId || selectedStep?.id === step.id ? '#7c3aed' : '#94a3b8', width: 14, height: 14, }, }) } }) }) // DB Table Nodes if (showDb) { const dbTablesInUse = new Set() visibleSteps.forEach(s => s.dbTables.forEach(t => dbTablesInUse.add(t))) let dbIdx = 0 dbTablesInUse.forEach(table => { const nodeId = `db-${table}` nodes.push({ id: nodeId, type: 'default', position: { x: -280, y: STEP_Y_START + dbIdx * 90 }, data: { label: (
DB
{table}
), }, style: { background: '#f1f5f9', color: '#475569', border: '2px solid #94a3b8', borderRadius: '50%', width: '90px', height: '90px', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '4px', }, }) // Connect steps to this DB table visibleSteps .filter(s => s.dbTables.includes(table)) .forEach(step => { edges.push({ id: `e-db-${table}-${step.id}`, source: nodeId, target: step.id, type: 'straight', style: { stroke: '#94a3b8', strokeWidth: 1, strokeDasharray: '6 3', }, labelStyle: { fontSize: 9, fill: '#94a3b8' }, label: step.dbMode !== 'none' ? step.dbMode : undefined, }) }) dbIdx++ }) } // RAG Collection Nodes if (showRag) { const ragInUse = new Set() visibleSteps.forEach(s => s.ragCollections.forEach(r => ragInUse.add(r))) let ragIdx = 0 ragInUse.forEach(collection => { const nodeId = `rag-${collection}` // Place RAG nodes to the right of all packages const rightX = (packageFilter === 'alle' ? 1280 : PACKAGE_X_OFFSET[packageFilter] || 0) + NODE_WIDTH + 180 nodes.push({ id: nodeId, type: 'default', position: { x: rightX, y: STEP_Y_START + ragIdx * 90 }, data: { label: (
RAG
{collection.replace('bp_', '')}
), }, style: { background: '#dcfce7', color: '#166534', border: '2px solid #22c55e', borderRadius: '50%', width: '90px', height: '90px', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '4px', }, }) // Connect steps to this RAG collection visibleSteps .filter(s => s.ragCollections.includes(collection)) .forEach(step => { edges.push({ id: `e-rag-${collection}-${step.id}`, source: nodeId, target: step.id, type: 'straight', style: { stroke: '#22c55e', strokeWidth: 1, strokeDasharray: '6 3', }, }) }) ragIdx++ }) } return { nodes, edges } }, [packageFilter, showDb, showRag, selectedStep]) }