mitai-jinkendo/frontend/src/components/workflow/panels/WorkflowResultViewer.jsx

310 lines
10 KiB
React
Raw Normal View History

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'
}}
>
{aggregated.analysis_core || aggregated.combined_analysis || '(Kein Output)'}
</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) => (
<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>
</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' }}>
{nodeStates.map((node) => (
<div
key={node.node_id}
style={{
border: '1px solid var(--border)',
borderRadius: '8px',
overflow: 'hidden'
}}
>
{/* 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' }}>
{node.output && (
<div style={{ marginBottom: '8px' }}>
<strong>Output:</strong>
<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)}
</pre>
</div>
)}
{node.error && (
<div style={{ color: 'var(--danger)', marginBottom: '8px' }}>
<strong>Error:</strong> {node.error}
</div>
)}
{node.metadata && (
<div style={{ marginTop: '8px' }}>
<strong>Metadata:</strong>
<pre
style={{
marginTop: '4px',
padding: '8px',
background: 'var(--bg)',
borderRadius: '4px',
fontSize: '11px',
overflowX: 'auto'
}}
>
{JSON.stringify(node.metadata, null, 2)}
</pre>
</div>
)}
</div>
)}
</div>
))}
</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>
)
}