diff --git a/apps/sim/app/api/workflows/[id]/deployed/route.ts b/apps/sim/app/api/workflows/[id]/deployed/route.ts index d0e0d724c2..027e3e6804 100644 --- a/apps/sim/app/api/workflows/[id]/deployed/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployed/route.ts @@ -69,4 +69,4 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const response = createErrorResponse(error.message || 'Failed to fetch deployed state', 500) return addNoCacheHeaders(response) } -} \ No newline at end of file +} diff --git a/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx b/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx index 518b96ea6c..f8a794b7fd 100644 --- a/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx @@ -1,7 +1,7 @@ 'use client' -import { useState, useEffect } from 'react' -import { Info, Loader2 } from 'lucide-react' +import { useState } from 'react' +import { Loader2 } from 'lucide-react' import { AlertDialog, AlertDialogAction, @@ -20,9 +20,8 @@ import { ApiKey } from '@/app/w/[id]/components/control-bar/components/deploy-mo import { DeployStatus } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/deploy-status/deploy-status' import { ExampleCommand } from '@/app/w/[id]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command' import { useNotificationStore } from '@/stores/notifications/store' +import type { WorkflowState } from '@/stores/workflows/workflow/types' import { DeployedWorkflowModal } from '../../../deployment-controls/components/deployed-workflow-modal' -import { WorkflowState } from '@/stores/workflows/workflow/types' - interface DeploymentInfoProps { isLoading?: boolean @@ -51,7 +50,7 @@ export function DeploymentInfo({ isUndeploying, workflowId, deployedState, - isLoadingDeployedState + isLoadingDeployedState, }: DeploymentInfoProps) { const [isViewingDeployed, setIsViewingDeployed] = useState(false) const { addNotification } = useNotificationStore() @@ -61,12 +60,13 @@ export function DeploymentInfo({ addNotification('error', 'Cannot view deployment: Workflow ID is missing', null) return } - + // If deployedState is already loaded, use it directly if (deployedState) { setIsViewingDeployed(true) return - } else if (!isLoadingDeployedState) { + } + if (!isLoadingDeployedState) { addNotification('error', 'Cannot view deployment: No deployed state available', workflowId) } } diff --git a/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/deploy-modal.tsx b/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/deploy-modal.tsx index dbb12d08c9..d38b474129 100644 --- a/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/deploy-modal.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/components/deploy-modal/deploy-modal.tsx @@ -27,7 +27,7 @@ import { useNotificationStore } from '@/stores/notifications/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' -import { WorkflowState } from '@/stores/workflows/workflow/types' +import type { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('DeployModal') @@ -618,8 +618,8 @@ export function DeployModal({ -
-
+
+
{activeTab === 'api' && ( <> {isDeployed ? ( @@ -637,12 +637,12 @@ export function DeployModal({ ) : ( <> {apiDeployError && ( -
-
API Deployment Error
+
+
API Deployment Error
{apiDeployError}
)} -
+
{ - return `${showingDeployed ? 'deployed' : 'current'}-preview-${activeWorkflowId}}`; - }, [showingDeployed, activeWorkflowId]); + return `${showingDeployed ? 'deployed' : 'current'}-preview-${activeWorkflowId}}` + }, [showingDeployed, activeWorkflowId]) return ( {/* Controls */} -
+
{/* Version toggle - only show if there's a current version */} {currentWorkflowState && ( -
-
- -
- +
+ + {/* Workflow preview with fixed height */} -
- {/* {sanitizedWorkflowState ? ( */} - +
+
diff --git a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-modal.tsx b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-modal.tsx index fb05a5518d..27c41c40ae 100644 --- a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-modal.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-modal.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useMemo, useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { AlertDialog, AlertDialogAction, @@ -13,19 +13,13 @@ import { AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { createLogger } from '@/lib/logs/console-logger' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import type { WorkflowState } from '@/stores/workflows/workflow/types' import { DeployedWorkflowCard } from './deployed-workflow-card' -import { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('DeployedWorkflowModal') @@ -45,15 +39,15 @@ export function DeployedWorkflowModal({ const [showRevertDialog, setShowRevertDialog] = useState(false) const { revertToDeployedState } = useWorkflowStore() const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) - + // Add instance ID to track component lifecycle - const modalOpenCount = useRef(0); - + const modalOpenCount = useRef(0) + useEffect(() => { if (isOpen) { - modalOpenCount.current += 1; + modalOpenCount.current += 1 } - }, [isOpen]); + }, [isOpen]) // Get current workflow state to compare with deployed state const currentWorkflowState = useWorkflowStore((state) => ({ @@ -88,13 +82,13 @@ export function DeployedWorkflowModal({ deployedWorkflowState={deployedWorkflowState} /> -
+
{needsRedeployment && ( - + - + Revert to Deployed Version? @@ -106,7 +100,7 @@ export function DeployedWorkflowModal({ Cancel Revert @@ -115,7 +109,7 @@ export function DeployedWorkflowModal({ )} -
diff --git a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/deployment-controls.tsx b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/deployment-controls.tsx index a5a4f17f8c..5a955efedb 100644 --- a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/deployment-controls.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/deployment-controls.tsx @@ -1,12 +1,12 @@ 'use client' -import { useState, useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { Loader2, Rocket } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { WorkflowState } from '@/stores/workflows/workflow/types' +import type { WorkflowState } from '@/stores/workflows/workflow/types' import { DeployModal } from '../deploy-modal/deploy-modal' interface DeploymentControlsProps { @@ -40,27 +40,27 @@ export function DeploymentControls({ const [isDeploying, _setIsDeploying] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) - + // Track the last seen workflow ID const lastWorkflowIdRef = useRef(null) - + // Update last seen workflow ID useEffect(() => { if (activeWorkflowId !== lastWorkflowIdRef.current) { - lastWorkflowIdRef.current = activeWorkflowId; + lastWorkflowIdRef.current = activeWorkflowId } - }, [activeWorkflowId]); - + }, [activeWorkflowId]) + // Refetch deployed state wrapper const refetchWithErrorHandling = async () => { - if (!activeWorkflowId) return; - + if (!activeWorkflowId) return + try { - await refetchDeployedState(); + await refetchDeployedState() } catch (error) { // Silent error handling } - }; + } // Update parent component when workflow-specific status changes useEffect(() => { diff --git a/apps/sim/app/w/[id]/components/control-bar/control-bar.tsx b/apps/sim/app/w/[id]/components/control-bar/control-bar.tsx index 4b348a642b..5ce3a754c6 100644 --- a/apps/sim/app/w/[id]/components/control-bar/control-bar.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/control-bar.tsx @@ -46,7 +46,7 @@ import { usePanelStore } from '@/stores/panel/store' import { useGeneralStore } from '@/stores/settings/general/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' -import { WorkflowState } from '@/stores/workflows/workflow/types' +import type { WorkflowState } from '@/stores/workflows/workflow/types' import { getKeyboardShortcutText, useKeyboardShortcuts, @@ -87,7 +87,7 @@ export function ControlBar() { showNotification, removeNotification, } = useNotificationStore() - const { history, revertToHistoryState, lastSaved } = useWorkflowStore() + const { history, revertToHistoryState, lastSaved } = useWorkflowStore() const { workflows, updateWorkflow, @@ -215,15 +215,15 @@ export function ControlBar() { try { setIsLoadingDeployedState(true) - + const response = await fetch(`/api/workflows/${requestWorkflowId}/deployed`) - + // Check if the workflow ID changed during the request (user navigated away) if (requestWorkflowId !== useWorkflowRegistry.getState().activeWorkflowId) { logger.debug('Workflow changed during deployed state fetch, ignoring response') return } - + if (!response.ok) { if (response.status === 404) { // No deployed state found @@ -232,16 +232,15 @@ export function ControlBar() { } throw new Error(`Failed to fetch deployed state: ${response.statusText}`) } - + const data = await response.json() - + // Final check to ensure we're still on the same workflow if (requestWorkflowId === useWorkflowRegistry.getState().activeWorkflowId) { setDeployedState(data.deployedState || null) } else { logger.debug('Workflow changed after deployed state response, ignoring result') } - } catch (error) { logger.error('Error fetching deployed state:', { error }) // Only set error state if we're still on the same workflow @@ -263,7 +262,7 @@ export function ControlBar() { setDeployedState(null) setIsLoadingDeployedState(false) } - + // Then fetch the new deployed state fetchDeployedState() }, [activeWorkflowId, isDeployed]) @@ -276,7 +275,7 @@ export function ControlBar() { setIsLoadingDeployedState(false) return } - + if (isDeployed) { // When deployment status becomes true, reset the needsRedeployment flag setNeedsRedeployment(false) diff --git a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx index 697e038d63..c3642e0d3e 100644 --- a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx @@ -43,9 +43,10 @@ export function OutputSelect({ if (block.type === 'starter') return // Add defensive check to ensure block.name exists and is a string - const blockName = block.name && typeof block.name === 'string' - ? block.name.replace(/\s+/g, '').toLowerCase() - : `block-${block.id}`; + const blockName = + block.name && typeof block.name === 'string' + ? block.name.replace(/\s+/g, '').toLowerCase() + : `block-${block.id}` // Add response outputs if (block.outputs && typeof block.outputs === 'object') { @@ -97,9 +98,10 @@ export function OutputSelect({ const output = workflowOutputs.find((o) => o.id === validOutputs[0]) if (output) { // Add defensive check for output.blockName - const blockNameText = output.blockName && typeof output.blockName === 'string' - ? output.blockName.replace(/\s+/g, '').toLowerCase() - : `block-${output.blockId}`; + const blockNameText = + output.blockName && typeof output.blockName === 'string' + ? output.blockName.replace(/\s+/g, '').toLowerCase() + : `block-${output.blockId}` return `${blockNameText}.${output.path}` } return placeholder diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/checkbox-list.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/checkbox-list.tsx index c393c3ea1c..842462abfb 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/checkbox-list.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/checkbox-list.tsx @@ -3,7 +3,6 @@ import { Label } from '@/components/ui/label' import { cn } from '@/lib/utils' import { useSubBlockValue } from '../hooks/use-sub-block-value' - interface CheckboxListProps { blockId: string subBlockId: string @@ -14,36 +13,34 @@ interface CheckboxListProps { subBlockValues?: Record } -export function CheckboxList({ - blockId, - subBlockId, - title, - options, +export function CheckboxList({ + blockId, + subBlockId, + title, + options, layout, isPreview = false, - subBlockValues + subBlockValues, }: CheckboxListProps) { return (
{options.map((option) => { - const [storeValue, setStoreValue] = useSubBlockValue( - blockId, - option.id - ) - + const [storeValue, setStoreValue] = useSubBlockValue(blockId, option.id) + // Get preview value for this specific option - const previewValue = isPreview && subBlockValues ? subBlockValues[option.id]?.value : undefined - + const previewValue = + isPreview && subBlockValues ? subBlockValues[option.id]?.value : undefined + // Use preview value when in preview mode, otherwise use store value const value = isPreview ? previewValue : storeValue - + const handleChange = (checked: boolean) => { // Only update store when not in preview mode if (!isPreview) { setStoreValue(checked) } } - + return (
{ - return language === 'json' ? 'Describe the JSON schema you need...' : 'Describe the function you need...' + return language === 'json' + ? 'Describe the JSON schema you need...' + : 'Describe the function you need...' }, [language]) // State management @@ -89,7 +91,7 @@ export function Code({ setCollapsedValue(blockId, collapsedStateKey, !isCollapsed) } // Use preview value when in preview mode, otherwise use store value or prop value - const value = isPreview ? previewValue : (propValue !== undefined ? propValue : storeValue) + const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue // AI Code Generation Hook const handleStreamStart = () => { @@ -208,7 +210,7 @@ export function Code({ clearTimeout(timeoutId) resizeObserver.disconnect() } - }, [code]) + }, [code]) // Handlers const handleDrop = (e: React.DragEvent) => { @@ -325,7 +327,7 @@ export function Code({ onDragOver={(e) => e.preventDefault()} onDrop={handleDrop} > -
+
{!isCollapsed && !isAiStreaming && !isPreview && ( Add Metric @@ -129,7 +136,7 @@ export function EvalInput({ size='sm' onClick={() => removeMetric(metric.id)} disabled={isPreview || metrics.length === 1} - className="h-8 w-8 text-destructive hover:text-destructive" + className='h-8 w-8 text-destructive hover:text-destructive' > Delete Metric @@ -159,9 +166,9 @@ export function EvalInput({ name='name' value={metric.name} onChange={(e) => updateMetric(metric.id, 'name', e.target.value)} - placeholder="Accuracy" + placeholder='Accuracy' disabled={isPreview} - className="placeholder:text-muted-foreground/50" + className='placeholder:text-muted-foreground/50' />
@@ -170,9 +177,9 @@ export function EvalInput({ updateMetric(metric.id, 'description', e.target.value)} - placeholder="How accurate is the response?" + placeholder='How accurate is the response?' disabled={isPreview} - className="placeholder:text-muted-foreground/50" + className='placeholder:text-muted-foreground/50' />
@@ -185,7 +192,7 @@ export function EvalInput({ onChange={(e) => updateRange(metric.id, 'min', e.target.value)} onBlur={(e) => updateThreshold(metric.id, e.target.value)} disabled={isPreview} - className="placeholder:text-muted-foreground/50" + className='placeholder:text-muted-foreground/50' />
@@ -196,7 +203,7 @@ export function EvalInput({ onChange={(e) => updateRange(metric.id, 'max', e.target.value)} onBlur={(e) => updateThreshold(metric.id, e.target.value)} disabled={isPreview} - className="placeholder:text-muted-foreground/50" + className='placeholder:text-muted-foreground/50' />
diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/file-selector-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/file-selector-input.tsx index 386a5cb62b..a5d2cd635a 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/file-selector-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/file-selector-input.tsx @@ -5,7 +5,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp import { env } from '@/lib/env' import type { SubBlockConfig } from '@/blocks/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { createLogger } from '@/lib/logs/console-logger' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import type { ConfluenceFileInfo } from './components/confluence-file-selector' import { ConfluenceFileSelector } from './components/confluence-file-selector' @@ -20,7 +19,6 @@ import { MicrosoftFileSelector } from './components/microsoft-file-selector' import type { TeamsMessageInfo } from './components/teams-message-selector' import { TeamsMessageSelector } from './components/teams-message-selector' - interface FileSelectorInputProps { blockId: string subBlock: SubBlockConfig @@ -29,12 +27,12 @@ interface FileSelectorInputProps { previewValue?: any | null } -export function FileSelectorInput({ - blockId, - subBlock, +export function FileSelectorInput({ + blockId, + subBlock, disabled, isPreview = false, - previewValue + previewValue, }: FileSelectorInputProps) { const { getValue, setValue } = useSubBlockStore() const { activeWorkflowId } = useWorkflowRegistry() @@ -66,31 +64,33 @@ export function FileSelectorInput({ // Get the current value from the store or prop value if in preview mode useEffect(() => { if (isPreview && previewValue !== undefined) { - const value = previewValue; + const value = previewValue if (value && typeof value === 'string') { if (isJira) { - setSelectedIssueId(value); + setSelectedIssueId(value) } else if (isDiscord) { - setSelectedChannelId(value); + setSelectedChannelId(value) + } else if (isMicrosoftTeams) { + setSelectedMessageId(value) } else { - setSelectedFileId(value); + setSelectedFileId(value) } } } else { - const value = getValue(blockId, subBlock.id); + const value = getValue(blockId, subBlock.id) if (value && typeof value === 'string') { if (isJira) { - setSelectedIssueId(value); + setSelectedIssueId(value) } else if (isDiscord) { - setSelectedChannelId(value); + setSelectedChannelId(value) } else if (isMicrosoftTeams) { - setSelectedMessageId(value); + setSelectedMessageId(value) } else { - setSelectedFileId(value); + setSelectedFileId(value) } } } - }, [blockId, subBlock.id, getValue, isJira, isDiscord, isPreview, previewValue]); + }, [blockId, subBlock.id, getValue, isJira, isDiscord, isMicrosoftTeams, isPreview, previewValue]) // Handle file selection const handleFileChange = (fileId: string, info?: any) => { diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx index 860f2ac79d..6cbb44d0d8 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Progress } from '@/components/ui/progress' @@ -10,7 +10,6 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { useSubBlockValue } from '../hooks/use-sub-block-value' - interface FileUploadProps { blockId: string subBlockId: string @@ -41,7 +40,7 @@ export function FileUpload({ acceptedTypes = '*', multiple = false, // Default to single file for backward compatibility isPreview = false, - previewValue + previewValue, }: FileUploadProps) { // State management - handle both single file and array of files const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) @@ -89,7 +88,7 @@ export function FileUpload({ */ const handleFileChange = async (e: React.ChangeEvent) => { if (isPreview) return - + e.stopPropagation() const files = e.target.files diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/folder-selector/components/folder-selector-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/folder-selector/components/folder-selector-input.tsx index 2be4a5953f..b723fe9525 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/folder-selector/components/folder-selector-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/folder-selector/components/folder-selector-input.tsx @@ -1,11 +1,7 @@ 'use client' import { useEffect, useState } from 'react' -<<<<<<< HEAD import type { SubBlockConfig } from '@/blocks/types' -import { createLogger } from '@/lib/logs/console-logger' -======= ->>>>>>> 86800d81 (fix: removed comments) import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { type FolderInfo, FolderSelector } from '../folder-selector' @@ -22,7 +18,7 @@ export function FolderSelectorInput({ subBlock, disabled = false, isPreview = false, - previewValue + previewValue, }: FolderSelectorInputProps) { const { getValue, setValue } = useSubBlockStore() const [selectedFolderId, setSelectedFolderId] = useState('') @@ -31,27 +27,27 @@ export function FolderSelectorInput({ // Get the current value from the store or prop value if in preview mode useEffect(() => { if (isPreview && previewValue !== undefined) { - setSelectedFolderId(previewValue); + setSelectedFolderId(previewValue) } else { - const value = getValue(blockId, subBlock.id); + const value = getValue(blockId, subBlock.id) if (value && typeof value === 'string') { - setSelectedFolderId(value); + setSelectedFolderId(value) } else { - const defaultValue = 'INBOX'; - setSelectedFolderId(defaultValue); + const defaultValue = 'INBOX' + setSelectedFolderId(defaultValue) if (!isPreview) { - setValue(blockId, subBlock.id, defaultValue); + setValue(blockId, subBlock.id, defaultValue) } } } - }, [blockId, subBlock.id, getValue, setValue, isPreview, previewValue]); + }, [blockId, subBlock.id, getValue, setValue, isPreview, previewValue]) // Handle folder selection const handleFolderChange = (folderId: string, info?: FolderInfo) => { - setSelectedFolderId(folderId); - setFolderInfo(info || null); + setSelectedFolderId(folderId) + setFolderInfo(info || null) if (!isPreview) { - setValue(blockId, subBlock.id, folderId); + setValue(blockId, subBlock.id, folderId) } } diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx index fc3f86034d..b6fa1994c0 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx @@ -53,7 +53,7 @@ export function LongInput({ const containerRef = useRef(null) // Use preview value when in preview mode, otherwise use store value or prop value - const value = isPreview ? previewValue : (propValue !== undefined ? propValue : storeValue) + const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue // Calculate initial height based on rows prop with reasonable defaults const getInitialHeight = () => { @@ -83,14 +83,14 @@ export function LongInput({ const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value const newCursorPosition = e.target.selectionStart ?? 0 - + if (onChange) { onChange(newValue) } else if (!isPreview) { // Only update store when not in preview mode setStoreValue(newValue) } - + setCursorPosition(newCursorPosition) // Check for environment variables trigger diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/project-selector/project-selector-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/project-selector/project-selector-input.tsx index f74e1cc185..c67c379379 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/project-selector/project-selector-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/project-selector/project-selector-input.tsx @@ -2,18 +2,13 @@ import { useEffect, useState } from 'react' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' -<<<<<<< HEAD import type { SubBlockConfig } from '@/blocks/types' -import { createLogger } from '@/lib/logs/console-logger' -======= ->>>>>>> 86800d81 (fix: removed comments) import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { type DiscordServerInfo, DiscordServerSelector } from './components/discord-server-selector' import { type JiraProjectInfo, JiraProjectSelector } from './components/jira-project-selector' import { type LinearProjectInfo, LinearProjectSelector } from './components/linear-project-selector' import { type LinearTeamInfo, LinearTeamSelector } from './components/linear-team-selector' - interface ProjectSelectorInputProps { blockId: string subBlock: SubBlockConfig @@ -29,7 +24,7 @@ export function ProjectSelectorInput({ disabled = false, onProjectSelect, isPreview = false, - previewValue + previewValue, }: ProjectSelectorInputProps) { const { getValue, setValue } = useSubBlockStore() const [selectedProjectId, setSelectedProjectId] = useState('') @@ -47,14 +42,14 @@ export function ProjectSelectorInput({ // Get the current value from the store or prop value if in preview mode useEffect(() => { if (isPreview && previewValue !== undefined) { - setSelectedProjectId(previewValue); + setSelectedProjectId(previewValue) } else { - const value = getValue(blockId, subBlock.id); + const value = getValue(blockId, subBlock.id) if (value && typeof value === 'string') { - setSelectedProjectId(value); + setSelectedProjectId(value) } } - }, [blockId, subBlock.id, getValue, isPreview, previewValue]); + }, [blockId, subBlock.id, getValue, isPreview, previewValue]) // Handle project selection const handleProjectChange = ( diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx index a5be0e3792..a088192b72 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx @@ -23,12 +23,12 @@ interface ScheduleConfigProps { previewValue?: any | null } -export function ScheduleConfig({ - blockId, - subBlockId, +export function ScheduleConfig({ + blockId, + subBlockId, isConnecting, isPreview = false, - previewValue + previewValue, }: ScheduleConfigProps) { const [error, setError] = useState(null) const [scheduleId, setScheduleId] = useState(null) @@ -58,7 +58,7 @@ export function ScheduleConfig({ const isScheduleEnabled = startWorkflow === 'schedule' const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) - + // Use preview value when in preview mode, otherwise use store value const value = isPreview ? previewValue : storeValue @@ -152,7 +152,7 @@ export function ScheduleConfig({ const handleSaveSchedule = async (): Promise => { if (isPreview) return false - + setIsSaving(true) setError(null) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx index d4af8abece..4aed0f8571 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx @@ -34,24 +34,21 @@ export function ShortInput({ onChange, value: propValue, isPreview = false, - previewValue + previewValue, }: ShortInputProps) { const [isFocused, setIsFocused] = useState(false) const [showEnvVars, setShowEnvVars] = useState(false) const [showTags, setShowTags] = useState(false) const validatePropValue = (value: any): string => { - if (value === undefined || value === null) return ''; - if (typeof value === 'string') return value; + if (value === undefined || value === null) return '' + if (typeof value === 'string') return value try { - return String(value); + return String(value) } catch { - return ''; + return '' } } - const [storeValue, setStoreValue] = useSubBlockValue( - blockId, - subBlockId - ) + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) const [searchTerm, setSearchTerm] = useState('') const [cursorPosition, setCursorPosition] = useState(0) const inputRef = useRef(null) @@ -62,7 +59,7 @@ export function ShortInput({ const reactFlowInstance = useReactFlow() // Use preview value when in preview mode, otherwise use store value or prop value - const value = isPreview ? previewValue : (propValue !== undefined ? propValue : storeValue) + const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue // Check if this input is API key related const isApiKeyField = useMemo(() => { diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/slider-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/slider-input.tsx index 471fa69b14..157dfeda44 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/slider-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/slider-input.tsx @@ -1,8 +1,7 @@ -import { useEffect, useMemo } from 'react' +import { useEffect } from 'react' import { Slider } from '@/components/ui/slider' import { useSubBlockValue } from '../hooks/use-sub-block-value' - interface SliderInputProps { blockId: string subBlockId: string @@ -21,20 +20,19 @@ export function SliderInput({ min = 0, max = 100, defaultValue = 50, - step = 1, + step = 0.1, integer = false, isPreview = false, - previewValue + previewValue, }: SliderInputProps) { const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) - + // Use preview value when in preview mode, otherwise use store value const value = isPreview ? previewValue : storeValue // Clamp the value within bounds while preserving relative position when possible - const normalizedValue = value !== null && value !== undefined - ? Math.max(min, Math.min(max, value)) - : defaultValue + const normalizedValue = + value !== null && value !== undefined ? Math.max(min, Math.min(max, value)) : defaultValue const displayValue = normalizedValue ?? defaultValue @@ -53,20 +51,29 @@ export function SliderInput({ } return ( -
-
- -
-
- {displayValue} +
+ setStoreValue(integer ? Math.round(value[0]) : value[0])} + disabled={isPreview} + className='[&_[class*=SliderTrack]]:h-1 [&_[role=slider]]:h-4 [&_[role=slider]]:w-4' + /> +
{ + const percentage = ((normalizedValue - min) / (max - min)) * 100 + const bias = -25 * Math.sin((percentage * Math.PI) / 50) + return percentage === 0 ? 0 : percentage === 100 ? 100 : 50 + bias + })()}%)`, + top: '24px', + }} + > + {integer ? Math.round(normalizedValue).toString() : Number(normalizedValue).toFixed(1)}
) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/starter/input-format.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/starter/input-format.tsx index a94b4d5615..6bac8da852 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/starter/input-format.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/starter/input-format.tsx @@ -12,7 +12,6 @@ import { Label } from '@/components/ui/label' import { cn } from '@/lib/utils' import { useSubBlockValue } from '../../hooks/use-sub-block-value' - interface InputField { id: string name: string @@ -35,14 +34,14 @@ const DEFAULT_FIELD: InputField = { collapsed: true, } -export function InputFormat({ - blockId, +export function InputFormat({ + blockId, subBlockId, isPreview = false, - previewValue + previewValue, }: InputFormatProps) { const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) - + // Use preview value when in preview mode, otherwise use store value const value = isPreview ? previewValue : storeValue const fields: InputField[] = value || [DEFAULT_FIELD] @@ -50,7 +49,7 @@ export function InputFormat({ // Field operations const addField = () => { if (isPreview) return - + const newField: InputField = { ...DEFAULT_FIELD, id: crypto.randomUUID(), @@ -71,7 +70,9 @@ export function InputFormat({ const toggleCollapse = (id: string) => { if (isPreview) return - setStoreValue(fields.map((f: InputField) => (f.id === id ? { ...f, collapsed: !f.collapsed } : f))) + setStoreValue( + fields.map((f: InputField) => (f.id === id ? { ...f, collapsed: !f.collapsed } : f)) + ) } // Field header @@ -98,10 +99,16 @@ export function InputFormat({ )}
-
e.stopPropagation()}> -
@@ -158,8 +165,12 @@ export function InputFormat({ -
- - - - - No tools found. - {toolBlocks.map((block) => ( - - { - if (!isPreview) { - handleSelectTool(block) - setOpen(false) - } - }} - disabled={isPreview || isToolAlreadySelected(block.type)} - > -
-
- - {block.name} -
-
-
-
- ))} - - - + + + + No tools found + + { if (!isPreview) { setCustomToolModalOpen(true) setOpen(false) } }} + className='mb-1 flex cursor-pointer items-center gap-2' disabled={isPreview} > -
- - Create Custom Tool +
+
- - - - + Create Tool + + + {/* Display saved custom tools at the top */} + {customTools.length > 0 && ( + <> + +
+ Custom Tools +
+ + {customTools.map((customTool) => ( + { + const newTool: StoredTool = { + type: 'custom-tool', + title: customTool.title, + params: {}, + isExpanded: true, + schema: customTool.schema, + code: customTool.code, + usageControl: 'auto', + } + + if (isWide) { + setStoreValue([ + ...selectedTools.map((tool, index) => ({ + ...tool, + isExpanded: + Math.floor(selectedTools.length / 2) === + Math.floor(index / 2), + })), + newTool, + ]) + } else { + setStoreValue([ + ...selectedTools.map((tool) => ({ + ...tool, + isExpanded: false, + })), + newTool, + ]) + } + setOpen(false) + }} + className='flex cursor-pointer items-center gap-2' + > +
+ +
+ {customTool.title} +
+ ))} +
+ + + )} + + {/* Display built-in tools */} + {toolBlocks.some((block) => customFilter(block.name, searchQuery || '') > 0) && ( + <> +
+ Built-in Tools +
+ + {toolBlocks.map((block) => ( + handleSelectTool(block)} + className='flex cursor-pointer items-center gap-2' + > +
+ +
+ {block.name} +
+ ))} +
+ + )} + + + ) : ( diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx index 4a9e9e3ad6..65d1a89df7 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx @@ -295,12 +295,12 @@ interface WebhookConfigProps { } } -export function WebhookConfig({ - blockId, - subBlockId, - isConnecting, +export function WebhookConfig({ + blockId, + subBlockId, + isConnecting, isPreview = false, - value: propValue + value: propValue, }: WebhookConfigProps) { const [isModalOpen, setIsModalOpen] = useState(false) const [isSaving, setIsSaving] = useState(false) @@ -323,7 +323,7 @@ export function WebhookConfig({ // Store provider-specific configuration const [storeProviderConfig, setProviderConfig] = useSubBlockValue(blockId, 'providerConfig') - + // Use prop values when available (preview mode), otherwise use store values const webhookProvider = propValue?.webhookProvider ?? storeWebhookProvider const webhookPath = propValue?.webhookPath ?? storeWebhookPath @@ -570,7 +570,7 @@ export function WebhookConfig({ 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.labels', ]} - label="Select Gmail account" + label='Select Gmail account' disabled={isConnecting || isSaving || isDeleting || isPreview} />
diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts index 9f5bf2fa4a..e14122c2c5 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts @@ -153,8 +153,8 @@ function storeApiKeyValue( export function useSubBlockValue( blockId: string, subBlockId: string, - triggerWorkflowUpdate: boolean = false, - isPreview: boolean = false, + triggerWorkflowUpdate = false, + isPreview = false, directValue?: T ): readonly [T | null, (value: T) => void] { const blockType = useWorkflowStore( @@ -300,4 +300,4 @@ export function useSubBlockValue( }, [storeValue, initialValue]) return [valueRef.current as T | null, setValue] as const -} \ No newline at end of file +} diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx index 0adcf93173..ffc4558f5f 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx @@ -1,10 +1,10 @@ import { Info } from 'lucide-react' import { Label } from '@/components/ui/label' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { getBlock } from '@/blocks/index' import type { SubBlockConfig } from '@/blocks/types' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { CheckboxList } from './components/checkbox-list' import { Code } from './components/code' import { ConditionInput } from './components/condition-input' @@ -35,12 +35,12 @@ interface SubBlockProps { subBlockValues?: Record } -export function SubBlock({ - blockId, - config, +export function SubBlock({ + blockId, + config, isConnecting, isPreview = false, - subBlockValues + subBlockValues, }: SubBlockProps) { const handleMouseDown = (e: React.MouseEvent) => { e.stopPropagation() @@ -66,7 +66,7 @@ export function SubBlock({ const renderInput = () => { const previewValue = getPreviewValue() - + switch (config.type) { case 'short-input': return ( @@ -121,13 +121,15 @@ export function SubBlock({ /> ) case 'table': - return + return ( +
+ ) case 'code': return ( ) case 'switch': - return + return ( + + ) case 'tool-input': - return + return ( + + ) case 'checkbox-list': return ( ) case 'eval-input': - return + return ( + + ) case 'date-input': return ( - ) - case 'webhook-config': + case 'webhook-config': { // For webhook config, we need to construct the value from multiple subblock values - const webhookValue = isPreview && subBlockValues ? { - webhookProvider: subBlockValues['webhookProvider']?.value, - webhookPath: subBlockValues['webhookPath']?.value, - providerConfig: subBlockValues['providerConfig']?.value, - } : previewValue - + const webhookValue = + isPreview && subBlockValues + ? { + webhookProvider: subBlockValues.webhookProvider?.value, + webhookPath: subBlockValues.webhookPath?.value, + providerConfig: subBlockValues.providerConfig?.value, + } + : previewValue + return ( - ) + } case 'schedule-config': return ( - { // Only allow changes in non-preview mode if (!isPreview) { @@ -269,36 +283,44 @@ export function SubBlock({ /> ) case 'file-selector': - return + return ( + + ) case 'project-selector': - return + return ( + + ) case 'folder-selector': - return + return ( + + ) case 'input-format': - return + return ( + + ) default: return
Unknown input type: {config.type}
} @@ -343,4 +365,4 @@ export function SubBlock({ {renderInput()} ) -} \ No newline at end of file +} diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index 2ec26f026e..b50f375d75 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -32,14 +32,20 @@ export function WorkflowBlock({ id, data }: NodeProps) { // State management const [isConnecting, setIsConnecting] = useState(false) + const [isEditing, setIsEditing] = useState(false) const [editedName, setEditedName] = useState('') + const [isLoadingScheduleInfo, setIsLoadingScheduleInfo] = useState(false) const [scheduleInfo, setScheduleInfo] = useState<{ scheduleTiming: string nextRunAt: string | null lastRanAt: string | null timezone: string + status?: string + isDisabled?: boolean + id?: string } | null>(null) + const [isLoadingWebhookInfo, setIsLoadingWebhookInfo] = useState(false) const [webhookInfo, setWebhookInfo] = useState<{ webhookPath: string provider: string @@ -59,8 +65,9 @@ export function WorkflowBlock({ id, data }: NodeProps) { ) const isWide = useWorkflowStore((state) => state.blocks[id]?.isWide ?? false) const blockHeight = useWorkflowStore((state) => state.blocks[id]?.height ?? 0) - const hasActiveSchedule = useWorkflowStore((state) => state.hasActiveSchedule ?? false) const hasActiveWebhook = useWorkflowStore((state) => state.hasActiveWebhook ?? false) + const blockAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false) + const toggleBlockAdvancedMode = useWorkflowStore((state) => state.toggleBlockAdvancedMode) // Workflow store actions const updateBlockName = useWorkflowStore((state) => state.updateBlockName) @@ -71,47 +78,97 @@ export function WorkflowBlock({ id, data }: NodeProps) { const isActiveBlock = useExecutionStore((state) => state.activeBlockIds.has(id)) const isActive = dataIsActive || isActiveBlock - // Get schedule information for the tooltip - useEffect(() => { - if (type === 'starter' && hasActiveSchedule) { - const fetchScheduleInfo = async () => { - try { - const workflowId = useWorkflowRegistry.getState().activeWorkflowId - if (!workflowId) return + const reactivateSchedule = async (scheduleId: string) => { + try { + const response = await fetch(`/api/schedules/${scheduleId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'reactivate' }), + }) - const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, { - cache: 'no-store', - headers: { - 'Cache-Control': 'no-cache', - }, - }) + if (response.ok) { + fetchScheduleInfo() + } else { + console.error('Failed to reactivate schedule') + } + } catch (error) { + console.error('Error reactivating schedule:', error) + } + } - if (response.ok) { - const data = await response.json() - if (data.schedule) { - let scheduleTiming = 'Unknown schedule' - if (data.schedule.cronExpression) { - scheduleTiming = parseCronToHumanReadable(data.schedule.cronExpression) - } + const fetchScheduleInfo = async () => { + try { + setIsLoadingScheduleInfo(true) + const workflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!workflowId) return - setScheduleInfo({ - scheduleTiming, - nextRunAt: data.schedule.nextRunAt, - lastRanAt: data.schedule.lastRanAt, - timezone: data.schedule.timezone || 'UTC', - }) - } - } - } catch (error) { - console.error('Error fetching schedule info:', error) - } + const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, { + cache: 'no-store', + headers: { + 'Cache-Control': 'no-cache', + }, + }) + + if (!response.ok) { + setScheduleInfo(null) + return } - fetchScheduleInfo() - } else if (!hasActiveSchedule) { + const data = await response.json() + + if (!data.schedule) { + setScheduleInfo(null) + return + } + + let scheduleTiming = 'Unknown schedule' + if (data.schedule.cronExpression) { + scheduleTiming = parseCronToHumanReadable(data.schedule.cronExpression) + } + + const baseInfo = { + scheduleTiming, + nextRunAt: data.schedule.nextRunAt as string | null, + lastRanAt: data.schedule.lastRanAt as string | null, + timezone: data.schedule.timezone || 'UTC', + status: data.schedule.status as string, + isDisabled: data.schedule.status === 'disabled', + id: data.schedule.id as string, + } + + try { + const statusRes = await fetch(`/api/schedules/${baseInfo.id}/status`, { + cache: 'no-store', + headers: { 'Cache-Control': 'no-cache' }, + }) + + if (statusRes.ok) { + const statusData = await statusRes.json() + setScheduleInfo({ + scheduleTiming: baseInfo.scheduleTiming, + nextRunAt: statusData.nextRunAt ?? baseInfo.nextRunAt, + lastRanAt: statusData.lastRanAt ?? baseInfo.lastRanAt, + timezone: baseInfo.timezone, + status: statusData.status ?? baseInfo.status, + isDisabled: statusData.isDisabled ?? baseInfo.isDisabled, + id: baseInfo.id, + }) + return + } + } catch (err) { + console.error('Error fetching schedule status:', err) + } + + setScheduleInfo(baseInfo) + } catch (error) { + console.error('Error fetching schedule info:', error) setScheduleInfo(null) + } finally { + setIsLoadingScheduleInfo(false) } - }, [type, hasActiveSchedule]) + } // Get webhook information for the tooltip useEffect(() => { @@ -202,7 +259,7 @@ export function WorkflowBlock({ id, data }: NodeProps) { // Get the appropriate state for conditional evaluation let stateToUse: Record = {} - + if (data.isPreview && data.subBlockValues) { // In preview mode, use the preview values stateToUse = data.subBlockValues @@ -315,7 +372,6 @@ export function WorkflowBlock({ id, data }: NodeProps) { // Check if this is a starter block and has active schedule or webhook const isStarterBlock = type === 'starter' - const showScheduleIndicator = isStarterBlock && hasActiveSchedule const showWebhookIndicator = isStarterBlock && hasActiveWebhook const getProviderName = (providerId: string): string => { @@ -332,6 +388,8 @@ export function WorkflowBlock({ id, data }: NodeProps) { return providers[providerId] || 'Webhook' } + const shouldShowScheduleBadge = isStarterBlock && !isLoadingScheduleInfo && scheduleInfo !== null + return (
) { )} {/* Schedule indicator badge - displayed for starter blocks with active schedules */} - {showScheduleIndicator && ( + {shouldShowScheduleBadge && ( ) { key={`${id}-${rowIndex}-${blockIndex}`} className={cn('space-y-1', subBlock.layout === 'half' ? 'flex-1' : 'w-full')} > - ) {
) -} \ No newline at end of file +} diff --git a/apps/sim/app/w/[id]/hooks/use-deployment-change-detection.ts b/apps/sim/app/w/[id]/hooks/use-deployment-change-detection.ts index c38297be9f..8adb8dcf3c 100644 --- a/apps/sim/app/w/[id]/hooks/use-deployment-change-detection.ts +++ b/apps/sim/app/w/[id]/hooks/use-deployment-change-detection.ts @@ -54,7 +54,6 @@ export function useDeploymentChangeDetection(activeWorkflowId: string | null, is // Always update the needsRedeployment flag based on API response to handle both true and false // This ensures it's updated when changes are detected and when changes are no longer detected if (data.needsRedeployment) { - // Update local state setNeedsRedeployment(true) @@ -66,7 +65,6 @@ export function useDeploymentChangeDetection(activeWorkflowId: string | null, is .getState() .getWorkflowDeploymentStatus(requestedWorkflowId) if (currentStatus?.needsRedeployment) { - // Update local state setNeedsRedeployment(false) diff --git a/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx b/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx index 89685d113f..d319b076ed 100644 --- a/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx +++ b/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx @@ -7,27 +7,19 @@ import ReactFlow, { ConnectionLineType, type Edge, type EdgeTypes, - Handle, type Node, - type NodeProps, type NodeTypes, - Position, ReactFlowProvider, } from 'reactflow' import 'reactflow/dist/style.css' -import { Card } from '@/components/ui/card' -import { Label } from '@/components/ui/label' +import { createLogger } from '@/lib/logs/console-logger' import { cn } from '@/lib/utils' import { LoopTool } from '@/app/w/[id]/components/loop-node/loop-config' -import { createLogger } from '@/lib/logs/console-logger' import { WorkflowBlock } from '@/app/w/[id]/components/workflow-block/workflow-block' import { WorkflowEdge } from '@/app/w/[id]/components/workflow-edge/workflow-edge' -// import { LoopInput } from '@/app/w/[id]/components/workflow-loop/components/loop-input/loop-input' -// 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' -import { WorkflowState } from '@/stores/workflows/workflow/types' +import type { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('WorkflowPreview') @@ -68,20 +60,37 @@ export function WorkflowPreview({ defaultZoom, }: WorkflowPreviewProps) { // Track structure changes efficiently - const blocksStructure = useMemo(() => ({ - count: Object.keys(workflowState.blocks || {}).length, - ids: Object.keys(workflowState.blocks || {}).join(',') - }), [workflowState.blocks]); - - const loopsStructure = useMemo(() => ({ - count: Object.keys(workflowState.loops || {}).length, - ids: Object.keys(workflowState.loops || {}).join(',') - }), [workflowState.loops]); - - const edgesStructure = useMemo(() => ({ - count: workflowState.edges.length, - ids: workflowState.edges.map(e => e.id).join(',') - }), [workflowState.edges]); + const blocksStructure = useMemo( + () => ({ + count: Object.keys(workflowState.blocks || {}).length, + ids: Object.keys(workflowState.blocks || {}).join(','), + }), + [workflowState.blocks] + ) + + const loopsStructure = useMemo( + () => ({ + count: Object.keys(workflowState.loops || {}).length, + ids: Object.keys(workflowState.loops || {}).join(','), + }), + [workflowState.loops] + ) + + const parallelsStructure = useMemo( + () => ({ + count: Object.keys(workflowState.parallels || {}).length, + ids: Object.keys(workflowState.parallels || {}).join(','), + }), + [workflowState.parallels] + ) + + const edgesStructure = useMemo( + () => ({ + count: workflowState.edges.length, + ids: workflowState.edges.map((e) => e.id).join(','), + }), + [workflowState.edges] + ) // Transform blocks and loops into ReactFlow nodes const nodes: Node[] = useMemo(() => { @@ -105,18 +114,18 @@ 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; + 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 ? cloneDeep(block.subBlocks) : {}; + const subBlocksClone = block.subBlocks ? cloneDeep(block.subBlocks) : {} nodeArray.push({ id: blockId, @@ -166,15 +175,22 @@ export function WorkflowPreview({ }) }) } - logger.info(`Preview node created: ${blockId}`, { + logger.info(`Preview node created: ${blockId}`, { blockType: block.type, - hasSubBlocks: block.subBlocks && Object.keys(block.subBlocks).length > 0 - }); + hasSubBlocks: block.subBlocks && Object.keys(block.subBlocks).length > 0, + }) }) - - + return nodeArray - }, [blocksStructure, loopsStructure, showSubBlocks, workflowState.blocks, workflowState.loops]) + }, [ + blocksStructure, + loopsStructure, + parallelsStructure, + showSubBlocks, + workflowState.blocks, + workflowState.loops, + workflowState.parallels, + ]) // Transform edges const edges: Edge[] = useMemo(() => { @@ -186,14 +202,11 @@ export function WorkflowPreview({ targetHandle: edge.targetHandle, type: 'workflowEdge', })) - }, [edgesStructure, workflowState.edges]) + }, [edgesStructure, workflowState.edges, workflowState.parallels]) return ( -
+
- +
) : workflow.thumbnail ? ( diff --git a/apps/sim/app/w/marketplace/marketplace.tsx b/apps/sim/app/w/marketplace/marketplace.tsx index b8dde8e252..a4a0253094 100644 --- a/apps/sim/app/w/marketplace/marketplace.tsx +++ b/apps/sim/app/w/marketplace/marketplace.tsx @@ -30,6 +30,7 @@ export interface Workflow { targetHandle?: string }> loops: Record + parallels?: Record } } @@ -54,6 +55,7 @@ export interface MarketplaceWorkflow { targetHandle?: string }> loops: Record + parallels?: Record } } diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index eee80d857e..555d40ae06 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -1,6 +1,6 @@ -import { Edge } from 'reactflow' -import { BlockOutput, SubBlockType } from '@/blocks/types' -import { DeploymentStatus } from '../registry/types' +import type { Edge } from 'reactflow' +import type { BlockOutput, SubBlockType } from '@/blocks/types' +import type { DeploymentStatus } from '../registry/types' export interface Position { x: number @@ -64,6 +64,12 @@ export interface Loop { forEachItems?: any[] | Record | string // Items or expression } +export interface Parallel { + id: string + nodes: string[] + distribution?: any[] | Record | string // Items or expression +} + export interface WorkflowState { blocks: Record edges: Edge[]