From 12d4d7c63badbfce907edf7baf96e8f5379622db Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 12:38:55 +0200 Subject: [PATCH 01/10] feat: Add comprehensive debug information for workflow nodes Backend changes: - workflow_models.py: Add debug_prompt, debug_raw_response, debug_node_type, debug_prompt_slug, metadata fields to NodeExecutionState - workflow_executor.py: Capture and store debug info for analysis, logic, and join nodes when enable_debug=True - Analysis nodes: store full prompt + raw AI response - Logic nodes: store expression + evaluation result - Join nodes: store strategy + path statistics Frontend changes: - Analysis.jsx: Enable debug mode by default (debug=true) for all workflow executions This allows developers to see exactly what prompt was sent to the AI, what response was received, and how each node was processed - essential for debugging workflow issues. --- backend/workflow_executor.py | 36 +++++++++++++++++++++++++++------ backend/workflow_models.py | 9 +++++++++ frontend/src/pages/Analysis.jsx | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 6e36ad4..4672926 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -314,11 +314,11 @@ async def execute_node( # Logic Nodes (Phase 3) if node.type == "logic": - return execute_logic_node(node, context, graph) + return execute_logic_node(node, context, graph, enable_debug) # Join Nodes (Phase 4) if node.type == "join": - return execute_join_node(node, context, graph) + return execute_join_node(node, context, graph, enable_debug) # Analysis Nodes if node.type == "analysis": @@ -398,6 +398,11 @@ async def execute_node( decision_signals=parsed["decision_signals"], normalized_signals=normalized_signals, reasoning_anchors=parsed.get("reasoning_anchors"), + # Debug information (nur wenn enable_debug=True) + debug_prompt=augmented_prompt if enable_debug else None, + debug_raw_response=llm_response if enable_debug else None, + debug_node_type="analysis", + debug_prompt_slug=node.prompt_slug if enable_debug else None, started_at=started_at, completed_at=datetime.utcnow().isoformat() ) @@ -419,7 +424,8 @@ async def execute_node( def execute_logic_node( node, context: Dict[str, Any], - graph: WorkflowGraph + graph: WorkflowGraph, + enable_debug: bool = False ) -> NodeExecutionState: """ Führt Logic Node aus (Phase 3). @@ -519,7 +525,15 @@ def execute_logic_node( "evaluation_result": result if not error else None, "error": error, "activated_edges": activated_edges - }) + }), + # Debug information + debug_prompt=json.dumps(expression.model_dump() if hasattr(expression, 'model_dump') else str(expression), ensure_ascii=False) if enable_debug and expression else None, + debug_raw_response=json.dumps({ + "result": result, + "error": error, + "activated_edges": activated_edges + }, ensure_ascii=False) if enable_debug else None, + debug_node_type="logic" ) except Exception as e: @@ -536,7 +550,8 @@ def execute_logic_node( def execute_join_node( node, context: Dict[str, Any], - graph: WorkflowGraph + graph: WorkflowGraph, + enable_debug: bool = False ) -> NodeExecutionState: """ Führt Join Node aus (Phase 4). @@ -608,7 +623,16 @@ def execute_join_node( normalized_signals=consolidated_signals_list, metadata=join_result.metadata, started_at=started_at, - completed_at=datetime.utcnow().isoformat() + completed_at=datetime.utcnow().isoformat(), + # Debug information + debug_prompt=f"Join Strategy: {node.join_strategy.value if node.join_strategy else 'wait_all'}\nPaths: {total_count}\nMinimum Required: {node.min_paths if node.min_paths else 'all'}" if enable_debug else None, + debug_raw_response=json.dumps({ + "executed_paths": executed_count, + "total_paths": total_count, + "strategy": node.join_strategy.value if node.join_strategy else 'wait_all', + "consolidated_nodes": list(join_result.consolidated_analysis_core.keys()) + }, ensure_ascii=False) if enable_debug else None, + debug_node_type="join" ) except Exception as e: diff --git a/backend/workflow_models.py b/backend/workflow_models.py index 0467285..1d67142 100644 --- a/backend/workflow_models.py +++ b/backend/workflow_models.py @@ -349,6 +349,15 @@ class NodeExecutionState(BaseModel): normalized_signals: List[NormalizedSignal] = Field(default_factory=list, description="Normalisierte Signale (Phase 2)") reasoning_anchors: Optional[str] = Field(None, description="Begründungsanker aus ## Begründung") + # Debug Information (nur wenn enable_debug=True) + debug_prompt: Optional[str] = Field(None, description="Vollständiger Prompt der an die KI gesendet wurde") + debug_raw_response: Optional[str] = Field(None, description="Rohe KI-Antwort (ungeparst)") + debug_node_type: Optional[str] = Field(None, description="Node-Typ (analysis, logic, join, etc.)") + debug_prompt_slug: Optional[str] = Field(None, description="Verwendeter Prompt-Slug (bei ANALYSIS nodes)") + + # Metadata (für Join Nodes und andere Zusatzinfos) + metadata: Optional[Dict[str, Any]] = Field(None, description="Zusätzliche Metadaten (z.B. Join-Statistiken)") + # Error & Timing error: Optional[str] = Field(None, description="Fehlermeldung bei failed") started_at: Optional[str] = Field(None, description="Start-Timestamp (ISO)") diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 8f03c81..5ddcb56 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -382,7 +382,7 @@ export default function Analysis() { setLoading(slug); setError(null); setNewResult(null); setProgress(null) try { // Use SSE-based executor for long-running workflows - const result = await api.executeUnifiedPromptStream(slug, null, null, false, true, (event) => { + const result = await api.executeUnifiedPromptStream(slug, null, null, true, true, (event) => { // Progress callback: update UI in real-time if (event.type === 'execution_started') { setProgress({ total_nodes: 0, completed_nodes: 0, current_node_label: 'Starte...' }) From a515a5d5634603b95d6d30bf2e35d809700936f6 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 12:41:12 +0200 Subject: [PATCH 02/10] feat: Add WorkflowDebugPanel component to display per-node debug information - Created WorkflowDebugPanel.jsx: Collapsible panel showing debug info for each workflow node - Shows prompt sent to AI - Shows raw AI response - Shows parsed results - Shows normalized signals - Color-coded status (executed/failed/skipped) - Expandable/collapsible per node - Updated Analysis.jsx: - Added WorkflowDebugPanel import - Store node_states in newResult for debugging - Display WorkflowDebugPanel below InsightCard (both locations) This makes it easy to debug workflow issues by seeing exactly what happened at each node. --- .../src/components/WorkflowDebugPanel.jsx | 352 ++++++++++++++++++ frontend/src/pages/Analysis.jsx | 16 +- 2 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/WorkflowDebugPanel.jsx 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 && (() => { From 7388776b29cb840e0ea630cf17fe09d5cbf03fda Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 12:52:29 +0200 Subject: [PATCH 03/10] fix: Add human-readable labels to workflow nodes in debug output - workflow_executor.py: Store prompt_slug or generated label in debug_prompt_slug for all nodes - This makes it easy to identify nodes in the debug panel - Example: 'wf_nutrition_basis' instead of 'node_5' - Helps identify which node is which when debugging workflows --- backend/workflow_executor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 4672926..809b40a 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -166,13 +166,16 @@ async def execute_workflow( enable_debug=enable_debug ) + # Add human-readable label to node_state for debug UI + node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" + if not node_state.debug_prompt_slug: + node_state.debug_prompt_slug = node_label + node_states.append(node_state) context["node_results"][node_id] = node_state # NEW: Progress-Callback aufrufen (für SSE Streaming) if progress_callback: - # Create a meaningful label for the node - node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" await progress_callback("node_complete", { "node_id": node_id, "node_type": node.type, From 0a27533262f3f6ad45bc20027075abdc7381b71c Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 12:58:25 +0200 Subject: [PATCH 04/10] feat: Highlight failed nodes in WorkflowDebugPanel - Failed nodes now have: - Red border (2px instead of 1px) - Light red background (#D85A3010) - Red shadow/glow effect Makes it immediately obvious which nodes had errors. --- frontend/src/components/WorkflowDebugPanel.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/WorkflowDebugPanel.jsx b/frontend/src/components/WorkflowDebugPanel.jsx index fa0175b..7f80c42 100644 --- a/frontend/src/components/WorkflowDebugPanel.jsx +++ b/frontend/src/components/WorkflowDebugPanel.jsx @@ -111,14 +111,18 @@ export default function WorkflowDebugPanel({ nodeStates }) { const label = getNodeLabel(node) const statusColor = getStatusColor(node.status) + const hasFailed = node.status === 'failed' + const hasError = node.error != null + return (
{/* Node Header */} From 736dc58d8180f16a2df14a9cdbdc9e9d56f357a0 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 15:31:37 +0200 Subject: [PATCH 05/10] feat: Show debug info in WorkflowResultViewer Display per node: - debug_prompt (prompt sent to AI) - debug_raw_response (raw AI response) - analysis_core (parsed results) - normalized_signals (decision signals with status) - Failed nodes: red border + red background NO other changes - executeWorkflow still used --- .../workflow/panels/WorkflowResultViewer.jsx | 94 ++++++++++++------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx index 6a596a5..923e961 100644 --- a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx +++ b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx @@ -192,13 +192,17 @@ export function WorkflowResultViewer({ result, onClose }) { Node States (Debug)
- {nodeStates.map((node) => ( + {nodeStates.map((node) => { + const hasFailed = node.status === 'failed' + return (
{/* Node Header */} @@ -237,45 +241,65 @@ export function WorkflowResultViewer({ result, onClose }) { {/* Node Details */} {expandedNodes[node.node_id] && (
- {node.output && ( + {/* Error (show first) */} + {node.error && ( +
+
Error:
+
{node.error}
+
+ )} + + {/* Debug Prompt */} + {node.debug_prompt && ( +
+
Prompt:
+
{node.debug_prompt}
+
+ )} + + {/* Debug Raw Response */} + {node.debug_raw_response && ( +
+
Rohe Antwort:
+
{node.debug_raw_response}
+
+ )} + + {/* Analysis Core */} + {node.analysis_core && ( +
+
Ergebnis:
+
{node.analysis_core}
+
+ )} + + {/* Normalized Signals */} + {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} +
+ ))} +
+ )} + + {/* Output (fallback) */} + {node.output && !node.analysis_core && (
Output: -
-                            {typeof node.output === 'string'
-                              ? node.output
-                              : JSON.stringify(node.output, null, 2)}
+                          
+                            {typeof node.output === 'string' ? node.output : JSON.stringify(node.output, null, 2)}
                           
)} - {node.error && ( -
- Error: {node.error} -
- )} + + {/* Metadata */} {node.metadata && (
Metadata: -
+                          
                             {JSON.stringify(node.metadata, null, 2)}
                           
@@ -283,7 +307,7 @@ export function WorkflowResultViewer({ result, onClose }) {
)}
- ))} + )})}
)} From f97d15288d2b6535335439fd4f0e611a195b142c Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 15:36:50 +0200 Subject: [PATCH 06/10] fix: Show debug_prompt_slug instead of node_id Priority: debug_prompt_slug > node_label > node_type-shortid > node_id --- .../workflow/panels/WorkflowResultViewer.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx index 923e961..9971180 100644 --- a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx +++ b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx @@ -220,13 +220,13 @@ export function WorkflowResultViewer({ result, onClose }) { }} >
- {node.node_type === 'start' && '🚀'} - {node.node_type === 'analysis' && '🤖'} - {node.node_type === 'logic' && '⚡'} - {node.node_type === 'join' && '🔀'} - {node.node_type === 'end' && '🏁'} + {(node.debug_node_type || node.node_type) === 'start' && '🚀'} + {(node.debug_node_type || node.node_type) === 'analysis' && '🤖'} + {(node.debug_node_type || node.node_type) === 'logic' && '⚡'} + {(node.debug_node_type || node.node_type) === 'join' && '🔀'} + {(node.debug_node_type || node.node_type) === 'end' && '🏁'} {' '} - {node.node_label || node.node_id} + {node.debug_prompt_slug || node.node_label || ((node.debug_node_type || node.node_type) ? `${node.debug_node_type || node.node_type}-${node.node_id.substring(0, 8)}` : node.node_id)} {node.status === 'skipped' && ( (skipped) From 5fa2ea2e6bf1f96665324002b9edfa13241d76b2 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 15:43:42 +0200 Subject: [PATCH 07/10] feat: Show node.name from workflow editor in debug panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add name field to WorkflowNode model - Add node_name field to NodeExecutionState - Set node_name in execute_workflow from node.name - Display priority: node_name > debug_prompt_slug > node_label > node_id User sees 'Qualitätseinschätzung' instead of 'node_abc123' --- backend/workflow_executor.py | 5 ++++- backend/workflow_models.py | 2 ++ .../src/components/workflow/panels/WorkflowResultViewer.jsx | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 809b40a..691482e 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -166,10 +166,13 @@ async def execute_workflow( enable_debug=enable_debug ) - # Add human-readable label to node_state for debug UI + # Add human-readable label and name to node_state for debug UI node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" if not node_state.debug_prompt_slug: node_state.debug_prompt_slug = node_label + # Set node_name from graph (user-defined name in editor) + if not node_state.node_name and hasattr(node, 'name'): + node_state.node_name = node.name node_states.append(node_state) context["node_results"][node_id] = node_state diff --git a/backend/workflow_models.py b/backend/workflow_models.py index 1d67142..7b9e01e 100644 --- a/backend/workflow_models.py +++ b/backend/workflow_models.py @@ -196,6 +196,7 @@ class WorkflowNode(BaseModel): """ id: str = Field(..., description="Eindeutige Knoten-ID") type: NodeType = Field(..., description="Knotentyp") + name: Optional[str] = Field(None, description="Node-Name (vom Editor)") position: Optional[Position] = Field(None, description="Position im visuellen Editor") # ANALYSIS-Knoten @@ -341,6 +342,7 @@ class NodeExecutionState(BaseModel): Erweitert NodeState um Phase-1-Komponenten (analysis_core, decision_signals, etc.) """ node_id: str = Field(..., description="Knoten-ID") + node_name: Optional[str] = Field(None, description="Node-Name aus Workflow-Editor") status: NodeStatus = Field(..., description="Ausführungsstatus") # Phase 1 Result Container diff --git a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx index 9971180..51a839a 100644 --- a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx +++ b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx @@ -226,7 +226,7 @@ export function WorkflowResultViewer({ result, onClose }) { {(node.debug_node_type || node.node_type) === 'join' && '🔀'} {(node.debug_node_type || node.node_type) === 'end' && '🏁'} {' '} - {node.debug_prompt_slug || node.node_label || ((node.debug_node_type || node.node_type) ? `${node.debug_node_type || node.node_type}-${node.node_id.substring(0, 8)}` : node.node_id)} + {node.node_name || node.debug_prompt_slug || node.node_label || node.node_id} {node.status === 'skipped' && ( (skipped) From b7062d32bfce27c1e6097bb511fe40359d8ce702 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 15:54:22 +0200 Subject: [PATCH 08/10] Revert "feat: Show node.name from workflow editor in debug panel" This reverts commit 5fa2ea2e6bf1f96665324002b9edfa13241d76b2. --- backend/workflow_executor.py | 5 +---- backend/workflow_models.py | 2 -- .../src/components/workflow/panels/WorkflowResultViewer.jsx | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 691482e..809b40a 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -166,13 +166,10 @@ async def execute_workflow( enable_debug=enable_debug ) - # Add human-readable label and name to node_state for debug UI + # Add human-readable label to node_state for debug UI node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" if not node_state.debug_prompt_slug: node_state.debug_prompt_slug = node_label - # Set node_name from graph (user-defined name in editor) - if not node_state.node_name and hasattr(node, 'name'): - node_state.node_name = node.name node_states.append(node_state) context["node_results"][node_id] = node_state diff --git a/backend/workflow_models.py b/backend/workflow_models.py index 7b9e01e..1d67142 100644 --- a/backend/workflow_models.py +++ b/backend/workflow_models.py @@ -196,7 +196,6 @@ class WorkflowNode(BaseModel): """ id: str = Field(..., description="Eindeutige Knoten-ID") type: NodeType = Field(..., description="Knotentyp") - name: Optional[str] = Field(None, description="Node-Name (vom Editor)") position: Optional[Position] = Field(None, description="Position im visuellen Editor") # ANALYSIS-Knoten @@ -342,7 +341,6 @@ class NodeExecutionState(BaseModel): Erweitert NodeState um Phase-1-Komponenten (analysis_core, decision_signals, etc.) """ node_id: str = Field(..., description="Knoten-ID") - node_name: Optional[str] = Field(None, description="Node-Name aus Workflow-Editor") status: NodeStatus = Field(..., description="Ausführungsstatus") # Phase 1 Result Container diff --git a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx index 51a839a..9971180 100644 --- a/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx +++ b/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx @@ -226,7 +226,7 @@ export function WorkflowResultViewer({ result, onClose }) { {(node.debug_node_type || node.node_type) === 'join' && '🔀'} {(node.debug_node_type || node.node_type) === 'end' && '🏁'} {' '} - {node.node_name || node.debug_prompt_slug || node.node_label || node.node_id} + {node.debug_prompt_slug || node.node_label || ((node.debug_node_type || node.node_type) ? `${node.debug_node_type || node.node_type}-${node.node_id.substring(0, 8)}` : node.node_id)} {node.status === 'skipped' && ( (skipped) From d5325acee6adcdd5cb825a523f5dc971b9c930de Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 18:03:02 +0200 Subject: [PATCH 09/10] fix: Use node.name in node_label calculation (minimal change) - Add name field to WorkflowNode model - Extend node_label priority: node.name > prompt_slug > node_type-id - No new fields in NodeExecutionState (uses existing debug_prompt_slug) - Simpler approach than previous attempt to avoid 504 timeout --- backend/workflow_executor.py | 5 ++++- backend/workflow_models.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 809b40a..22304a4 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -167,7 +167,10 @@ async def execute_workflow( ) # Add human-readable label to node_state for debug UI - node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" + # Priority: node.name (user-defined) > prompt_slug > node_type-id + node_label = node.name if hasattr(node, 'name') and node.name else ( + node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" + ) if not node_state.debug_prompt_slug: node_state.debug_prompt_slug = node_label diff --git a/backend/workflow_models.py b/backend/workflow_models.py index 1d67142..3645cca 100644 --- a/backend/workflow_models.py +++ b/backend/workflow_models.py @@ -196,6 +196,7 @@ class WorkflowNode(BaseModel): """ id: str = Field(..., description="Eindeutige Knoten-ID") type: NodeType = Field(..., description="Knotentyp") + name: Optional[str] = Field(None, description="Node-Name (vom Editor)") position: Optional[Position] = Field(None, description="Position im visuellen Editor") # ANALYSIS-Knoten From df8e732709b0191cd0709a4687ba684f88e3a34f Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 13 Apr 2026 18:09:12 +0200 Subject: [PATCH 10/10] fix: Use correct field 'label' instead of 'name' for node display - Frontend saves node name as 'label' (workflowSerializer.js:19) - Changed WorkflowNode.name to WorkflowNode.label - Changed node.name to node.label in workflow_executor.py - Priority: node.label > prompt_slug > node_type-id - Verified against frontend serialization code --- backend/workflow_executor.py | 4 ++-- backend/workflow_models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/workflow_executor.py b/backend/workflow_executor.py index 22304a4..1f12fba 100644 --- a/backend/workflow_executor.py +++ b/backend/workflow_executor.py @@ -167,8 +167,8 @@ async def execute_workflow( ) # Add human-readable label to node_state for debug UI - # Priority: node.name (user-defined) > prompt_slug > node_type-id - node_label = node.name if hasattr(node, 'name') and node.name else ( + # Priority: node.label (user-defined) > prompt_slug > node_type-id + node_label = node.label if hasattr(node, 'label') and node.label else ( node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}" ) if not node_state.debug_prompt_slug: diff --git a/backend/workflow_models.py b/backend/workflow_models.py index 3645cca..5f2f98e 100644 --- a/backend/workflow_models.py +++ b/backend/workflow_models.py @@ -196,7 +196,7 @@ class WorkflowNode(BaseModel): """ id: str = Field(..., description="Eindeutige Knoten-ID") type: NodeType = Field(..., description="Knotentyp") - name: Optional[str] = Field(None, description="Node-Name (vom Editor)") + label: Optional[str] = Field(None, description="Node-Label (vom Editor, z.B. 'Qualitätseinschätzung')") position: Optional[Position] = Field(None, description="Position im visuellen Editor") # ANALYSIS-Knoten