fix: subblock rerender fixed

This commit is contained in:
Adam Gough
2025-05-17 11:11:54 -04:00
parent a0f80c8102
commit 590764e5dc
9 changed files with 453 additions and 42 deletions

View File

@@ -1,11 +1,14 @@
'use client'
import { useState } from 'react'
import { useState, useEffect, useMemo } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { createLogger } from '@/lib/logs/console-logger'
import { WorkflowPreview } from '@/app/w/components/workflow-preview/workflow-preview'
const logger = createLogger('DeployedWorkflowCard')
interface DeployedWorkflowCardProps {
currentWorkflowState?: {
blocks: Record<string, any>
@@ -29,6 +32,72 @@ export function DeployedWorkflowCard({
const workflowToShow = showingDeployed ? deployedWorkflowState : currentWorkflowState
console.log('workflowToShow', workflowToShow)
// Add detailed logging for debugging
useEffect(() => {
if (workflowToShow) {
// Log basic stats
const blockCount = Object.keys(workflowToShow.blocks || {}).length;
const blocksWithSubBlocks = Object.values(workflowToShow.blocks || {})
.filter(block => block.subBlocks && Object.keys(block.subBlocks).length > 0);
logger.info(`[WORKFLOW-STATE] ${showingDeployed ? 'Deployed' : 'Current'} workflow with ${blockCount} blocks`, {
type: showingDeployed ? 'deployed' : 'current',
blockCount,
blocksWithSubBlocksCount: blocksWithSubBlocks.length,
// Log a sample of a block with subblocks if any exist
sampleBlock: blocksWithSubBlocks.length > 0
? {
id: blocksWithSubBlocks[0].id,
type: blocksWithSubBlocks[0].type,
subBlocksCount: Object.keys(blocksWithSubBlocks[0].subBlocks || {}).length,
subBlocksSample: Object.entries(blocksWithSubBlocks[0].subBlocks || {}).slice(0, 2)
}
: null
});
// For deep debug, log each block's subblocks (limited data for readability)
Object.entries(workflowToShow.blocks || {}).forEach(([blockId, block]) => {
if (block.subBlocks && Object.keys(block.subBlocks).length > 0) {
logger.info(`[BLOCK-SUBBLOCKS] ${showingDeployed ? 'Deployed' : 'Current'} block ${blockId}`, {
blockId,
blockType: block.type,
subBlocksCount: Object.keys(block.subBlocks).length,
// Just log IDs to avoid huge logs, but include a couple of values as examples
subBlockIds: Object.keys(block.subBlocks),
sampleValues: Object.entries(block.subBlocks).slice(0, 2).map(([id, value]) => ({ id, value }))
});
}
});
}
}, [workflowToShow, showingDeployed]);
// Create sanitized workflow state
const sanitizedWorkflowState = useMemo(() => {
if (!workflowToShow) return null;
// Filter out invalid blocks and make deep clone to avoid reference issues
return {
blocks: Object.fromEntries(
Object.entries(workflowToShow.blocks || {})
.filter(([_, block]) => block && block.type) // Filter out invalid blocks
.map(([id, block]) => {
// Deep clone the block to avoid any reference sharing
const clonedBlock = JSON.parse(JSON.stringify(block));
return [id, clonedBlock];
})
),
edges: workflowToShow.edges ? JSON.parse(JSON.stringify(workflowToShow.edges)) : [],
loops: workflowToShow.loops ? JSON.parse(JSON.stringify(workflowToShow.loops)) : {}
};
}, [workflowToShow]);
// Generate a unique key for the workflow preview
const previewKey = useMemo(() => {
return `${showingDeployed ? 'deployed' : 'current'}-preview-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}, [showingDeployed]);
{console.log('sanitizedWorkflowState', sanitizedWorkflowState)}
return (
<Card className={cn('relative overflow-hidden', className)}>
<CardHeader
@@ -62,11 +131,11 @@ export function DeployedWorkflowCard({
<CardContent className='p-0'>
{/* Workflow preview with fixed height */}
<div className='h-[500px] w-full'>
{workflowToShow ? (
<div className="h-[500px] w-full">
{sanitizedWorkflowState ? (
<WorkflowPreview
key={showingDeployed ? 'deployed-preview' : 'current-preview'}
workflowState={workflowToShow}
key={previewKey}
workflowState={sanitizedWorkflowState}
showSubBlocks={true}
height='100%'
width='100%'

View File

@@ -1,6 +1,6 @@
'use client'
import { useEffect, useState } from 'react'
import { useEffect, useState, useMemo } from 'react'
import {
AlertDialog,
AlertDialogAction,
@@ -71,6 +71,45 @@ export function DeployedWorkflowModal({
loops: state.loops,
parallels: state.parallels,
}))
// Sanitize states to ensure no invalid blocks are passed to components
const sanitizedCurrentState = useMemo(() => {
if (!currentWorkflowState) return undefined;
return {
blocks: Object.fromEntries(
Object.entries(currentWorkflowState.blocks || {})
.filter(([_, block]) => block && block.type)
.map(([id, block]) => {
// Deep clone the block to avoid any reference sharing
return [id, JSON.parse(JSON.stringify(block))];
})
),
edges: currentWorkflowState.edges ? [...currentWorkflowState.edges] : [],
loops: currentWorkflowState.loops ? {...currentWorkflowState.loops} : {}
};
}, [currentWorkflowState]);
const sanitizedDeployedState = useMemo(() => {
if (!deployedWorkflowState) return {
blocks: {},
edges: [],
loops: {}
};
return {
blocks: Object.fromEntries(
Object.entries(deployedWorkflowState.blocks || {})
.filter(([_, block]) => block && block.type)
.map(([id, block]) => {
// Deep clone the block to avoid any reference sharing
return [id, JSON.parse(JSON.stringify(block))];
})
),
edges: deployedWorkflowState.edges ? [...deployedWorkflowState.edges] : [],
loops: deployedWorkflowState.loops ? {...deployedWorkflowState.loops} : {}
};
}, [deployedWorkflowState]);
const handleRevert = () => {
<<<<<<< HEAD
@@ -107,8 +146,8 @@ export function DeployedWorkflowModal({
</div>
) : (
<DeployedWorkflowCard
currentWorkflowState={currentWorkflowState}
deployedWorkflowState={deployedWorkflowState}
currentWorkflowState={sanitizedCurrentState}
deployedWorkflowState={sanitizedDeployedState}
/>
)}

View File

@@ -6,8 +6,11 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { createLogger } from '@/lib/logs/console-logger'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
const logger = createLogger('Dropdown')
interface DropdownProps {
options:
| Array<string | { label: string; id: string }>
@@ -15,12 +18,32 @@ interface DropdownProps {
defaultValue?: string
blockId: string
subBlockId: string
isPreview?: boolean
value?: string
}
export function Dropdown({ options, defaultValue, blockId, subBlockId }: DropdownProps) {
const [value, setValue] = useSubBlockValue<string>(blockId, subBlockId, true)
export function Dropdown({
options,
defaultValue,
blockId,
subBlockId,
isPreview = false,
value: propValue
}: DropdownProps) {
const [value, setValue] = useSubBlockValue<string>(blockId, subBlockId, true, isPreview, propValue)
const [storeInitialized, setStoreInitialized] = useState(false)
// Log when in preview mode to verify it's working
useEffect(() => {
if (isPreview) {
logger.info(`[PREVIEW] Dropdown for ${blockId}:${subBlockId}`, {
isPreview,
propValue,
value
});
}
}, [isPreview, propValue, value, blockId, subBlockId]);
// Evaluate options if it's a function
const evaluatedOptions = useMemo(() => {
return typeof options === 'function' ? options() : options

View File

@@ -19,6 +19,8 @@ interface LongInputProps {
isConnecting: boolean
config: SubBlockConfig
rows?: number
isPreview?: boolean
value?: string
}
// Constants
@@ -33,8 +35,10 @@ export function LongInput({
isConnecting,
config,
rows,
isPreview = false,
value: propValue,
}: LongInputProps) {
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
const [value, setValue] = useSubBlockValue(blockId, subBlockId, false, isPreview, propValue)
const [showEnvVars, setShowEnvVars] = useState(false)
const [showTags, setShowTags] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
@@ -44,6 +48,17 @@ export function LongInput({
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
const containerRef = useRef<HTMLDivElement>(null)
// Log when in preview mode to verify it's working
useEffect(() => {
if (isPreview) {
logger.info(`[PREVIEW] LongInput for ${blockId}:${subBlockId}`, {
isPreview,
propValue,
value
});
}
}, [isPreview, propValue, value, blockId, subBlockId]);
// Calculate initial height based on rows prop with reasonable defaults
const getInitialHeight = () => {
// Use provided rows or default, then convert to pixels with a minimum

View File

@@ -20,6 +20,7 @@ interface ShortInputProps {
config: SubBlockConfig
value?: string
onChange?: (value: string) => void
isPreview?: boolean
}
export function ShortInput({
@@ -31,11 +32,18 @@ export function ShortInput({
config,
value: propValue,
onChange,
isPreview = false,
}: ShortInputProps) {
const [isFocused, setIsFocused] = useState(false)
const [showEnvVars, setShowEnvVars] = useState(false)
const [showTags, setShowTags] = useState(false)
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
const [storeValue, setStoreValue] = useSubBlockValue(
blockId,
subBlockId,
false, // No workflow update needed
isPreview,
propValue
)
const [searchTerm, setSearchTerm] = useState('')
const [cursorPosition, setCursorPosition] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
@@ -45,8 +53,10 @@ export function ShortInput({
// Get ReactFlow instance for zoom control
const reactFlowInstance = useReactFlow()
// Use either controlled or uncontrolled value
const value = propValue !== undefined ? propValue : storeValue
// Use either controlled or uncontrolled value, prioritizing the direct value if in preview mode
const value = isPreview && propValue !== undefined
? propValue
: (propValue !== undefined ? propValue : storeValue)
// Check if this input is API key related
const isApiKeyField = useMemo(() => {

View File

@@ -1,10 +1,17 @@
import { useCallback, useEffect, useRef } from 'react'
import { isEqual } from 'lodash'
<<<<<<< HEAD
import { getProviderFromModel } from '@/providers/utils'
=======
import { createLogger } from '@/lib/logs/console-logger'
>>>>>>> 6f129dfc (fix: subblock rerender fixed)
import { useGeneralStore } from '@/stores/settings/general/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
// Add logger for diagnostic logging
const logger = createLogger('useSubBlockValue')
/**
* Helper to handle API key auto-fill for provider-based blocks
* Used for agent, router, evaluator, and any other blocks that use LLM providers
@@ -148,12 +155,20 @@ function storeApiKeyValue(
* @param blockId The ID of the block containing the sub-block
* @param subBlockId The ID of the sub-block
* @param triggerWorkflowUpdate Whether to trigger a workflow update when the value changes
* @param isPreview Whether this is being used in preview mode
* @param directValue The direct value to use when in preview mode
* @returns A tuple containing the current value and a setter function
*/
export function useSubBlockValue<T = any>(
blockId: string,
subBlockId: string,
<<<<<<< HEAD
triggerWorkflowUpdate = false
=======
triggerWorkflowUpdate: boolean = false,
isPreview: boolean = false,
directValue?: T
>>>>>>> 6f129dfc (fix: subblock rerender fixed)
): readonly [T | null, (value: T) => void] {
const blockType = useWorkflowStore(
useCallback((state) => state.blocks?.[blockId]?.type, [blockId])
@@ -171,12 +186,124 @@ export function useSubBlockValue<T = any>(
// Previous model reference for detecting model changes
const prevModelRef = useRef<string | null>(null)
// Ref to track if we're in preview mode and have direct values
const previewDataRef = useRef<{
isInPreview: boolean,
directValue: T | null
}>({
isInPreview: isPreview,
directValue: directValue as T || null
})
// Get value from subblock store - always call this hook unconditionally
const storeValue = useSubBlockStore(
useCallback((state) => state.getValue(blockId, subBlockId), [blockId, subBlockId])
)
// Directly update preview data when props change
useEffect(() => {
if (isPreview && directValue !== undefined) {
previewDataRef.current = {
isInPreview: true,
directValue: directValue as T || null
};
valueRef.current = directValue as T || null;
logger.info(`[PREVIEW-DIRECT] Using direct value for ${blockId}:${subBlockId}`, {
directValue,
isPreview
});
}
}, [isPreview, directValue, blockId, subBlockId]);
// Check for preview mode first and get direct subblock values if available
useEffect(() => {
// Skip DOM-based detection if already in preview mode with direct values
if (isPreview && directValue !== undefined) return;
try {
// Try to find if this component is within a preview parent
const parentBlock = document.querySelector(`[data-id="${blockId}"]`);
if (parentBlock) {
const isPreviewContext = parentBlock.closest('.preview-mode') != null;
// Get direct subblock values from parent's data props if in preview mode
if (isPreviewContext) {
const dataProps = parentBlock.getAttribute('data-props');
if (dataProps) {
const parsedProps = JSON.parse(dataProps);
const directValue = parsedProps?.data?.subBlockValues?.[subBlockId];
// If we have direct values in preview mode, use them
if (directValue !== undefined && directValue !== null) {
// Save the values in our ref
previewDataRef.current = {
isInPreview: true,
directValue: directValue
};
// Update valueRef directly to use the preview value
valueRef.current = directValue;
logger.info(`[PREVIEW-DOM] Using direct subblock value for ${blockId}:${subBlockId}`, {
directValue,
storeValue
});
}
}
} else {
// Reset preview flag if we're no longer in preview mode
previewDataRef.current.isInPreview = false;
}
}
} catch (e) {
// Ignore errors in preview detection
}
}, [blockId, subBlockId, isPreview, directValue, storeValue]);
// Add logging to trace where values are coming from
useEffect(() => {
// Skip if we've already determined we're in preview mode
if (previewDataRef.current.isInPreview) return;
// Check if we're in preview context
let isPreviewContext = false;
let directSubBlockValue = null;
try {
// Try to find if this component is within a preview parent
const parentBlock = document.querySelector(`[data-id="${blockId}"]`);
if (parentBlock) {
isPreviewContext = parentBlock.closest('.preview-mode') != null;
// Try to find the parent data to see if subBlockValues was passed directly
const dataProps = parentBlock.getAttribute('data-props');
if (dataProps) {
const parsedProps = JSON.parse(dataProps);
directSubBlockValue = parsedProps?.data?.subBlockValues?.[subBlockId];
}
}
logger.info(`[DATA-TRACE] SubBlock value source for ${blockId}:${subBlockId}`, {
blockId,
subBlockId,
storeValueExists: storeValue !== undefined,
initialValueExists: initialValue !== null,
isPreviewContext,
hasDirectSubBlockValue: directSubBlockValue !== undefined && directSubBlockValue !== null,
valueFromStore: storeValue,
valueFromInitial: initialValue,
valueFromDirect: directSubBlockValue,
actuallyUsing: previewDataRef.current.isInPreview ? 'directValue' :
(storeValue !== undefined ? 'globalStore' :
(initialValue !== null ? 'initialValue' : 'null')),
});
} catch (e) {
// Ignore errors - this is just diagnostic logging
}
}, [blockId, subBlockId, storeValue, initialValue]);
// Check if this is an API key field that could be auto-filled
const isApiKey =
subBlockId === 'apiKey' || (subBlockId?.toLowerCase().includes('apikey') ?? false)
@@ -196,10 +323,46 @@ export function useSubBlockValue<T = any>(
// Compute the modelValue based on block type
const modelValue = isProviderBasedBlock ? (modelSubBlockValue as string) : null
// Hook to set a value in the subblock store
const setValue = useCallback(
// Initialize valueRef on first render
useEffect(() => {
// If we're in preview mode with direct values, use those
if (previewDataRef.current.isInPreview) {
valueRef.current = previewDataRef.current.directValue;
} else {
// Otherwise use the store value or initial value
valueRef.current = storeValue !== undefined ? storeValue : initialValue;
}
}, [])
// Update the ref if the store value changes
// This ensures we're always working with the latest value
useEffect(() => {
// Skip updates from global store if we're using preview values
if (previewDataRef.current.isInPreview) return;
// Use deep comparison for objects to prevent unnecessary updates
if (!isEqual(valueRef.current, storeValue)) {
valueRef.current = storeValue !== undefined ? storeValue : initialValue
}
}, [storeValue, initialValue])
// Create a preview-aware setValue function
const setValueWithPreview = useCallback(
(newValue: T) => {
// Use deep comparison to avoid unnecessary updates for complex objects
// If we're in preview mode, just update the local valueRef for display
// but don't update the global store
if (previewDataRef.current.isInPreview) {
// Only update if the value has changed
if (!isEqual(valueRef.current, newValue)) {
valueRef.current = newValue;
// Update the ref as well
previewDataRef.current.directValue = newValue;
}
// Return early without updating global state
return;
}
// For non-preview mode, use the normal setValue logic
if (!isEqual(valueRef.current, newValue)) {
valueRef.current = newValue
@@ -228,11 +391,6 @@ export function useSubBlockValue<T = any>(
[blockId, subBlockId, blockType, isApiKey, storeValue, triggerWorkflowUpdate, modelValue]
)
// Initialize valueRef on first render
useEffect(() => {
valueRef.current = storeValue !== undefined ? storeValue : initialValue
}, [])
// When component mounts, check for existing API key in toolParamsStore
useEffect(() => {
// Skip autofill if the feature is disabled in settings
@@ -288,14 +446,5 @@ export function useSubBlockValue<T = any>(
isProviderBasedBlock,
])
// Update the ref if the store value changes
// This ensures we're always working with the latest value
useEffect(() => {
// Use deep comparison for objects to prevent unnecessary updates
if (!isEqual(valueRef.current, storeValue)) {
valueRef.current = storeValue !== undefined ? storeValue : initialValue
}
}, [storeValue, initialValue])
return [valueRef.current as T | null, setValue] as const
return [valueRef.current as T | null, setValueWithPreview] as const
}

View File

@@ -1,6 +1,12 @@
import { useEffect } from 'react'
import { Info } from 'lucide-react'
import { Label } from '@/components/ui/label'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
<<<<<<< HEAD
=======
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { createLogger } from '@/lib/logs/console-logger'
>>>>>>> 6f129dfc (fix: subblock rerender fixed)
import { getBlock } from '@/blocks/index'
import type { SubBlockConfig } from '@/blocks/types'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
@@ -28,13 +34,36 @@ import { TimeInput } from './components/time-input'
import { ToolInput } from './components/tool-input/tool-input'
import { WebhookConfig } from './components/webhook/webhook'
// Add logger
const logger = createLogger('SubBlock')
interface SubBlockProps {
blockId: string
config: SubBlockConfig
isConnecting: boolean
isPreview?: boolean
previewValue?: any
}
export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
export function SubBlock({
blockId,
config,
isConnecting,
isPreview = false,
previewValue = undefined
}: SubBlockProps) {
// Add debugging logs to trace parent context
useEffect(() => {
logger.info(`[TRACE] SubBlock ${config.id} for block ${blockId}`, {
blockId,
subBlockId: config.id,
subBlockTitle: config.title,
isPreview: isPreview,
previewValue: previewValue,
usingGlobalStore: !isPreview
});
}, [blockId, config.id, config.title, isPreview, previewValue]);
const handleMouseDown = (e: React.MouseEvent) => {
e.stopPropagation()
}
@@ -50,6 +79,9 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
}
const renderInput = () => {
// Get the subblock value from the config if available
const directValue = isPreview ? previewValue : undefined;
switch (config.type) {
case 'short-input':
return (
@@ -60,6 +92,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
password={config.password}
isConnecting={isConnecting}
config={config}
isPreview={isPreview}
value={directValue}
/>
)
case 'long-input':
@@ -71,6 +105,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
isConnecting={isConnecting}
rows={config.rows}
config={config}
isPreview={isPreview}
value={directValue}
/>
)
case 'dropdown':
@@ -80,6 +116,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
blockId={blockId}
subBlockId={config.id}
options={config.options as string[]}
isPreview={isPreview}
value={directValue}
/>
</div>
)

View File

@@ -8,6 +8,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { parseCronToHumanReadable } from '@/lib/schedules/utils'
import { cn, formatDateTime, validateName } from '@/lib/utils'
import type { BlockConfig, SubBlockConfig } from '@/blocks/types'
import { createLogger } from '@/lib/logs/console-logger'
import { useExecutionStore } from '@/stores/execution/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { mergeSubblockState } from '@/stores/workflows/utils'
@@ -16,18 +17,38 @@ import { ActionBar } from './components/action-bar/action-bar'
import { ConnectionBlocks } from './components/connection-blocks/connection-blocks'
import { SubBlock } from './components/sub-block/sub-block'
// Add logger for diagnostic logging
const logger = createLogger('WorkflowBlock')
interface WorkflowBlockProps {
type: string
config: BlockConfig
name: string
isActive?: boolean
isPending?: boolean
isPreview?: boolean
isReadOnly?: boolean
subBlockValues?: Record<string, any>
blockState?: any
}
// Combine both interfaces into a single component
export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
const { type, config, name, isActive: dataIsActive, isPending } = data
// Add logging for debug purposes
useEffect(() => {
if (data.isPreview === true) {
logger.info(`[PREVIEW-RENDER] Block ${id} rendering in preview mode`, {
blockId: id,
blockType: type,
subBlockValues: data.subBlockValues ? Object.keys(data.subBlockValues) : [],
hasDirectSubBlocks: Boolean(data.subBlockValues && Object.keys(data.subBlockValues).length > 0),
isReadOnly: data.isReadOnly,
});
}
}, [id, type, data.isPreview, data.subBlockValues, data.isReadOnly]);
// State management
const [isConnecting, setIsConnecting] = useState(false)
const [isEditing, setIsEditing] = useState(false)
@@ -256,11 +277,15 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
let currentRow: SubBlockConfig[] = []
let currentRowWidth = 0
// Get merged state for this block
// Get merged state for this block - use direct props if in preview mode
const blocks = useWorkflowStore.getState().blocks
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId || undefined
const mergedState = mergeSubblockState(blocks, activeWorkflowId, blockId)[blockId]
const isAdvancedMode = useWorkflowStore.getState().blocks[blockId]?.advancedMode ?? false
const isAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false)
// If in preview mode with direct subBlockValues, use those instead of global state
const mergedState = data.isPreview && data.subBlockValues
? { [blockId]: { subBlocks: data.subBlockValues } }
: mergeSubblockState(blocks, activeWorkflowId, blockId)
// Filter visible blocks and those that meet their conditions
const visibleSubBlocks = subBlocks.filter((block) => {
@@ -276,9 +301,9 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
if (!block.condition) return true
// Get the values of the fields this block depends on from merged state
const fieldValue = mergedState?.subBlocks[block.condition.field]?.value
const fieldValue = mergedState?.[blockId]?.subBlocks[block.condition.field]?.value
const andFieldValue = block.condition.and
? mergedState?.subBlocks[block.condition.and.field]?.value
? mergedState?.[blockId]?.subBlocks[block.condition.and.field]?.value
: undefined
// Check if the condition value is an array
@@ -398,6 +423,15 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
isPending && 'ring-2 ring-amber-500',
'z-[20]'
)}
data-id={id}
data-props={data.isPreview ? JSON.stringify({
isPreview: data.isPreview,
isReadOnly: data.isReadOnly,
blockType: type,
data: {
subBlockValues: data.subBlockValues
}
}) : undefined}
>
{/* Show debug indicator for pending blocks */}
{isPending && (
@@ -703,7 +737,14 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
key={`${id}-${rowIndex}-${blockIndex}`}
className={cn('space-y-1', subBlock.layout === 'half' ? 'flex-1' : 'w-full')}
>
<SubBlock blockId={id} config={subBlock} isConnecting={isConnecting} />
<SubBlock
blockId={id}
config={subBlock}
isConnecting={isConnecting}
isPreview={data.isPreview}
previewValue={data.isPreview && data.subBlockValues ?
(data.subBlockValues[subBlock.id]?.value || null) : null}
/>
</div>
))}
</div>

View File

@@ -26,7 +26,11 @@ import { WorkflowEdge } from '@/app/w/[id]/components/workflow-edge/workflow-edg
// import { LoopLabel } from '@/app/w/[id]/components/workflow-loop/components/loop-label/loop-label'
// import { createLoopNode } from '@/app/w/[id]/components/workflow-loop/workflow-loop'
import { getBlock } from '@/blocks'
<<<<<<< HEAD
import type { SubBlockConfig } from '@/blocks/types'
=======
import { cn } from '@/lib/utils'
>>>>>>> 6f129dfc (fix: subblock rerender fixed)
const logger = createLogger('WorkflowPreview')
@@ -81,6 +85,14 @@ export function WorkflowPreview({
defaultPosition,
defaultZoom,
}: WorkflowPreviewProps) {
// Use effect to log the workflow state once outside of useMemo
useEffect(() => {
logger.info('WorkflowPreview received new state', {
blockCount: Object.keys(workflowState?.blocks || {}).length,
withSubBlocks: Object.values(workflowState?.blocks || {}).filter(b => b.subBlocks && Object.keys(b.subBlocks).length > 0).length,
});
}, [workflowState]);
// Transform blocks and loops into ReactFlow nodes
const nodes: Node[] = useMemo(() => {
const nodeArray: Node[] = []
@@ -102,12 +114,20 @@ export function WorkflowPreview({
// Add block nodes using the same approach as workflow.tsx
Object.entries(workflowState.blocks).forEach(([blockId, block]) => {
if (!block || !block.type) {
logger.warn(`Skipping invalid block: ${blockId}`);
return;
}
const blockConfig = getBlock(block.type)
if (!blockConfig) {
logger.error(`No configuration found for block type: ${block.type}`, { blockId })
return
return;
}
// Create a deep clone of subBlocks to avoid any references to the original state
const subBlocksClone = block.subBlocks ? JSON.parse(JSON.stringify(block.subBlocks)) : {};
nodeArray.push({
id: blockId,
type: 'workflowBlock',
@@ -120,9 +140,10 @@ export function WorkflowPreview({
blockState: block,
isReadOnly: true, // Set read-only mode for preview
isPreview: true, // Indicate this is a preview
subBlockValues: block.subBlocks || {}, // Use empty object as fallback
subBlockValues: subBlocksClone, // Use the deep clone to avoid reference issues
},
})
<<<<<<< HEAD
// Add children of this block if it's a loop
if (block.type === 'loop') {
@@ -156,6 +177,12 @@ export function WorkflowPreview({
})
})
}
=======
logger.info(`Preview node created: ${blockId}`, {
blockType: block.type,
hasSubBlocks: block.subBlocks && Object.keys(block.subBlocks).length > 0
});
>>>>>>> 6f129dfc (fix: subblock rerender fixed)
})
return nodeArray
@@ -179,7 +206,7 @@ export function WorkflowPreview({
return (
<ReactFlowProvider>
<div style={{ height, width }} className={className}>
<div style={{ height, width }} className={cn(className, 'preview-mode')}>
<ReactFlow
nodes={nodes}
edges={edges}