diff --git a/frontend/src/components/WorkflowDebugPanel.jsx b/frontend/src/components/WorkflowDebugPanel.jsx new file mode 100644 index 0000000..fa0175b --- /dev/null +++ b/frontend/src/components/WorkflowDebugPanel.jsx @@ -0,0 +1,352 @@ +import React, { useState } from 'react' +import { ChevronDown, ChevronUp } from 'lucide-react' + +/** + * WorkflowDebugPanel - Zeigt Debug-Informationen für jeden Node eines Workflows + * + * @param {Object} props + * @param {Array} props.nodeStates - Array von NodeExecutionState Objekten + */ +export default function WorkflowDebugPanel({ nodeStates }) { + const [expandedNodes, setExpandedNodes] = useState(new Set()) + const [showAll, setShowAll] = useState(false) + + if (!nodeStates || nodeStates.length === 0) { + return null + } + + // Filter nodes that have debug information + const debugNodes = nodeStates.filter(ns => + ns.debug_prompt || ns.debug_raw_response || ns.debug_node_type + ) + + if (debugNodes.length === 0) { + return ( +
+

+ Keine Debug-Informationen verfügbar. Führe den Workflow mit debug=true aus. +

+
+ ) + } + + const toggleNode = (nodeId) => { + const newExpanded = new Set(expandedNodes) + if (newExpanded.has(nodeId)) { + newExpanded.delete(nodeId) + } else { + newExpanded.add(nodeId) + } + setExpandedNodes(newExpanded) + } + + const toggleAll = () => { + if (showAll) { + setExpandedNodes(new Set()) + } else { + setExpandedNodes(new Set(debugNodes.map(n => n.node_id))) + } + setShowAll(!showAll) + } + + const getStatusColor = (status) => { + switch (status) { + case 'executed': return '#1D9E75' + case 'failed': return '#D85A30' + case 'skipped': return '#888' + default: return 'var(--text2)' + } + } + + const getNodeLabel = (node) => { + if (node.debug_prompt_slug) return node.debug_prompt_slug + if (node.debug_node_type) return `${node.debug_node_type}-${node.node_id.substring(0, 8)}` + return node.node_id + } + + return ( +
+
+

+ 🔍 Debug-Informationen ({debugNodes.length} Nodes) +

+ +
+ +
+ {debugNodes.map((node, idx) => { + const isExpanded = expandedNodes.has(node.node_id) + const label = getNodeLabel(node) + const statusColor = getStatusColor(node.status) + + return ( +
+ {/* Node Header */} +
toggleNode(node.node_id)} + style={{ + padding: '12px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + cursor: 'pointer', + background: isExpanded ? 'var(--surface)' : 'transparent', + transition: 'background 0.2s' + }} + > +
+ + #{idx + 1} + + + {label} + + + {node.status} + + {node.debug_node_type && ( + + {node.debug_node_type} + + )} +
+ {isExpanded ? : } +
+ + {/* Node Debug Content */} + {isExpanded && ( +
+ {node.error && ( +
+
+ Error: +
+
+                        {node.error}
+                      
+
+ )} + + {node.debug_prompt && ( +
+
+ Prompt: +
+
+                        {node.debug_prompt}
+                      
+
+ )} + + {node.debug_raw_response && ( +
+
+ Rohe Antwort: +
+
+                        {node.debug_raw_response}
+                      
+
+ )} + + {node.analysis_core && ( +
+
+ Geparste Ergebnisse: +
+
+                        {node.analysis_core}
+                      
+
+ )} + + {node.normalized_signals && node.normalized_signals.length > 0 && ( +
+
+ Signale ({node.normalized_signals.length}): +
+
+ {node.normalized_signals.map((sig, i) => ( +
+ {sig.question_type}:{' '} + + "{sig.raw_value}" → "{sig.normalized_value}" + {' '} + + {sig.status} + +
+ ))} +
+
+ )} +
+ )} +
+ ) + })} +
+
+ ) +} diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 5ddcb56..8676417 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -10,6 +10,7 @@ import { import { useAuth } from '../context/AuthContext' import Markdown from '../utils/Markdown' import UsageBadge from '../components/UsageBadge' +import WorkflowDebugPanel from '../components/WorkflowDebugPanel' import dayjs from 'dayjs' import 'dayjs/locale/de' dayjs.locale('de') @@ -442,7 +443,14 @@ export default function Analysis() { } } - setNewResult({ scope: slug, content, metadata }) + setNewResult({ + scope: slug, + content, + metadata, + node_states: result.node_states, // For workflow debug panel + result_type: result.type, + aggregated_result: result.aggregated_result + }) await loadAll() setTab('run') } catch(e) { @@ -534,6 +542,9 @@ export default function Analysis() { defaultOpen={true} prompts={prompts} /> + {newResult.node_states && ( + + )} )} @@ -591,6 +602,9 @@ export default function Analysis() { defaultOpen={true} prompts={prompts} /> + {newResult.node_states && ( + + )} )} {activeCategoryKey && (() => {