fix: Use SSE streaming in WorkflowExecutePanel to prevent 504 timeout
All checks were successful
Deploy Development / deploy (push) Successful in 53s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s

ROOT CAUSE:
- WorkflowExecutePanel used api.executeWorkflow (synchronous POST)
- Long workflows (10+ nodes, 60-90s) → 504 Gateway Timeout
- Reverse proxy kills request after 60s

SOLUTION:
- Switch to api.executeUnifiedPromptStream (SSE)
- Real-time progress updates during execution
- No timeout (streaming connection stays alive)
- Progress display: X/Y Nodes • Current node label

Changes:
- Replace executeWorkflow with executeUnifiedPromptStream
- Add progress callback for node_complete events
- Add progress state + UI display
- Flexbox column layout for progress bar

Fixes: 504 Gateway Timeout bei langen Workflows im Workflow Designer
This commit is contained in:
Lars 2026-04-13 15:18:50 +02:00
parent 680269e971
commit b1d596e0ab

View File

@ -6,11 +6,10 @@ import { api } from '../../../utils/api'
*
* Features:
* - Execute Button mit Loading State
* - SSE Streaming (kein Timeout bei langen Workflows)
* - Real-time Progress
* - Error Handling
* - Success State
* - Debug Mode Toggle
*
* Part 2: Frontend Execute Integration
*/
export function WorkflowExecutePanel({
currentPrompt,
@ -20,6 +19,7 @@ export function WorkflowExecutePanel({
const [executing, setExecuting] = useState(false)
const [error, setError] = useState(null)
const [debugMode, setDebugMode] = useState(true)
const [progress, setProgress] = useState(null)
const handleExecute = async () => {
if (!currentPrompt || !currentPrompt.slug) {
@ -30,14 +30,28 @@ export function WorkflowExecutePanel({
try {
setExecuting(true)
setError(null)
setProgress({ current: 0, total: 0, label: 'Starte...' })
console.log('🚀 Executing workflow:', currentPrompt.slug)
console.log('🚀 Executing workflow via SSE:', currentPrompt.slug)
const result = await api.executeWorkflow(
const result = await api.executeUnifiedPromptStream(
currentPrompt.slug,
null, // variables (später erweiterbar)
null, // modules
null, // timeframes
debugMode,
false // save (nicht in ai_insights speichern)
false, // save (nicht in ai_insights speichern)
(event) => {
// Progress callback für real-time updates
if (event.type === 'execution_started') {
setProgress({ current: 0, total: 0, label: 'Gestartet...' })
} else if (event.type === 'node_complete') {
setProgress({
current: event.completed_nodes || 0,
total: event.total_nodes || 0,
label: event.node_label || 'Processing...'
})
}
}
)
console.log('✅ Workflow execution completed:', result)
@ -57,81 +71,107 @@ export function WorkflowExecutePanel({
setError(e.message)
} finally {
setExecuting(false)
setProgress(null)
}
}
return (
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
{/* Debug Mode Toggle */}
<label
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
{/* Debug Mode Toggle */}
<label
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
fontSize: '12px',
color: 'var(--text2)',
cursor: 'pointer'
}}
title="Debug-Modus zeigt detaillierte Node-States im Ergebnis"
>
<input
type="checkbox"
checked={debugMode}
onChange={(e) => setDebugMode(e.target.checked)}
disabled={executing}
/>
Debug
</label>
{/* Execute Button */}
<button
className="btn-primary"
onClick={handleExecute}
disabled={disabled || executing || !currentPrompt}
title={
!currentPrompt
? 'Workflow muss zuerst gespeichert werden'
: disabled
? 'Workflow hat Validierungsfehler'
: 'Workflow ausführen'
}
style={{
minWidth: '120px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px'
}}
>
{executing ? (
<>
<span className="spinner" style={{ width: 14, height: 14 }} />
Executing...
</>
) : (
<>
Execute
</>
)}
</button>
{/* Error Display */}
{error && (
<div
style={{
padding: '6px 12px',
background: 'var(--danger)',
color: 'white',
borderRadius: '4px',
fontSize: '12px',
maxWidth: '300px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
title={error}
>
{error}
</div>
)}
</div>
{/* Progress Display */}
{progress && (
<div style={{
padding: '8px 12px',
background: 'var(--surface2)',
borderRadius: '4px',
fontSize: '12px',
color: 'var(--text2)',
cursor: 'pointer'
}}
title="Debug-Modus zeigt detaillierte Node-States im Ergebnis"
>
<input
type="checkbox"
checked={debugMode}
onChange={(e) => setDebugMode(e.target.checked)}
disabled={executing}
/>
Debug
</label>
{/* Execute Button */}
<button
className="btn-primary"
onClick={handleExecute}
disabled={disabled || executing || !currentPrompt}
title={
!currentPrompt
? 'Workflow muss zuerst gespeichert werden'
: disabled
? 'Workflow hat Validierungsfehler'
: 'Workflow ausführen'
}
style={{
minWidth: '120px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px'
}}
>
{executing ? (
<>
<span className="spinner" style={{ width: 14, height: 14 }} />
Executing...
</>
) : (
<>
Execute
</>
)}
</button>
{/* Error Display */}
{error && (
<div
style={{
padding: '6px 12px',
background: 'var(--danger)',
color: 'white',
borderRadius: '4px',
fontSize: '12px',
maxWidth: '300px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
title={error}
>
{error}
}}>
<span className="spinner" style={{ width: 12, height: 12 }} />
<span>
{progress.total > 0
? `${progress.current}/${progress.total} Nodes`
: 'Läuft...'
}
{progress.label && `${progress.label}`}
</span>
</div>
)}
</div>