| | 'use client'; |
| |
|
| | import { motion } from 'framer-motion'; |
| | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; |
| | import { Progress } from '@/components/ui/progress'; |
| | import { Badge } from '@/components/ui/badge'; |
| | import { CheckCircle, Circle, Loader2, FileText, BarChart3, Users, Mail } from 'lucide-react'; |
| | import { Workflow } from '@/lib/types'; |
| | import { cn } from '@/lib/utils'; |
| |
|
| | interface WorkflowProgressProps { |
| | workflow: Workflow; |
| | } |
| |
|
| | const WORKFLOW_STEPS = [ |
| | { |
| | key: 'document_analysis', |
| | label: 'Patent Analysis', |
| | description: 'Extracting key innovations and TRL assessment', |
| | icon: FileText, |
| | progressRange: [0, 30], |
| | }, |
| | { |
| | key: 'market_analysis', |
| | label: 'Market Research', |
| | description: 'Identifying commercialization opportunities', |
| | icon: BarChart3, |
| | progressRange: [30, 60], |
| | }, |
| | { |
| | key: 'matchmaking', |
| | label: 'Partner Matching', |
| | description: 'Finding relevant stakeholders with semantic search', |
| | icon: Users, |
| | progressRange: [60, 85], |
| | }, |
| | { |
| | key: 'outreach', |
| | label: 'Brief Generation', |
| | description: 'Creating valorization brief document', |
| | icon: Mail, |
| | progressRange: [85, 100], |
| | }, |
| | ]; |
| |
|
| | export function WorkflowProgress({ workflow }: WorkflowProgressProps) { |
| | |
| | const currentStepIndex = workflow.current_step |
| | ? WORKFLOW_STEPS.findIndex((step) => step.key === workflow.current_step) |
| | : Math.floor(workflow.progress / 25); |
| |
|
| | const getStepStatus = (stepIndex: number) => { |
| | if (workflow.status === 'failed') { |
| | return stepIndex <= currentStepIndex ? 'failed' : 'pending'; |
| | } |
| | if (workflow.status === 'completed') { |
| | return 'completed'; |
| | } |
| | if (stepIndex < currentStepIndex) { |
| | return 'completed'; |
| | } |
| | if (stepIndex === currentStepIndex) { |
| | return 'in-progress'; |
| | } |
| | return 'pending'; |
| | }; |
| |
|
| | return ( |
| | <div className="w-full max-w-3xl mx-auto space-y-6"> |
| | {/* Overall Progress */} |
| | <Card> |
| | <CardHeader> |
| | <div className="flex items-center justify-between"> |
| | <CardTitle className="text-2xl"> |
| | {workflow.status === 'completed' && '✅ Analysis Complete'} |
| | {workflow.status === 'failed' && '❌ Analysis Failed'} |
| | {workflow.status === 'running' && '⚡ Analyzing Patent...'} |
| | {workflow.status === 'queued' && '⏳ Queued for Processing'} |
| | </CardTitle> |
| | <Badge |
| | variant={ |
| | workflow.status === 'completed' |
| | ? 'default' |
| | : workflow.status === 'failed' |
| | ? 'destructive' |
| | : 'secondary' |
| | } |
| | className="text-sm" |
| | > |
| | {workflow.status.toUpperCase()} |
| | </Badge> |
| | </div> |
| | </CardHeader> |
| | <CardContent> |
| | <div className="space-y-2"> |
| | <div className="flex justify-between text-sm"> |
| | <span className="text-gray-600">Overall Progress</span> |
| | <span className="font-medium">{workflow.progress}%</span> |
| | </div> |
| | <Progress value={workflow.progress} className="h-3" /> |
| | </div> |
| | </CardContent> |
| | </Card> |
| | |
| | {/* Workflow Steps */} |
| | <div className="space-y-4"> |
| | {WORKFLOW_STEPS.map((step, index) => { |
| | const status = getStepStatus(index); |
| | const Icon = step.icon; |
| | |
| | return ( |
| | <motion.div |
| | key={step.key} |
| | initial={{ opacity: 0, x: -20 }} |
| | animate={{ opacity: 1, x: 0 }} |
| | transition={{ delay: index * 0.1 }} |
| | > |
| | <Card |
| | className={cn( |
| | 'transition-all', |
| | status === 'in-progress' && 'border-blue-500 bg-blue-50', |
| | status === 'completed' && 'border-green-200 bg-green-50', |
| | status === 'failed' && 'border-red-200 bg-red-50' |
| | )} |
| | > |
| | <CardContent className="p-6"> |
| | <div className="flex items-start space-x-4"> |
| | {/* Status Icon */} |
| | <div |
| | className={cn( |
| | 'flex h-12 w-12 shrink-0 items-center justify-center rounded-full', |
| | status === 'completed' && 'bg-green-100', |
| | status === 'in-progress' && 'bg-blue-100', |
| | status === 'pending' && 'bg-gray-100', |
| | status === 'failed' && 'bg-red-100' |
| | )} |
| | > |
| | {status === 'completed' && ( |
| | <CheckCircle className="h-6 w-6 text-green-600" /> |
| | )} |
| | {status === 'in-progress' && ( |
| | <Loader2 className="h-6 w-6 text-blue-600 animate-spin" /> |
| | )} |
| | {status === 'pending' && ( |
| | <Circle className="h-6 w-6 text-gray-400" /> |
| | )} |
| | {status === 'failed' && ( |
| | <Circle className="h-6 w-6 text-red-600" /> |
| | )} |
| | </div> |
| | |
| | {/* Step Content */} |
| | <div className="flex-1 min-w-0"> |
| | <div className="flex items-center space-x-3 mb-1"> |
| | <Icon |
| | className={cn( |
| | 'h-5 w-5', |
| | status === 'completed' && 'text-green-600', |
| | status === 'in-progress' && 'text-blue-600', |
| | status === 'pending' && 'text-gray-400', |
| | status === 'failed' && 'text-red-600' |
| | )} |
| | /> |
| | <h3 |
| | className={cn( |
| | 'text-lg font-semibold', |
| | status === 'completed' && 'text-green-900', |
| | status === 'in-progress' && 'text-blue-900', |
| | status === 'pending' && 'text-gray-500', |
| | status === 'failed' && 'text-red-900' |
| | )} |
| | > |
| | {step.label} |
| | </h3> |
| | <Badge |
| | variant={ |
| | status === 'completed' |
| | ? 'default' |
| | : status === 'in-progress' |
| | ? 'secondary' |
| | : 'outline' |
| | } |
| | className="text-xs" |
| | > |
| | {status === 'completed' && 'Done'} |
| | {status === 'in-progress' && 'Processing...'} |
| | {status === 'pending' && 'Pending'} |
| | {status === 'failed' && 'Failed'} |
| | </Badge> |
| | </div> |
| | <p |
| | className={cn( |
| | 'text-sm', |
| | status === 'completed' && 'text-green-700', |
| | status === 'in-progress' && 'text-blue-700', |
| | status === 'pending' && 'text-gray-500', |
| | status === 'failed' && 'text-red-700' |
| | )} |
| | > |
| | {step.description} |
| | </p> |
| | |
| | {/* Step Progress Bar (only for in-progress step) */} |
| | {status === 'in-progress' && ( |
| | <motion.div |
| | initial={{ opacity: 0, y: -5 }} |
| | animate={{ opacity: 1, y: 0 }} |
| | className="mt-3" |
| | > |
| | <Progress |
| | value={ |
| | ((workflow.progress - step.progressRange[0]) / |
| | (step.progressRange[1] - step.progressRange[0])) * |
| | 100 |
| | } |
| | className="h-2" |
| | /> |
| | </motion.div> |
| | )} |
| | </div> |
| | </div> |
| | </CardContent> |
| | </Card> |
| | </motion.div> |
| | ); |
| | })} |
| | </div> |
| | |
| | {/* Error Display */} |
| | {workflow.error && ( |
| | <motion.div |
| | initial={{ opacity: 0, y: 10 }} |
| | animate={{ opacity: 1, y: 0 }} |
| | > |
| | <Card className="border-red-200 bg-red-50"> |
| | <CardContent className="p-6"> |
| | <div className="flex items-start space-x-3"> |
| | <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-red-100"> |
| | <span className="text-xl">⚠️</span> |
| | </div> |
| | <div> |
| | <h3 className="font-semibold text-red-900">Error Occurred</h3> |
| | <p className="text-sm text-red-700 mt-1">{workflow.error}</p> |
| | </div> |
| | </div> |
| | </CardContent> |
| | </Card> |
| | </motion.div> |
| | )} |
| | |
| | {/* Completion Message */} |
| | {workflow.status === 'completed' && ( |
| | <motion.div |
| | initial={{ opacity: 0, scale: 0.95 }} |
| | animate={{ opacity: 1, scale: 1 }} |
| | transition={{ duration: 0.5 }} |
| | > |
| | <Card className="border-green-200 bg-gradient-to-br from-green-50 to-emerald-50"> |
| | <CardContent className="p-6"> |
| | <div className="text-center space-y-2"> |
| | <div className="flex justify-center"> |
| | <div className="flex h-16 w-16 items-center justify-center rounded-full bg-green-100"> |
| | <CheckCircle className="h-10 w-10 text-green-600" /> |
| | </div> |
| | </div> |
| | <h3 className="text-xl font-bold text-green-900"> |
| | Analysis Complete! |
| | </h3> |
| | <p className="text-green-700"> |
| | Your patent analysis is ready. Redirecting to results... |
| | </p> |
| | </div> |
| | </CardContent> |
| | </Card> |
| | </motion.div> |
| | )} |
| | </div> |
| | ); |
| | } |
| |
|