mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
fix: subblock rerender fixed
This commit is contained in:
@@ -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%'
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user