import { useState, useCallback, useEffect } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useNodesState, useEdgesState, addEdge } from 'reactflow' import { api } from '../utils/api' import { validateWorkflowGraph } from '../utils/workflowValidation' import { serializeToWorkflowGraph, deserializeFromWorkflowGraph } from '../utils/workflowSerializer' import { WorkflowCanvas } from '../components/workflow/WorkflowCanvas' import { StartNode } from '../components/workflow/nodes/StartNode' import { EndNode } from '../components/workflow/nodes/EndNode' import { AnalysisNode } from '../components/workflow/nodes/AnalysisNode' import { LogicNode } from '../components/workflow/nodes/LogicNode' import { JoinNode } from '../components/workflow/nodes/JoinNode' import { QuestionAugmentationPanel } from '../components/workflow/panels/QuestionAugmentationPanel' import { LogicExpressionEditor } from '../components/workflow/panels/LogicExpressionEditor' import { FallbackConfig } from '../components/workflow/panels/FallbackConfig' import { JoinConfig } from '../components/workflow/panels/JoinConfig' import '../styles/workflowEditor.css' // Node-Type Mapping const nodeTypes = { start: StartNode, end: EndNode, analysis: AnalysisNode, logic: LogicNode, join: JoinNode } let nodeIdCounter = 1 export default function WorkflowEditorPage() { const navigate = useNavigate() const { id } = useParams() // prompt_id wenn vorhanden // State const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const [selectedNodeId, setSelectedNodeId] = useState(null) const selectedNode = selectedNodeId ? nodes.find(n => n.id === selectedNodeId) : null const [currentPrompt, setCurrentPrompt] = useState(null) const [workflowName, setWorkflowName] = useState('Neuer Workflow') const [workflowDescription, setWorkflowDescription] = useState('') const [validationErrors, setValidationErrors] = useState([]) const [validationWarnings, setValidationWarnings] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [availablePrompts, setAvailablePrompts] = useState([]) // Load available basis prompts for Analysis nodes useEffect(() => { async function loadPrompts() { try { const prompts = await api.listAdminPrompts() // Filter nur type='base' Prompts const basisPrompts = prompts.filter(p => p.type === 'base') setAvailablePrompts(basisPrompts) } catch (e) { console.error('Failed to load prompts:', e) } } loadPrompts() }, []) // Load workflow wenn ID vorhanden useEffect(() => { if (id && id !== 'new') { console.log('π useEffect: Loading workflow with ID:', id) loadWorkflow(id) // UUID as string, no parseInt! } }, [id]) // Auto-Validation useEffect(() => { const { errors, warnings } = validateWorkflowGraph(nodes, edges) setValidationErrors(errors) setValidationWarnings(warnings) }, [nodes, edges]) // ββ Handlers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ const onConnect = useCallback( (params) => setEdges((eds) => addEdge(params, eds)), [setEdges] ) const onNodeClick = useCallback((event, node) => { setSelectedNodeId(node.id) }, []) const handleAddNode = (nodeType) => { const newNode = { id: `node_${nodeIdCounter++}`, type: nodeType, position: { x: 250, y: 100 + nodes.length * 100 }, data: { label: `${nodeType.charAt(0).toUpperCase() + nodeType.slice(1)} ${nodeIdCounter - 1}` } } setNodes((nds) => [...nds, newNode]) } const handleNodeUpdate = (nodeId, updates) => { console.log('π§ handleNodeUpdate:', { nodeId, updates }) setNodes((nds) => { const updated = nds.map((n) => (n.id === nodeId ? { ...n, data: { ...n.data, ...updates } } : n)) console.log('π Nodes after update:', updated.find(n => n.id === nodeId)) return updated }) } const handleDeleteNode = () => { if (!selectedNode) return setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id)) setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id)) setSelectedNodeId(null) } const handleSave = async () => { console.log('πΎ handleSave called') try { setLoading(true) setError(null) // Validierung const { errors, isValid } = validateWorkflowGraph(nodes, edges) if (!isValid) { setError(`Validierung fehlgeschlagen: ${errors.length} Fehler gefunden`) return } // Serialisieren const graph_data = serializeToWorkflowGraph(nodes, edges, { created_at: currentPrompt?.created_at, version: '1.0' }) console.log('π Serialized graph_data:', graph_data) if (currentPrompt) { // Update existing console.log('π Updating existing workflow:', currentPrompt.id) await api.updateUnifiedPrompt(currentPrompt.id, { type: 'workflow', name: workflowName, description: workflowDescription, graph_data }) alert('Workflow gespeichert!') } else { // Create new console.log('β¨ Creating new workflow') const result = await api.createUnifiedPrompt({ type: 'workflow', name: workflowName, description: workflowDescription, graph_data }) console.log('β Workflow created:', result) setCurrentPrompt({ id: result.id, name: workflowName }) alert('Workflow erstellt!') console.log('π Navigating to:', `/workflow-editor/${result.id}`) navigate(`/workflow-editor/${result.id}`) } } catch (e) { console.error('β handleSave error:', e) setError(e.message) } finally { setLoading(false) } } const loadWorkflow = async (promptId) => { console.log('π¦ loadWorkflow called with:', promptId) try { setLoading(true) setError(null) const prompt = await api.getPrompt(promptId) console.log('β Prompt loaded:', prompt) console.log('π graph_data:', prompt.graph_data) if (prompt.type !== 'workflow') { throw new Error('Nicht ein Workflow') } // Deserialisieren const { nodes: loadedNodes, edges: loadedEdges } = deserializeFromWorkflowGraph(prompt.graph_data) console.log('π― Deserialized:', { nodes: loadedNodes, edges: loadedEdges }) setNodes(loadedNodes) setEdges(loadedEdges) setCurrentPrompt(prompt) setWorkflowName(prompt.name) setWorkflowDescription(prompt.description || '') // nodeIdCounter aktualisieren const maxId = Math.max( ...loadedNodes.map((n) => parseInt(n.id.replace('node_', '')) || 0), 0 ) nodeIdCounter = maxId + 1 console.log('β Workflow loaded successfully, nodes:', loadedNodes.length, 'edges:', loadedEdges.length) } catch (e) { console.error('β loadWorkflow error:', e) setError(e.message) } finally { setLoading(false) } } const handleValidate = () => { const { errors, warnings } = validateWorkflowGraph(nodes, edges) setValidationErrors(errors) setValidationWarnings(warnings) if (errors.length === 0) { alert(`β Workflow ist valide!\n\n${warnings.length} Warnungen`) } else { alert(`β Validierung fehlgeschlagen!\n\n${errors.length} Fehler, ${warnings.length} Warnungen`) } } const handleNew = () => { if (confirm('Neuen Workflow erstellen? Ungespeicherte Γnderungen gehen verloren.')) { setNodes([]) setEdges([]) setCurrentPrompt(null) setWorkflowName('Neuer Workflow') setWorkflowDescription('') setSelectedNodeId(null) navigate('/workflow-editor/new') } } const handleDelete = async () => { if (!currentPrompt) return if (!confirm(`Workflow "${workflowName}" wirklich lΓΆschen?`)) return try { setLoading(true) await api.deletePrompt(currentPrompt.id) alert('Workflow gelΓΆscht') navigate('/admin/prompts') } catch (e) { setError(e.message) } finally { setLoading(false) } } // ββ Render ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ return (