This commit is contained in:
Siddharth Ganesan
2025-07-08 20:42:44 -07:00
parent c7b77bd303
commit 6cb15a620a
2 changed files with 69 additions and 66 deletions

View File

@@ -12,8 +12,8 @@ import {
} from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { useWorkflowYamlStore } from '@/stores/workflows/yaml/store'
const logger = createLogger('ExportControls')
@@ -26,7 +26,7 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
const [isExporting, setIsExporting] = useState(false)
const workflowState = useWorkflowStore()
const { workflows, activeWorkflowId } = useWorkflowRegistry()
const getYaml = useWorkflowYamlStore(state => state.getYaml)
const getYaml = useWorkflowYamlStore((state) => state.getYaml)
const currentWorkflow = activeWorkflowId ? workflows[activeWorkflowId] : null
@@ -68,12 +68,12 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
parallels: workflowState.parallels,
},
exportedAt: new Date().toISOString(),
version: '1.0'
version: '1.0',
}
const jsonContent = JSON.stringify(exportData, null, 2)
const filename = `${currentWorkflow.name.replace(/[^a-z0-9]/gi, '_')}_workflow.json`
downloadFile(jsonContent, filename, 'application/json')
logger.info('Workflow exported as JSON')
} catch (error) {
@@ -93,7 +93,7 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
try {
const yamlContent = getYaml()
const filename = `${currentWorkflow.name.replace(/[^a-z0-9]/gi, '_')}_workflow.yaml`
downloadFile(yamlContent, filename, 'text/yaml')
logger.info('Workflow exported as YAML')
} catch (error) {
@@ -108,9 +108,9 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant='ghost'
size='icon'
<Button
variant='ghost'
size='icon'
disabled={disabled || isExporting || !currentWorkflow}
className='hover:text-foreground'
>
@@ -120,46 +120,41 @@ export function ExportControls({ disabled = false }: ExportControlsProps) {
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent>
{disabled
? 'Export not available'
: !currentWorkflow
{disabled
? 'Export not available'
: !currentWorkflow
? 'No workflow to export'
: 'Export Workflow'
}
: 'Export Workflow'}
</TooltipContent>
</Tooltip>
<DropdownMenuContent align='end' className='w-48'>
<DropdownMenuItem
<DropdownMenuItem
onClick={handleExportJson}
disabled={isExporting || !currentWorkflow}
className='flex items-center gap-2 cursor-pointer'
className='flex cursor-pointer items-center gap-2'
>
<FileText className='h-4 w-4' />
<div className='flex flex-col'>
<span>Export as JSON</span>
<span className='text-muted-foreground text-xs'>
Full workflow data
</span>
<span className='text-muted-foreground text-xs'>Full workflow data</span>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
<DropdownMenuItem
onClick={handleExportYaml}
disabled={isExporting || !currentWorkflow}
className='flex items-center gap-2 cursor-pointer'
className='flex cursor-pointer items-center gap-2'
>
<FileText className='h-4 w-4' />
<div className='flex flex-col'>
<span>Export as YAML</span>
<span className='text-muted-foreground text-xs'>
Condensed workflow language
</span>
<span className='text-muted-foreground text-xs'>Condensed workflow language</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
}

View File

@@ -1,12 +1,12 @@
import { dump as yamlDump } from 'js-yaml'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { dump as yamlDump } from 'js-yaml'
import { getBlock } from '@/blocks'
import { createLogger } from '@/lib/logs/console-logger'
import { useWorkflowStore } from '../workflow/store'
import { useSubBlockStore } from '../subblock/store'
import type { BlockState, WorkflowState } from '../workflow/types'
import { getBlock } from '@/blocks'
import type { SubBlockConfig } from '@/blocks/types'
import { useSubBlockStore } from '../subblock/store'
import { useWorkflowStore } from '../workflow/store'
import type { BlockState, WorkflowState } from '../workflow/types'
const logger = createLogger('WorkflowYamlStore')
@@ -58,13 +58,13 @@ function extractBlockInputs(blockState: BlockState, blockId: string): Record<str
// Process each subBlock configuration
blockConfig.subBlocks.forEach((subBlockConfig: SubBlockConfig) => {
const subBlockId = subBlockConfig.id
// Skip hidden or conditional fields that aren't active
if (subBlockConfig.hidden) return
// Get value from subblock store or fallback to block state
const value = subBlockStore.getValue(blockId, subBlockId) ??
blockState.subBlocks[subBlockId]?.value
const value =
subBlockStore.getValue(blockId, subBlockId) ?? blockState.subBlocks[subBlockId]?.value
// Include value if it exists and isn't empty
if (value !== undefined && value !== null && value !== '') {
@@ -76,14 +76,14 @@ function extractBlockInputs(blockState: BlockState, blockId: string): Record<str
inputs[subBlockId] = value
}
break
case 'checkbox-list':
// Checkbox lists return arrays
if (Array.isArray(value) && value.length > 0) {
inputs[subBlockId] = value
}
break
case 'code':
// Code blocks should preserve formatting
if (typeof value === 'string' && value.trim()) {
@@ -92,24 +92,31 @@ function extractBlockInputs(blockState: BlockState, blockId: string): Record<str
inputs[subBlockId] = value
}
break
case 'switch':
// Boolean values
inputs[subBlockId] = Boolean(value)
break
case 'slider':
// Numeric values
if (typeof value === 'number' || (typeof value === 'string' && !isNaN(Number(value)))) {
if (
typeof value === 'number' ||
(typeof value === 'string' && !Number.isNaN(Number(value)))
) {
inputs[subBlockId] = Number(value)
}
break
default:
// Text inputs, dropdowns, etc.
if (typeof value === 'string' && value.trim()) {
inputs[subBlockId] = value.trim()
} else if (typeof value === 'object' || typeof value === 'number' || typeof value === 'boolean') {
} else if (
typeof value === 'object' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
inputs[subBlockId] = value
}
break
@@ -125,8 +132,8 @@ function extractBlockInputs(blockState: BlockState, blockId: string): Record<str
*/
function findPrecedingBlocks(blockId: string, edges: any[]): string[] {
return edges
.filter(edge => edge.target === blockId)
.map(edge => edge.source)
.filter((edge) => edge.target === blockId)
.map((edge) => edge.source)
.filter((source, index, arr) => arr.indexOf(source) === index) // Remove duplicates
}
@@ -135,8 +142,8 @@ function findPrecedingBlocks(blockId: string, edges: any[]): string[] {
*/
function findFollowingBlocks(blockId: string, edges: any[]): string[] {
return edges
.filter(edge => edge.source === blockId)
.map(edge => edge.target)
.filter((edge) => edge.source === blockId)
.map((edge) => edge.target)
.filter((target, index, arr) => arr.indexOf(target) === index) // Remove duplicates
}
@@ -147,7 +154,7 @@ function generateWorkflowYaml(workflowState: WorkflowState): string {
try {
const yamlWorkflow: YamlWorkflow = {
version: '1.0',
blocks: {}
blocks: {},
}
// Process each block
@@ -158,7 +165,7 @@ function generateWorkflowYaml(workflowState: WorkflowState): string {
const yamlBlock: YamlBlock = {
type: blockState.type,
name: blockState.name
name: blockState.name,
}
// Only include inputs if they exist
@@ -170,7 +177,7 @@ function generateWorkflowYaml(workflowState: WorkflowState): string {
if (preceding.length > 0) {
yamlBlock.preceding = preceding
}
if (following.length > 0) {
yamlBlock.following = following
}
@@ -183,7 +190,7 @@ function generateWorkflowYaml(workflowState: WorkflowState): string {
indent: 2,
lineWidth: -1, // Disable line wrapping
noRefs: true,
sortKeys: false
sortKeys: false,
})
} catch (error) {
logger.error('Failed to generate workflow YAML:', error)
@@ -200,32 +207,32 @@ export const useWorkflowYamlStore = create<WorkflowYamlStore>()(
generateYaml: () => {
const workflowState = useWorkflowStore.getState()
const yaml = generateWorkflowYaml(workflowState)
set({
yaml,
lastGenerated: Date.now()
lastGenerated: Date.now(),
})
},
getYaml: () => {
const currentTime = Date.now()
const { yaml, lastGenerated } = get()
// Auto-refresh if data is stale (older than 1 second) or never generated
if (!lastGenerated || currentTime - lastGenerated > 1000) {
get().generateYaml()
return get().yaml
}
return yaml
},
refreshYaml: () => {
get().generateYaml()
}
},
}),
{
name: 'workflow-yaml-store'
name: 'workflow-yaml-store',
}
)
)
@@ -236,16 +243,17 @@ let lastWorkflowState: { blockCount: number; edgeCount: number } | null = null
useWorkflowStore.subscribe((state) => {
const currentState = {
blockCount: Object.keys(state.blocks).length,
edgeCount: state.edges.length
edgeCount: state.edges.length,
}
// Only refresh if the structure has changed
if (!lastWorkflowState ||
lastWorkflowState.blockCount !== currentState.blockCount ||
lastWorkflowState.edgeCount !== currentState.edgeCount) {
if (
!lastWorkflowState ||
lastWorkflowState.blockCount !== currentState.blockCount ||
lastWorkflowState.edgeCount !== currentState.edgeCount
) {
lastWorkflowState = currentState
// Debounce the refresh to avoid excessive updates
const refreshYaml = useWorkflowYamlStore.getState().refreshYaml
setTimeout(refreshYaml, 100)
@@ -257,12 +265,12 @@ let lastSubBlockChangeTime = 0
useSubBlockStore.subscribe((state) => {
const currentTime = Date.now()
// Debounce rapid changes
if (currentTime - lastSubBlockChangeTime > 100) {
lastSubBlockChangeTime = currentTime
const refreshYaml = useWorkflowYamlStore.getState().refreshYaml
setTimeout(refreshYaml, 100)
}
})
})