2026-04-09 12:58:03 +02:00
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* WorkflowResultViewer - Zeigt Execution-Ergebnisse eines Workflows
|
|
|
|
|
|
*
|
|
|
|
|
|
* Features:
|
|
|
|
|
|
* - Aggregated Result (Final Output)
|
|
|
|
|
|
* - Node States (wenn Debug Mode aktiv)
|
|
|
|
|
|
* - Collapsible Sections
|
|
|
|
|
|
* - Copy to Clipboard
|
|
|
|
|
|
*
|
|
|
|
|
|
* Part 2: Frontend Execute Integration
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function WorkflowResultViewer({ result, onClose }) {
|
|
|
|
|
|
const [expandedNodes, setExpandedNodes] = useState({})
|
|
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const toggleNode = (nodeId) => {
|
|
|
|
|
|
setExpandedNodes((prev) => ({ ...prev, [nodeId]: !prev[nodeId] }))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const copyToClipboard = (text) => {
|
|
|
|
|
|
navigator.clipboard.writeText(text)
|
|
|
|
|
|
alert('In Zwischenablage kopiert')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const aggregated = result.aggregated_result || {}
|
|
|
|
|
|
const nodeStates = result.node_states || []
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
width: '600px',
|
|
|
|
|
|
background: 'var(--surface)',
|
|
|
|
|
|
borderLeft: '1px solid var(--border)',
|
|
|
|
|
|
boxShadow: '-2px 0 8px rgba(0,0,0,0.1)',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
zIndex: 1000
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '16px',
|
|
|
|
|
|
borderBottom: '1px solid var(--border)',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
background: 'var(--surface2)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 style={{ margin: 0, fontSize: '18px' }}>
|
|
|
|
|
|
Workflow-Ergebnis
|
|
|
|
|
|
{result.status === 'completed' && (
|
|
|
|
|
|
<span style={{ color: 'var(--accent)', marginLeft: '8px' }}>✓</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{result.status === 'failed' && (
|
|
|
|
|
|
<span style={{ color: 'var(--danger)', marginLeft: '8px' }}>✗</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: 'none',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
fontSize: '24px',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
lineHeight: 1
|
|
|
|
|
|
}}
|
|
|
|
|
|
title="Schließen"
|
|
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
|
padding: '16px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Execution Info */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
marginBottom: '16px',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
background: 'var(--bg)',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
fontSize: '12px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div><strong>Execution ID:</strong> {result.execution_id}</div>
|
|
|
|
|
|
<div><strong>Status:</strong> {result.status}</div>
|
|
|
|
|
|
{aggregated.total_nodes && (
|
|
|
|
|
|
<div><strong>Nodes:</strong> {aggregated.executed_nodes}/{aggregated.total_nodes}</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{aggregated.failed_nodes > 0 && (
|
|
|
|
|
|
<div style={{ color: 'var(--danger)' }}>
|
|
|
|
|
|
<strong>Failed Nodes:</strong> {aggregated.failed_nodes}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Final Output */}
|
|
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginBottom: '8px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<h3 style={{ margin: 0, fontSize: '14px', fontWeight: 600 }}>
|
|
|
|
|
|
Final Output
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{aggregated.analysis_core && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="btn-secondary"
|
|
|
|
|
|
style={{ fontSize: '11px', padding: '4px 8px' }}
|
|
|
|
|
|
onClick={() => copyToClipboard(aggregated.analysis_core)}
|
|
|
|
|
|
>
|
|
|
|
|
|
📋 Copy
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
background: 'var(--bg)',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
whiteSpace: 'pre-wrap',
|
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
|
lineHeight: 1.6,
|
|
|
|
|
|
maxHeight: '400px',
|
|
|
|
|
|
overflowY: 'auto'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 20:45:08 +02:00
|
|
|
|
{aggregated.analysis_core || '(Kein Output)'}
|
2026-04-09 12:58:03 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* All Signals */}
|
|
|
|
|
|
{aggregated.all_signals && aggregated.all_signals.length > 0 && (
|
|
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
|
|
|
|
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', fontWeight: 600 }}>
|
|
|
|
|
|
All Signals ({aggregated.all_signals.length})
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
background: 'var(--bg)',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
maxHeight: '200px',
|
|
|
|
|
|
overflowY: 'auto'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{aggregated.all_signals.map((signal, idx) => (
|
2026-04-09 18:38:09 +02:00
|
|
|
|
<div key={idx} style={{ marginBottom: '8px', fontFamily: 'monospace' }}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>{signal.question_type || 'unknown'}:</strong>{' '}
|
|
|
|
|
|
{signal.normalized_value || signal.raw_value || 'null'}{' '}
|
|
|
|
|
|
<span style={{ color: 'var(--text3)' }}>
|
|
|
|
|
|
({signal.status})
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-04-09 12:58:03 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Node States (Debug Info) */}
|
|
|
|
|
|
{nodeStates.length > 0 && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', fontWeight: 600 }}>
|
|
|
|
|
|
Node States (Debug)
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
2026-04-13 15:31:37 +02:00
|
|
|
|
{nodeStates.map((node) => {
|
|
|
|
|
|
const hasFailed = node.status === 'failed'
|
|
|
|
|
|
return (
|
2026-04-09 12:58:03 +02:00
|
|
|
|
<div
|
|
|
|
|
|
key={node.node_id}
|
|
|
|
|
|
style={{
|
2026-04-13 15:31:37 +02:00
|
|
|
|
border: hasFailed ? '2px solid #D85A30' : '1px solid var(--border)',
|
2026-04-09 12:58:03 +02:00
|
|
|
|
borderRadius: '8px',
|
2026-04-13 15:31:37 +02:00
|
|
|
|
background: hasFailed ? '#D85A3010' : 'transparent',
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
boxShadow: hasFailed ? '0 0 0 2px #D85A3020' : 'none'
|
2026-04-09 12:58:03 +02:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Node Header */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={() => toggleNode(node.node_id)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '10px 12px',
|
|
|
|
|
|
background: 'var(--surface2)',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
|
fontWeight: 500
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
{node.node_type === 'start' && '🚀'}
|
|
|
|
|
|
{node.node_type === 'analysis' && '🤖'}
|
|
|
|
|
|
{node.node_type === 'logic' && '⚡'}
|
|
|
|
|
|
{node.node_type === 'join' && '🔀'}
|
|
|
|
|
|
{node.node_type === 'end' && '🏁'}
|
|
|
|
|
|
{' '}
|
|
|
|
|
|
{node.node_label || node.node_id}
|
|
|
|
|
|
{node.status === 'skipped' && (
|
|
|
|
|
|
<span style={{ color: 'var(--text3)', marginLeft: '8px' }}>
|
|
|
|
|
|
(skipped)
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ fontSize: '16px' }}>
|
|
|
|
|
|
{expandedNodes[node.node_id] ? '▼' : '▶'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Node Details */}
|
|
|
|
|
|
{expandedNodes[node.node_id] && (
|
|
|
|
|
|
<div style={{ padding: '12px', fontSize: '12px' }}>
|
2026-04-13 15:31:37 +02:00
|
|
|
|
{/* Error (show first) */}
|
|
|
|
|
|
{node.error && (
|
|
|
|
|
|
<div style={{ marginBottom: '12px', padding: '12px', background: '#D85A3015', border: '1px solid #D85A30', borderRadius: '6px' }}>
|
|
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 600, color: '#D85A30', marginBottom: '6px' }}>Error:</div>
|
|
|
|
|
|
<pre style={{ margin: 0, fontSize: '12px', color: '#D85A30', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{node.error}</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Debug Prompt */}
|
|
|
|
|
|
{node.debug_prompt && (
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text2)', marginBottom: '6px', textTransform: 'uppercase' }}>Prompt:</div>
|
|
|
|
|
|
<pre style={{ margin: 0, padding: '12px', background: 'var(--surface2)', borderRadius: '6px', fontSize: '11px', color: 'var(--text1)', whiteSpace: 'pre-wrap', maxHeight: '300px', overflowY: 'auto', border: '1px solid var(--border)' }}>{node.debug_prompt}</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Debug Raw Response */}
|
|
|
|
|
|
{node.debug_raw_response && (
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text2)', marginBottom: '6px', textTransform: 'uppercase' }}>Rohe Antwort:</div>
|
|
|
|
|
|
<pre style={{ margin: 0, padding: '12px', background: 'var(--surface2)', borderRadius: '6px', fontSize: '11px', color: 'var(--text1)', whiteSpace: 'pre-wrap', maxHeight: '300px', overflowY: 'auto', border: '1px solid var(--border)' }}>{node.debug_raw_response}</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Analysis Core */}
|
|
|
|
|
|
{node.analysis_core && (
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text2)', marginBottom: '6px', textTransform: 'uppercase' }}>Ergebnis:</div>
|
|
|
|
|
|
<pre style={{ margin: 0, padding: '12px', background: 'var(--surface2)', borderRadius: '6px', fontSize: '11px', color: 'var(--text1)', whiteSpace: 'pre-wrap', maxHeight: '300px', overflowY: 'auto', border: '1px solid var(--border)' }}>{node.analysis_core}</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Normalized Signals */}
|
|
|
|
|
|
{node.normalized_signals && node.normalized_signals.length > 0 && (
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text2)', marginBottom: '6px', textTransform: 'uppercase' }}>Signale ({node.normalized_signals.length}):</div>
|
|
|
|
|
|
{node.normalized_signals.map((sig, i) => (
|
|
|
|
|
|
<div key={i} style={{ padding: '6px', background: 'var(--surface2)', borderRadius: '4px', fontSize: '11px', marginBottom: '4px', border: '1px solid var(--border)' }}>
|
|
|
|
|
|
<span style={{ color: 'var(--text2)' }}>{sig.question_type}:</span> <span style={{ fontWeight: 500 }}>"{sig.raw_value}" → "{sig.normalized_value}"</span> <span style={{ fontSize: '10px', padding: '2px 4px', borderRadius: '2px', background: sig.status === 'valid' ? '#1D9E7520' : '#D85A3020', color: sig.status === 'valid' ? '#1D9E75' : '#D85A30' }}>{sig.status}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Output (fallback) */}
|
|
|
|
|
|
{node.output && !node.analysis_core && (
|
2026-04-09 12:58:03 +02:00
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<strong>Output:</strong>
|
2026-04-13 15:31:37 +02:00
|
|
|
|
<pre style={{ marginTop: '4px', padding: '8px', background: 'var(--bg)', borderRadius: '4px', fontSize: '11px', overflowX: 'auto', maxHeight: '200px', overflowY: 'auto' }}>
|
|
|
|
|
|
{typeof node.output === 'string' ? node.output : JSON.stringify(node.output, null, 2)}
|
2026-04-09 12:58:03 +02:00
|
|
|
|
</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-04-13 15:31:37 +02:00
|
|
|
|
|
|
|
|
|
|
{/* Metadata */}
|
2026-04-09 12:58:03 +02:00
|
|
|
|
{node.metadata && (
|
|
|
|
|
|
<div style={{ marginTop: '8px' }}>
|
|
|
|
|
|
<strong>Metadata:</strong>
|
2026-04-13 15:31:37 +02:00
|
|
|
|
<pre style={{ marginTop: '4px', padding: '8px', background: 'var(--bg)', borderRadius: '4px', fontSize: '11px', overflowX: 'auto' }}>
|
2026-04-09 12:58:03 +02:00
|
|
|
|
{JSON.stringify(node.metadata, null, 2)}
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-04-13 15:31:37 +02:00
|
|
|
|
)})}
|
2026-04-09 12:58:03 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Error (if failed) */}
|
|
|
|
|
|
{result.error && (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
marginTop: '16px',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
background: 'var(--danger)',
|
|
|
|
|
|
color: 'white',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
fontSize: '13px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<strong>Error:</strong> {result.error}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|