golem-flask-backend / src /components /WorkflowMonitor.tsx
mememechez's picture
Deploy final cleaned source code
ca28016
raw
history blame
21.4 kB
'use client';
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Input } from '@/components/ui/input';
import { SafeTimestamp } from './ClientTimeStampProvider';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Activity,
CheckCircle,
AlertCircle,
Clock,
Zap,
Filter,
Search,
RefreshCw,
Eye,
Trash2,
TrendingUp,
Brain
} from 'lucide-react';
interface WorkflowStatus {
workflow_id: string;
status: string;
current_stage: number;
stages: Array<{
name: string;
status: string;
description: string;
started_at?: string;
completed_at?: string;
error?: string;
}>;
progress_percentage: number;
created_at: string;
metrics?: {
consciousness_scores?: number[];
accuracy_scores?: number[];
total_time?: number;
error_count?: number;
};
error?: string;
}
interface WorkflowMonitorProps {
workflows?: WorkflowStatus[];
}
export default function WorkflowMonitor({ workflows: propWorkflows }: WorkflowMonitorProps) {
const [workflows, setWorkflows] = useState<WorkflowStatus[]>(propWorkflows || []);
const [filteredWorkflows, setFilteredWorkflows] = useState<WorkflowStatus[]>([]);
const [statusFilter, setStatusFilter] = useState<string>('all');
const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedWorkflow, setSelectedWorkflow] = useState<WorkflowStatus | null>(null);
// Fetch workflows on component mount and periodically
useEffect(() => {
fetchWorkflows();
const interval = setInterval(fetchWorkflows, 5000); // Refresh every 5 seconds
return () => clearInterval(interval);
}, []);
// Update filtered workflows when filters change
useEffect(() => {
let filtered = workflows;
// Apply status filter
if (statusFilter !== 'all') {
filtered = filtered.filter(w => w.status === statusFilter);
}
// Apply search filter
if (searchTerm) {
filtered = filtered.filter(w =>
w.workflow_id.toLowerCase().includes(searchTerm.toLowerCase()) ||
w.stages.some(stage => stage.name.toLowerCase().includes(searchTerm.toLowerCase()))
);
}
setFilteredWorkflows(filtered);
}, [workflows, statusFilter, searchTerm]);
const fetchWorkflows = async () => {
if (propWorkflows) return; // Don't fetch if workflows are provided as props
setIsLoading(true);
try {
const response = await fetch('/api/ai-architect/workflows');
if (response.ok) {
const data = await response.json();
setWorkflows(data);
}
} catch (error) {
console.error('Failed to fetch workflows:', error);
} finally {
setIsLoading(false);
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'completed': return <CheckCircle className="w-4 h-4 text-green-500" />;
case 'running': return <Activity className="w-4 h-4 text-blue-500 animate-pulse" />;
case 'failed': return <AlertCircle className="w-4 h-4 text-red-500" />;
case 'initializing': return <Clock className="w-4 h-4 text-yellow-500" />;
default: return <Clock className="w-4 h-4 text-gray-400" />;
}
};
const getStatusBadge = (status: string) => {
const variants = {
completed: 'border-green-500 text-green-400',
running: 'border-blue-500 text-blue-400 animate-pulse',
failed: 'border-red-500 text-red-400',
initializing: 'border-yellow-500 text-yellow-400'
};
return (
<Badge variant="outline" className={variants[status as keyof typeof variants] || 'border-gray-500 text-gray-400'}>
{status.toUpperCase()}
</Badge>
);
};
const formatDuration = (startTime: string, endTime?: string) => {
const start = new Date(startTime);
const end = endTime ? new Date(endTime) : new Date();
const duration = Math.round((end.getTime() - start.getTime()) / 1000 / 60); // minutes
if (duration < 60) {
return `${duration}m`;
} else {
const hours = Math.floor(duration / 60);
const minutes = duration % 60;
return `${hours}h ${minutes}m`;
}
};
const deleteWorkflow = async (workflowId: string) => {
if (!confirm('Are you sure you want to delete this workflow?')) return;
try {
// This would be an actual delete endpoint
setWorkflows(prev => prev.filter(w => w.workflow_id !== workflowId));
} catch (error) {
console.error('Failed to delete workflow:', error);
}
};
const getOverallStats = () => {
const total = workflows.length;
const completed = workflows.filter(w => w.status === 'completed').length;
const running = workflows.filter(w => w.status === 'running').length;
const failed = workflows.filter(w => w.status === 'failed').length;
const avgConsciousness = workflows.length > 0
? workflows.reduce((sum, w) => {
const scores = w.metrics?.consciousness_scores || [];
return sum + (scores.length > 0 ? scores[0] : 0);
}, 0) / workflows.length
: 0;
const avgAccuracy = workflows.length > 0
? workflows.reduce((sum, w) => {
const scores = w.metrics?.accuracy_scores || [];
return sum + (scores.length > 0 ? scores[0] : 0);
}, 0) / workflows.length
: 0;
return { total, completed, running, failed, avgConsciousness, avgAccuracy };
};
const stats = getOverallStats();
return (
<div className="w-full h-full flex flex-col space-y-6">
{/* Stats Overview */}
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
<Card className="bg-gradient-to-r from-blue-600/20 to-blue-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-blue-300">{stats.total}</div>
<div className="text-sm text-blue-400">Total Workflows</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-green-600/20 to-green-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-green-300">{stats.completed}</div>
<div className="text-sm text-green-400">Completed</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-blue-600/20 to-blue-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-blue-300">{stats.running}</div>
<div className="text-sm text-blue-400">Running</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-red-600/20 to-red-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-red-300">{stats.failed}</div>
<div className="text-sm text-red-400">Failed</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-purple-600/20 to-purple-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-purple-300">
{(stats.avgConsciousness * 100).toFixed(1)}%
</div>
<div className="text-sm text-purple-400">Avg Consciousness</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-orange-600/20 to-orange-800/20">
<CardContent className="p-4">
<div className="text-2xl font-bold text-orange-300">
{(stats.avgAccuracy * 100).toFixed(1)}%
</div>
<div className="text-sm text-orange-400">Avg Accuracy</div>
</CardContent>
</Card>
</div>
{/* Filters and Controls */}
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Activity className="w-5 h-5" />
<span>Workflow Monitor</span>
<Badge variant="outline">{filteredWorkflows.length} workflows</Badge>
</div>
<Button
variant="outline"
size="sm"
onClick={fetchWorkflows}
disabled={isLoading}
>
<RefreshCw className={`w-4 h-4 mr-1 ${isLoading ? 'animate-spin' : ''}`} />
Refresh
</Button>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<Search className="w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search workflows..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-64"
/>
</div>
<div className="flex items-center space-x-2">
<Filter className="w-4 h-4 text-muted-foreground" />
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-40">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="running">Running</SelectItem>
<SelectItem value="completed">Completed</SelectItem>
<SelectItem value="failed">Failed</SelectItem>
<SelectItem value="initializing">Initializing</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* Workflows List */}
<div className="flex-1 space-y-4 overflow-auto">
{filteredWorkflows.length === 0 ? (
<Card>
<CardContent className="p-8 text-center">
<Activity className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">No workflows found</h3>
<p className="text-muted-foreground">
{workflows.length === 0
? "No workflows have been created yet. Create your first workflow in the AI Architect panel."
: "No workflows match your current filters. Try adjusting the search or filter criteria."
}
</p>
</CardContent>
</Card>
) : (
filteredWorkflows.map((workflow) => (
<Card key={workflow.workflow_id} className="hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1 space-y-3">
{/* Header */}
<div className="flex items-center space-x-3">
{getStatusIcon(workflow.status)}
<span className="font-medium text-lg">{workflow.workflow_id}</span>
{getStatusBadge(workflow.status)}
<span className="text-sm text-muted-foreground">
Created {formatDuration(workflow.created_at)} ago
</span>
</div>
{/* Progress */}
<div className="flex items-center space-x-4">
<Progress value={workflow.progress_percentage} className="flex-1" />
<span className="text-sm font-medium">
{Math.round(workflow.progress_percentage)}%
</span>
<span className="text-sm text-muted-foreground">
Stage {workflow.current_stage + 1}/{workflow.stages.length}
</span>
</div>
{/* Current Stage */}
{workflow.stages.length > 0 && workflow.current_stage < workflow.stages.length && (
<div className="flex items-center space-x-2">
<span className="text-sm text-muted-foreground">Current:</span>
<span className="text-sm font-medium">
{workflow.stages[workflow.current_stage]?.name.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
</span>
</div>
)}
{/* Metrics */}
{workflow.metrics && (
<div className="flex items-center space-x-6">
{workflow.metrics.consciousness_scores && workflow.metrics.consciousness_scores.length > 0 && (
<div className="flex items-center space-x-2">
<Brain className="w-4 h-4 text-purple-400" />
<span className="text-sm">
Consciousness: {(workflow.metrics.consciousness_scores[0] * 100).toFixed(1)}%
</span>
</div>
)}
{workflow.metrics.accuracy_scores && workflow.metrics.accuracy_scores.length > 0 && (
<div className="flex items-center space-x-2">
<TrendingUp className="w-4 h-4 text-green-400" />
<span className="text-sm">
Accuracy: {(workflow.metrics.accuracy_scores[0] * 100).toFixed(1)}%
</span>
</div>
)}
{workflow.metrics.total_time && (
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4 text-blue-400" />
<span className="text-sm">
Duration: {Math.round(workflow.metrics.total_time / 60)}m
</span>
</div>
)}
</div>
)}
{/* Error Display */}
{workflow.error && (
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<div className="flex items-center space-x-2">
<AlertCircle className="w-4 h-4 text-red-400" />
<span className="text-sm text-red-400 font-medium">Error:</span>
</div>
<p className="text-sm text-red-300 mt-1">{workflow.error}</p>
</div>
)}
</div>
{/* Actions */}
<div className="flex items-center space-x-2 ml-4">
<Button
variant="outline"
size="sm"
onClick={() => setSelectedWorkflow(workflow)}
>
<Eye className="w-4 h-4 mr-1" />
Details
</Button>
<Button
variant="outline"
size="sm"
onClick={() => deleteWorkflow(workflow.workflow_id)}
className="text-red-400 hover:text-red-300"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
{/* Detailed View Modal (Simple version) */}
{selectedWorkflow && (
<Card className="fixed inset-4 z-50 bg-background/95 backdrop-blur-sm border shadow-lg overflow-auto">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center space-x-2">
<Zap className="w-5 h-5 text-purple-400" />
<span>Workflow Details: {selectedWorkflow.workflow_id}</span>
</CardTitle>
<Button
variant="outline"
size="sm"
onClick={() => setSelectedWorkflow(null)}
>
Close
</Button>
</div>
</CardHeader>
<CardContent className="space-y-6">
{/* Status and Progress */}
<div className="flex items-center justify-between">
{getStatusBadge(selectedWorkflow.status)}
<div className="flex items-center space-x-4">
<Progress value={selectedWorkflow.progress_percentage} className="w-32" />
<span className="text-sm font-medium">
{Math.round(selectedWorkflow.progress_percentage)}%
</span>
</div>
</div>
{/* All Stages */}
<div className="space-y-3">
<h3 className="text-lg font-semibold">Workflow Stages</h3>
{selectedWorkflow.stages.map((stage, index) => (
<div
key={index}
className={`p-4 rounded-lg border ${
index === selectedWorkflow.current_stage ? 'border-blue-500 bg-blue-500/10' : 'border-border'
}`}
>
<div className="flex items-center space-x-3">
{getStatusIcon(stage.status)}
<span className="font-medium">
{stage.name.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
</span>
{getStatusBadge(stage.status)}
</div>
<p className="text-sm text-muted-foreground mt-2">{stage.description}</p>
{stage.error && (
<p className="text-sm text-red-400 mt-2">Error: {stage.error}</p>
)}
<div className="flex items-center space-x-4 mt-2 text-xs text-muted-foreground">
{stage.started_at && (
<span>Started: <SafeTimestamp timestamp={new Date(stage.started_at)} format="datetime" /></span>
)}
{stage.completed_at && (
<span>Completed: <SafeTimestamp timestamp={new Date(stage.completed_at)} format="datetime" /></span>
)}
</div>
</div>
))}
</div>
{/* Detailed Metrics */}
{selectedWorkflow.metrics && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<h4 className="font-medium">Performance Metrics</h4>
{selectedWorkflow.metrics.consciousness_scores && (
<div className="flex justify-between">
<span className="text-sm text-muted-foreground">Consciousness Score:</span>
<span className="text-sm font-medium">
{(selectedWorkflow.metrics.consciousness_scores[0] * 100).toFixed(2)}%
</span>
</div>
)}
{selectedWorkflow.metrics.accuracy_scores && (
<div className="flex justify-between">
<span className="text-sm text-muted-foreground">Model Accuracy:</span>
<span className="text-sm font-medium">
{(selectedWorkflow.metrics.accuracy_scores[0] * 100).toFixed(2)}%
</span>
</div>
)}
{selectedWorkflow.metrics.total_time && (
<div className="flex justify-between">
<span className="text-sm text-muted-foreground">Total Time:</span>
<span className="text-sm font-medium">
{Math.round(selectedWorkflow.metrics.total_time / 60)} minutes
</span>
</div>
)}
</div>
<div className="space-y-2">
<h4 className="font-medium">System Metrics</h4>
<div className="flex justify-between">
<span className="text-sm text-muted-foreground">Error Count:</span>
<span className="text-sm font-medium">
{selectedWorkflow.metrics.error_count || 0}
</span>
</div>
<div className="flex justify-between">
<span className="text-sm text-muted-foreground">Created:</span>
<span className="text-sm font-medium">
<SafeTimestamp timestamp={new Date(selectedWorkflow.created_at)} format="datetime" />
</span>
</div>
</div>
</div>
)}
</CardContent>
</Card>
)}
</div>
);
}