- 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.
357 lines
12 KiB
JavaScript
357 lines
12 KiB
JavaScript
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 (
|
|
<div style={{
|
|
padding: '12px',
|
|
background: 'var(--surface)',
|
|
borderRadius: '8px',
|
|
border: '1px solid var(--border)',
|
|
marginTop: '16px'
|
|
}}>
|
|
<p style={{ margin: 0, color: 'var(--text2)', fontSize: '13px' }}>
|
|
Keine Debug-Informationen verfügbar. Führe den Workflow mit debug=true aus.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
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 (
|
|
<div style={{
|
|
marginTop: '24px',
|
|
padding: '16px',
|
|
background: 'var(--surface)',
|
|
borderRadius: '12px',
|
|
border: '1px solid var(--border)'
|
|
}}>
|
|
<div style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: '16px'
|
|
}}>
|
|
<h3 style={{
|
|
margin: 0,
|
|
fontSize: '16px',
|
|
fontWeight: 600,
|
|
color: 'var(--text1)'
|
|
}}>
|
|
🔍 Debug-Informationen ({debugNodes.length} Nodes)
|
|
</h3>
|
|
<button
|
|
onClick={toggleAll}
|
|
className="btn"
|
|
style={{
|
|
padding: '6px 12px',
|
|
fontSize: '13px',
|
|
minWidth: 'auto'
|
|
}}
|
|
>
|
|
{showAll ? 'Alle zuklappen' : 'Alle aufklappen'}
|
|
</button>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{debugNodes.map((node, idx) => {
|
|
const isExpanded = expandedNodes.has(node.node_id)
|
|
const label = getNodeLabel(node)
|
|
const statusColor = getStatusColor(node.status)
|
|
|
|
const hasFailed = node.status === 'failed'
|
|
const hasError = node.error != null
|
|
|
|
return (
|
|
<div
|
|
key={node.node_id}
|
|
style={{
|
|
border: hasFailed ? '2px solid #D85A30' : '1px solid var(--border)',
|
|
borderRadius: '8px',
|
|
background: hasFailed ? '#D85A3010' : 'var(--bg)',
|
|
overflow: 'hidden',
|
|
boxShadow: hasFailed ? '0 0 0 2px #D85A3020' : 'none'
|
|
}}
|
|
>
|
|
{/* Node Header */}
|
|
<div
|
|
onClick={() => 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'
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
<span style={{
|
|
fontSize: '13px',
|
|
color: 'var(--text3)',
|
|
fontFamily: 'monospace'
|
|
}}>
|
|
#{idx + 1}
|
|
</span>
|
|
<span style={{
|
|
fontWeight: 500,
|
|
color: 'var(--text1)',
|
|
fontSize: '14px'
|
|
}}>
|
|
{label}
|
|
</span>
|
|
<span style={{
|
|
fontSize: '12px',
|
|
padding: '2px 8px',
|
|
borderRadius: '4px',
|
|
background: statusColor,
|
|
color: 'white',
|
|
fontWeight: 500
|
|
}}>
|
|
{node.status}
|
|
</span>
|
|
{node.debug_node_type && (
|
|
<span style={{
|
|
fontSize: '11px',
|
|
padding: '2px 6px',
|
|
borderRadius: '4px',
|
|
background: 'var(--surface2)',
|
|
color: 'var(--text2)',
|
|
fontFamily: 'monospace'
|
|
}}>
|
|
{node.debug_node_type}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
|
</div>
|
|
|
|
{/* Node Debug Content */}
|
|
{isExpanded && (
|
|
<div style={{ padding: '0 12px 12px 12px' }}>
|
|
{node.error && (
|
|
<div style={{
|
|
padding: '12px',
|
|
background: '#D85A3015',
|
|
border: '1px solid #D85A30',
|
|
borderRadius: '6px',
|
|
marginBottom: '12px'
|
|
}}>
|
|
<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>
|
|
)}
|
|
|
|
{node.debug_prompt && (
|
|
<div style={{ marginBottom: '12px' }}>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
fontWeight: 600,
|
|
color: 'var(--text2)',
|
|
marginBottom: '6px',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.5px'
|
|
}}>
|
|
Prompt:
|
|
</div>
|
|
<pre style={{
|
|
margin: 0,
|
|
padding: '12px',
|
|
background: 'var(--surface2)',
|
|
borderRadius: '6px',
|
|
fontSize: '12px',
|
|
lineHeight: '1.6',
|
|
color: 'var(--text1)',
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
maxHeight: '400px',
|
|
overflowY: 'auto',
|
|
border: '1px solid var(--border)'
|
|
}}>
|
|
{node.debug_prompt}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{node.debug_raw_response && (
|
|
<div style={{ marginBottom: '12px' }}>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
fontWeight: 600,
|
|
color: 'var(--text2)',
|
|
marginBottom: '6px',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.5px'
|
|
}}>
|
|
Rohe Antwort:
|
|
</div>
|
|
<pre style={{
|
|
margin: 0,
|
|
padding: '12px',
|
|
background: 'var(--surface2)',
|
|
borderRadius: '6px',
|
|
fontSize: '12px',
|
|
lineHeight: '1.6',
|
|
color: 'var(--text1)',
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
maxHeight: '400px',
|
|
overflowY: 'auto',
|
|
border: '1px solid var(--border)'
|
|
}}>
|
|
{node.debug_raw_response}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{node.analysis_core && (
|
|
<div>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
fontWeight: 600,
|
|
color: 'var(--text2)',
|
|
marginBottom: '6px',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.5px'
|
|
}}>
|
|
Geparste Ergebnisse:
|
|
</div>
|
|
<pre style={{
|
|
margin: 0,
|
|
padding: '12px',
|
|
background: 'var(--surface2)',
|
|
borderRadius: '6px',
|
|
fontSize: '12px',
|
|
lineHeight: '1.6',
|
|
color: 'var(--text1)',
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
maxHeight: '300px',
|
|
overflowY: 'auto',
|
|
border: '1px solid var(--border)'
|
|
}}>
|
|
{node.analysis_core}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{node.normalized_signals && node.normalized_signals.length > 0 && (
|
|
<div style={{ marginTop: '12px' }}>
|
|
<div style={{
|
|
fontSize: '12px',
|
|
fontWeight: 600,
|
|
color: 'var(--text2)',
|
|
marginBottom: '6px',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.5px'
|
|
}}>
|
|
Signale ({node.normalized_signals.length}):
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
|
{node.normalized_signals.map((sig, i) => (
|
|
<div
|
|
key={i}
|
|
style={{
|
|
padding: '8px',
|
|
background: 'var(--surface2)',
|
|
borderRadius: '4px',
|
|
fontSize: '12px',
|
|
border: '1px solid var(--border)'
|
|
}}
|
|
>
|
|
<span style={{ color: 'var(--text2)' }}>{sig.question_type}:</span>{' '}
|
|
<span style={{ fontWeight: 500, color: 'var(--text1)' }}>
|
|
"{sig.raw_value}" → "{sig.normalized_value}"
|
|
</span>{' '}
|
|
<span style={{
|
|
fontSize: '11px',
|
|
padding: '2px 6px',
|
|
borderRadius: '3px',
|
|
background: sig.status === 'valid' ? '#1D9E7520' : '#D85A3020',
|
|
color: sig.status === 'valid' ? '#1D9E75' : '#D85A30'
|
|
}}>
|
|
{sig.status}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|