1+ 'use client'
2+
3+ import { useState , useRef } from 'react'
4+ import { Upload , FileText , Plus , AlertCircle , CheckCircle } from 'lucide-react'
5+ import { Button } from '@/components/ui/button'
6+ import {
7+ DropdownMenu ,
8+ DropdownMenuContent ,
9+ DropdownMenuItem ,
10+ DropdownMenuTrigger ,
11+ } from '@/components/ui/dropdown-menu'
12+ import {
13+ Dialog ,
14+ DialogContent ,
15+ DialogDescription ,
16+ DialogFooter ,
17+ DialogHeader ,
18+ DialogTitle ,
19+ } from '@/components/ui/dialog'
20+ import { Textarea } from '@/components/ui/textarea'
21+ import { Tooltip , TooltipContent , TooltipTrigger } from '@/components/ui/tooltip'
22+ import { Alert , AlertDescription } from '@/components/ui/alert'
23+ import { createLogger } from '@/lib/logs/console-logger'
24+ import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
25+ import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
26+ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
27+ import { importWorkflowFromYaml , parseWorkflowYaml } from '@/stores/workflows/yaml/importer'
28+ import { useRouter } from 'next/navigation'
29+ import { useParams } from 'next/navigation'
30+
31+ const logger = createLogger ( 'ImportControls' )
32+
33+ interface ImportControlsProps {
34+ disabled ?: boolean
35+ }
36+
37+ export function ImportControls ( { disabled = false } : ImportControlsProps ) {
38+ const [ isImporting , setIsImporting ] = useState ( false )
39+ const [ showYamlDialog , setShowYamlDialog ] = useState ( false )
40+ const [ yamlContent , setYamlContent ] = useState ( '' )
41+ const [ importResult , setImportResult ] = useState < {
42+ success : boolean
43+ errors : string [ ]
44+ warnings : string [ ]
45+ summary ?: string
46+ } | null > ( null )
47+
48+ const fileInputRef = useRef < HTMLInputElement > ( null )
49+ const router = useRouter ( )
50+ const params = useParams ( )
51+ const workspaceId = params . workspaceId as string
52+
53+ // Stores and hooks
54+ const { createWorkflow } = useWorkflowRegistry ( )
55+ const { collaborativeAddBlock, collaborativeAddEdge } = useCollaborativeWorkflow ( )
56+ const subBlockStore = useSubBlockStore ( )
57+
58+ const handleFileUpload = async ( event : React . ChangeEvent < HTMLInputElement > ) => {
59+ const file = event . target . files ?. [ 0 ]
60+ if ( ! file ) return
61+
62+ try {
63+ const content = await file . text ( )
64+ setYamlContent ( content )
65+ setShowYamlDialog ( true )
66+ } catch ( error ) {
67+ logger . error ( 'Failed to read file:' , error )
68+ setImportResult ( {
69+ success : false ,
70+ errors : [ `Failed to read file: ${ error instanceof Error ? error . message : 'Unknown error' } ` ] ,
71+ warnings : [ ]
72+ } )
73+ }
74+
75+ // Reset file input
76+ if ( fileInputRef . current ) {
77+ fileInputRef . current . value = ''
78+ }
79+ }
80+
81+ const handleYamlImport = async ( ) => {
82+ if ( ! yamlContent . trim ( ) ) {
83+ setImportResult ( {
84+ success : false ,
85+ errors : [ 'YAML content is required' ] ,
86+ warnings : [ ]
87+ } )
88+ return
89+ }
90+
91+ setIsImporting ( true )
92+ setImportResult ( null )
93+
94+ try {
95+ // First validate the YAML without importing
96+ const { data : yamlWorkflow , errors : parseErrors } = parseWorkflowYaml ( yamlContent )
97+
98+ if ( ! yamlWorkflow || parseErrors . length > 0 ) {
99+ setImportResult ( {
100+ success : false ,
101+ errors : parseErrors ,
102+ warnings : [ ]
103+ } )
104+ return
105+ }
106+
107+ // Create a new workflow
108+ logger . info ( 'Creating new workflow for YAML import' )
109+ const newWorkflowId = await createWorkflow ( {
110+ name : `Imported Workflow - ${ new Date ( ) . toLocaleString ( ) } ` ,
111+ description : 'Workflow imported from YAML' ,
112+ workspaceId,
113+ } )
114+
115+ // Navigate to the new workflow
116+ router . push ( `/workspace/${ workspaceId } /w/${ newWorkflowId } ` )
117+
118+ // Small delay to ensure navigation and workflow initialization
119+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
120+
121+ // Import the YAML into the new workflow
122+ const result = await importWorkflowFromYaml ( yamlContent , {
123+ addBlock : collaborativeAddBlock ,
124+ addEdge : collaborativeAddEdge ,
125+ applyAutoLayout : ( ) => {
126+ // Trigger auto layout
127+ window . dispatchEvent ( new CustomEvent ( 'trigger-auto-layout' ) )
128+ } ,
129+ setSubBlockValue : ( blockId : string , subBlockId : string , value : any ) => {
130+ subBlockStore . setValue ( blockId , subBlockId , value )
131+ } ,
132+ getExistingBlocks : ( ) => {
133+ // This will be called after navigation, so we need to get blocks from the store
134+ const { useWorkflowStore } = require ( '@/stores/workflows/workflow/store' )
135+ return useWorkflowStore . getState ( ) . blocks
136+ }
137+ } )
138+
139+ setImportResult ( result )
140+
141+ if ( result . success ) {
142+ // Close dialog on success
143+ setTimeout ( ( ) => {
144+ setShowYamlDialog ( false )
145+ setYamlContent ( '' )
146+ setImportResult ( null )
147+ } , 2000 )
148+ }
149+
150+ } catch ( error ) {
151+ logger . error ( 'Failed to import YAML workflow:' , error )
152+ setImportResult ( {
153+ success : false ,
154+ errors : [ `Import failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ] ,
155+ warnings : [ ]
156+ } )
157+ } finally {
158+ setIsImporting ( false )
159+ }
160+ }
161+
162+ const handleOpenYamlDialog = ( ) => {
163+ setYamlContent ( '' )
164+ setImportResult ( null )
165+ setShowYamlDialog ( true )
166+ }
167+
168+ const isDisabled = disabled || isImporting
169+
170+ return (
171+ < >
172+ < Tooltip >
173+ < TooltipTrigger asChild >
174+ < DropdownMenu >
175+ < DropdownMenuTrigger asChild >
176+ { isDisabled ? (
177+ < div className = 'inline-flex h-10 w-10 cursor-not-allowed items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm opacity-50 ring-offset-background transition-colors [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0' >
178+ < Plus className = 'h-4 w-4' />
179+ </ div >
180+ ) : (
181+ < Button
182+ variant = 'ghost'
183+ size = 'icon'
184+ className = 'hover:text-primary'
185+ disabled = { isDisabled }
186+ >
187+ < Plus className = 'h-4 w-4' />
188+ < span className = 'sr-only' > Import Workflow</ span >
189+ </ Button >
190+ ) }
191+ </ DropdownMenuTrigger >
192+ < DropdownMenuContent align = 'end' className = 'w-64' >
193+ < DropdownMenuItem
194+ onClick = { ( ) => fileInputRef . current ?. click ( ) }
195+ disabled = { isDisabled }
196+ className = 'flex cursor-pointer items-center gap-2'
197+ >
198+ < Upload className = 'h-4 w-4' />
199+ < div className = 'flex flex-col' >
200+ < span > Upload YAML File</ span >
201+ < span className = 'text-muted-foreground text-xs' > Import from .yaml or .yml file</ span >
202+ </ div >
203+ </ DropdownMenuItem >
204+
205+ < DropdownMenuItem
206+ onClick = { handleOpenYamlDialog }
207+ disabled = { isDisabled }
208+ className = 'flex cursor-pointer items-center gap-2'
209+ >
210+ < FileText className = 'h-4 w-4' />
211+ < div className = 'flex flex-col' >
212+ < span > Paste YAML</ span >
213+ < span className = 'text-muted-foreground text-xs' > Import from YAML text</ span >
214+ </ div >
215+ </ DropdownMenuItem >
216+ </ DropdownMenuContent >
217+ </ DropdownMenu >
218+ </ TooltipTrigger >
219+ < TooltipContent >
220+ { isDisabled
221+ ? isImporting
222+ ? 'Importing workflow...'
223+ : 'Cannot import workflow'
224+ : 'Import Workflow from YAML' }
225+ </ TooltipContent >
226+ </ Tooltip >
227+
228+ { /* Hidden file input */ }
229+ < input
230+ ref = { fileInputRef }
231+ type = 'file'
232+ accept = '.yaml,.yml'
233+ onChange = { handleFileUpload }
234+ className = 'hidden'
235+ />
236+
237+ { /* YAML Import Dialog */ }
238+ < Dialog open = { showYamlDialog } onOpenChange = { setShowYamlDialog } >
239+ < DialogContent className = 'max-w-4xl max-h-[80vh] flex flex-col' >
240+ < DialogHeader >
241+ < DialogTitle > Import Workflow from YAML</ DialogTitle >
242+ < DialogDescription >
243+ Paste your workflow YAML content below. This will create a new workflow with the blocks and connections defined in the YAML.
244+ </ DialogDescription >
245+ </ DialogHeader >
246+
247+ < div className = 'flex-1 space-y-4 overflow-hidden' >
248+ < Textarea
249+ placeholder = { `version: "1.0"
250+ blocks:
251+ start:
252+ type: "starter"
253+ name: "Start"
254+ inputs:
255+ startWorkflow: "manual"
256+ following:
257+ - "process"
258+
259+ process:
260+ type: "agent"
261+ name: "Process Data"
262+ inputs:
263+ systemPrompt: "You are a helpful assistant"
264+ userPrompt: "Process the data"
265+ model: "gpt-4"
266+ preceding:
267+ - "start"` }
268+ value = { yamlContent }
269+ onChange = { ( e ) => setYamlContent ( e . target . value ) }
270+ className = 'min-h-[300px] font-mono text-sm'
271+ disabled = { isImporting }
272+ />
273+
274+ { /* Import Result */ }
275+ { importResult && (
276+ < div className = 'space-y-2' >
277+ { importResult . success ? (
278+ < Alert >
279+ < CheckCircle className = 'h-4 w-4' />
280+ < AlertDescription >
281+ < div className = 'font-medium text-green-700' > Import Successful!</ div >
282+ { importResult . summary && (
283+ < div className = 'mt-1 text-sm' > { importResult . summary } </ div >
284+ ) }
285+ { importResult . warnings . length > 0 && (
286+ < div className = 'mt-2' >
287+ < div className = 'text-sm font-medium' > Warnings:</ div >
288+ < ul className = 'mt-1 space-y-1 text-sm' >
289+ { importResult . warnings . map ( ( warning , index ) => (
290+ < li key = { index } className = 'text-yellow-700' > • { warning } </ li >
291+ ) ) }
292+ </ ul >
293+ </ div >
294+ ) }
295+ </ AlertDescription >
296+ </ Alert >
297+ ) : (
298+ < Alert variant = 'destructive' >
299+ < AlertCircle className = 'h-4 w-4' />
300+ < AlertDescription >
301+ < div className = 'font-medium' > Import Failed</ div >
302+ { importResult . errors . length > 0 && (
303+ < ul className = 'mt-2 space-y-1 text-sm' >
304+ { importResult . errors . map ( ( error , index ) => (
305+ < li key = { index } > • { error } </ li >
306+ ) ) }
307+ </ ul >
308+ ) }
309+ </ AlertDescription >
310+ </ Alert >
311+ ) }
312+ </ div >
313+ ) }
314+ </ div >
315+
316+ < DialogFooter >
317+ < Button
318+ variant = 'outline'
319+ onClick = { ( ) => setShowYamlDialog ( false ) }
320+ disabled = { isImporting }
321+ >
322+ Cancel
323+ </ Button >
324+ < Button
325+ onClick = { handleYamlImport }
326+ disabled = { isImporting || ! yamlContent . trim ( ) }
327+ >
328+ { isImporting ? 'Importing...' : 'Import Workflow' }
329+ </ Button >
330+ </ DialogFooter >
331+ </ DialogContent >
332+ </ Dialog >
333+ </ >
334+ )
335+ }
0 commit comments