From 2a0224f6aef07dc04e22a974b7a5b942bc0052aa Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 8 Jul 2025 20:54:15 -0700 Subject: [PATCH] Initial yaml --- .../import-controls/import-controls.tsx | 335 ++++++++++++ .../components/control-bar/control-bar.tsx | 2 + apps/sim/stores/workflows/yaml/importer.ts | 511 ++++++++++++++++++ 3 files changed, 848 insertions(+) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/import-controls/import-controls.tsx create mode 100644 apps/sim/stores/workflows/yaml/importer.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/import-controls/import-controls.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/import-controls/import-controls.tsx new file mode 100644 index 0000000000..5f85599c9d --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/import-controls/import-controls.tsx @@ -0,0 +1,335 @@ +'use client' + +import { useState, useRef } from 'react' +import { Upload, FileText, Plus, AlertCircle, CheckCircle } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Textarea } from '@/components/ui/textarea' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { createLogger } from '@/lib/logs/console-logger' +import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { importWorkflowFromYaml, parseWorkflowYaml } from '@/stores/workflows/yaml/importer' +import { useRouter } from 'next/navigation' +import { useParams } from 'next/navigation' + +const logger = createLogger('ImportControls') + +interface ImportControlsProps { + disabled?: boolean +} + +export function ImportControls({ disabled = false }: ImportControlsProps) { + const [isImporting, setIsImporting] = useState(false) + const [showYamlDialog, setShowYamlDialog] = useState(false) + const [yamlContent, setYamlContent] = useState('') + const [importResult, setImportResult] = useState<{ + success: boolean + errors: string[] + warnings: string[] + summary?: string + } | null>(null) + + const fileInputRef = useRef(null) + const router = useRouter() + const params = useParams() + const workspaceId = params.workspaceId as string + + // Stores and hooks + const { createWorkflow } = useWorkflowRegistry() + const { collaborativeAddBlock, collaborativeAddEdge } = useCollaborativeWorkflow() + const subBlockStore = useSubBlockStore() + + const handleFileUpload = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + + try { + const content = await file.text() + setYamlContent(content) + setShowYamlDialog(true) + } catch (error) { + logger.error('Failed to read file:', error) + setImportResult({ + success: false, + errors: [`Failed to read file: ${error instanceof Error ? error.message : 'Unknown error'}`], + warnings: [] + }) + } + + // Reset file input + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + + const handleYamlImport = async () => { + if (!yamlContent.trim()) { + setImportResult({ + success: false, + errors: ['YAML content is required'], + warnings: [] + }) + return + } + + setIsImporting(true) + setImportResult(null) + + try { + // First validate the YAML without importing + const { data: yamlWorkflow, errors: parseErrors } = parseWorkflowYaml(yamlContent) + + if (!yamlWorkflow || parseErrors.length > 0) { + setImportResult({ + success: false, + errors: parseErrors, + warnings: [] + }) + return + } + + // Create a new workflow + logger.info('Creating new workflow for YAML import') + const newWorkflowId = await createWorkflow({ + name: `Imported Workflow - ${new Date().toLocaleString()}`, + description: 'Workflow imported from YAML', + workspaceId, + }) + + // Navigate to the new workflow + router.push(`/workspace/${workspaceId}/w/${newWorkflowId}`) + + // Small delay to ensure navigation and workflow initialization + await new Promise(resolve => setTimeout(resolve, 1000)) + + // Import the YAML into the new workflow + const result = await importWorkflowFromYaml(yamlContent, { + addBlock: collaborativeAddBlock, + addEdge: collaborativeAddEdge, + applyAutoLayout: () => { + // Trigger auto layout + window.dispatchEvent(new CustomEvent('trigger-auto-layout')) + }, + setSubBlockValue: (blockId: string, subBlockId: string, value: any) => { + subBlockStore.setValue(blockId, subBlockId, value) + }, + getExistingBlocks: () => { + // This will be called after navigation, so we need to get blocks from the store + const { useWorkflowStore } = require('@/stores/workflows/workflow/store') + return useWorkflowStore.getState().blocks + } + }) + + setImportResult(result) + + if (result.success) { + // Close dialog on success + setTimeout(() => { + setShowYamlDialog(false) + setYamlContent('') + setImportResult(null) + }, 2000) + } + + } catch (error) { + logger.error('Failed to import YAML workflow:', error) + setImportResult({ + success: false, + errors: [`Import failed: ${error instanceof Error ? error.message : 'Unknown error'}`], + warnings: [] + }) + } finally { + setIsImporting(false) + } + } + + const handleOpenYamlDialog = () => { + setYamlContent('') + setImportResult(null) + setShowYamlDialog(true) + } + + const isDisabled = disabled || isImporting + + return ( + <> + + + + + {isDisabled ? ( +
+ +
+ ) : ( + + )} +
+ + fileInputRef.current?.click()} + disabled={isDisabled} + className='flex cursor-pointer items-center gap-2' + > + +
+ Upload YAML File + Import from .yaml or .yml file +
+
+ + + +
+ Paste YAML + Import from YAML text +
+
+
+
+
+ + {isDisabled + ? isImporting + ? 'Importing workflow...' + : 'Cannot import workflow' + : 'Import Workflow from YAML'} + +
+ + {/* Hidden file input */} + + + {/* YAML Import Dialog */} + + + + Import Workflow from YAML + + Paste your workflow YAML content below. This will create a new workflow with the blocks and connections defined in the YAML. + + + +
+