'use client'
import React, { useState, useEffect, useCallback } from 'react'
import {
Play,
Pause,
StepForward,
SkipForward,
Square,
Bug,
Eye,
Code,
ChevronDown,
ChevronRight,
CheckCircle2,
XCircle,
Clock,
AlertCircle,
} from 'lucide-react'
import {
workflowService,
Workflow,
WorkflowExecution,
WorkflowStep,
StepExecutionResult,
} from '../services/workflowService'
interface WorkflowDebuggerProps {
workflow: Workflow
executionId: string | null
onExecutionStart?: (executionId: string) => void
}
export default function WorkflowDebugger({
workflow,
executionId,
onExecutionStart,
}: WorkflowDebuggerProps) {
const [execution, setExecution] = useState<WorkflowExecution | null>(null)
const [loading, setLoading] = useState(false)
const [polling, setPolling] = useState(false)
const [expandedSteps, setExpandedSteps] = useState<Set<string>>(new Set())
const [selectedStepId, setSelectedStepId] = useState<string | null>(null)
const [variables, setVariables] = useState<Record<string, any>>({})
const [debugMode, setDebugMode] = useState(true)
// Poll execution status
useEffect(() => {
if (!executionId || !polling) return
const interval = setInterval(async () => {
try {
const status = await workflowService.getExecutionStatus(executionId)
setExecution(status)
// Stop polling if execution is complete or failed
if (['completed', 'failed', 'cancelled'].includes(status.status)) {
setPolling(false)
}
} catch (error) {
console.error('Failed to poll execution status:', error)
}
}, 1000) // Poll every second
return () => clearInterval(interval)
}, [executionId, polling])
// Load execution status on mount or when executionId changes
useEffect(() => {
if (executionId) {
loadExecutionStatus()
}
}, [executionId])
const loadExecutionStatus = async () => {
if (!executionId) return
try {
const status = await workflowService.getExecutionStatus(executionId)
setExecution(status)
setPolling(['running', 'debugging', 'paused'].includes(status.status))
} catch (error) {
console.error('Failed to load execution status:', error)
}
}
const handleStart = async () => {
if (!workflow) return
// Collect required variables
const requiredVars: Record<string, any> = {}
for (const variable of workflow.variables || []) {
if (variable.required && !variables[variable.name]) {
alert(`Please provide required variable: ${variable.name}`)
return
}
if (variables[variable.name] !== undefined) {
requiredVars[variable.name] = variables[variable.name]
} else if (variable.default_value !== undefined) {
requiredVars[variable.name] = variable.default_value
}
}
setLoading(true)
try {
const result = await workflowService.executeWorkflow(workflow.id, requiredVars, debugMode)
setPolling(true)
if (onExecutionStart) {
onExecutionStart(result.execution_id)
}
await loadExecutionStatus()
} catch (error) {
console.error('Failed to start execution:', error)
alert(`Failed to start execution: ${error}`)
} finally {
setLoading(false)
}
}
const handlePause = async () => {
if (!executionId) return
try {
await workflowService.pauseExecution(executionId)
await loadExecutionStatus()
} catch (error) {
console.error('Failed to pause execution:', error)
alert(`Failed to pause: ${error}`)
}
}
const handleResume = async () => {
if (!executionId) return
try {
await workflowService.resumeExecution(executionId)
setPolling(true)
await loadExecutionStatus()
} catch (error) {
console.error('Failed to resume execution:', error)
alert(`Failed to resume: ${error}`)
}
}
const handleStep = async () => {
if (!executionId) return
try {
await workflowService.stepExecution(executionId)
setPolling(true)
await loadExecutionStatus()
} catch (error) {
console.error('Failed to step execution:', error)
alert(`Failed to step: ${error}`)
}
}
const handleContinue = async () => {
if (!executionId) return
try {
await workflowService.continueExecution(executionId)
setPolling(true)
await loadExecutionStatus()
} catch (error) {
console.error('Failed to continue execution:', error)
alert(`Failed to continue: ${error}`)
}
}
const toggleStepExpand = (stepId: string) => {
const newExpanded = new Set(expandedSteps)
if (newExpanded.has(stepId)) {
newExpanded.delete(stepId)
} else {
newExpanded.add(stepId)
}
setExpandedSteps(newExpanded)
}
const getStepStatus = (stepId: string): StepExecutionResult | null => {
if (!execution) return null
return execution.step_results.find((r) => r.step_id === stepId) || null
}
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return '#10b981'
case 'failed':
return '#ef4444'
case 'running':
return '#3b82f6'
case 'pending':
return '#6b7280'
case 'skipped':
return '#9ca3af'
default:
return '#6b7280'
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case 'completed':
return <CheckCircle2 size={16} color="#10b981" />
case 'failed':
return <XCircle size={16} color="#ef4444" />
case 'running':
return <Clock size={16} color="#3b82f6" />
case 'pending':
return <Clock size={16} color="#6b7280" />
default:
return null
}
}
const isCurrentStep = (stepId: string) => {
return execution?.current_step_id === stepId
}
const sortedSteps = [...(workflow.steps || [])].sort((a, b) => a.order - b.order)
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
{/* Debug Controls */}
<div
style={{
padding: '16px',
backgroundColor: '#1f2937',
borderRadius: '8px',
border: '1px solid #374151',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px' }}>
<Bug size={20} color="#fbbf24" />
<h3 style={{ fontSize: '18px', fontWeight: '600', color: '#f9fafb', margin: 0 }}>
Debug Controls
</h3>
<label
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
marginLeft: 'auto',
color: '#d1d5db',
fontSize: '14px',
}}
>
<input
type="checkbox"
checked={debugMode}
onChange={(e) => setDebugMode(e.target.checked)}
style={{ width: '16px', height: '16px' }}
/>
Debug Mode (Step-by-step)
</label>
</div>
{!executionId ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{/* Variable Input */}
{workflow.variables && workflow.variables.length > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<label style={{ fontSize: '14px', fontWeight: '500', color: '#d1d5db' }}>
Execution Variables:
</label>
{workflow.variables.map((variable) => (
<div key={variable.name} style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<label style={{ fontSize: '12px', color: '#9ca3af' }}>
{variable.name}
{variable.required && <span style={{ color: '#ef4444' }}> *</span>}
{variable.description && (
<span style={{ color: '#6b7280', marginLeft: '8px' }}> - {variable.description}</span>
)}
</label>
<input
type={variable.type === 'number' ? 'number' : 'text'}
value={variables[variable.name] || variable.default_value || ''}
onChange={(e) =>
setVariables({ ...variables, [variable.name]: e.target.value })
}
placeholder={variable.default_value ? String(variable.default_value) : ''}
style={{
padding: '8px 12px',
backgroundColor: '#111827',
border: '1px solid #374151',
borderRadius: '6px',
color: '#f9fafb',
fontSize: '14px',
}}
/>
</div>
))}
</div>
)}
<button
onClick={handleStart}
disabled={loading}
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '10px 20px',
backgroundColor: '#10b981',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '14px',
fontWeight: '500',
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1,
}}
>
<Play size={16} />
{loading ? 'Starting...' : 'Start Execution'}
</button>
</div>
) : (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
{execution?.status === 'running' && (
<button
onClick={handlePause}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '8px 16px',
backgroundColor: '#f59e0b',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
}}
>
<Pause size={14} />
Pause
</button>
)}
{execution?.status === 'paused' && (
<>
<button
onClick={handleResume}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '8px 16px',
backgroundColor: '#10b981',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
}}
>
<Play size={14} />
Resume
</button>
<button
onClick={handleStep}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '8px 16px',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
}}
>
<StepForward size={14} />
Step
</button>
</>
)}
{execution?.status === 'debugging' && (
<>
<button
onClick={handleStep}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '8px 16px',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
}}
>
<StepForward size={14} />
Step
</button>
<button
onClick={handleContinue}
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '8px 16px',
backgroundColor: '#8b5cf6',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '13px',
cursor: 'pointer',
}}
>
<SkipForward size={14} />
Continue
</button>
</>
)}
<div
style={{
padding: '6px 12px',
backgroundColor: '#111827',
borderRadius: '6px',
fontSize: '12px',
color: '#d1d5db',
border: '1px solid #374151',
}}
>
Status: <strong style={{ color: getStatusColor(execution?.status || 'pending') }}>
{execution?.status?.toUpperCase() || 'PENDING'}
</strong>
</div>
</div>
)}
</div>
{/* Steps List */}
<div
style={{
flex: 1,
overflow: 'auto',
padding: '16px',
backgroundColor: '#111827',
borderRadius: '8px',
border: '1px solid #374151',
}}
>
<h4 style={{ fontSize: '16px', fontWeight: '600', color: '#f9fafb', marginBottom: '12px' }}>
Workflow Steps
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{sortedSteps.map((step) => {
const stepResult = getStepStatus(step.id)
const isCurrent = isCurrentStep(step.id)
const isExpanded = expandedSteps.has(step.id)
return (
<div
key={step.id}
style={{
backgroundColor: isCurrent ? '#1e3a8a' : '#1f2937',
border: `2px solid ${isCurrent ? '#3b82f6' : '#374151'}`,
borderRadius: '6px',
padding: '12px',
cursor: 'pointer',
}}
onClick={() => {
toggleStepExpand(step.id)
setSelectedStepId(step.id)
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: '120px' }}>
{isExpanded ? (
<ChevronDown size={16} color="#9ca3af" />
) : (
<ChevronRight size={16} color="#9ca3af" />
)}
<span style={{ fontSize: '12px', color: '#9ca3af', fontWeight: '500' }}>
#{step.order}
</span>
{stepResult && getStatusIcon(stepResult.status)}
{step.breakpoint && <Bug size={14} color="#fbbf24" />}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '14px', fontWeight: '500', color: '#f9fafb' }}>
{step.name}
</div>
<div style={{ fontSize: '12px', color: '#9ca3af', marginTop: '2px' }}>
{step.type}
{step.mcp_server && step.tool_name && ` • ${step.mcp_server}/${step.tool_name}`}
</div>
</div>
{stepResult && (
<div
style={{
padding: '4px 8px',
backgroundColor: '#111827',
borderRadius: '4px',
fontSize: '11px',
color: getStatusColor(stepResult.status),
fontWeight: '500',
}}
>
{stepResult.status.toUpperCase()}
</div>
)}
</div>
{isExpanded && (
<div style={{ marginTop: '12px', paddingTop: '12px', borderTop: '1px solid #374151' }}>
{/* Step Details */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ fontSize: '12px', color: '#9ca3af' }}>
<strong style={{ color: '#d1d5db' }}>Type:</strong> {step.type}
</div>
{step.mcp_server && (
<div style={{ fontSize: '12px', color: '#9ca3af' }}>
<strong style={{ color: '#d1d5db' }}>MCP Server:</strong> {step.mcp_server}
</div>
)}
{step.tool_name && (
<div style={{ fontSize: '12px', color: '#9ca3af' }}>
<strong style={{ color: '#d1d5db' }}>Tool:</strong> {step.tool_name}
</div>
)}
{step.arguments && Object.keys(step.arguments).length > 0 && (
<div>
<div style={{ fontSize: '12px', fontWeight: '500', color: '#d1d5db', marginBottom: '4px' }}>
Arguments:
</div>
<pre
style={{
padding: '8px',
backgroundColor: '#0f172a',
borderRadius: '4px',
fontSize: '11px',
color: '#94a3b8',
overflow: 'auto',
maxHeight: '150px',
}}
>
{JSON.stringify(step.arguments, null, 2)}
</pre>
</div>
)}
{/* Step Result */}
{stepResult && (
<div style={{ marginTop: '8px' }}>
<div style={{ fontSize: '12px', fontWeight: '500', color: '#d1d5db', marginBottom: '4px' }}>
Execution Result:
</div>
{stepResult.error_message && (
<div
style={{
padding: '8px',
backgroundColor: '#7f1d1d',
borderRadius: '4px',
fontSize: '12px',
color: '#fca5a5',
marginBottom: '8px',
}}
>
<AlertCircle size={14} style={{ display: 'inline', marginRight: '6px' }} />
{stepResult.error_message}
</div>
)}
{stepResult.mcp_tool_call && (
<div style={{ marginBottom: '8px' }}>
<div style={{ fontSize: '11px', fontWeight: '500', color: '#9ca3af', marginBottom: '4px' }}>
MCP Tool Call:
</div>
<pre
style={{
padding: '8px',
backgroundColor: '#0f172a',
borderRadius: '4px',
fontSize: '11px',
color: '#94a3b8',
overflow: 'auto',
maxHeight: '100px',
}}
>
{JSON.stringify(stepResult.mcp_tool_call, null, 2)}
</pre>
</div>
)}
{stepResult.mcp_tool_response && (
<div>
<div style={{ fontSize: '11px', fontWeight: '500', color: '#9ca3af', marginBottom: '4px' }}>
MCP Tool Response:
</div>
<pre
style={{
padding: '8px',
backgroundColor: '#0f172a',
borderRadius: '4px',
fontSize: '11px',
color: '#94a3b8',
overflow: 'auto',
maxHeight: '200px',
}}
>
{JSON.stringify(stepResult.mcp_tool_response, null, 2)}
</pre>
</div>
)}
{stepResult.output_data && (
<div style={{ marginTop: '8px' }}>
<div style={{ fontSize: '11px', fontWeight: '500', color: '#9ca3af', marginBottom: '4px' }}>
Output Data:
</div>
<pre
style={{
padding: '8px',
backgroundColor: '#0f172a',
borderRadius: '4px',
fontSize: '11px',
color: '#94a3b8',
overflow: 'auto',
maxHeight: '150px',
}}
>
{JSON.stringify(stepResult.output_data, null, 2)}
</pre>
</div>
)}
</div>
)}
</div>
</div>
)}
</div>
)
})}
</div>
</div>
{/* Variables Panel */}
{execution && execution.step_outputs && Object.keys(execution.step_outputs).length > 0 && (
<div
style={{
padding: '16px',
backgroundColor: '#1f2937',
borderRadius: '8px',
border: '1px solid #374151',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
<Eye size={16} color="#60a5fa" />
<h4 style={{ fontSize: '14px', fontWeight: '600', color: '#f9fafb', margin: 0 }}>
Step Outputs (Variables)
</h4>
</div>
<pre
style={{
padding: '12px',
backgroundColor: '#111827',
borderRadius: '6px',
fontSize: '12px',
color: '#94a3b8',
overflow: 'auto',
maxHeight: '200px',
}}
>
{JSON.stringify(execution.step_outputs, null, 2)}
</pre>
</div>
)}
</div>
)
}