33import { useEffect , useState } from 'react'
44import { useRouter } from 'next/navigation'
55import { formatDistanceToNow } from 'date-fns'
6- import { Bell , History , Loader2 , Play , Rocket , Store , Trash2 } from 'lucide-react'
6+ import { Bell , ChevronDown , History , Loader2 , Play , Rocket , Store , Trash2 } from 'lucide-react'
77import {
88 AlertDialog ,
99 AlertDialogAction ,
@@ -22,6 +22,7 @@ import {
2222 DropdownMenuItem ,
2323 DropdownMenuTrigger ,
2424} from '@/components/ui/dropdown-menu'
25+ import { Progress } from '@/components/ui/progress'
2526import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip'
2627import { createLogger } from '@/lib/logs/console-logger'
2728import { cn } from '@/lib/utils'
@@ -35,6 +36,9 @@ import { NotificationDropdownItem } from './components/notification-dropdown-ite
3536
3637const logger = createLogger ( 'ControlBar' )
3738
39+ // Predefined run count options
40+ const RUN_COUNT_OPTIONS = [ 1 , 5 , 10 , 25 , 50 , 100 ]
41+
3842/**
3943 * Control bar for managing workflows - handles editing, deletion, deployment,
4044 * history, notifications and execution.
@@ -76,6 +80,12 @@ export function ControlBar() {
7680 // Marketplace modal state
7781 const [ isMarketplaceModalOpen , setIsMarketplaceModalOpen ] = useState ( false )
7882
83+ // Multiple runs state
84+ const [ runCount , setRunCount ] = useState ( 1 )
85+ const [ completedRuns , setCompletedRuns ] = useState ( 0 )
86+ const [ isMultiRunning , setIsMultiRunning ] = useState ( false )
87+ const [ showRunProgress , setShowRunProgress ] = useState ( false )
88+
7989 // Get notifications for current workflow
8090 const workflowNotifications = activeWorkflowId
8191 ? getWorkflowNotifications ( activeWorkflowId )
@@ -286,6 +296,35 @@ export function ControlBar() {
286296 setIsMarketplaceModalOpen ( true )
287297 }
288298
299+ /**
300+ * Handle multiple workflow runs
301+ */
302+ const handleMultipleRuns = async ( ) => {
303+ if ( isExecuting || isMultiRunning || runCount <= 0 ) return
304+
305+ // Reset state for a new batch of runs
306+ setCompletedRuns ( 0 )
307+ setIsMultiRunning ( true )
308+ setShowRunProgress ( runCount > 1 )
309+
310+ try {
311+ // Run the workflow multiple times sequentially
312+ for ( let i = 0 ; i < runCount ; i ++ ) {
313+ await handleRunWorkflow ( )
314+ setCompletedRuns ( i + 1 )
315+ }
316+ } catch ( error ) {
317+ logger . error ( 'Error during multiple workflow runs:' , { error } )
318+ addNotification ( 'error' , 'Failed to complete all workflow runs' , activeWorkflowId )
319+ } finally {
320+ setIsMultiRunning ( false )
321+ // Keep progress visible for a moment after completion
322+ if ( runCount > 1 ) {
323+ setTimeout ( ( ) => setShowRunProgress ( false ) , 2000 )
324+ }
325+ }
326+ }
327+
289328 /**
290329 * Render workflow name section (editable/non-editable)
291330 */
@@ -449,44 +488,51 @@ export function ControlBar() {
449488 /**
450489 * Render notifications dropdown
451490 */
452- const renderNotificationsDropdown = ( ) => (
453- < DropdownMenu open = { notificationsOpen } onOpenChange = { setNotificationsOpen } >
454- < Tooltip >
455- < TooltipTrigger asChild >
456- < DropdownMenuTrigger asChild >
457- < Button variant = "ghost" size = "icon" >
458- < Bell />
459- < span className = "sr-only" > Notifications</ span >
460- </ Button >
461- </ DropdownMenuTrigger >
462- </ TooltipTrigger >
463- { ! notificationsOpen && < TooltipContent > Notifications</ TooltipContent > }
464- </ Tooltip >
465-
466- { workflowNotifications . length === 0 ? (
467- < DropdownMenuContent align = "end" className = "w-40" >
468- < DropdownMenuItem className = "text-sm text-muted-foreground" >
469- No new notifications
470- </ DropdownMenuItem >
471- </ DropdownMenuContent >
472- ) : (
473- < DropdownMenuContent align = "end" className = "w-60 max-h-[300px] overflow-y-auto" >
474- { [ ...workflowNotifications ]
475- . sort ( ( a , b ) => b . timestamp - a . timestamp )
476- . map ( ( notification ) => (
477- < NotificationDropdownItem
478- key = { notification . id }
479- id = { notification . id }
480- type = { notification . type }
481- message = { notification . message }
482- timestamp = { notification . timestamp }
483- options = { notification . options }
484- />
485- ) ) }
486- </ DropdownMenuContent >
487- ) }
488- </ DropdownMenu >
489- )
491+ const renderNotificationsDropdown = ( ) => {
492+ // Ensure we're only showing notifications for the current workflow
493+ const currentWorkflowNotifications = activeWorkflowId
494+ ? notifications . filter ( ( n ) => n . workflowId === activeWorkflowId )
495+ : [ ]
496+
497+ return (
498+ < DropdownMenu open = { notificationsOpen } onOpenChange = { setNotificationsOpen } >
499+ < Tooltip >
500+ < TooltipTrigger asChild >
501+ < DropdownMenuTrigger asChild >
502+ < Button variant = "ghost" size = "icon" >
503+ < Bell />
504+ < span className = "sr-only" > Notifications</ span >
505+ </ Button >
506+ </ DropdownMenuTrigger >
507+ </ TooltipTrigger >
508+ { ! notificationsOpen && < TooltipContent > Notifications</ TooltipContent > }
509+ </ Tooltip >
510+
511+ { currentWorkflowNotifications . length === 0 ? (
512+ < DropdownMenuContent align = "end" className = "w-40" >
513+ < DropdownMenuItem className = "text-sm text-muted-foreground" >
514+ No new notifications
515+ </ DropdownMenuItem >
516+ </ DropdownMenuContent >
517+ ) : (
518+ < DropdownMenuContent align = "end" className = "w-60 max-h-[300px] overflow-y-auto" >
519+ { [ ...currentWorkflowNotifications ]
520+ . sort ( ( a , b ) => b . timestamp - a . timestamp )
521+ . map ( ( notification ) => (
522+ < NotificationDropdownItem
523+ key = { notification . id }
524+ id = { notification . id }
525+ type = { notification . type }
526+ message = { notification . message }
527+ timestamp = { notification . timestamp }
528+ options = { notification . options }
529+ />
530+ ) ) }
531+ </ DropdownMenuContent >
532+ ) }
533+ </ DropdownMenu >
534+ )
535+ }
490536
491537 /**
492538 * Render publish button
@@ -520,31 +566,78 @@ export function ControlBar() {
520566 )
521567
522568 /**
523- * Render run workflow button
569+ * Render run workflow button with multi-run dropdown
524570 */
525571 const renderRunButton = ( ) => (
526- < Button
527- className = { cn (
528- // Base styles
529- 'gap-2 ml-1 font-medium' ,
530- // Brand color with hover states
531- 'bg-[#7F2FFF] hover:bg-[#7028E6]' ,
532- // Hover effect with brand color
533- 'shadow-[0_0_0_0_#7F2FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]' ,
534- // Text color and transitions
535- 'text-white transition-all duration-200' ,
536- // Running state animation
537- isExecuting &&
538- 'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20' ,
539- // Disabled state
540- 'disabled:opacity-50 disabled:hover:bg-[#7F2FFF] disabled:hover:shadow-none'
572+ < div className = "flex items-center" >
573+ { showRunProgress && (
574+ < div className = "mr-3 w-28" >
575+ < Progress value = { ( completedRuns / runCount ) * 100 } className = "h-2 bg-muted" />
576+ < p className = "text-xs text-muted-foreground mt-1 text-center" >
577+ { completedRuns } /{ runCount } runs
578+ </ p >
579+ </ div >
541580 ) }
542- onClick = { handleRunWorkflow }
543- disabled = { isExecuting }
544- >
545- < Play className = { cn ( 'h-3.5 w-3.5' , 'fill-current stroke-current' ) } />
546- { isExecuting ? 'Running' : 'Run' }
547- </ Button >
581+
582+ < div className = "flex ml-1" >
583+ { /* Main Run Button */ }
584+ < Button
585+ className = { cn (
586+ 'gap-2 font-medium' ,
587+ 'bg-[#7F2FFF] hover:bg-[#7028E6]' ,
588+ 'shadow-[0_0_0_0_#7F2FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]' ,
589+ 'text-white transition-all duration-200' ,
590+ ( isExecuting || isMultiRunning ) &&
591+ 'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20' ,
592+ 'disabled:opacity-50 disabled:hover:bg-[#7F2FFF] disabled:hover:shadow-none' ,
593+ 'rounded-r-none border-r border-r-[#6420cc] py-2 px-4 h-10'
594+ ) }
595+ onClick = { handleMultipleRuns }
596+ disabled = { isExecuting || isMultiRunning }
597+ >
598+ < Play className = { cn ( 'h-3.5 w-3.5' , 'fill-current stroke-current' ) } />
599+ { isMultiRunning
600+ ? `Running ${ completedRuns } /${ runCount } `
601+ : isExecuting
602+ ? 'Running'
603+ : runCount === 1
604+ ? 'Run'
605+ : `Run (${ runCount } )` }
606+ </ Button >
607+
608+ { /* Dropdown Trigger */ }
609+ < DropdownMenu >
610+ < DropdownMenuTrigger asChild >
611+ < Button
612+ className = { cn (
613+ 'px-2 font-medium' ,
614+ 'bg-[#7F2FFF] hover:bg-[#7028E6]' ,
615+ 'shadow-[0_0_0_0_#7F2FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]' ,
616+ 'text-white transition-all duration-200' ,
617+ ( isExecuting || isMultiRunning ) &&
618+ 'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20' ,
619+ 'disabled:opacity-50 disabled:hover:bg-[#7F2FFF] disabled:hover:shadow-none' ,
620+ 'rounded-l-none h-10'
621+ ) }
622+ disabled = { isExecuting || isMultiRunning }
623+ >
624+ < ChevronDown className = "h-4 w-4" />
625+ </ Button >
626+ </ DropdownMenuTrigger >
627+ < DropdownMenuContent align = "end" className = "w-20" >
628+ { RUN_COUNT_OPTIONS . map ( ( count ) => (
629+ < DropdownMenuItem
630+ key = { count }
631+ onClick = { ( ) => setRunCount ( count ) }
632+ className = { cn ( 'justify-center' , runCount === count && 'bg-muted' ) }
633+ >
634+ { count }
635+ </ DropdownMenuItem >
636+ ) ) }
637+ </ DropdownMenuContent >
638+ </ DropdownMenu >
639+ </ div >
640+ </ div >
548641 )
549642
550643 return (
0 commit comments