@@ -30,6 +30,7 @@ import { Textarea } from '@/components/ui/textarea'
3030import { cn } from '@/lib/utils'
3131import { sanitizeForCopilot } from '@/lib/workflows/json-sanitizer'
3232import { formatEditSequence } from '@/lib/workflows/training/compute-edit-sequence'
33+ import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
3334import { useCopilotTrainingStore } from '@/stores/copilot-training/store'
3435
3536/**
@@ -52,6 +53,8 @@ export function TrainingModal() {
5253 markDatasetSent,
5354 } = useCopilotTrainingStore ( )
5455
56+ const currentWorkflow = useCurrentWorkflow ( )
57+
5558 const [ localPrompt , setLocalPrompt ] = useState ( currentPrompt )
5659 const [ localTitle , setLocalTitle ] = useState ( currentTitle )
5760 const [ copiedId , setCopiedId ] = useState < string | null > ( null )
@@ -63,6 +66,11 @@ export function TrainingModal() {
6366 const [ sendingSelected , setSendingSelected ] = useState ( false )
6467 const [ sentDatasets , setSentDatasets ] = useState < Set < string > > ( new Set ( ) )
6568 const [ failedDatasets , setFailedDatasets ] = useState < Set < string > > ( new Set ( ) )
69+ const [ sendingLiveWorkflow , setSendingLiveWorkflow ] = useState ( false )
70+ const [ liveWorkflowSent , setLiveWorkflowSent ] = useState ( false )
71+ const [ liveWorkflowFailed , setLiveWorkflowFailed ] = useState ( false )
72+ const [ liveWorkflowTitle , setLiveWorkflowTitle ] = useState ( '' )
73+ const [ liveWorkflowDescription , setLiveWorkflowDescription ] = useState ( '' )
6674
6775 const handleStart = ( ) => {
6876 if ( localTitle . trim ( ) && localPrompt . trim ( ) ) {
@@ -285,6 +293,46 @@ export function TrainingModal() {
285293 }
286294 }
287295
296+ const handleSendLiveWorkflow = async ( ) => {
297+ if ( ! liveWorkflowTitle . trim ( ) || ! liveWorkflowDescription . trim ( ) ) {
298+ return
299+ }
300+
301+ setLiveWorkflowSent ( false )
302+ setLiveWorkflowFailed ( false )
303+ setSendingLiveWorkflow ( true )
304+
305+ try {
306+ const sanitizedWorkflow = sanitizeForCopilot ( currentWorkflow . workflowState )
307+
308+ const response = await fetch ( '/api/copilot/training/examples' , {
309+ method : 'POST' ,
310+ headers : { 'Content-Type' : 'application/json' } ,
311+ body : JSON . stringify ( {
312+ json : JSON . stringify ( sanitizedWorkflow ) ,
313+ source_path : liveWorkflowTitle ,
314+ summary : liveWorkflowDescription ,
315+ } ) ,
316+ } )
317+
318+ if ( ! response . ok ) {
319+ const error = await response . json ( )
320+ throw new Error ( error . error || 'Failed to send live workflow' )
321+ }
322+
323+ setLiveWorkflowSent ( true )
324+ setLiveWorkflowTitle ( '' )
325+ setLiveWorkflowDescription ( '' )
326+ setTimeout ( ( ) => setLiveWorkflowSent ( false ) , 5000 )
327+ } catch ( error ) {
328+ console . error ( 'Failed to send live workflow:' , error )
329+ setLiveWorkflowFailed ( true )
330+ setTimeout ( ( ) => setLiveWorkflowFailed ( false ) , 5000 )
331+ } finally {
332+ setSendingLiveWorkflow ( false )
333+ }
334+ }
335+
288336 return (
289337 < Dialog open = { showModal } onOpenChange = { toggleModal } >
290338 < DialogContent className = 'max-w-3xl' >
@@ -335,24 +383,24 @@ export function TrainingModal() {
335383 ) }
336384
337385 < Tabs defaultValue = { isTraining ? 'datasets' : 'new' } className = 'mt-4' >
338- < TabsList className = 'grid w-full grid-cols-2 ' >
386+ < TabsList className = 'grid w-full grid-cols-3 ' >
339387 < TabsTrigger value = 'new' disabled = { isTraining } >
340388 New Session
341389 </ TabsTrigger >
342390 < TabsTrigger value = 'datasets' > Datasets ({ datasets . length } )</ TabsTrigger >
391+ < TabsTrigger value = 'live' > Send Live State</ TabsTrigger >
343392 </ TabsList >
344393
345394 { /* New Training Session Tab */ }
346395 < TabsContent value = 'new' className = 'space-y-4' >
347- { startSnapshot && (
348- < div className = 'rounded-lg border bg-muted/50 p-3' >
349- < p className = 'font-medium text-muted-foreground text-sm' > Current Workflow State</ p >
350- < p className = 'text-sm' >
351- { Object . keys ( startSnapshot . blocks ) . length } blocks, { startSnapshot . edges . length } { ' ' }
352- edges
353- </ p >
354- </ div >
355- ) }
396+ < div className = 'rounded-lg border bg-muted/50 p-3' >
397+ < p className = 'mb-2 font-medium text-muted-foreground text-sm' >
398+ Current Workflow State
399+ </ p >
400+ < p className = 'text-sm' >
401+ { currentWorkflow . getBlockCount ( ) } blocks, { currentWorkflow . getEdgeCount ( ) } edges
402+ </ p >
403+ </ div >
356404
357405 < div className = 'space-y-2' >
358406 < Label htmlFor = 'title' > Title</ Label >
@@ -628,6 +676,94 @@ export function TrainingModal() {
628676 </ >
629677 ) }
630678 </ TabsContent >
679+
680+ { /* Send Live State Tab */ }
681+ < TabsContent value = 'live' className = 'space-y-4' >
682+ < div className = 'rounded-lg border bg-muted/50 p-3' >
683+ < p className = 'mb-2 font-medium text-muted-foreground text-sm' >
684+ Current Workflow State
685+ </ p >
686+ < p className = 'text-sm' >
687+ { currentWorkflow . getBlockCount ( ) } blocks, { currentWorkflow . getEdgeCount ( ) } edges
688+ </ p >
689+ </ div >
690+
691+ < div className = 'space-y-2' >
692+ < Label htmlFor = 'live-title' > Title</ Label >
693+ < Input
694+ id = 'live-title'
695+ placeholder = 'e.g., Customer Onboarding Workflow'
696+ value = { liveWorkflowTitle }
697+ onChange = { ( e ) => setLiveWorkflowTitle ( e . target . value ) }
698+ />
699+ < p className = 'text-muted-foreground text-xs' >
700+ A short title identifying this workflow
701+ </ p >
702+ </ div >
703+
704+ < div className = 'space-y-2' >
705+ < Label htmlFor = 'live-description' > Description</ Label >
706+ < Textarea
707+ id = 'live-description'
708+ placeholder = 'Describe what this workflow does...'
709+ value = { liveWorkflowDescription }
710+ onChange = { ( e ) => setLiveWorkflowDescription ( e . target . value ) }
711+ rows = { 3 }
712+ />
713+ < p className = 'text-muted-foreground text-xs' >
714+ Explain the purpose and functionality of this workflow
715+ </ p >
716+ </ div >
717+
718+ < Button
719+ onClick = { handleSendLiveWorkflow }
720+ disabled = {
721+ ! liveWorkflowTitle . trim ( ) ||
722+ ! liveWorkflowDescription . trim ( ) ||
723+ sendingLiveWorkflow ||
724+ currentWorkflow . getBlockCount ( ) === 0
725+ }
726+ className = 'w-full'
727+ >
728+ { sendingLiveWorkflow ? (
729+ < >
730+ < div className = 'mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent' />
731+ Sending...
732+ </ >
733+ ) : liveWorkflowSent ? (
734+ < >
735+ < CheckCircle2 className = 'mr-2 h-4 w-4' />
736+ Sent Successfully
737+ </ >
738+ ) : liveWorkflowFailed ? (
739+ < >
740+ < XCircle className = 'mr-2 h-4 w-4' />
741+ Failed - Try Again
742+ </ >
743+ ) : (
744+ < >
745+ < Send className = 'mr-2 h-4 w-4' />
746+ Send Live Workflow State
747+ </ >
748+ ) }
749+ </ Button >
750+
751+ { liveWorkflowSent && (
752+ < div className = 'rounded-lg border bg-green-50 p-3 dark:bg-green-950/30' >
753+ < p className = 'text-green-700 text-sm dark:text-green-300' >
754+ Workflow state sent successfully!
755+ </ p >
756+ </ div >
757+ ) }
758+
759+ { liveWorkflowFailed && (
760+ < div className = 'rounded-lg border bg-red-50 p-3 dark:bg-red-950/30' >
761+ < p className = 'text-red-700 text-sm dark:text-red-300' >
762+ Failed to send workflow state. Please try again.
763+ </ p >
764+ </ div >
765+ ) }
766+ </ TabsContent >
631767 </ Tabs >
632768 </ DialogContent >
633769 </ Dialog >
0 commit comments